rmega 0.2.0 → 0.2.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.
- 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
|