rmega 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -46,7 +46,7 @@ module Rmega
46
46
  def download(&block)
47
47
  @local_file = allocate
48
48
 
49
- progress = Progress.new(filesize: filesize, verb: 'download')
49
+ progress = Progress.new(total: filesize, caption: 'Download')
50
50
 
51
51
  chunks.each do |start, size|
52
52
  pool.defer do
data/lib/rmega/options.rb CHANGED
@@ -3,7 +3,6 @@ require 'ostruct'
3
3
  module Rmega
4
4
  def self.default_options
5
5
  {
6
- show_progress: true,
7
6
  upload_timeout: 120,
8
7
  api_request_timeout: 20,
9
8
  api_url: 'https://eu.api.mega.co.nz/cs'
@@ -2,33 +2,48 @@ module Rmega
2
2
  class Progress
3
3
 
4
4
  def initialize(params)
5
- @filesize = params[:filesize]
6
- @verb = params[:verb].capitalize
7
- @progress = 0
5
+ @total = params[:total]
6
+ @caption = params[:caption]
7
+ @bytes = 0
8
+ @start_time = Time.now
8
9
 
9
- render
10
+ show
10
11
  end
11
12
 
12
- def render
13
- percentage = (100.0 * @progress / @filesize).round(2)
14
- message = "#{@verb} in progress #{format_bytes(@progress)} of #{format_bytes(@filesize)} (#{percentage}%)"
15
- rtrn = "\n" if @filesize == @progress
13
+ def show
14
+ percentage = (100.0 * @bytes / @total).round(2)
16
15
 
17
- print "\r#{' '*(message.size + 15)}\r#{message}#{rtrn}"
16
+ message = "[#{@caption}] #{humanize(@bytes)} of #{humanize(@total)}"
17
+
18
+ if ended?
19
+ message << ". Completed in #{elapsed_time} sec.\n"
20
+ else
21
+ message << " (#{percentage}%)"
22
+ end
23
+
24
+ blank_line = ' ' * (message.size + 15)
25
+ print "\r#{blank_line}\r#{message}"
18
26
  end
19
27
 
20
- def increment(bytes)
21
- @progress += bytes
28
+ def elapsed_time
29
+ (Time.now - @start_time).round(2)
30
+ end
22
31
 
23
- render
32
+ def ended?
33
+ @total == @bytes
34
+ end
35
+
36
+ def increment(bytes)
37
+ @bytes += bytes
38
+ show
24
39
  end
25
40
 
26
- def format_bytes(bytes, round = 2)
41
+ def humanize(bytes, round = 2)
27
42
  units = ['bytes', 'kb', 'MB', 'GB', 'TB', 'PB']
28
43
  e = (bytes == 0 ? 0 : Math.log(bytes)) / Math.log(1024)
29
44
  value = bytes.to_f / (1024 ** e.floor)
30
45
 
31
- "#{value.round(round)}#{units[e]}"
46
+ "#{value.round(round)} #{units[e]}"
32
47
  end
33
48
  end
34
49
  end
data/lib/rmega/session.rb CHANGED
@@ -11,11 +11,11 @@ module Rmega
11
11
  class Session
12
12
  include Loggable
13
13
 
14
- attr_accessor :email, :request_id, :sid, :master_key
14
+ attr_reader :email, :request_id, :sid, :master_key
15
15
 
16
16
  def initialize(email, password)
17
- self.email = email
18
- self.request_id = random_request_id
17
+ @email = email
18
+ @request_id = random_request_id
19
19
 
20
20
  login(password)
21
21
  end
@@ -32,27 +32,30 @@ module Rmega
32
32
 
33
33
  def login(password)
34
34
  uh = Crypto.stringhash Crypto.prepare_key_pw(password), email
35
- resp = request a: 'us', user: email, uh: uh
35
+ resp = request(a: 'us', user: email, uh: uh)
36
36
 
37
- # Decrypt the master key
38
- encrypted_key = Crypto.prepare_key_pw password
39
- self.master_key = Crypto.decrypt_key encrypted_key, Utils.base64_to_a32(resp['k'])
37
+ # Decrypts the master key
38
+ encrypted_key = Crypto.prepare_key_pw(password)
39
+ @master_key = Crypto.decrypt_key(encrypted_key, Utils.base64_to_a32(resp['k']))
40
40
 
41
- # Generate the session id
42
- self.sid = Crypto.decrypt_sid master_key, resp['csid'], resp['privk']
41
+ # Generates the session id
42
+ @sid = Crypto.decrypt_sid(@master_key, resp['csid'], resp['privk'])
43
43
  end
44
44
 
45
45
  def random_request_id
46
46
  rand(1E7..1E9).to_i
47
47
  end
48
48
 
49
+ def request_url
50
+ "#{api_url}?id=#{@request_id}".tap do |url|
51
+ url << "&sid=#{@sid}" if @sid
52
+ end
53
+ end
54
+
49
55
  def request(body)
50
- self.request_id += 1
51
- url = "#{api_url}?id=#{request_id}"
52
- url << "&sid=#{sid}" if sid
53
- logger.info "POST #{url}"
54
- logger.info "#{body.inspect}"
55
- response = HTTPClient.new.post url, [body].to_json, timeout: api_request_timeout
56
+ @request_id += 1
57
+ logger.debug "POST #{request_url}\n#{body.inspect}"
58
+ response = HTTPClient.new.post(request_url, [body].to_json, timeout: api_request_timeout)
56
59
  logger.debug "#{response.code}\n#{response.body}"
57
60
  resp = JSON.parse(response.body).first
58
61
  raise RequestError.new(resp) if RequestError.error_code?(resp)
data/lib/rmega/storage.rb CHANGED
@@ -1,5 +1,3 @@
1
- require 'rmega/utils'
2
- require 'rmega/crypto/crypto'
3
1
  require 'rmega/nodes/factory'
4
2
 
5
3
  module Rmega
@@ -21,12 +19,12 @@ module Rmega
21
19
  end
22
20
 
23
21
  def quota
24
- session.request a: 'uq', strg: 1
22
+ session.request(a: 'uq', strg: 1)
25
23
  end
26
24
 
27
25
  def nodes
28
- result = session.request(a: 'f', c: 1)
29
- result['f'].map { |node_data| Nodes::Factory.build(session, node_data) }
26
+ results = session.request(a: 'f', c: 1)['f']
27
+ results.map { |node_data| Nodes::Factory.build(session, node_data) }
30
28
  end
31
29
 
32
30
  def trash
@@ -34,68 +32,11 @@ module Rmega
34
32
  end
35
33
 
36
34
  def root
37
- @root_node ||= nodes.find { |n| n.type == :root }
35
+ @root ||= nodes.find { |n| n.type == :root }
38
36
  end
39
37
 
40
38
  def download(public_url, path)
41
39
  Nodes::Factory.build_from_url(session, public_url).download(path)
42
40
  end
43
-
44
- # TODO: refactor upload part
45
- def upload_url(filesize)
46
- session.request(a: 'u', s: filesize)['p']
47
- end
48
-
49
- def upload_chunk(url, start, chunk)
50
- response = HTTPClient.new.post "#{url}/#{start}", chunk, timeout: Rmega.options.upload_timeout
51
- response.body
52
- end
53
-
54
- def upload(local_path, parent_node = root)
55
- local_path = File.expand_path local_path
56
- filesize = File.size local_path
57
- upld_url = upload_url filesize
58
-
59
- ul_key = Crypto.random_key
60
- aes_key = ul_key[0..3]
61
- nonce = ul_key[4..5]
62
- local_file = File.open local_path, 'rb'
63
- file_handle = nil
64
- file_mac = [0, 0, 0, 0]
65
-
66
- Utils.show_progress :upload, filesize
67
-
68
- Utils.chunks(filesize).each do |chunk_start, chunk_size|
69
- buffer = local_file.read chunk_size
70
-
71
- # TODO: should be (chunk_start/0x1000000000) >>> 0, (chunk_start/0x10) >>> 0
72
- nonce = [nonce[0], nonce[1], (chunk_start/0x1000000000) >> 0, (chunk_start/0x10) >> 0]
73
-
74
- encrypted_buffer = Crypto::AesCtr.encrypt aes_key, nonce, buffer
75
- chunk_mac = encrypted_buffer[:mac]
76
-
77
- file_handle = upload_chunk upld_url, chunk_start, encrypted_buffer[:data]
78
-
79
- file_mac = [file_mac[0] ^ chunk_mac[0], file_mac[1] ^ chunk_mac[1],
80
- file_mac[2] ^ chunk_mac[2], file_mac[3] ^ chunk_mac[3]]
81
- file_mac = Crypto::Aes.encrypt ul_key[0..3], file_mac
82
- Utils.show_progress :upload, filesize, chunk_size
83
- end
84
-
85
- attribs = {n: File.basename(local_path)}
86
- encrypt_attribs = Utils.a32_to_base64 Crypto.encrypt_attributes(ul_key[0..3], attribs)
87
-
88
- meta_mac = [file_mac[0] ^ file_mac[1], file_mac[2] ^ file_mac[3]]
89
-
90
- key = [ul_key[0] ^ ul_key[4], ul_key[1] ^ ul_key[5], ul_key[2] ^ meta_mac[0],
91
- ul_key[3] ^ meta_mac[1], ul_key[4], ul_key[5], meta_mac[0], meta_mac[1]]
92
-
93
- encrypted_key = Utils.a32_to_base64 Crypto.encrypt_key(session.master_key, key)
94
- session.request a: 'p', t: parent_node.handle, n: [{h: file_handle, t: 0, a: encrypt_attribs, k: encrypted_key}]
95
-
96
- nil
97
- ensure
98
- local_file.close if local_file
99
- end
100
41
  end
101
42
  end
@@ -37,15 +37,15 @@ module Rmega
37
37
  def upload(&block)
38
38
  @local_file = ::File.open(local_path, 'rb')
39
39
 
40
- progress = Progress.new(filesize: filesize, verb: 'upload')
40
+ progress = Progress.new(total: filesize, caption: 'Upload')
41
41
 
42
42
  chunks.each do |start, size|
43
43
 
44
44
  pool.defer do
45
- clean_buffer = pool.syncronize { read_chunk(start, size) }
45
+ clean_buffer = pool.synchronize { read_chunk(start, size) }
46
46
  encrypted_buffer = yield(start, clean_buffer)
47
47
  @last_result = upload_chunk(start, encrypted_buffer)
48
- progress.increment(buffer.size)
48
+ progress.increment(clean_buffer.size)
49
49
  end
50
50
  end
51
51
 
data/lib/rmega/utils.rb CHANGED
@@ -1,5 +1,3 @@
1
- require 'rmega/utils'
2
-
3
1
  module Rmega
4
2
  module Utils
5
3
  extend self
data/lib/rmega/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Rmega
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
data/lib/rmega.rb CHANGED
@@ -3,7 +3,6 @@ require 'active_support/core_ext/object/to_json'
3
3
  require 'active_support/core_ext/module/delegation'
4
4
  require 'active_support/core_ext/string/inflections'
5
5
  require 'httpclient'
6
- require 'ruby-progressbar'
7
6
  require 'execjs'
8
7
  require 'rmega/version'
9
8
  require 'rmega/options'
@@ -0,0 +1,79 @@
1
+ require 'integration_spec_helper'
2
+ require 'fileutils'
3
+
4
+ describe 'File upload' do
5
+
6
+ if account_file_exists?
7
+
8
+ before(:all) { @storage = login }
9
+
10
+ context 'upload a small file to the root folder' do
11
+
12
+ before(:all) do
13
+ @name = "i_like_trains_#{rand(1E20)}"
14
+ @content = @name
15
+ end
16
+
17
+ def find_file
18
+ @storage.root.files.find { |f| f.name == @name }
19
+ end
20
+
21
+ let(:path) { File.join(temp_folder, @name) }
22
+
23
+ before do
24
+ File.open(path, 'wb') { |f| f.write(@content) }
25
+ @storage.root.upload(path)
26
+ end
27
+
28
+ it 'finds the uploaded file' do
29
+ file = find_file
30
+ file.delete
31
+ expect(file).not_to be_nil
32
+ end
33
+
34
+ context 'download the same file' do
35
+
36
+ let(:download_path) { "#{path}.downloaded" }
37
+
38
+ before do
39
+ file = find_file
40
+ file.download(download_path)
41
+ file.delete
42
+ end
43
+
44
+ it 'has the expected content' do
45
+ expect(File.read(download_path)).to eql @content
46
+ end
47
+ end
48
+ end
49
+
50
+ context 'upload a big file to a specific folder' do
51
+
52
+ before(:all) do
53
+ @name = "mine_turtles_#{rand(1E20)}"
54
+ @path = File.join(temp_folder, @name)
55
+ @buffer = "rofl" * 1024
56
+
57
+ File.open(@path, 'wb') do |f|
58
+ 512.times { f.write(@buffer) }
59
+ end
60
+ end
61
+
62
+ let!(:folder) { @storage.root.create_folder(@name) }
63
+
64
+ before(:all) { folder.upload(@path) }
65
+
66
+ it 'finds the uploaded file and verify its content' do
67
+ file = folder.files.find { |f| f.name == @name }
68
+ download_path = "#{@path}.downloaded"
69
+ file.download(download_path)
70
+
71
+ File.open(download_path, 'rb') do |f|
72
+ 512.times { expect(f.read(@buffer.size)).to eq(@buffer) }
73
+ end
74
+ end
75
+
76
+ after { folder.delete }
77
+ end
78
+ end
79
+ end
@@ -22,6 +22,6 @@ def temp_folder
22
22
  end
23
23
 
24
24
  RSpec.configure do |config|
25
- config.before { FileUtils.mkdir_p(temp_folder) }
26
- config.after { FileUtils.rm_rf(temp_folder) }
25
+ config.before(:all) { FileUtils.mkdir_p(temp_folder) }
26
+ config.after(:all) { FileUtils.rm_rf(temp_folder) }
27
27
  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.1.0
4
+ version: 0.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -148,6 +148,7 @@ files:
148
148
  - lib/rmega/version.rb
149
149
  - rmega.gemspec
150
150
  - spec/integration/file_download_spec.rb
151
+ - spec/integration/file_upload_spec.rb
151
152
  - spec/integration/folder_operations_spec.rb
152
153
  - spec/integration/login_spec.rb
153
154
  - spec/integration_spec_helper.rb
@@ -181,6 +182,7 @@ specification_version: 3
181
182
  summary: mega.co.nz ruby api
182
183
  test_files:
183
184
  - spec/integration/file_download_spec.rb
185
+ - spec/integration/file_upload_spec.rb
184
186
  - spec/integration/folder_operations_spec.rb
185
187
  - spec/integration/login_spec.rb
186
188
  - spec/integration_spec_helper.rb