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 CHANGED
@@ -1,4 +1,5 @@
1
- # Rmega
1
+ # Rmega [![Build Status](https://travis-ci.org/daniele-m/rmega.png)](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
- # Download a file
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
- # Download a file using a given url
28
- storage.download 'https://mega.co.nz/#!cER0GYbD!ZCHruEzLghAcEZuD44Dp0k--6m5duA08Xl4a_bUZYMI', '~/Download'
30
+ ```ruby
31
+ file = storage.nodes_by_name(/document1/i).first
32
+ file.name # => "MyDocument1.pdf"
33
+ file.download '~/Downloads'
29
34
 
30
- # Upload a file (to the root node)
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.upload '~/Downloads/my_file.zip', folder_node
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
- # Trash a node
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
@@ -1,2 +1,6 @@
1
1
  #!/usr/bin/env rake
2
2
  require "bundler/gem_tasks"
3
+
4
+ task :default => :spec
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new
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::INFO
33
+ logger.level = Logger::ERROR
30
34
  logger
31
35
  end
32
36
  end
@@ -9,7 +9,7 @@ module Rmega
9
9
  end
10
10
 
11
11
  def self.is_error_code? number
12
- !!Integer(number) rescue false
12
+ (Integer(number) && number.to_i < 0) rescue false
13
13
  end
14
14
 
15
15
  def self.all_messages
@@ -2,6 +2,10 @@ module Rmega
2
2
  module Crypto
3
3
  extend self
4
4
 
5
+ def random_key
6
+ Array.new(6).map { rand(0..0xFFFFFFFF) }
7
+ end
8
+
5
9
  def prepare_key ary
6
10
  pkey = [0x93C467E3,0x7DB0C7A4,0xD1BE3F81,0x0152CB56]
7
11
  65536.times do
@@ -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, dir: 1, root: 2, inbox: 3, trash: 4}
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 = session.storage.trash_node.public_handle
34
- session.request a: 'm', n: handle, t: trash_node_public_handle
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
- session.request a: 'g', g: 1, p: public_handle
61
+ request a: 'g', g: 1, p: public_handle
42
62
  end
43
63
 
44
64
  def public_handle
45
- @public_handle ||= session.request(a: 'l', n: 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
- founded_type = self.class.types.find { |k, v| data['t'] == v }
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
- session.request a: 'd', n: handle
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 debug message
13
- Rmega.logger.debug message
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
- debug "POST #{url}"
65
- debug "#{body.inspect}"
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 finders
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.new(session, node_data) }
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 = Array.new(6).map { rand(0..0xFFFFFFFF) }
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
@@ -1,3 +1,3 @@
1
1
  module Rmega
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
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 = Rmega.login valid_account['email'], valid_account['password']
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 valid_account
14
- @valid_account ||= YAML.load_file account_file_path
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.3
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-03 00:00:00.000000000 Z
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/node.rb
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