leecher 0.2.3 → 1.0.0
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/.gitignore +5 -4
- data/.travis.yml +2 -0
- data/Gemfile +1 -2
- data/Gemfile.lock +48 -0
- data/README +58 -0
- data/Rakefile +5 -7
- data/bin/leecher +2 -265
- data/bin/leecher-aria2-event-hook +12 -0
- data/config/amqp.example.yml +28 -0
- data/config/arguments.rb +12 -0
- data/config/aria2.example.yml +6 -0
- data/config/boot.rb +64 -0
- data/config/environment.rb +22 -0
- data/config/environments/development.rb +2 -0
- data/config/environments/production.rb +5 -0
- data/config/environments/test.rb +2 -0
- data/config/gheed.example.yml +2 -0
- data/config/leecher.example.yml +1 -0
- data/config/post-daemonize/readme +5 -0
- data/config/pre-daemonize/amqp.rb +6 -0
- data/config/pre-daemonize/json.rb +3 -0
- data/config/pre-daemonize/readme +12 -0
- data/config/pre-daemonize/safely.rb +13 -0
- data/leecher.gemspec +13 -4
- data/lib/leecher.rb +24 -2
- data/lib/leecher/aria2/client.rb +65 -0
- data/lib/leecher/client.rb +31 -211
- data/lib/leecher/gheed/client.rb +35 -0
- data/lib/leecher/metalink_queue.rb +68 -0
- data/lib/leecher/version.rb +1 -1
- data/libexec/leecher-daemon.rb +34 -0
- data/script/console +4 -0
- data/script/destroy +4 -0
- data/script/generate +4 -0
- data/spec/aria2/client_spec.rb +26 -0
- data/spec/client_spec.rb +42 -83
- data/spec/metalink_queue_spec.rb +25 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +0 -2
- data/tasks/rspec.rake +6 -0
- metadata +134 -90
- data/config/aria2.conf +0 -15
- data/config/client.yml +0 -32
- data/lib/leecher/log.rb +0 -91
- data/lib/leecher/shellout.rb +0 -27
- data/spec/shellout_spec.rb +0 -26
data/.gitignore
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
2
|
-
.
|
3
|
-
|
4
|
-
|
1
|
+
log
|
2
|
+
config/gheed.yml
|
3
|
+
config/amqp.yml
|
4
|
+
config/aria2.yml
|
5
|
+
config/leecher.yml
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/Gemfile.lock
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
leecher (1.0.0)
|
5
|
+
amqp
|
6
|
+
daemon-kit
|
7
|
+
erubis
|
8
|
+
multi_json
|
9
|
+
safely
|
10
|
+
yajl-ruby
|
11
|
+
|
12
|
+
GEM
|
13
|
+
remote: http://rubygems.org/
|
14
|
+
specs:
|
15
|
+
amq-client (0.9.4)
|
16
|
+
amq-protocol (>= 0.9.4)
|
17
|
+
eventmachine
|
18
|
+
amq-protocol (0.9.4)
|
19
|
+
amqp (0.9.7)
|
20
|
+
amq-client (~> 0.9.4)
|
21
|
+
amq-protocol (>= 0.9.4)
|
22
|
+
eventmachine
|
23
|
+
daemon-kit (0.1.8.2)
|
24
|
+
eventmachine (>= 0.12.10)
|
25
|
+
safely (>= 0.3.1)
|
26
|
+
diff-lcs (1.1.3)
|
27
|
+
erubis (2.7.0)
|
28
|
+
eventmachine (0.12.10)
|
29
|
+
multi_json (1.3.6)
|
30
|
+
rake (0.9.2.2)
|
31
|
+
rspec (2.11.0)
|
32
|
+
rspec-core (~> 2.11.0)
|
33
|
+
rspec-expectations (~> 2.11.0)
|
34
|
+
rspec-mocks (~> 2.11.0)
|
35
|
+
rspec-core (2.11.0)
|
36
|
+
rspec-expectations (2.11.1)
|
37
|
+
diff-lcs (~> 1.1.3)
|
38
|
+
rspec-mocks (2.11.1)
|
39
|
+
safely (0.3.2)
|
40
|
+
yajl-ruby (1.1.0)
|
41
|
+
|
42
|
+
PLATFORMS
|
43
|
+
ruby
|
44
|
+
|
45
|
+
DEPENDENCIES
|
46
|
+
leecher!
|
47
|
+
rake
|
48
|
+
rspec
|
data/README
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
daemon-kit README
|
2
|
+
================
|
3
|
+
|
4
|
+
daemon-kit has generated a skeleton Ruby daemon for you to build on. Please read
|
5
|
+
through this file to ensure you get going quickly.
|
6
|
+
|
7
|
+
Directories
|
8
|
+
===========
|
9
|
+
|
10
|
+
bin/
|
11
|
+
leecher - Stub executable to control your daemon with
|
12
|
+
|
13
|
+
config/
|
14
|
+
Environment configuration files
|
15
|
+
|
16
|
+
lib/
|
17
|
+
Place for your libraries
|
18
|
+
|
19
|
+
libexec/
|
20
|
+
leecher.rb - Your daemon code
|
21
|
+
|
22
|
+
log/
|
23
|
+
Log files based on the environment name
|
24
|
+
|
25
|
+
spec/
|
26
|
+
rspec's home
|
27
|
+
|
28
|
+
tasks/
|
29
|
+
Place for rake tasks
|
30
|
+
|
31
|
+
vendor/
|
32
|
+
Place for unpacked gems and DaemonKit
|
33
|
+
|
34
|
+
tmp/
|
35
|
+
Scratch folder
|
36
|
+
|
37
|
+
|
38
|
+
Logging
|
39
|
+
=======
|
40
|
+
|
41
|
+
One of the biggest issues with writing daemons are getting insight into what your
|
42
|
+
daemons are doing. Logging with daemon-kit is simplified as DaemonKit creates log
|
43
|
+
files per environment in log.
|
44
|
+
|
45
|
+
On all environments except production the log level is set to DEBUG, but you can
|
46
|
+
toggle the log level by sending the running daemon SIGUSR1 and SIGUSR2 signals.
|
47
|
+
SIGUSR1 will toggle between DEBUG and INFO levels, SIGUSR2 will blatantly set the
|
48
|
+
level to DEBUG.
|
49
|
+
|
50
|
+
Bundler
|
51
|
+
=======
|
52
|
+
|
53
|
+
daemon-kit uses bundler to ease the nightmare of dependency loading in Ruby
|
54
|
+
projects. daemon-kit and its generators all create/update the Gemfile in the
|
55
|
+
root of the daemon. You can satisfy the project's dependencies by running
|
56
|
+
`bundle install` from within the project root.
|
57
|
+
|
58
|
+
For more information on bundler, please see http://github.com/carlhuda/bundler
|
data/Rakefile
CHANGED
@@ -1,10 +1,8 @@
|
|
1
|
-
require
|
1
|
+
require "bundler"
|
2
2
|
Bundler::GemHelper.install_tasks
|
3
3
|
|
4
|
-
require
|
5
|
-
desc "Run specs"
|
6
|
-
RSpec::Core::RakeTask.new do |t|
|
7
|
-
t.rspec_opts = %w(-fs --color)
|
8
|
-
# t.ruby_opts = %w(-w)
|
9
|
-
end
|
4
|
+
require 'daemon_kit/tasks'
|
10
5
|
|
6
|
+
Dir[File.join(File.dirname(__FILE__), 'tasks/*.rake')].each { |rake| load rake }
|
7
|
+
|
8
|
+
task default: :spec
|
data/bin/leecher
CHANGED
@@ -1,268 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
4
|
-
class Cli
|
3
|
+
require File.expand_path('../../config/environment', __FILE__)
|
5
4
|
|
6
|
-
|
7
|
-
require "yaml"
|
8
|
-
require "erubis"
|
9
|
-
require "fileutils"
|
10
|
-
require "leecher/log"
|
11
|
-
require "leecher/client"
|
12
|
-
|
13
|
-
|
14
|
-
RC_OK = 0
|
15
|
-
RC_USAGE = 1
|
16
|
-
RC_CONF_MISSING = 2
|
17
|
-
RC_CATASTROPHE = 99
|
18
|
-
|
19
|
-
|
20
|
-
def initialize()
|
21
|
-
@config_dirname = File.join(ENV["HOME"], ".leecher")
|
22
|
-
@foreground = false
|
23
|
-
end
|
24
|
-
|
25
|
-
|
26
|
-
def parse_opts(argv)
|
27
|
-
optparser = OptionParser.new() do |o|
|
28
|
-
o.banner = "leecher [options]"
|
29
|
-
|
30
|
-
o.separator("")
|
31
|
-
o.separator("options:")
|
32
|
-
|
33
|
-
o.on("--config=DIR",
|
34
|
-
"Dir containing settings",
|
35
|
-
"Default: #{@config_dirname}") do |v|
|
36
|
-
@config_dirname = v
|
37
|
-
end
|
38
|
-
|
39
|
-
o.on("-f", "--[no-]foreground",
|
40
|
-
"Run as a loop in a daemon?",
|
41
|
-
"Default #{@foreground}") do |v|
|
42
|
-
@foreground = v
|
43
|
-
end
|
44
|
-
|
45
|
-
o.on("-q", "--print-queue",
|
46
|
-
"Print the active queue") do |v|
|
47
|
-
do_print_queue()
|
48
|
-
Kernel.exit!(RC_OK)
|
49
|
-
end
|
50
|
-
|
51
|
-
o.on("-p", "--pause",
|
52
|
-
"Pause downloads") do |v|
|
53
|
-
do_pause()
|
54
|
-
Kernel.exit!(RC_OK)
|
55
|
-
end
|
56
|
-
|
57
|
-
o.on("-r", "--unpause",
|
58
|
-
"Unpause downloads") do |v|
|
59
|
-
do_unpause()
|
60
|
-
Kernel.exit!(RC_OK)
|
61
|
-
end
|
62
|
-
|
63
|
-
o.separator("")
|
64
|
-
o.on("-h", "--help", "You're reading it :-)") do
|
65
|
-
puts o
|
66
|
-
Kernel.exit!(RC_USAGE)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
optparser.parse(argv)
|
71
|
-
self
|
72
|
-
end
|
73
|
-
|
74
|
-
def do_getting_started()
|
75
|
-
log.info("We'll ask you a few questions then write you a config in #{@config_dirname}")
|
76
|
-
log.info(".. you can change where your config lives with -c/--config")
|
77
|
-
|
78
|
-
STDOUT.puts("\n")
|
79
|
-
config = { :config_dirname => @config_dirname }
|
80
|
-
[
|
81
|
-
[:username, "your username on the website?", :required],
|
82
|
-
[:password, ".. and the password for that user?", :password],
|
83
|
-
[:host, "where does the amqp queue live?", "nzb.trim.za.net"],
|
84
|
-
[:aria2_bin, "which aria2 must I use?", %x{which aria2c}.strip()],
|
85
|
-
[:aria2_dir, "what whould you like your default download directory to be?", Dir.pwd],
|
86
|
-
[:aria2_connections, "how many connections would you like to download with?", 5],
|
87
|
-
[:aria2_rpc_user, "what is your aria2 rpc username?", :required],
|
88
|
-
[:aria2_rpc_password, "what is your aria2 rpc password?", :password],
|
89
|
-
[:aria2_rpc_port, "what is your aria2 rpc port?", 6800],
|
90
|
-
].each do |setting, question, default|
|
91
|
-
config[setting] = get_user_input(question, default)
|
92
|
-
end
|
93
|
-
|
94
|
-
FileUtils.mkdir_p(@config_dirname)
|
95
|
-
erb = Erubis::Eruby.new(File.read(File.join(File.dirname(__FILE__),
|
96
|
-
"../config/client.yml")))
|
97
|
-
File.open(File.join(@config_dirname, "config.yml"), "w") do |f|
|
98
|
-
f.puts(erb.result(config))
|
99
|
-
end
|
100
|
-
|
101
|
-
erb = Erubis::Eruby.new(File.read(File.join(File.dirname(__FILE__),
|
102
|
-
"../config/aria2.conf")))
|
103
|
-
|
104
|
-
File.open(File.join(@config_dirname, "aria2.conf"), "w") do |f|
|
105
|
-
f.puts(erb.result(config))
|
106
|
-
end
|
107
|
-
|
108
|
-
STDOUT.puts("\n")
|
109
|
-
log.info("Looking good, now take us for a real spin!")
|
110
|
-
end
|
111
|
-
|
112
|
-
# Somewhat lifted from
|
113
|
-
# http://sourceforge.net/apps/trac/aria2/browser/trunk/doc/xmlrpc/aria2mon
|
114
|
-
def do_print_queue()
|
115
|
-
config = load_config(@config_dirname)
|
116
|
-
client = make_client(config, @log)
|
117
|
-
log.info("Asking aria2 about it's active downloads ..")
|
118
|
-
active = client.call_rpc("aria2.tellActive")
|
119
|
-
active.each do |entry|
|
120
|
-
gid = entry["gid"]
|
121
|
-
complete, total, speed = [
|
122
|
-
"completedLength",
|
123
|
-
"totalLength",
|
124
|
-
"downloadSpeed",
|
125
|
-
].map do |field|
|
126
|
-
entry[field].to_i
|
127
|
-
end
|
128
|
-
remaining = total - complete
|
129
|
-
eta_str = if speed == 0
|
130
|
-
"N/A"
|
131
|
-
else
|
132
|
-
remsec = remaining/speed
|
133
|
-
hr = remsec/3600
|
134
|
-
remsec = remsec%3600
|
135
|
-
min = remsec/60
|
136
|
-
remsec = remsec%60
|
137
|
-
result = ""
|
138
|
-
result += "#{hr}h" if hr > 0
|
139
|
-
result += "#{min}m" if min > 0
|
140
|
-
result += "#{remsec}s"
|
141
|
-
end
|
142
|
-
no_files = entry["files"].size
|
143
|
-
first_file = entry["files"].first["uris"].first["uri"]
|
144
|
-
log.info("##{gid}: #{no_files} file(s) (currently: #{File.basename(first_file)}). "\
|
145
|
-
"#{complete}/#{total} (#{"%.2f" % (complete.to_f * 100 / total)}%). "\
|
146
|
-
"ETA: #{eta_str}")
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
def do_pause()
|
151
|
-
config = load_config(@config_dirname)
|
152
|
-
client = make_client(config, @log)
|
153
|
-
log.info("Asking aria2 to pause ..")
|
154
|
-
log.debug(client.call_rpc("aria2.pauseAll"))
|
155
|
-
end
|
156
|
-
|
157
|
-
def do_unpause()
|
158
|
-
config = load_config(@config_dirname)
|
159
|
-
client = make_client(config, @log)
|
160
|
-
log.info("Asking aria2 to pause ..")
|
161
|
-
log.debug(client.call_rpc("aria2.unpauseAll"))
|
162
|
-
end
|
163
|
-
|
164
|
-
def get_user_input(question, default)
|
165
|
-
default_text = case default
|
166
|
-
when :required, :password; nil
|
167
|
-
when :disable; "blank to disable"
|
168
|
-
else; "default: #{default}"
|
169
|
-
end
|
170
|
-
|
171
|
-
response = ask([
|
172
|
-
question,
|
173
|
-
default_text ? "(#{default_text})" : nil,
|
174
|
-
].compact.join(" "),
|
175
|
-
default == :password)
|
176
|
-
|
177
|
-
if response.empty?
|
178
|
-
response = case default
|
179
|
-
when :required, :password
|
180
|
-
log.error("That option was required! Try again :-)")
|
181
|
-
get_user_input(question, default)
|
182
|
-
when :disable
|
183
|
-
nil
|
184
|
-
else
|
185
|
-
default
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
return response
|
190
|
-
end
|
191
|
-
|
192
|
-
def ask(question, hide_input = false)
|
193
|
-
STDOUT.write(question + " ")
|
194
|
-
system("stty -echo") if hide_input
|
195
|
-
response = STDIN.gets().strip()
|
196
|
-
if hide_input
|
197
|
-
STDOUT.puts()
|
198
|
-
system("stty echo")
|
199
|
-
end
|
200
|
-
response
|
201
|
-
end
|
202
|
-
|
203
|
-
def run()
|
204
|
-
config = load_config(@config_dirname)
|
205
|
-
unless @foreground
|
206
|
-
log.info("Fading into the ether ..")
|
207
|
-
make_daemon(config[:daemon])
|
208
|
-
end
|
209
|
-
client = make_client(config, @log)
|
210
|
-
client.run()
|
211
|
-
Kernel.exit(RC_OK)
|
212
|
-
end
|
213
|
-
|
214
|
-
def log
|
215
|
-
@log ||= Leecher::Log.new()
|
216
|
-
end
|
217
|
-
|
218
|
-
def load_config(config_dirname)
|
219
|
-
unless File.exist?(config_dirname)
|
220
|
-
log.info("You don't have a leecher config setup")
|
221
|
-
do_getting_started()
|
222
|
-
end
|
223
|
-
|
224
|
-
config = YAML.load_file(File.join(config_dirname, "config.yml"))
|
225
|
-
if (log_conf = config[:log])
|
226
|
-
@log = Leecher::Log.new(config[:log][:level],
|
227
|
-
config[:log][:filename])
|
228
|
-
end
|
229
|
-
config
|
230
|
-
rescue Errno::ENOENT
|
231
|
-
log.error("No such config file #{config_dirname.inspect()}")
|
232
|
-
Kernel.exit!(RC_CONF_MISSING)
|
233
|
-
end
|
234
|
-
|
235
|
-
def make_daemon(config)
|
236
|
-
raise log.fatal("First fork failed") if (pid = Kernel.fork()) == -1
|
237
|
-
Kernel.exit!(RC_OK) unless pid.nil?
|
238
|
-
Process.setsid()
|
239
|
-
File.open(File.join(@config_dirname, "pid"), "w") do |f|
|
240
|
-
f.puts(Process.pid)
|
241
|
-
end
|
242
|
-
Dir.chdir(config[:chdir]) if config.has_key? :chdir
|
243
|
-
File.umask(config[:umask]) if config.has_key? :umask
|
244
|
-
[
|
245
|
-
[STDIN, :stdin],
|
246
|
-
[STDOUT, :stdout],
|
247
|
-
[STDERR, :stderr],
|
248
|
-
].each do |io, key|
|
249
|
-
if config.has_key? key
|
250
|
-
io.reopen(*config[key])
|
251
|
-
else
|
252
|
-
io.reopen("/dev/null")
|
253
|
-
end
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
|
-
def make_client(config, log)
|
258
|
-
Leecher::Client.new(config[:username],
|
259
|
-
config[:password],
|
260
|
-
config[:metalink_queue_name],
|
261
|
-
config[:bunny_opts],
|
262
|
-
config[:aria2_opts],
|
263
|
-
log)
|
264
|
-
end
|
265
|
-
end
|
266
|
-
end
|
267
|
-
|
268
|
-
Leecher::Cli.new().parse_opts(ARGV).run()
|
5
|
+
DaemonKit::Application.exec(DAEMON_ROOT + '/libexec/leecher-daemon.rb')
|
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.expand_path('../../config/environment', __FILE__)
|
4
|
+
|
5
|
+
leecher = Leecher.new
|
6
|
+
|
7
|
+
unless ARGV.size == 3
|
8
|
+
raise ArgumentError.new("#{ARGV.inspect} should be in format: [gid, number-of-files, file-path]")
|
9
|
+
end
|
10
|
+
|
11
|
+
gid, no_files, file_path = ARGV
|
12
|
+
leecher.state_change(Integer(gid))
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# AMQP client configuration file
|
2
|
+
|
3
|
+
# These values will be used to configure the ampq gem, any values
|
4
|
+
# omitted will let the gem use it's own defaults.
|
5
|
+
|
6
|
+
# The configuration specifies the following keys:
|
7
|
+
# * user - Username for the broker
|
8
|
+
# * pass - Password for the broker
|
9
|
+
# * host - Hostname where the broker is running
|
10
|
+
# * vhost - Vhost to connect to
|
11
|
+
# * port - Port where the broker is running
|
12
|
+
# * ssl - Use ssl or not
|
13
|
+
# * timeout - Timeout
|
14
|
+
|
15
|
+
defaults: &defaults
|
16
|
+
user: guest
|
17
|
+
pass: guest
|
18
|
+
host: localhost
|
19
|
+
vhost: /
|
20
|
+
|
21
|
+
development:
|
22
|
+
<<: *defaults
|
23
|
+
|
24
|
+
test:
|
25
|
+
<<: *defaults
|
26
|
+
|
27
|
+
production:
|
28
|
+
<<: *defaults
|