rmega 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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