leecher 0.0.1 → 0.1.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/bin/leecher +35 -24
- data/config/aria2.conf +15 -0
- data/config/client.yml +8 -5
- data/lib/leecher/client.rb +105 -26
- data/lib/leecher/log.rb +4 -2
- data/lib/leecher/shellout.rb +1 -1
- data/lib/leecher/version.rb +1 -1
- data/spec/client_spec.rb +24 -20
- data/spec/shellout_spec.rb +26 -0
- metadata +6 -4
data/bin/leecher
CHANGED
@@ -6,6 +6,7 @@ module Leecher
|
|
6
6
|
require "optparse"
|
7
7
|
require "yaml"
|
8
8
|
require "erubis"
|
9
|
+
require "fileutils"
|
9
10
|
require "leecher/log"
|
10
11
|
require "leecher/client"
|
11
12
|
|
@@ -17,7 +18,7 @@ module Leecher
|
|
17
18
|
|
18
19
|
|
19
20
|
def initialize()
|
20
|
-
@
|
21
|
+
@config_dirname = File.join(ENV["HOME"], ".leecher")
|
21
22
|
@foreground = false
|
22
23
|
end
|
23
24
|
|
@@ -29,10 +30,10 @@ module Leecher
|
|
29
30
|
o.separator("")
|
30
31
|
o.separator("options:")
|
31
32
|
|
32
|
-
o.on("--config=
|
33
|
-
"
|
34
|
-
"Default: #{@
|
35
|
-
@
|
33
|
+
o.on("--config=DIR",
|
34
|
+
"Dir containing settings",
|
35
|
+
"Default: #{@config_dirname}") do |v|
|
36
|
+
@config_dirname = v
|
36
37
|
end
|
37
38
|
|
38
39
|
o.on("-f", "--[no-]foreground",
|
@@ -41,12 +42,6 @@ module Leecher
|
|
41
42
|
@foreground = v
|
42
43
|
end
|
43
44
|
|
44
|
-
o.on("--getting-started",
|
45
|
-
"Run a tutorial which will give you a config") do
|
46
|
-
do_getting_started()
|
47
|
-
Kernel.exit!(RC_OK)
|
48
|
-
end
|
49
|
-
|
50
45
|
o.separator("")
|
51
46
|
o.on("-h", "--help", "You're reading it :-)") do
|
52
47
|
puts o
|
@@ -59,28 +54,41 @@ module Leecher
|
|
59
54
|
end
|
60
55
|
|
61
56
|
def do_getting_started()
|
62
|
-
log.info("We'll ask you a few questions then write you a config in #{@
|
57
|
+
log.info("We'll ask you a few questions then write you a config in #{@config_dirname}")
|
63
58
|
log.info(".. you can change where your config lives with -c/--config")
|
64
59
|
|
65
|
-
STDOUT.puts("\n
|
66
|
-
config =
|
60
|
+
STDOUT.puts("\n")
|
61
|
+
config = { :config_dirname => @config_dirname }
|
67
62
|
[
|
68
63
|
[:username, "your username on the website?", :required],
|
69
64
|
[:password, ".. and the password for that user?", :password],
|
70
|
-
[:log_fn, "filename to log to:",
|
65
|
+
[:log_fn, "filename to log to:", File.join(@config_dirname, "log")],
|
71
66
|
[:host, "where does the amqp queue live?", "nzb.trim.za.net"],
|
72
67
|
[:aria2_bin, "which aria2 must I use?", %x{which aria2c}.strip()],
|
68
|
+
[:aria2_dir, "what whould you like your default download directory to be?", Dir.pwd],
|
69
|
+
[:aria2_connections, "how many connections would you like to download with?", 5],
|
70
|
+
[:aria2_rpc_user, "what is your aria2 rpc username?", :required],
|
71
|
+
[:aria2_rpc_password, "what is your aria2 rpc password?", :password],
|
72
|
+
[:aria2_rpc_port, "what is your aria2 rpc port?", 6800],
|
73
73
|
].each do |setting, question, default|
|
74
74
|
config[setting] = get_user_input(question, default)
|
75
75
|
end
|
76
|
-
|
76
|
+
|
77
|
+
FileUtils.mkdir_p(@config_dirname)
|
77
78
|
erb = Erubis::Eruby.new(File.read(File.join(File.dirname(__FILE__),
|
78
79
|
"../config/client.yml")))
|
79
|
-
File.open(@
|
80
|
+
File.open(File.join(@config_dirname, "config.yml"), "w") do |f|
|
81
|
+
f.puts(erb.result(config))
|
82
|
+
end
|
83
|
+
|
84
|
+
erb = Erubis::Eruby.new(File.read(File.join(File.dirname(__FILE__),
|
85
|
+
"../config/aria2.conf")))
|
86
|
+
|
87
|
+
File.open(File.join(@config_dirname, "aria2.conf"), "w") do |f|
|
80
88
|
f.puts(erb.result(config))
|
81
89
|
end
|
82
90
|
|
83
|
-
STDOUT.puts("\n
|
91
|
+
STDOUT.puts("\n")
|
84
92
|
log.info("Looking good, now take us for a real spin!")
|
85
93
|
end
|
86
94
|
|
@@ -124,7 +132,7 @@ module Leecher
|
|
124
132
|
end
|
125
133
|
|
126
134
|
def run()
|
127
|
-
config = load_config(@
|
135
|
+
config = load_config(@config_dirname)
|
128
136
|
unless @foreground
|
129
137
|
log.info("Fading into the ether ..")
|
130
138
|
make_daemon(config[:daemon])
|
@@ -143,17 +151,20 @@ module Leecher
|
|
143
151
|
@log ||= Leecher::Log.new()
|
144
152
|
end
|
145
153
|
|
146
|
-
def load_config(
|
147
|
-
|
154
|
+
def load_config(config_dirname)
|
155
|
+
unless File.exist?(config_dirname)
|
156
|
+
log.info("You don't have a leecher config setup")
|
157
|
+
do_getting_started()
|
158
|
+
end
|
159
|
+
|
160
|
+
config = YAML.load_file(File.join(config_dirname, "config.yml"))
|
148
161
|
if (log_conf = config[:log])
|
149
162
|
@log = Leecher::Log.new(config[:log][:level],
|
150
163
|
config[:log][:filename])
|
151
164
|
end
|
152
165
|
config
|
153
166
|
rescue Errno::ENOENT
|
154
|
-
log.error("No such config file #{
|
155
|
-
log.info("New user? Try #{Leecher::Log::ANSI_GREEN}"\
|
156
|
-
"--getting-started#{Leecher::Log::ANSI_NORMAL}")
|
167
|
+
log.error("No such config file #{config_dirname.inspect()}")
|
157
168
|
Kernel.exit!(RC_CONF_MISSING)
|
158
169
|
end
|
159
170
|
|
data/config/aria2.conf
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
dir=<%= aria2_dir %>
|
2
|
+
log=/tmp/aria.log
|
3
|
+
check-integrity=true
|
4
|
+
max-connection-per-server=<%= aria2_connections %>
|
5
|
+
min-split-size=1M
|
6
|
+
follow-torrent=mem
|
7
|
+
max-overall-upload-limit=30K
|
8
|
+
follow-metalink=mem
|
9
|
+
metalink-servers=<%= aria2_connections %>
|
10
|
+
rpc-listen-all=false
|
11
|
+
rpc-listen-port=<%= aria2_rpc_port %>
|
12
|
+
rpc-passwd=<%= aria2_rpc_password %>
|
13
|
+
rpc-user=<%= aria2_rpc_user %>
|
14
|
+
allow-piece-length-change=true
|
15
|
+
save-session=<%= File.join(config_dirname, "session.aria2") %>
|
data/config/client.yml
CHANGED
@@ -19,11 +19,14 @@
|
|
19
19
|
:heartbeat: 300
|
20
20
|
:connect_timeout: 5
|
21
21
|
:aria2_opts:
|
22
|
+
:config: <%= File.join(config_dirname, "aria2.conf") %>
|
22
23
|
:bin: <%= aria2_bin %>
|
23
|
-
:args: [-
|
24
|
+
:args: [-D, --enable-rpc]
|
25
|
+
:user: <%= aria2_rpc_user %>
|
26
|
+
:password: <%= aria2_rpc_password %>
|
27
|
+
:port: <%= aria2_rpc_port %>
|
24
28
|
:daemon:
|
25
|
-
:chdir:
|
29
|
+
:chdir: <%= File.join(config_dirname) %>
|
26
30
|
:umask: 0000
|
27
|
-
:
|
28
|
-
:
|
29
|
-
:stderr: nil
|
31
|
+
:stdout: <%= File.join(config_dirname, "stdout") %>
|
32
|
+
:stderr: <%= File.join(config_dirname, "stderr") %>
|
data/lib/leecher/client.rb
CHANGED
@@ -4,10 +4,25 @@ module Leecher
|
|
4
4
|
|
5
5
|
require "bunny"
|
6
6
|
require "leecher/shellout"
|
7
|
+
require 'xmlrpc/client'
|
7
8
|
|
8
9
|
include Leecher::Shellout
|
9
10
|
|
10
11
|
class ShutdownError < RuntimeError; end
|
12
|
+
|
13
|
+
# This is annoying: Bunny's errors don't inherit from a
|
14
|
+
# Bunny::Error parent, so we have to keep track of em :-(
|
15
|
+
BunnyErrors =
|
16
|
+
[
|
17
|
+
Bunny::ConnectionError,
|
18
|
+
Bunny::ForcedChannelCloseError,
|
19
|
+
Bunny::ForcedConnectionCloseError,
|
20
|
+
Bunny::MessageError,
|
21
|
+
Bunny::ProtocolError,
|
22
|
+
Bunny::ServerDownError,
|
23
|
+
Bunny::UnsubscribeError,
|
24
|
+
Bunny::AcknowledgementError,
|
25
|
+
]
|
11
26
|
|
12
27
|
attr_accessor :username
|
13
28
|
attr_accessor :password
|
@@ -15,6 +30,7 @@ module Leecher
|
|
15
30
|
attr_accessor :bunny_opts
|
16
31
|
attr_accessor :aria2_opts
|
17
32
|
attr_accessor :log
|
33
|
+
attr_accessor :rpc_server
|
18
34
|
|
19
35
|
def initialize(username,
|
20
36
|
password,
|
@@ -28,6 +44,7 @@ module Leecher
|
|
28
44
|
self.bunny_opts = bunny_opts
|
29
45
|
self.aria2_opts = aria2_opts
|
30
46
|
self.log = log
|
47
|
+
self.rpc_server = make_rpc_client(aria2_opts)
|
31
48
|
end
|
32
49
|
|
33
50
|
|
@@ -39,18 +56,45 @@ module Leecher
|
|
39
56
|
@dont_kill_me = false
|
40
57
|
[:INT, :TERM].each do |sig|
|
41
58
|
trap(sig) do
|
59
|
+
log.info("Caught SIG#{sig}")
|
60
|
+
|
61
|
+
# Already asked to go away - impatient users :-(
|
62
|
+
if @graceful_shutdown
|
63
|
+
log.info("I'm tired of catching this so I'm going to (unsafely) go away.")
|
64
|
+
raise ShutdownError.new("SIG#{sig}")
|
65
|
+
end
|
66
|
+
|
42
67
|
@graceful_shutdown = true
|
43
|
-
log.info("Caught SIG{sig}")
|
44
68
|
raise ShutdownError.new("SIG#{sig}") unless @dont_kill_me
|
45
69
|
end
|
46
70
|
end
|
47
71
|
|
48
|
-
q = get_queue(@bunny, username, metalink_queue_name)
|
49
72
|
until @graceful_shutdown
|
50
73
|
begin
|
74
|
+
q = get_queue(@bunny, username, metalink_queue_name)
|
51
75
|
drain_metalink_queue(q)
|
52
76
|
rescue ShutdownError => e
|
53
77
|
log.info(["Going away because of", e.message].join(": "))
|
78
|
+
rescue *BunnyErrors => e
|
79
|
+
log.warn("Something has gone wrong with our "\
|
80
|
+
"connection. We're going to try start again.")
|
81
|
+
e.backtrace.each do |line|
|
82
|
+
log.debug(line)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Silently try throw away our existing bunny.
|
86
|
+
if @bunny.status == :connected
|
87
|
+
@bunny.stop() rescue Exception
|
88
|
+
end
|
89
|
+
|
90
|
+
log.debug("We've shot the old bunny - we'll make a new one in "\
|
91
|
+
"a few seconds.")
|
92
|
+
Kernel.sleep(3)
|
93
|
+
|
94
|
+
# Start again.
|
95
|
+
log.debug("Reconnecting to rabbitmq")
|
96
|
+
@bunny = make_bunny(self.bunny_opts)
|
97
|
+
@bunny.start()
|
54
98
|
rescue Exception => e
|
55
99
|
log.error([e.class.name, e.message].join(": "))
|
56
100
|
e.backtrace.each do |line|
|
@@ -67,35 +111,29 @@ module Leecher
|
|
67
111
|
def get_queue(bunny, *queue_joinable_name)
|
68
112
|
bunny.queue(File.join(*queue_joinable_name))
|
69
113
|
end
|
70
|
-
|
114
|
+
|
115
|
+
# Continuously get messages (metalinks) off the rabbitmq queue and
|
116
|
+
# invoke aria2c to download things.
|
71
117
|
def drain_metalink_queue(q)
|
118
|
+
log.info("Subscribing to #{q.name.inspect()}")
|
119
|
+
|
72
120
|
next_queue_message(q) do |msg|
|
73
121
|
@dont_kill_me = true
|
74
122
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
stderr_wr.close()
|
82
|
-
|
83
|
-
if rc == 0
|
84
|
-
log.info("aria2c exited happily")
|
123
|
+
log.info("Will call aria2.addUri #{msg.inspect()}")
|
124
|
+
|
125
|
+
#FIXME is it possible something can go wrong but not throw an exception?
|
126
|
+
begin
|
127
|
+
status = call_rpc("aria2.addUri", [msg])
|
128
|
+
log.info("Added uri to aria with gid: #{status}")
|
85
129
|
return true
|
86
|
-
|
87
|
-
log.error("
|
88
|
-
stdout_rd.each_line do |line|
|
89
|
-
log.error(line)
|
90
|
-
end
|
91
|
-
stderr_rd.each_line do |line|
|
92
|
-
log.debug(line)
|
93
|
-
end
|
130
|
+
rescue Exception => e
|
131
|
+
log.error("Failed to add uri to aria2c: #{e.message}")
|
94
132
|
return false
|
95
133
|
end
|
96
|
-
|
97
|
-
@dont_kill_me = false
|
98
134
|
end
|
135
|
+
ensure
|
136
|
+
@dont_kill_me = false
|
99
137
|
end
|
100
138
|
|
101
139
|
def next_queue_message(q, &block)
|
@@ -114,11 +152,52 @@ module Leecher
|
|
114
152
|
end
|
115
153
|
end
|
116
154
|
|
117
|
-
def
|
155
|
+
def make_rpc_client(aria2_opts)
|
156
|
+
XMLRPC::Client.new2("http://#{aria2_opts[:user]}:#{aria2_opts[:password]}@127.0.0.1:#{aria2_opts[:port]}/rpc")
|
157
|
+
end
|
158
|
+
|
159
|
+
def call_rpc(method, *args)
|
160
|
+
retried = false
|
161
|
+
begin
|
162
|
+
if args.nil? || args.empty?
|
163
|
+
rpc_server.call(method)
|
164
|
+
else
|
165
|
+
rpc_server.call(method, *args)
|
166
|
+
end
|
167
|
+
rescue Errno::ECONNREFUSED => e
|
168
|
+
if retried == true
|
169
|
+
raise e
|
170
|
+
else
|
171
|
+
retried = true
|
172
|
+
shellout(make_aria_exec, nil, nil, nil)
|
173
|
+
sleep 10
|
174
|
+
retry
|
175
|
+
end
|
176
|
+
rescue Errno::EPIPE => e
|
177
|
+
# The RPC client doesn't realise if the server closes the
|
178
|
+
# connection. So, we retry (which seems to remake the connection?).
|
179
|
+
if retried == true
|
180
|
+
raise e
|
181
|
+
else
|
182
|
+
retried = true
|
183
|
+
retry
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def make_aria_exec
|
189
|
+
args = aria2_opts[:args]
|
190
|
+
|
191
|
+
if aria2_opts.has_key?(:config)
|
192
|
+
args.push([
|
193
|
+
"--conf-path",
|
194
|
+
aria2_opts[:config],
|
195
|
+
].join("="))
|
196
|
+
end
|
197
|
+
|
118
198
|
[
|
119
199
|
aria2_opts[:bin],
|
120
|
-
|
121
|
-
uri.to_s(),
|
200
|
+
args.join(" "),
|
122
201
|
].join(" ")
|
123
202
|
end
|
124
203
|
end # class Client
|
data/lib/leecher/log.rb
CHANGED
@@ -61,6 +61,7 @@ module Leecher
|
|
61
61
|
|
62
62
|
if @log_fd and not @log_fd.closed?
|
63
63
|
@log_fd.puts("%s %s %s" % [iso8601_timestr, level.to_s.upcase, message])
|
64
|
+
@log_fd.flush()
|
64
65
|
end
|
65
66
|
end
|
66
67
|
|
@@ -82,8 +83,9 @@ module Leecher
|
|
82
83
|
}
|
83
84
|
def log_for_humans(iso8601_timestr, log_level, log_msg)
|
84
85
|
level_str, level_colour = HUMAN_LOG_LEVELS[log_level]
|
85
|
-
puts("#{ANSI_BOLD_WHITE}#{iso8601_timestr}#{ANSI_NORMAL} "\
|
86
|
-
|
86
|
+
STDOUT.puts("#{ANSI_BOLD_WHITE}#{iso8601_timestr}#{ANSI_NORMAL} "\
|
87
|
+
"#{level_colour}#{"%5.5s" % level_str}#{ANSI_NORMAL} #{log_msg}")
|
88
|
+
STDOUT.flush()
|
87
89
|
end
|
88
90
|
end
|
89
91
|
end
|
data/lib/leecher/shellout.rb
CHANGED
data/lib/leecher/version.rb
CHANGED
data/spec/client_spec.rb
CHANGED
@@ -41,34 +41,38 @@ describe Leecher::Client do
|
|
41
41
|
end
|
42
42
|
|
43
43
|
describe "#drain_metalink_queue" do
|
44
|
-
|
45
|
-
|
46
|
-
@
|
47
|
-
|
48
|
-
|
49
|
-
|
44
|
+
|
45
|
+
before do
|
46
|
+
@q = double("q")
|
47
|
+
@q.should_receive(:name).
|
48
|
+
any_number_of_times.
|
49
|
+
and_return("qq")
|
50
50
|
end
|
51
51
|
|
52
|
-
|
52
|
+
|
53
|
+
it "should rpc to aria2" do
|
53
54
|
@c.stub(:next_queue_message).
|
54
55
|
and_yield("http://some/url")
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
56
|
+
rpc_server = double("rpc_server")
|
57
|
+
rpc_server.stub(:call).
|
58
|
+
and_return(true)
|
59
|
+
@c.stub(:rpc_server).
|
60
|
+
and_return(rpc_server)
|
61
|
+
@c.drain_metalink_queue(@q).should be_true
|
60
62
|
end
|
61
63
|
|
62
|
-
it "should return according to the
|
64
|
+
it "should return according to the success of the rpc" do
|
63
65
|
@c.stub(:next_queue_message).
|
64
66
|
and_yield("http://some/url")
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
67
|
+
rpc_server = double("rpc_server")
|
68
|
+
@c.stub(:rpc_server).
|
69
|
+
and_return(rpc_server)
|
70
|
+
rpc_server.stub(:call).
|
71
|
+
and_return(true)
|
72
|
+
@c.drain_metalink_queue(@q).should be_true
|
73
|
+
rpc_server.stub(:call).
|
74
|
+
and_raise(RuntimeError)
|
75
|
+
@c.drain_metalink_queue(@q).should be_false
|
72
76
|
end
|
73
77
|
end
|
74
78
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
require 'leecher/shellout'
|
4
|
+
|
5
|
+
include Leecher::Shellout
|
6
|
+
|
7
|
+
describe Leecher::Shellout do
|
8
|
+
it "should return 0 for true" do
|
9
|
+
shellout("/bin/true").success?.should be_true
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should return 1 for false" do
|
13
|
+
shellout("/bin/false").success?.should be_false
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should work with pipes" do
|
17
|
+
stdin_rd, stdin_wr = IO.pipe()
|
18
|
+
stdout_rd, stdout_wr = IO.pipe()
|
19
|
+
stdin_wr.puts("foo")
|
20
|
+
stdin_wr.close()
|
21
|
+
child_status = shellout("cat", stdin_rd, stdout_wr)
|
22
|
+
stdout_wr.close()
|
23
|
+
stdout_rd.gets().should == "foo\n"
|
24
|
+
stdout_rd.close()
|
25
|
+
end
|
26
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: leecher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
- 0
|
9
8
|
- 1
|
10
|
-
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Marc Bowes
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-05-09 00:00:00 +02:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -74,6 +74,7 @@ files:
|
|
74
74
|
- Gemfile
|
75
75
|
- Rakefile
|
76
76
|
- bin/leecher
|
77
|
+
- config/aria2.conf
|
77
78
|
- config/client.yml
|
78
79
|
- leecher.gemspec
|
79
80
|
- lib/leecher.rb
|
@@ -82,6 +83,7 @@ files:
|
|
82
83
|
- lib/leecher/shellout.rb
|
83
84
|
- lib/leecher/version.rb
|
84
85
|
- spec/client_spec.rb
|
86
|
+
- spec/shellout_spec.rb
|
85
87
|
- spec/spec_helper.rb
|
86
88
|
has_rdoc: true
|
87
89
|
homepage: ""
|