rmega 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -2
- data/.travis.yml +6 -3
- data/CHANGELOG.md +11 -0
- data/LICENSE +1 -1
- data/README.md +20 -17
- data/TODO.md +0 -2
- data/bin/rmega-dl +24 -18
- data/bin/rmega-up +14 -4
- data/lib/rmega/cli.rb +39 -44
- data/lib/rmega/loggable.rb +3 -1
- data/lib/rmega/nodes/downloadable.rb +1 -1
- data/lib/rmega/nodes/expandable.rb +3 -5
- data/lib/rmega/nodes/factory.rb +2 -2
- data/lib/rmega/nodes/node.rb +32 -2
- data/lib/rmega/nodes/root.rb +6 -0
- data/lib/rmega/nodes/uploadable.rb +4 -6
- data/lib/rmega/options.rb +4 -2
- data/lib/rmega/progress.rb +4 -0
- data/lib/rmega/storage.rb +4 -1
- data/lib/rmega/version.rb +1 -1
- data/lib/rmega.rb +5 -8
- data/rmega.gemspec +0 -2
- data/spec/integration/file_download_spec.rb +2 -2
- data/spec/integration/file_integrity_spec.rb +26 -19
- data/spec/integration/file_upload_spec.rb +13 -16
- data/spec/integration/folder_download_spec.rb +1 -1
- data/spec/integration/folder_operations_spec.rb +9 -3
- data/spec/integration/login_spec.rb +1 -1
- data/spec/integration/resume_download_spec.rb +4 -16
- data/spec/integration/rmega-dl_spec.rb +35 -0
- data/spec/integration/rmega-up_spec.rb +67 -0
- data/spec/integration_spec_helper.rb +10 -5
- metadata +17 -29
- data/spec/integration/rmega_account.yml.example +0 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 137fdede22cdd2e053dea69f98b227700a8b606f
|
4
|
+
data.tar.gz: 49ed20b4c7b0aa5419861c59f842f5a35a2a883d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0de9a225767de5aeacb5a82629a10df8e8017bd2f49c2514904a9bb59b451d300c554ae990512644b064aa16e24a085989a16aa16a400094b1d8ef3a3c3ff652
|
7
|
+
data.tar.gz: 75d935e07f6957227d87800831efa7e07a5966cb134324798e6f5b37b4780e726f97e094d984ab5f137e002a19475fd167bb8142c5af0b4556ee6152907f7531
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
## 0.2.1
|
2
|
+
|
3
|
+
### New Features
|
4
|
+
* \#14 Files and folders can now be renamed with `Node#rename`
|
5
|
+
|
6
|
+
### Changes
|
7
|
+
* `rmega-dl`, `rmega-up` commands now properly traverse the cloud storage when searching for a file/folder to download/upload
|
8
|
+
* `rmega-dl`: fixed scan for Mega links in a webpage
|
9
|
+
* The configuration file (~/.rmega) format is changed (from JSON to YAML)
|
10
|
+
* Dropped dependency to ActiveSupport
|
11
|
+
|
1
12
|
## 0.2.0
|
2
13
|
|
3
14
|
### New Features
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,27 +1,36 @@
|
|
1
1
|
# Rmega
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
Pure ruby library for <img src="https://mega.co.nz/favicon.ico" alt=""/> **MEGA** [https://mega.nz/](https://mega.nz/).
|
4
|
+
Works on Linux and OSX with Ruby 1.9.3+.
|
5
5
|
|
6
|
+
## Installation
|
6
7
|
|
7
|
-
|
8
|
+
```
|
9
|
+
gem install rmega
|
10
|
+
```
|
8
11
|
|
12
|
+
## Command line usage
|
9
13
|
|
10
|
-
|
14
|
+
Since version 0.2.0 you can use the commands `rmega-dl` and `rmega-up` to easily download and upload files to MEGA.
|
11
15
|
|
12
|
-
|
13
|
-
|
16
|
+
* Downloads are resumable
|
17
|
+
* HTTP proxy support
|
18
|
+
* See the CHANGELOG file for more info
|
14
19
|
|
15
|
-
|
20
|
+
<img src="https://i.imgur.com/VVl55wj.gif"/>
|
16
21
|
|
17
|
-
|
18
|
-
|
19
|
-
|
22
|
+
*Pro tips:*
|
23
|
+
|
24
|
+
* Streaming: you can use a video player (e.g. VLC) to play videos while downloading them.
|
25
|
+
* Super privacy: you can use it combined with [torsocks](https://github.com/dgoulet/torsocks/) to download and upload files through the Tor network (very slow).
|
26
|
+
|
27
|
+
## DSL usage
|
20
28
|
|
21
29
|
### Login
|
22
30
|
|
23
31
|
```ruby
|
24
|
-
|
32
|
+
require "rmega"
|
33
|
+
storage = Rmega.login("your@email.com", "your_password")
|
25
34
|
```
|
26
35
|
|
27
36
|
### Browsing
|
@@ -139,9 +148,3 @@ end
|
|
139
148
|
3. Commit your changes (`git commit -am 'Added some feature'`)
|
140
149
|
4. Push to the branch (`git push origin my-new-feature`)
|
141
150
|
5. Create new Pull Request
|
142
|
-
|
143
|
-
|
144
|
-
## Copyright
|
145
|
-
|
146
|
-
Copyright (c) 2013 Daniele Molteni
|
147
|
-
MIT License
|
data/TODO.md
CHANGED
data/bin/rmega-dl
CHANGED
@@ -14,6 +14,11 @@ end
|
|
14
14
|
OptionParser.new do |opts|
|
15
15
|
opts.banner = "Usage:\n"
|
16
16
|
opts.banner << "\t#{File.basename(__FILE__)} url [options]\n"
|
17
|
+
opts.banner << "\t#{File.basename(__FILE__)} path [options]\n"
|
18
|
+
opts.banner << "Examples:\n"
|
19
|
+
opts.banner << "\t#{File.basename(__FILE__)} 'https://mega.nz/#!aBkHBKLX!n4kqzbJooqc3o_s96PZjN1tEJzQ4QQwskHf7YqKa'\n"
|
20
|
+
opts.banner << "\t#{File.basename(__FILE__)} https://www.reddit.com/r/megalinks\n"
|
21
|
+
opts.banner << "\t#{File.basename(__FILE__)} /remote/docs/myfile.txt -u email@localhost\n"
|
17
22
|
opts.banner << "Options:"
|
18
23
|
|
19
24
|
opts.on("-o PATH", "--output", "Local destination path") { |path|
|
@@ -23,25 +28,26 @@ OptionParser.new do |opts|
|
|
23
28
|
apply_opt_parser_options(opts)
|
24
29
|
end.parse!
|
25
30
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
raise("
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
"
|
31
|
+
cli_rescue do
|
32
|
+
if cli_options[:user]
|
33
|
+
session = Rmega::Session.new.login(cli_options[:user], cli_options[:pass] || cli_prompt_password)
|
34
|
+
root = session.storage.root
|
35
|
+
node = traverse_storage(root, cli_options[:url].dup)
|
36
|
+
raise("Node not found - #{cli_options[:url]}") unless node
|
37
|
+
node.download(cli_options[:output] || Dir.pwd)
|
38
|
+
else
|
39
|
+
urls = [cli_options[:url]]
|
40
|
+
|
41
|
+
unless mega_url?(cli_options[:url])
|
42
|
+
html = Rmega::Session.new.http_get_content(cli_options[:url])
|
43
|
+
urls = html.scan(Rmega::Nodes::Factory::URL_REGEXP).flatten.uniq
|
44
|
+
raise("Nothing to download") if urls.empty?
|
40
45
|
end
|
41
46
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
47
|
+
urls.each_with_index do |url, index|
|
48
|
+
node = Rmega::Nodes::Factory.build_from_url(url)
|
49
|
+
print "[#{index+1}/#{urls.size}] " if urls.size > 1
|
50
|
+
node.download(cli_options[:output] || Dir.pwd)
|
51
|
+
end
|
46
52
|
end
|
47
53
|
end
|
data/bin/rmega-up
CHANGED
@@ -14,18 +14,28 @@ end
|
|
14
14
|
OptionParser.new do |opts|
|
15
15
|
opts.banner = "Usage:\n"
|
16
16
|
opts.banner << "\t#{File.basename(__FILE__)} path [options]\n"
|
17
|
+
opts.banner << "Examples:\n"
|
18
|
+
opts.banner << "\t#{File.basename(__FILE__)} /local/file.txt -u email@localhost -r /remote/docs\n"
|
17
19
|
opts.banner << "Options:"
|
18
20
|
|
21
|
+
opts.on("-r PATH", "--remote-path", "Remote path") { |path|
|
22
|
+
cli_options[:remote_path] = path
|
23
|
+
}
|
24
|
+
|
19
25
|
apply_opt_parser_options(opts)
|
20
26
|
end.parse!
|
21
27
|
|
22
|
-
|
28
|
+
cli_rescue do
|
23
29
|
raise("File not found - #{cli_options[:path]}") unless File.exists?(cli_options[:path])
|
24
30
|
|
25
31
|
user = cli_options[:user] || raise("User email is required")
|
26
|
-
|
32
|
+
session = Rmega::Session.new.login(user, cli_options[:pass] ||= cli_prompt_password)
|
27
33
|
|
28
|
-
session = Rmega::Session.new.login(user, pass)
|
29
34
|
root = session.storage.root
|
30
|
-
root
|
35
|
+
node = traverse_storage(root, cli_options[:remote_path].to_s.dup, :only_folders => true)
|
36
|
+
|
37
|
+
raise("Node not found - #{cli_options[:remote_path]}") unless node
|
38
|
+
raise("Node cannot be a file - #{cli_options[:remote_path]}") if node.type == :file
|
39
|
+
|
40
|
+
node.upload(cli_options[:path])
|
31
41
|
end
|
data/lib/rmega/cli.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
require 'optparse'
|
2
2
|
require 'io/console'
|
3
|
-
require '
|
3
|
+
require 'yaml'
|
4
4
|
|
5
5
|
module Rmega
|
6
6
|
module CLI
|
7
7
|
module Helpers
|
8
8
|
def cli_options
|
9
|
-
$cli_options ||= {
|
9
|
+
$cli_options ||= {}
|
10
10
|
end
|
11
11
|
|
12
12
|
def cli_prompt_password
|
@@ -18,10 +18,6 @@ module Rmega
|
|
18
18
|
return password
|
19
19
|
end
|
20
20
|
|
21
|
-
def scan_mega_urls(text)
|
22
|
-
text.to_s.scan(Nodes::Factory::URL_REGEXP).flatten.map { |s| "https://mega.co.nz/##{s}" }
|
23
|
-
end
|
24
|
-
|
25
21
|
def mega_url?(url)
|
26
22
|
Nodes::Factory.url?(url)
|
27
23
|
end
|
@@ -30,46 +26,34 @@ module Rmega
|
|
30
26
|
File.expand_path('~/.rmega')
|
31
27
|
end
|
32
28
|
|
33
|
-
def write_configuration_file
|
34
|
-
opts = {options: cli_options[:options]}
|
35
|
-
if cli_options[:user]
|
36
|
-
opts[:user] = cli_options[:user]
|
37
|
-
opts[:pass] = cli_options[:pass] || cli_prompt_password
|
38
|
-
end
|
39
|
-
File.open(configuration_filepath, 'wb') { |file| file.write(opts.to_json) }
|
40
|
-
FileUtils.chmod(0600, configuration_filepath)
|
41
|
-
puts "Options saved into #{configuration_filepath}"
|
42
|
-
end
|
43
|
-
|
44
29
|
def read_configuration_file
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
end
|
30
|
+
return unless File.exists?(configuration_filepath)
|
31
|
+
cli_options = YAML.load_file(configuration_filepath)
|
32
|
+
cli_options.keys.each { |k| cli_options[k.to_sym] = cli_options.delete(k) }
|
33
|
+
puts "Loaded configuration file #{configuration_filepath}" if cli_options[:debug]
|
50
34
|
rescue Exception => ex
|
51
35
|
raise(ex) if cli_options[:debug]
|
52
36
|
end
|
53
37
|
|
54
38
|
def apply_cli_options
|
55
|
-
|
56
|
-
|
57
|
-
cli_options[:options].each do |key, value|
|
39
|
+
cli_options.each do |key, value|
|
58
40
|
Rmega.options.__send__("#{key}=", value)
|
59
41
|
end
|
42
|
+
Rmega.logger.level = ::Logger::DEBUG if cli_options[:debug]
|
43
|
+
Rmega.options.show_progress = true
|
60
44
|
end
|
61
45
|
|
62
46
|
def apply_opt_parser_options(opts)
|
63
47
|
opts.on("-t NUM", "--thread_pool_size", "Number of threads to use") { |n|
|
64
|
-
cli_options[:
|
48
|
+
cli_options[:thread_pool_size] = n.to_i
|
65
49
|
}
|
66
50
|
|
67
51
|
opts.on("--proxy-addr ADDRESS", "Http proxy address") { |value|
|
68
|
-
cli_options[:
|
52
|
+
cli_options[:http_proxy_address] = value
|
69
53
|
}
|
70
54
|
|
71
55
|
opts.on("--proxy-port PORT", "Http proxy port") { |value|
|
72
|
-
cli_options[:
|
56
|
+
cli_options[:http_proxy_port] = value.to_i
|
73
57
|
}
|
74
58
|
|
75
59
|
opts.on("-u", "--user USER_EMAIL", "User email address") { |value|
|
@@ -80,10 +64,6 @@ module Rmega
|
|
80
64
|
cli_options[:pass] = value
|
81
65
|
}
|
82
66
|
|
83
|
-
opts.on("--write-cfg", "Write a configuration file with the given options") {
|
84
|
-
cli_options[:write_cfg] = true
|
85
|
-
}
|
86
|
-
|
87
67
|
opts.on("--debug", "Debug mode") {
|
88
68
|
cli_options[:debug] = true
|
89
69
|
}
|
@@ -91,29 +71,44 @@ module Rmega
|
|
91
71
|
opts.on("-v", "--version", "Print the version number") {
|
92
72
|
puts Rmega::VERSION
|
93
73
|
puts Rmega::HOMEPAGE
|
94
|
-
exit
|
74
|
+
exit!(0)
|
95
75
|
}
|
96
76
|
end
|
97
77
|
|
98
|
-
def
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
if
|
104
|
-
|
78
|
+
def traverse_storage(node, path, opts = {})
|
79
|
+
path.gsub!(/^\/|\/$/, "")
|
80
|
+
curr_part = path.split("/")[0] || ""
|
81
|
+
last_part = (path.split("/")[1..-1] || []).join("/")
|
82
|
+
|
83
|
+
if curr_part.empty?
|
84
|
+
if node.type == :root or node.type == :folder
|
85
|
+
return node
|
86
|
+
else
|
87
|
+
return nil
|
88
|
+
end
|
105
89
|
else
|
106
|
-
|
107
|
-
|
108
|
-
|
90
|
+
n = node.folders.find { |n| n.name.casecmp(curr_part).zero? }
|
91
|
+
n ||= node.files.find { |n| n.name.casecmp(curr_part).zero? } unless opts[:only_folders]
|
92
|
+
|
93
|
+
if last_part.empty?
|
94
|
+
return n
|
95
|
+
else
|
96
|
+
return traverse_storage(n, last_part)
|
97
|
+
end
|
109
98
|
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def cli_rescue
|
102
|
+
read_configuration_file
|
103
|
+
apply_cli_options
|
104
|
+
yield
|
110
105
|
rescue Interrupt
|
111
106
|
puts "\nInterrupted"
|
112
107
|
rescue Exception => ex
|
113
108
|
if cli_options[:debug]
|
114
109
|
raise(ex)
|
115
110
|
else
|
116
|
-
puts "\
|
111
|
+
$stderr.puts "\nERROR: #{ex.message}"
|
117
112
|
end
|
118
113
|
end
|
119
114
|
end
|
data/lib/rmega/loggable.rb
CHANGED
@@ -69,7 +69,7 @@ module Rmega
|
|
69
69
|
path = ::File.expand_path(path)
|
70
70
|
path = Dir.exists?(path) ? ::File.join(path, name) : path
|
71
71
|
|
72
|
-
progress = Progress.new(filesize, caption: 'Download')
|
72
|
+
progress = Progress.new(filesize, caption: 'Download', filename: self.name)
|
73
73
|
pool = Pool.new
|
74
74
|
|
75
75
|
@resumed_download = allocated?(path)
|
@@ -7,15 +7,13 @@ module Rmega
|
|
7
7
|
node_key = NodeKey.random
|
8
8
|
|
9
9
|
# encrypt attributes
|
10
|
-
|
11
|
-
|
12
|
-
attributes_str << ("\x00" * (16 - (attributes_str.size % 16)))
|
13
|
-
encrypted_attributes = aes_cbc_encrypt(node_key.aes_key, attributes_str)
|
10
|
+
_attr = serialize_attributes(:n => name.strip)
|
11
|
+
_attr = aes_cbc_encrypt(node_key.aes_key, _attr)
|
14
12
|
|
15
13
|
# Encrypt node key
|
16
14
|
encrypted_key = aes_ecb_encrypt(session.master_key, node_key.aes_key)
|
17
15
|
|
18
|
-
n = [{h: 'xxxxxxxx', t: 1, a: Utils.base64urlencode(
|
16
|
+
n = [{h: 'xxxxxxxx', t: 1, a: Utils.base64urlencode(_attr), k: Utils.base64urlencode(encrypted_key)}]
|
19
17
|
data = session.request(a: 'p', t: handle, n: n)
|
20
18
|
return Folder.new(session, data['f'][0])
|
21
19
|
end
|
data/lib/rmega/nodes/factory.rb
CHANGED
@@ -16,9 +16,9 @@ module Rmega
|
|
16
16
|
module Factory
|
17
17
|
extend self
|
18
18
|
|
19
|
-
URL_REGEXP = /
|
19
|
+
URL_REGEXP = /(http.:\/\/[w\.]*mega\.[a-z\.]+\/\#[A-Z0-9\_\-\!\=]+)/i
|
20
20
|
|
21
|
-
FOLDER_URL_REGEXP =
|
21
|
+
FOLDER_URL_REGEXP = /\#\F/
|
22
22
|
|
23
23
|
def url?(string)
|
24
24
|
string.to_s =~ URL_REGEXP
|
data/lib/rmega/nodes/node.rb
CHANGED
@@ -8,7 +8,10 @@ module Rmega
|
|
8
8
|
|
9
9
|
attr_reader :data, :session
|
10
10
|
|
11
|
-
|
11
|
+
# Delegate to :session
|
12
|
+
[:request, :shared_keys, :rsa_privk, :master_key, :storage].each do |name|
|
13
|
+
__send__(:define_method, name) { |*args| session.__send__(name, *args) }
|
14
|
+
end
|
12
15
|
|
13
16
|
TYPES = {0 => :file, 1 => :folder, 2 => :root, 3 => :inbox, 4 => :trash}
|
14
17
|
|
@@ -25,6 +28,30 @@ module Rmega
|
|
25
28
|
@public_handle ||= request(a: 'l', n: handle)
|
26
29
|
end
|
27
30
|
|
31
|
+
def serialize_attributes(hash)
|
32
|
+
str = "MEGA"
|
33
|
+
str << hash.to_json
|
34
|
+
str << ("\x00" * (16 - (str.size % 16)))
|
35
|
+
return str
|
36
|
+
end
|
37
|
+
|
38
|
+
def rename(new_name)
|
39
|
+
node_key = NodeKey.load(decrypted_file_key)
|
40
|
+
|
41
|
+
_attr = serialize_attributes(attributes.merge("n" => new_name))
|
42
|
+
_attr = aes_cbc_encrypt(node_key.aes_key, _attr)
|
43
|
+
_attr = Utils.base64urlencode(_attr)
|
44
|
+
|
45
|
+
resp = request(:a => "a", :attr => _attr, :key => Utils.base64urlencode(node_key.aes_key), :n => handle)
|
46
|
+
|
47
|
+
if resp != 0
|
48
|
+
raise("Rename failed")
|
49
|
+
else
|
50
|
+
@data['a'] = _attr
|
51
|
+
return self
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
28
55
|
def handle
|
29
56
|
data['h']
|
30
57
|
end
|
@@ -122,7 +149,10 @@ module Rmega
|
|
122
149
|
encrypted = Utils.base64urldecode(encrypted)
|
123
150
|
encrypted.strip! if encrypted.size % 16 != 0 # Fix possible errors
|
124
151
|
json = aes_cbc_decrypt(node_key.aes_key, encrypted)
|
125
|
-
|
152
|
+
# Remove invalid bytes at the end of the string
|
153
|
+
json.strip!
|
154
|
+
json.gsub!(/^MEGA\{(.+)\}.*/, '{\1}')
|
155
|
+
return JSON.parse(json)
|
126
156
|
end
|
127
157
|
|
128
158
|
def type
|
data/lib/rmega/nodes/root.rb
CHANGED
@@ -46,7 +46,7 @@ module Rmega
|
|
46
46
|
pool = Pool.new
|
47
47
|
read_mutex = Mutex.new
|
48
48
|
|
49
|
-
progress = Progress.new(filesize, caption: 'Upload')
|
49
|
+
progress = Progress.new(filesize, caption: 'Upload', filename: ::File.basename(path))
|
50
50
|
|
51
51
|
chunk_macs = {}
|
52
52
|
|
@@ -69,10 +69,8 @@ module Rmega
|
|
69
69
|
pool.shutdown
|
70
70
|
|
71
71
|
# encrypt attributes
|
72
|
-
|
73
|
-
|
74
|
-
attributes_str << ("\x00" * (16 - (attributes_str.size % 16)))
|
75
|
-
encrypted_attributes = aes_cbc_encrypt(rnd_node_key.aes_key, attributes_str)
|
72
|
+
_attr = serialize_attributes(:n => ::File.basename(path))
|
73
|
+
_attr = aes_cbc_encrypt(rnd_node_key.aes_key, _attr)
|
76
74
|
|
77
75
|
# Calculate meta_mac
|
78
76
|
file_mac = aes_cbc_mac(rnd_node_key.aes_key, chunk_macs.sort.map(&:last).join, "\x0"*16)
|
@@ -80,7 +78,7 @@ module Rmega
|
|
80
78
|
encrypted_key = aes_ecb_encrypt(session.master_key, rnd_node_key.generate)
|
81
79
|
|
82
80
|
resp = request(a: 'p', t: handle, n: [
|
83
|
-
{h: file_handle, t: 0, a: Utils.base64urlencode(
|
81
|
+
{h: file_handle, t: 0, a: Utils.base64urlencode(_attr), k: Utils.base64urlencode(encrypted_key)}
|
84
82
|
])
|
85
83
|
|
86
84
|
return Nodes::Factory.build(session, resp['f'][0])
|
data/lib/rmega/options.rb
CHANGED
@@ -8,7 +8,7 @@ module Rmega
|
|
8
8
|
http_read_timeout: 180,
|
9
9
|
# http_proxy_address: '127.0.0.1',
|
10
10
|
# http_proxy_port: 8080,
|
11
|
-
show_progress:
|
11
|
+
show_progress: false,
|
12
12
|
file_integrity_check: true,
|
13
13
|
api_url: 'https://eu.api.mega.co.nz/cs'
|
14
14
|
}
|
@@ -19,7 +19,9 @@ module Rmega
|
|
19
19
|
end
|
20
20
|
|
21
21
|
module Options
|
22
|
-
|
22
|
+
def self.included(base)
|
23
|
+
base.__send__(:extend, ClassMethods)
|
24
|
+
end
|
23
25
|
|
24
26
|
def options
|
25
27
|
Rmega.options
|
data/lib/rmega/progress.rb
CHANGED
data/lib/rmega/storage.rb
CHANGED
@@ -6,7 +6,10 @@ module Rmega
|
|
6
6
|
|
7
7
|
attr_reader :session
|
8
8
|
|
9
|
-
|
9
|
+
# Delegate to :session
|
10
|
+
[:master_key, :shared_keys].each do |name|
|
11
|
+
__send__(:define_method, name) { |*args| session.__send__(name, *args) }
|
12
|
+
end
|
10
13
|
|
11
14
|
def initialize(session)
|
12
15
|
@session = session
|
data/lib/rmega/version.rb
CHANGED
data/lib/rmega.rb
CHANGED
@@ -6,10 +6,12 @@ require 'net/http'
|
|
6
6
|
require 'base64'
|
7
7
|
require 'openssl'
|
8
8
|
require 'digest/md5'
|
9
|
+
require 'json'
|
9
10
|
|
10
|
-
|
11
|
-
require '
|
12
|
-
require '
|
11
|
+
# Used only in specs
|
12
|
+
require 'yaml'
|
13
|
+
require 'tmpdir'
|
14
|
+
require 'fileutils'
|
13
15
|
|
14
16
|
require 'rmega/version'
|
15
17
|
require 'rmega/loggable'
|
@@ -26,11 +28,6 @@ require 'rmega/session'
|
|
26
28
|
require 'rmega/storage'
|
27
29
|
require 'rmega/nodes/factory'
|
28
30
|
|
29
|
-
# Used only in specs
|
30
|
-
require 'yaml'
|
31
|
-
require 'tmpdir'
|
32
|
-
require 'fileutils'
|
33
|
-
|
34
31
|
module Rmega
|
35
32
|
def self.login(email, password)
|
36
33
|
Session.new.login(email, password).storage
|
data/rmega.gemspec
CHANGED
@@ -4,7 +4,7 @@ describe 'File download' do
|
|
4
4
|
|
5
5
|
context 'given a public mega url (a small file)' do
|
6
6
|
|
7
|
-
let(:url) { 'https://mega.
|
7
|
+
let(:url) { 'https://mega.nz/#!QQhADCbL!vUY_phwxvkC004t5NKx7vynL16SvFfHYFkiX5vUlgjQ' }
|
8
8
|
|
9
9
|
it 'downloads the related file' do
|
10
10
|
Rmega.download(url, temp_folder)
|
@@ -15,7 +15,7 @@ describe 'File download' do
|
|
15
15
|
|
16
16
|
context 'given a public mega url (a big file)' do
|
17
17
|
|
18
|
-
let(:url) { 'https://mega.
|
18
|
+
let(:url) { 'https://mega.nz/#!oAhCnBKR!CPeG8X92nBjvFsBF9EprZNW_TqIUwItHMkF9G2IZEIo' }
|
19
19
|
|
20
20
|
it 'downloads the related file' do
|
21
21
|
Rmega.download(url, temp_folder)
|
@@ -1,39 +1,46 @@
|
|
1
1
|
require 'integration_spec_helper'
|
2
2
|
|
3
|
-
describe 'File integrity
|
3
|
+
describe 'File integrity' do
|
4
4
|
|
5
|
-
if
|
5
|
+
if account?
|
6
6
|
|
7
7
|
before(:all) do
|
8
8
|
@storage = login
|
9
9
|
end
|
10
10
|
|
11
|
-
|
11
|
+
context "when a file is renamed" do
|
12
12
|
|
13
|
-
|
13
|
+
let(:path) { "#{temp_folder}/testfile_#{SecureRandom.hex(6)}" }
|
14
14
|
|
15
|
-
|
15
|
+
let(:new_name) { "testfile_#{SecureRandom.hex(6)}" }
|
16
|
+
|
17
|
+
it 'it does not get corrupted' do
|
18
|
+
File.write(path, SecureRandom.hex(24))
|
19
|
+
file = @storage.root.upload(path)
|
20
|
+
file.rename(new_name)
|
21
|
+
expect(file.name).to eq(new_name)
|
22
|
+
file = @storage.nodes.find { |n| n.handle == file.handle }
|
23
|
+
file.delete
|
24
|
+
expect(file.name).to eq(new_name)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
[12, 6_000].each do |size|
|
16
29
|
|
17
30
|
context "when a file (#{size} bytes) is uploaded and then downloaded" do
|
18
31
|
|
19
|
-
let(:
|
32
|
+
let(:path) { "#{temp_folder}/testfile_#{SecureRandom.hex(6)}" }
|
20
33
|
|
21
|
-
let(:
|
34
|
+
let(:content) { SecureRandom.random_bytes(size) }
|
22
35
|
|
23
|
-
|
24
|
-
File.open(path, 'wb') { |f| f.write(content) }
|
25
|
-
file = @storage.root.upload(path)
|
26
|
-
@file = @storage.nodes.find { |n| n.handle == file.handle }
|
27
|
-
expect(@file.name).to eq(name)
|
28
|
-
@file.download(path+".downloaded")
|
29
|
-
end
|
36
|
+
let(:content_hash) { Digest::MD5.hexdigest(content) }
|
30
37
|
|
31
38
|
it 'it does not get corrupted' do
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
39
|
+
File.write(path, content)
|
40
|
+
file = @storage.root.upload(path)
|
41
|
+
file.download("#{path}.downloaded")
|
42
|
+
file.delete
|
43
|
+
expect(Digest::MD5.file("#{path}.downloaded").hexdigest).to eq(content_hash)
|
37
44
|
end
|
38
45
|
end
|
39
46
|
end
|
@@ -2,29 +2,26 @@ require 'integration_spec_helper'
|
|
2
2
|
|
3
3
|
describe 'File upload' do
|
4
4
|
|
5
|
-
if
|
5
|
+
if account?
|
6
6
|
|
7
|
-
before(:all)
|
8
|
-
|
9
|
-
|
7
|
+
before(:all) do
|
8
|
+
@storage = login
|
9
|
+
end
|
10
10
|
|
11
|
-
|
11
|
+
[12, 6_000].each do |size|
|
12
12
|
|
13
|
-
[12, 1_024_000].each do |size|
|
14
13
|
context "when a file (#{size} bytes) is uploaded" do
|
15
14
|
|
16
|
-
|
17
|
-
File.open(path, 'wb') { |f| f.write(OpenSSL::Random.random_bytes(size)) }
|
18
|
-
@file = @storage.root.upload(path)
|
19
|
-
end
|
15
|
+
let(:path) { "#{temp_folder}/testfile_#{SecureRandom.hex(6)}" }
|
20
16
|
|
21
|
-
|
22
|
-
found_node = @storage.root.files.find { |f| f.handle == @file.handle }
|
23
|
-
expect(found_node).not_to be_nil
|
24
|
-
end
|
17
|
+
let(:content) { SecureRandom.random_bytes(size) }
|
25
18
|
|
26
|
-
|
27
|
-
|
19
|
+
it 'is found' do
|
20
|
+
File.write(path, content)
|
21
|
+
file = @storage.root.upload(path)
|
22
|
+
file = @storage.root.files.find { |f| f.handle == file.handle }
|
23
|
+
file.delete
|
24
|
+
expect(file).not_to be_nil
|
28
25
|
end
|
29
26
|
end
|
30
27
|
end
|
@@ -4,7 +4,7 @@ describe 'Folder download' do
|
|
4
4
|
|
5
5
|
context 'given a public mega url (folder)' do
|
6
6
|
|
7
|
-
let(:url) { 'https://mega.
|
7
|
+
let(:url) { 'https://mega.nz/#F!oQYEUaBD!QtYCjQDbBzefFeIM994FIg' }
|
8
8
|
|
9
9
|
it 'downloads the related file' do
|
10
10
|
Rmega.download(url, temp_folder)
|
@@ -2,16 +2,16 @@ require 'integration_spec_helper'
|
|
2
2
|
|
3
3
|
describe 'Folders operations' do
|
4
4
|
|
5
|
-
if
|
5
|
+
if account?
|
6
6
|
|
7
7
|
before(:all) do
|
8
8
|
@storage = login
|
9
9
|
end
|
10
10
|
|
11
|
-
let(:name) { "test_folder" }
|
12
|
-
|
13
11
|
context 'when #create_folder is called on a node' do
|
14
12
|
|
13
|
+
let(:name) { "testfolder_#{SecureRandom.hex(5)}" }
|
14
|
+
|
15
15
|
before do
|
16
16
|
@folder = @storage.root.create_folder(name)
|
17
17
|
end
|
@@ -27,6 +27,9 @@ describe 'Folders operations' do
|
|
27
27
|
end
|
28
28
|
|
29
29
|
context 'searching for a folder by its handle' do
|
30
|
+
|
31
|
+
let(:name) { "testfolder_#{SecureRandom.hex(5)}" }
|
32
|
+
|
30
33
|
before do
|
31
34
|
@folder = @storage.root.create_folder(name)
|
32
35
|
end
|
@@ -42,6 +45,9 @@ describe 'Folders operations' do
|
|
42
45
|
end
|
43
46
|
|
44
47
|
context 'when #create_folder under created folder' do
|
48
|
+
|
49
|
+
let(:name) { "testfolder_#{SecureRandom.hex(5)}" }
|
50
|
+
|
45
51
|
before do
|
46
52
|
@folder = @storage.root.create_folder(name)
|
47
53
|
@sub_folder = @folder.create_folder(name)
|
@@ -3,7 +3,7 @@ require 'integration_spec_helper'
|
|
3
3
|
module Rmega
|
4
4
|
describe 'Resumable download' do
|
5
5
|
|
6
|
-
let(:download_url) { 'https://mega.
|
6
|
+
let(:download_url) { 'https://mega.nz/#!oAhCnBKR!CPeG8X92nBjvFsBF9EprZNW_TqIUwItHMkF9G2IZEIo' }
|
7
7
|
|
8
8
|
let(:destination_file) { "#{temp_folder}/temp.txt" }
|
9
9
|
|
@@ -25,26 +25,14 @@ module Rmega
|
|
25
25
|
node.file_io_synchronize { content = File.read(destination_file) }
|
26
26
|
content.strip!
|
27
27
|
break if content.size > 5_000_000
|
28
|
-
sleep(
|
28
|
+
sleep(0.5)
|
29
29
|
end
|
30
30
|
|
31
31
|
thread.kill
|
32
|
-
sleep(1)
|
33
32
|
|
34
|
-
|
35
|
-
node.download(destination_file)
|
36
|
-
end
|
37
|
-
|
38
|
-
loop do
|
39
|
-
# todo: i saw this failing becausa destination_file was missing :/
|
40
|
-
node.file_io_synchronize { content = File.read(destination_file) }
|
41
|
-
content.strip!
|
42
|
-
expect(content.size).to be > 5_000_000
|
43
|
-
break if content.size >= 15_728_640
|
44
|
-
sleep(1)
|
45
|
-
end
|
33
|
+
sleep(2)
|
46
34
|
|
47
|
-
|
35
|
+
node.download(destination_file)
|
48
36
|
|
49
37
|
md5 = Digest::MD5.file(destination_file).hexdigest
|
50
38
|
expect(md5).to eq("0451dc82ac003dbef703342e40a1b8f6")
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'integration_spec_helper'
|
2
|
+
|
3
|
+
describe "rmega-dl" do
|
4
|
+
|
5
|
+
let(:url) { 'https://mega.nz/#!QQhADCbL!vUY_phwxvkC004t5NKx7vynL16SvFfHYFkiX5vUlgjQ' }
|
6
|
+
|
7
|
+
def call(*args)
|
8
|
+
`bundle exec ./bin/rmega-dl #{args.join(' ')}`
|
9
|
+
end
|
10
|
+
|
11
|
+
context "without args" do
|
12
|
+
|
13
|
+
it "shows the help" do
|
14
|
+
expect(call).to match(/usage/i)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context "given a public link" do
|
19
|
+
it "downloads a file" do
|
20
|
+
call("'#{url}' -o #{temp_folder}")
|
21
|
+
downloaded_file = "#{temp_folder}/testfile.txt"
|
22
|
+
expect(File.read(downloaded_file)).to eq "helloworld!\n"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
if account?
|
27
|
+
context "given an account and a path" do
|
28
|
+
it "downloads a file" do
|
29
|
+
call("/test_folder/a.txt -u #{account['email']} --pass #{account['password']} -o #{temp_folder}")
|
30
|
+
downloaded_file = "#{temp_folder}/a.txt"
|
31
|
+
expect(File.read(downloaded_file)).to eq "hello\n"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'integration_spec_helper'
|
2
|
+
require 'open3'
|
3
|
+
|
4
|
+
describe "rmega-up" do
|
5
|
+
def call(*args)
|
6
|
+
Open3.capture2e("bundle exec ./bin/rmega-up #{args.join(' ')}").join("\n")
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:file_content) { SecureRandom.hex(10) }
|
10
|
+
|
11
|
+
let(:filename) { "testfile_"+SecureRandom.hex(5)+".txt" }
|
12
|
+
|
13
|
+
let(:filepath) { "#{temp_folder}/#{filename}" }
|
14
|
+
|
15
|
+
before do
|
16
|
+
File.write(filepath, file_content)
|
17
|
+
end
|
18
|
+
|
19
|
+
context "without args" do
|
20
|
+
|
21
|
+
it "shows the help" do
|
22
|
+
expect(call).to match(/usage/i)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "without username" do
|
27
|
+
it "fails" do
|
28
|
+
expect(call(filepath)).to match(/require/i)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "when the local file is missing" do
|
33
|
+
it "fails" do
|
34
|
+
expect(call("foobar.txt")).to match(/missing|not found/i)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
if account?
|
39
|
+
context "when the remote path is missing" do
|
40
|
+
it "fails" do
|
41
|
+
resp = call("#{filepath} -u #{account['email']} --pass #{account['password']} -r /foobar")
|
42
|
+
expect(resp).to match(/error/i)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "without specifying a remote folder" do
|
47
|
+
it "uploads a file to the root node" do
|
48
|
+
call("#{filepath} -u #{account['email']} --pass #{account['password']}")
|
49
|
+
storage = login
|
50
|
+
node = storage.root.files.find { |f| f.name == filename }
|
51
|
+
node.delete if node
|
52
|
+
expect(node).not_to be_nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "when specifying a remote folder" do
|
57
|
+
it "uploads a file into that folder" do
|
58
|
+
call("#{filepath} -u #{account['email']} --pass #{account['password']} -r test_folder2")
|
59
|
+
storage = login
|
60
|
+
node = storage.root.folders.find { |f| f.name == "test_folder2" }
|
61
|
+
node = node.files.find { |f| f.name == filename }
|
62
|
+
node.delete if node
|
63
|
+
expect(node).not_to be_nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -1,15 +1,21 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
def account_file_path
|
4
|
-
File.join
|
4
|
+
File.join(File.dirname(__FILE__), "account.yaml")
|
5
5
|
end
|
6
6
|
|
7
|
-
def
|
8
|
-
|
7
|
+
def account?
|
8
|
+
account
|
9
9
|
end
|
10
10
|
|
11
11
|
def account
|
12
|
-
|
12
|
+
if ENV["MEGA_EMAIL"] and ENV["MEGA_PASSWORD"]
|
13
|
+
{'email' => ENV["MEGA_EMAIL"], 'password' => ENV["MEGA_PASSWORD"]}
|
14
|
+
elsif File.exists?(account_file_path)
|
15
|
+
YAML.load_file(account_file_path)
|
16
|
+
else
|
17
|
+
nil
|
18
|
+
end
|
13
19
|
end
|
14
20
|
|
15
21
|
def login
|
@@ -22,7 +28,6 @@ end
|
|
22
28
|
|
23
29
|
RSpec.configure do |config|
|
24
30
|
config.before(:all) do
|
25
|
-
Rmega.options.show_progress = false
|
26
31
|
FileUtils.mkdir_p(temp_folder)
|
27
32
|
end
|
28
33
|
|
metadata
CHANGED
@@ -1,69 +1,55 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rmega
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- topac
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-09-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pry
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rspec
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: activesupport
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - '>='
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
type: :runtime
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - '>='
|
52
|
+
- - ">="
|
67
53
|
- !ruby/object:Gem::Version
|
68
54
|
version: '0'
|
69
55
|
description: mega.co.nz ruby api
|
@@ -75,8 +61,8 @@ executables:
|
|
75
61
|
extensions: []
|
76
62
|
extra_rdoc_files: []
|
77
63
|
files:
|
78
|
-
- .gitignore
|
79
|
-
- .travis.yml
|
64
|
+
- ".gitignore"
|
65
|
+
- ".travis.yml"
|
80
66
|
- CHANGELOG.md
|
81
67
|
- Gemfile
|
82
68
|
- LICENSE
|
@@ -125,7 +111,8 @@ files:
|
|
125
111
|
- spec/integration/folder_operations_spec.rb
|
126
112
|
- spec/integration/login_spec.rb
|
127
113
|
- spec/integration/resume_download_spec.rb
|
128
|
-
- spec/integration/
|
114
|
+
- spec/integration/rmega-dl_spec.rb
|
115
|
+
- spec/integration/rmega-up_spec.rb
|
129
116
|
- spec/integration_spec_helper.rb
|
130
117
|
- spec/rmega/lib/cli_spec.rb
|
131
118
|
- spec/rmega/lib/session_spec.rb
|
@@ -142,17 +129,17 @@ require_paths:
|
|
142
129
|
- lib
|
143
130
|
required_ruby_version: !ruby/object:Gem::Requirement
|
144
131
|
requirements:
|
145
|
-
- -
|
132
|
+
- - ">="
|
146
133
|
- !ruby/object:Gem::Version
|
147
134
|
version: 1.9.3
|
148
135
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
149
136
|
requirements:
|
150
|
-
- -
|
137
|
+
- - ">="
|
151
138
|
- !ruby/object:Gem::Version
|
152
139
|
version: '0'
|
153
140
|
requirements: []
|
154
141
|
rubyforge_project:
|
155
|
-
rubygems_version: 2.4.
|
142
|
+
rubygems_version: 2.4.6
|
156
143
|
signing_key:
|
157
144
|
specification_version: 4
|
158
145
|
summary: mega.co.nz ruby api
|
@@ -164,7 +151,8 @@ test_files:
|
|
164
151
|
- spec/integration/folder_operations_spec.rb
|
165
152
|
- spec/integration/login_spec.rb
|
166
153
|
- spec/integration/resume_download_spec.rb
|
167
|
-
- spec/integration/
|
154
|
+
- spec/integration/rmega-dl_spec.rb
|
155
|
+
- spec/integration/rmega-up_spec.rb
|
168
156
|
- spec/integration_spec_helper.rb
|
169
157
|
- spec/rmega/lib/cli_spec.rb
|
170
158
|
- spec/rmega/lib/session_spec.rb
|