rmega 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +36 -17
- data/Rakefile +4 -0
- data/lib/rmega.rb +6 -2
- data/lib/rmega/api_request_error.rb +1 -1
- data/lib/rmega/crypto/crypto.rb +4 -0
- data/lib/rmega/nodes/file_node.rb +41 -0
- data/lib/rmega/nodes/folder_node.rb +29 -0
- data/lib/rmega/{node.rb → nodes/node.rb} +31 -44
- data/lib/rmega/session.rb +5 -5
- data/lib/rmega/storage.rb +7 -3
- data/lib/rmega/version.rb +1 -1
- data/rmega.gemspec +2 -0
- data/spec/integration/folder_operations_spec.rb +28 -0
- data/spec/integration/login_spec.rb +1 -2
- data/spec/integration_spec_helper.rb +6 -2
- metadata +39 -3
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
# Rmega
|
1
|
+
# Rmega [](https://travis-ci.org/daniele-m/rmega)
|
2
|
+
|
2
3
|
|
3
4
|
Ruby library for the Mega.co.nz API.
|
4
5
|
Tested using ruby 1.9.3+ (OpenSSL 0.9.8r+)
|
@@ -11,29 +12,53 @@ Tested using ruby 1.9.3+ (OpenSSL 0.9.8r+)
|
|
11
12
|
|
12
13
|
## Usage
|
13
14
|
|
15
|
+
$ gem install rmega
|
16
|
+
$ irb -r rmega
|
17
|
+
|
18
|
+
### Login and retrive all the files and folders
|
19
|
+
|
14
20
|
```ruby
|
15
21
|
storage = Rmega.login 'your_email', 'your_password'
|
16
22
|
|
17
23
|
# Fetch all the nodes (files, folders, ecc.)
|
18
24
|
nodes = storage.nodes
|
25
|
+
```
|
19
26
|
|
20
|
-
# Find all nodes which name match a regexp
|
21
|
-
nodes = storage.nodes_by_name /my.document/i
|
22
27
|
|
23
|
-
|
24
|
-
my_node.download '~/Download' # The name of the node is used
|
25
|
-
my_node.download '~/Download/mydocument_42.zip' # Specify a new name
|
28
|
+
### Download a file or a folder
|
26
29
|
|
27
|
-
|
28
|
-
storage.
|
30
|
+
```ruby
|
31
|
+
file = storage.nodes_by_name(/document1/i).first
|
32
|
+
file.name # => "MyDocument1.pdf"
|
33
|
+
file.download '~/Downloads'
|
29
34
|
|
30
|
-
|
35
|
+
folder = storage.nodes_by_name(/photos/i).first
|
36
|
+
folder.download '~/Downloads/MyAlbums'
|
37
|
+
```
|
38
|
+
|
39
|
+
|
40
|
+
### Download a file using a public url
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
storage.download 'https://mega.co.nz/#!cER0GYbD!ZCHruEA08Xl4a_bUZYMI', '~/Downloads'
|
44
|
+
```
|
45
|
+
|
46
|
+
|
47
|
+
### Upload a file
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
# Upload a file (to the root folder)
|
31
51
|
storage.upload '~/Downloads/my_file.zip'
|
32
52
|
|
33
53
|
# Upload a file to a specific folder
|
34
|
-
storage.
|
54
|
+
document_folder = storage.nodes_by_name(/photos/i).first
|
55
|
+
storage.upload '~/Downloads/my_file.zip', document_folder
|
56
|
+
```
|
57
|
+
|
58
|
+
### Other operations
|
35
59
|
|
36
|
-
|
60
|
+
```ruby
|
61
|
+
# Trash a file or a folder
|
37
62
|
my_node.trash
|
38
63
|
|
39
64
|
# Gets the public url (the sharable one) of a file
|
@@ -41,12 +66,6 @@ my_node.public_url
|
|
41
66
|
|
42
67
|
# See the attributes of a node
|
43
68
|
my_node.attributes
|
44
|
-
|
45
|
-
# Find all nodes of certain type
|
46
|
-
# types are: file, dir, root, inbox, trash
|
47
|
-
files = storage.nodes_by_type :file
|
48
|
-
folders = storage.nodes_by_type :dir
|
49
|
-
|
50
69
|
```
|
51
70
|
|
52
71
|
|
data/Rakefile
CHANGED
data/lib/rmega.rb
CHANGED
@@ -5,6 +5,8 @@ require "logger"
|
|
5
5
|
require "ostruct"
|
6
6
|
|
7
7
|
# Gems in the bundle
|
8
|
+
require 'active_support/core_ext/module/delegation'
|
9
|
+
require 'active_support/core_ext/string/inflections'
|
8
10
|
require "httpclient"
|
9
11
|
require "execjs"
|
10
12
|
require "ruby-progressbar"
|
@@ -17,7 +19,9 @@ require "rmega/crypto/aes"
|
|
17
19
|
require "rmega/crypto/aes_ctr"
|
18
20
|
require "rmega/crypto/crypto"
|
19
21
|
require "rmega/storage"
|
20
|
-
require "rmega/node"
|
22
|
+
require "rmega/nodes/node"
|
23
|
+
require "rmega/nodes/file_node"
|
24
|
+
require "rmega/nodes/folder_node"
|
21
25
|
require "rmega/session"
|
22
26
|
require "rmega/api_request_error"
|
23
27
|
|
@@ -26,7 +30,7 @@ module Rmega
|
|
26
30
|
@logger ||= begin
|
27
31
|
logger = Logger.new $stdout
|
28
32
|
logger.formatter = Proc.new { | severity, time, progname, msg| "#{msg}\n" }
|
29
|
-
logger.level = Logger::
|
33
|
+
logger.level = Logger::ERROR
|
30
34
|
logger
|
31
35
|
end
|
32
36
|
end
|
data/lib/rmega/crypto/crypto.rb
CHANGED
@@ -0,0 +1,41 @@
|
|
1
|
+
module Rmega
|
2
|
+
class FileNode < Node
|
3
|
+
def storage_url
|
4
|
+
@storage_url ||= data['g'] || request(a: 'g', g: 1, n: handle)['g']
|
5
|
+
end
|
6
|
+
|
7
|
+
def chunks
|
8
|
+
Storage.chunks filesize
|
9
|
+
end
|
10
|
+
|
11
|
+
def download path
|
12
|
+
path = File.expand_path path
|
13
|
+
path = Dir.exists?(path) ? File.join(path, name) : path
|
14
|
+
|
15
|
+
logger.info "Starting download into #{path}"
|
16
|
+
|
17
|
+
Utils.show_progress :download, filesize
|
18
|
+
|
19
|
+
k = decrypted_file_key
|
20
|
+
k = [k[0] ^ k[4], k[1] ^ k[5], k[2] ^ k[6], k[3] ^ k[7]]
|
21
|
+
nonce = decrypted_file_key[4..5]
|
22
|
+
|
23
|
+
file = File.open path, 'wb'
|
24
|
+
connection = HTTPClient.new.get_async storage_url
|
25
|
+
message = connection.pop
|
26
|
+
|
27
|
+
chunks.each do |chunk_start, chunk_size|
|
28
|
+
buffer = message.content.read chunk_size
|
29
|
+
# TODO: should be (chunk_start/0x1000000000) >>> 0, (chunk_start/0x10) >>> 0
|
30
|
+
nonce = [nonce[0], nonce[1], (chunk_start/0x1000000000) >> 0, (chunk_start/0x10) >> 0]
|
31
|
+
decryption_result = Crypto::AesCtr.decrypt(k, nonce, buffer)
|
32
|
+
file.write(decryption_result[:data])
|
33
|
+
Utils.show_progress :download, filesize, chunk_size
|
34
|
+
end
|
35
|
+
|
36
|
+
nil
|
37
|
+
ensure
|
38
|
+
file.close if file
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Rmega
|
2
|
+
class FolderNode < Node
|
3
|
+
def self.create session, parent_node, folder_name
|
4
|
+
key = Crypto.random_key
|
5
|
+
encrypted_attributes = Utils.a32_to_base64 Crypto.encrypt_attributes(key[0..3], {n: folder_name.strip})
|
6
|
+
encrypted_key = Utils.a32_to_base64 Crypto.encrypt_key(session.master_key, key)
|
7
|
+
n = [{h: 'xxxxxxxx', t: 1, a: encrypted_attributes, k: encrypted_key}]
|
8
|
+
data = session.request a: 'p', t: parent_node.handle, n: n
|
9
|
+
new session, data['f'][0]
|
10
|
+
end
|
11
|
+
|
12
|
+
def children
|
13
|
+
storage.nodes.select { |node| node.parent_handle == handle }
|
14
|
+
end
|
15
|
+
|
16
|
+
def download path
|
17
|
+
children.each do |node|
|
18
|
+
if node.type == :file
|
19
|
+
node.download path
|
20
|
+
elsif node.type == :folder
|
21
|
+
subfolder = File.expand_path File.join(path, node.name)
|
22
|
+
Dir.mkdir(subfolder) unless Dir.exists?(subfolder)
|
23
|
+
node.download subfolder
|
24
|
+
end
|
25
|
+
end
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -7,6 +7,13 @@ module Rmega
|
|
7
7
|
@data = data
|
8
8
|
end
|
9
9
|
|
10
|
+
def self.fabricate session, data
|
11
|
+
type_name = type_by_number(data['t']).to_s
|
12
|
+
node_class = Rmega.const_get("#{type_name}_node".camelize) rescue nil
|
13
|
+
node_class ||= Rmega::Node
|
14
|
+
node_class.new session, data
|
15
|
+
end
|
16
|
+
|
10
17
|
def self.initialize_by_public_url session, public_url
|
11
18
|
public_handle, key = public_url.split('!')[1, 2]
|
12
19
|
|
@@ -16,9 +23,17 @@ module Rmega
|
|
16
23
|
end
|
17
24
|
|
18
25
|
def self.types
|
19
|
-
{file: 0,
|
26
|
+
{file: 0, folder: 1, root: 2, inbox: 3, trash: 4}
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.type_by_number number
|
30
|
+
founded_type = types.find { |k, v| number == v }
|
31
|
+
founded_type.first if founded_type
|
20
32
|
end
|
21
33
|
|
34
|
+
def logger
|
35
|
+
Rmega.logger
|
36
|
+
end
|
22
37
|
|
23
38
|
# Member actions
|
24
39
|
|
@@ -30,25 +45,34 @@ module Rmega
|
|
30
45
|
end
|
31
46
|
|
32
47
|
def trash
|
33
|
-
trash_node_public_handle =
|
34
|
-
|
48
|
+
trash_node_public_handle = storage.trash_node.public_handle
|
49
|
+
request a: 'm', n: handle, t: trash_node_public_handle
|
35
50
|
end
|
36
51
|
|
37
52
|
|
53
|
+
# Delegate to session
|
54
|
+
|
55
|
+
delegate :storage, :request, :to => :session
|
56
|
+
|
57
|
+
|
38
58
|
# Other methods
|
39
59
|
|
40
60
|
def self.public_data session, public_handle
|
41
|
-
|
61
|
+
request a: 'g', g: 1, p: public_handle
|
42
62
|
end
|
43
63
|
|
44
64
|
def public_handle
|
45
|
-
@public_handle ||=
|
65
|
+
@public_handle ||= request(a: 'l', n: handle)
|
46
66
|
end
|
47
67
|
|
48
68
|
def handle
|
49
69
|
data['h']
|
50
70
|
end
|
51
71
|
|
72
|
+
def parent_handle
|
73
|
+
data['p']
|
74
|
+
end
|
75
|
+
|
52
76
|
def filesize
|
53
77
|
data['s']
|
54
78
|
end
|
@@ -85,48 +109,11 @@ module Rmega
|
|
85
109
|
end
|
86
110
|
|
87
111
|
def type
|
88
|
-
|
89
|
-
founded_type.first if founded_type
|
112
|
+
self.class.type_by_number data['t']
|
90
113
|
end
|
91
114
|
|
92
115
|
def delete
|
93
|
-
|
94
|
-
end
|
95
|
-
|
96
|
-
def storage_url
|
97
|
-
@storage_url ||= data['g'] || session.request(a: 'g', g: 1, n: handle)['g']
|
98
|
-
end
|
99
|
-
|
100
|
-
def chunks
|
101
|
-
Storage.chunks filesize
|
102
|
-
end
|
103
|
-
|
104
|
-
def download path
|
105
|
-
path = File.expand_path path
|
106
|
-
path = Dir.exists?(path) ? File.join(path, name) : path
|
107
|
-
|
108
|
-
Utils.show_progress :download, filesize
|
109
|
-
|
110
|
-
k = decrypted_file_key
|
111
|
-
k = [k[0] ^ k[4], k[1] ^ k[5], k[2] ^ k[6], k[3] ^ k[7]]
|
112
|
-
nonce = decrypted_file_key[4..5]
|
113
|
-
|
114
|
-
file = File.open path, 'wb'
|
115
|
-
connection = HTTPClient.new.get_async storage_url
|
116
|
-
message = connection.pop
|
117
|
-
|
118
|
-
chunks.each do |chunk_start, chunk_size|
|
119
|
-
buffer = message.content.read chunk_size
|
120
|
-
# TODO: should be (chunk_start/0x1000000000) >>> 0, (chunk_start/0x10) >>> 0
|
121
|
-
nonce = [nonce[0], nonce[1], (chunk_start/0x1000000000) >> 0, (chunk_start/0x10) >> 0]
|
122
|
-
decryption_result = Crypto::AesCtr.decrypt(k, nonce, buffer)
|
123
|
-
file.write(decryption_result[:data])
|
124
|
-
Utils.show_progress :download, filesize, chunk_size
|
125
|
-
end
|
126
|
-
|
127
|
-
nil
|
128
|
-
ensure
|
129
|
-
file.close if file
|
116
|
+
request a: 'd', n: handle
|
130
117
|
end
|
131
118
|
end
|
132
119
|
end
|
data/lib/rmega/session.rb
CHANGED
@@ -9,8 +9,8 @@ module Rmega
|
|
9
9
|
login password_str
|
10
10
|
end
|
11
11
|
|
12
|
-
def
|
13
|
-
Rmega.logger
|
12
|
+
def logger
|
13
|
+
Rmega.logger
|
14
14
|
end
|
15
15
|
|
16
16
|
|
@@ -61,10 +61,10 @@ module Rmega
|
|
61
61
|
self.request_id += 1
|
62
62
|
url = "#{api_url}?id=#{request_id}"
|
63
63
|
url << "&sid=#{sid}" if sid
|
64
|
-
|
65
|
-
|
64
|
+
logger.info "POST #{url}"
|
65
|
+
logger.info "#{body.inspect}"
|
66
66
|
response = HTTPClient.new.post url, [body].to_json, timeout: api_request_timeout
|
67
|
-
debug "#{response.code}\n#{response.body}"
|
67
|
+
logger.debug "#{response.code}\n#{response.body}"
|
68
68
|
resp = JSON.parse(response.body).first
|
69
69
|
raise ApiRequestError.new(resp) if ApiRequestError.is_error_code?(resp)
|
70
70
|
resp
|
data/lib/rmega/storage.rb
CHANGED
@@ -27,11 +27,11 @@ module Rmega
|
|
27
27
|
end
|
28
28
|
|
29
29
|
|
30
|
-
# Nodes
|
30
|
+
# Nodes management
|
31
31
|
|
32
32
|
def nodes
|
33
33
|
nodes = session.request a: 'f', c: 1
|
34
|
-
nodes['f'].map { |node_data| Node.
|
34
|
+
nodes['f'].map { |node_data| Node.fabricate(session, node_data) }
|
35
35
|
end
|
36
36
|
|
37
37
|
def nodes_by_type type
|
@@ -52,6 +52,10 @@ module Rmega
|
|
52
52
|
@root_node ||= nodes_by_type(:root).first
|
53
53
|
end
|
54
54
|
|
55
|
+
def create_folder parent_node, folder_name
|
56
|
+
FolderNode.create session, parent_node, folder_name
|
57
|
+
end
|
58
|
+
|
55
59
|
|
56
60
|
# Handle node download
|
57
61
|
|
@@ -101,7 +105,7 @@ module Rmega
|
|
101
105
|
filesize = File.size local_path
|
102
106
|
upld_url = upload_url filesize
|
103
107
|
|
104
|
-
ul_key =
|
108
|
+
ul_key = Crypto.random_key
|
105
109
|
aes_key = ul_key[0..3]
|
106
110
|
nonce = ul_key[4..5]
|
107
111
|
local_file = File.open local_path, 'rb'
|
data/lib/rmega/version.rb
CHANGED
data/rmega.gemspec
CHANGED
@@ -17,7 +17,9 @@ Gem::Specification.new do |gem|
|
|
17
17
|
|
18
18
|
gem.add_development_dependency "pry"
|
19
19
|
gem.add_development_dependency "rspec"
|
20
|
+
gem.add_development_dependency "rake"
|
20
21
|
gem.add_dependency "httpclient"
|
22
|
+
gem.add_dependency 'active_support'
|
21
23
|
gem.add_dependency "ruby-progressbar"
|
22
24
|
gem.add_dependency "execjs"
|
23
25
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'integration_spec_helper'
|
2
|
+
|
3
|
+
describe 'Folders operations' do
|
4
|
+
before :all do
|
5
|
+
@session = storage.session
|
6
|
+
@parent_node = @session.storage.root_node
|
7
|
+
@folder_name = "test_folder_#{rand.denominator}_#{rand.denominator}"
|
8
|
+
@folder_node = @session.storage.create_folder @parent_node, @folder_name
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'creates a new folder' do
|
12
|
+
@folder_node.should be_kind_of Rmega::Node
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'finds the folder' do
|
16
|
+
node = @session.storage.nodes.find { |n| n.name == @folder_name }
|
17
|
+
node.should_not be_nil
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'deletes the folder' do
|
21
|
+
lambda { @folder_node.delete }.should_not raise_error
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'does not find the folder anymore' do
|
25
|
+
node = @session.storage.nodes.find { |n| n.name == @folder_name }
|
26
|
+
node.should be_nil
|
27
|
+
end
|
28
|
+
end
|
@@ -1,11 +1,10 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
require 'integration_spec_helper'
|
3
2
|
|
4
3
|
describe 'Login process' do
|
5
4
|
if account_file_exists?
|
6
5
|
context 'when email and password are correct' do
|
7
6
|
it 'returns a valid object' do
|
8
|
-
object =
|
7
|
+
object = storage
|
9
8
|
object.should respond_to :nodes
|
10
9
|
end
|
11
10
|
end
|
@@ -10,6 +10,10 @@ def account_file_exists?
|
|
10
10
|
File.exists? account_file_path
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
14
|
-
@
|
13
|
+
def account
|
14
|
+
@account ||= YAML.load_file account_file_path
|
15
|
+
end
|
16
|
+
|
17
|
+
def storage
|
18
|
+
Rmega.login account['email'], account['password']
|
15
19
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rmega
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-03-
|
12
|
+
date: 2013-03-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pry
|
@@ -43,6 +43,22 @@ dependencies:
|
|
43
43
|
- - ! '>='
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
46
62
|
- !ruby/object:Gem::Dependency
|
47
63
|
name: httpclient
|
48
64
|
requirement: !ruby/object:Gem::Requirement
|
@@ -59,6 +75,22 @@ dependencies:
|
|
59
75
|
- - ! '>='
|
60
76
|
- !ruby/object:Gem::Version
|
61
77
|
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: active_support
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
62
94
|
- !ruby/object:Gem::Dependency
|
63
95
|
name: ruby-progressbar
|
64
96
|
requirement: !ruby/object:Gem::Requirement
|
@@ -110,12 +142,15 @@ files:
|
|
110
142
|
- lib/rmega/crypto/crypto.rb
|
111
143
|
- lib/rmega/crypto/rsa.rb
|
112
144
|
- lib/rmega/crypto/rsa_mega.js
|
113
|
-
- lib/rmega/
|
145
|
+
- lib/rmega/nodes/file_node.rb
|
146
|
+
- lib/rmega/nodes/folder_node.rb
|
147
|
+
- lib/rmega/nodes/node.rb
|
114
148
|
- lib/rmega/session.rb
|
115
149
|
- lib/rmega/storage.rb
|
116
150
|
- lib/rmega/utils.rb
|
117
151
|
- lib/rmega/version.rb
|
118
152
|
- rmega.gemspec
|
153
|
+
- spec/integration/folder_operations_spec.rb
|
119
154
|
- spec/integration/login_spec.rb
|
120
155
|
- spec/integration/rmega_account.yml.example
|
121
156
|
- spec/integration_spec_helper.rb
|
@@ -148,6 +183,7 @@ signing_key:
|
|
148
183
|
specification_version: 3
|
149
184
|
summary: mega.co.nz ruby api
|
150
185
|
test_files:
|
186
|
+
- spec/integration/folder_operations_spec.rb
|
151
187
|
- spec/integration/login_spec.rb
|
152
188
|
- spec/integration/rmega_account.yml.example
|
153
189
|
- spec/integration_spec_helper.rb
|