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.
- data/lib/rmega/downloader.rb +1 -1
- data/lib/rmega/options.rb +0 -1
- data/lib/rmega/progress.rb +29 -14
- data/lib/rmega/session.rb +18 -15
- data/lib/rmega/storage.rb +4 -63
- data/lib/rmega/uploader.rb +3 -3
- data/lib/rmega/utils.rb +0 -2
- data/lib/rmega/version.rb +1 -1
- data/lib/rmega.rb +0 -1
- data/spec/integration/file_upload_spec.rb +79 -0
- data/spec/integration_spec_helper.rb +2 -2
- metadata +3 -1
data/lib/rmega/downloader.rb
CHANGED
data/lib/rmega/options.rb
CHANGED
data/lib/rmega/progress.rb
CHANGED
@@ -2,33 +2,48 @@ module Rmega
|
|
2
2
|
class Progress
|
3
3
|
|
4
4
|
def initialize(params)
|
5
|
-
@
|
6
|
-
@
|
7
|
-
@
|
5
|
+
@total = params[:total]
|
6
|
+
@caption = params[:caption]
|
7
|
+
@bytes = 0
|
8
|
+
@start_time = Time.now
|
8
9
|
|
9
|
-
|
10
|
+
show
|
10
11
|
end
|
11
12
|
|
12
|
-
def
|
13
|
-
percentage = (100.0 * @
|
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
|
-
|
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
|
21
|
-
|
28
|
+
def elapsed_time
|
29
|
+
(Time.now - @start_time).round(2)
|
30
|
+
end
|
22
31
|
|
23
|
-
|
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
|
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
|
-
|
14
|
+
attr_reader :email, :request_id, :sid, :master_key
|
15
15
|
|
16
16
|
def initialize(email, password)
|
17
|
-
|
18
|
-
|
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
|
35
|
+
resp = request(a: 'us', user: email, uh: uh)
|
36
36
|
|
37
|
-
#
|
38
|
-
encrypted_key = Crypto.prepare_key_pw
|
39
|
-
|
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
|
-
#
|
42
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
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
|
22
|
+
session.request(a: 'uq', strg: 1)
|
25
23
|
end
|
26
24
|
|
27
25
|
def nodes
|
28
|
-
|
29
|
-
|
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
|
-
@
|
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
|
data/lib/rmega/uploader.rb
CHANGED
@@ -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(
|
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.
|
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(
|
48
|
+
progress.increment(clean_buffer.size)
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
data/lib/rmega/utils.rb
CHANGED
data/lib/rmega/version.rb
CHANGED
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.
|
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
|