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