rmega 0.1.0 → 0.1.1

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.
@@ -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