leecher 0.0.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.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Rakefile +10 -0
- data/bin/leecher +181 -0
- data/config/client.yml +29 -0
- data/leecher.gemspec +25 -0
- data/lib/leecher.rb +5 -0
- data/lib/leecher/client.rb +125 -0
- data/lib/leecher/log.rb +89 -0
- data/lib/leecher/shellout.rb +27 -0
- data/lib/leecher/version.rb +3 -0
- data/spec/client_spec.rb +74 -0
- data/spec/spec_helper.rb +2 -0
- metadata +121 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
data/bin/leecher
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module Leecher
|
4
|
+
class Cli
|
5
|
+
|
6
|
+
require "optparse"
|
7
|
+
require "yaml"
|
8
|
+
require "erubis"
|
9
|
+
require "leecher/log"
|
10
|
+
require "leecher/client"
|
11
|
+
|
12
|
+
|
13
|
+
RC_OK = 0
|
14
|
+
RC_USAGE = 1
|
15
|
+
RC_CONF_MISSING = 2
|
16
|
+
RC_CATASTROPHE = 99
|
17
|
+
|
18
|
+
|
19
|
+
def initialize()
|
20
|
+
@config_fn = File.join(ENV["HOME"], ".leecher.yml")
|
21
|
+
@foreground = false
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def parse_opts(argv)
|
26
|
+
optparser = OptionParser.new() do |o|
|
27
|
+
o.banner = "leecher [options]"
|
28
|
+
|
29
|
+
o.separator("")
|
30
|
+
o.separator("options:")
|
31
|
+
|
32
|
+
o.on("--config=FILE",
|
33
|
+
"File containing settings",
|
34
|
+
"Default: #{@config_fn}") do |v|
|
35
|
+
@config_fn = v
|
36
|
+
end
|
37
|
+
|
38
|
+
o.on("-f", "--[no-]foreground",
|
39
|
+
"Run as a loop in a daemon?",
|
40
|
+
"Default #{@foreground}") do |v|
|
41
|
+
@foreground = v
|
42
|
+
end
|
43
|
+
|
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
|
+
o.separator("")
|
51
|
+
o.on("-h", "--help", "You're reading it :-)") do
|
52
|
+
puts o
|
53
|
+
Kernel.exit!(RC_USAGE)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
optparser.parse(argv)
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
def do_getting_started()
|
62
|
+
log.info("We'll ask you a few questions then write you a config in #{@config_fn}")
|
63
|
+
log.info(".. you can change where your config lives with -c/--config")
|
64
|
+
|
65
|
+
STDOUT.puts("\n\n")
|
66
|
+
config = Hash.new()
|
67
|
+
[
|
68
|
+
[:username, "your username on the website?", :required],
|
69
|
+
[:password, ".. and the password for that user?", :password],
|
70
|
+
[:log_fn, "filename to log to:", :disable],
|
71
|
+
[:host, "where does the amqp queue live?", "nzb.trim.za.net"],
|
72
|
+
[:aria2_bin, "which aria2 must I use?", %x{which aria2c}.strip()],
|
73
|
+
].each do |setting, question, default|
|
74
|
+
config[setting] = get_user_input(question, default)
|
75
|
+
end
|
76
|
+
|
77
|
+
erb = Erubis::Eruby.new(File.read(File.join(File.dirname(__FILE__),
|
78
|
+
"../config/client.yml")))
|
79
|
+
File.open(@config_fn, "w") do |f|
|
80
|
+
f.puts(erb.result(config))
|
81
|
+
end
|
82
|
+
|
83
|
+
STDOUT.puts("\n\n")
|
84
|
+
log.info("Looking good, now take us for a real spin!")
|
85
|
+
end
|
86
|
+
|
87
|
+
def get_user_input(question, default)
|
88
|
+
default_text = case default
|
89
|
+
when :required, :password; nil
|
90
|
+
when :disable; "blank to disable"
|
91
|
+
else; "default: #{default}"
|
92
|
+
end
|
93
|
+
|
94
|
+
response = ask([
|
95
|
+
question,
|
96
|
+
default_text ? "(#{default_text})" : nil,
|
97
|
+
].compact.join(" "),
|
98
|
+
default == :password)
|
99
|
+
|
100
|
+
if response.empty?
|
101
|
+
response = case default
|
102
|
+
when :required, :password
|
103
|
+
log.error("That option was required! Try again :-)")
|
104
|
+
get_user_input(question, default)
|
105
|
+
when :disable
|
106
|
+
nil
|
107
|
+
else
|
108
|
+
default
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
return response
|
113
|
+
end
|
114
|
+
|
115
|
+
def ask(question, hide_input = false)
|
116
|
+
STDOUT.write(question + " ")
|
117
|
+
system("stty -echo") if hide_input
|
118
|
+
response = STDIN.gets().strip()
|
119
|
+
if hide_input
|
120
|
+
STDOUT.puts()
|
121
|
+
system("stty echo")
|
122
|
+
end
|
123
|
+
response
|
124
|
+
end
|
125
|
+
|
126
|
+
def run()
|
127
|
+
config = load_config(@config_fn)
|
128
|
+
unless @foreground
|
129
|
+
log.info("Fading into the ether ..")
|
130
|
+
make_daemon(config[:daemon])
|
131
|
+
end
|
132
|
+
client = Leecher::Client.new(config[:username],
|
133
|
+
config[:password],
|
134
|
+
config[:metalink_queue_name],
|
135
|
+
config[:bunny_opts],
|
136
|
+
config[:aria2_opts],
|
137
|
+
@log)
|
138
|
+
client.run()
|
139
|
+
Kernel.exit(RC_OK)
|
140
|
+
end
|
141
|
+
|
142
|
+
def log
|
143
|
+
@log ||= Leecher::Log.new()
|
144
|
+
end
|
145
|
+
|
146
|
+
def load_config(config_fn)
|
147
|
+
config = YAML.load_file(config_fn)
|
148
|
+
if (log_conf = config[:log])
|
149
|
+
@log = Leecher::Log.new(config[:log][:level],
|
150
|
+
config[:log][:filename])
|
151
|
+
end
|
152
|
+
config
|
153
|
+
rescue Errno::ENOENT
|
154
|
+
log.error("No such config file #{config_fn.inspect()}")
|
155
|
+
log.info("New user? Try #{Leecher::Log::ANSI_GREEN}"\
|
156
|
+
"--getting-started#{Leecher::Log::ANSI_NORMAL}")
|
157
|
+
Kernel.exit!(RC_CONF_MISSING)
|
158
|
+
end
|
159
|
+
|
160
|
+
def make_daemon(config)
|
161
|
+
raise log.fatal("First fork failed") if (pid = Kernel.fork()) == -1
|
162
|
+
Kernel.exit!(RC_OK) unless pid.nil?
|
163
|
+
Process.setsid()
|
164
|
+
Dir.chdir(config[:chdir]) if config.has_key? :chdir
|
165
|
+
File.umask(config[:umask]) if config.has_key? :umask
|
166
|
+
[
|
167
|
+
[STDIN, :stdin],
|
168
|
+
[STDOUT, :stdout],
|
169
|
+
[STDERR, :stderr],
|
170
|
+
].each do |io, key|
|
171
|
+
if config.has_key? key
|
172
|
+
io.reopen(*config[key])
|
173
|
+
else
|
174
|
+
io.reopen("/dev/null")
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
Leecher::Cli.new().parse_opts(ARGV).run()
|
data/config/client.yml
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
:username: <%= username %>
|
2
|
+
:password: <%= password %>
|
3
|
+
:metalink_queue_name: metalink
|
4
|
+
:log:
|
5
|
+
:level: :info
|
6
|
+
:filename: <%= log_fn %>
|
7
|
+
:bunny_opts:
|
8
|
+
:host: <%= host %>
|
9
|
+
:port: 5672
|
10
|
+
:vhost: /
|
11
|
+
:user: <%= username %>
|
12
|
+
:pass: <%= password %>
|
13
|
+
:ssl: false
|
14
|
+
:verify_ssl: false
|
15
|
+
:logfile: false
|
16
|
+
:logfile: false
|
17
|
+
:frame_max: 131072
|
18
|
+
:channel_max: 0
|
19
|
+
:heartbeat: 300
|
20
|
+
:connect_timeout: 5
|
21
|
+
:aria2_opts:
|
22
|
+
:bin: <%= aria2_bin %>
|
23
|
+
:args: [-x5, -V, --follow-metalink=mem]
|
24
|
+
:daemon:
|
25
|
+
:chdir: nil
|
26
|
+
:umask: 0000
|
27
|
+
:stdin: nil
|
28
|
+
:stdout: nil
|
29
|
+
:stderr: nil
|
data/leecher.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "leecher/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "leecher"
|
7
|
+
s.version = Leecher::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Marc Bowes"]
|
10
|
+
s.email = ["marcbowes+leecher@gmail.com"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = %q{Download files off an AMQP queue}
|
13
|
+
s.description = %q{A server populates a queue, this client downloads those files}
|
14
|
+
|
15
|
+
s.rubyforge_project = "leecher"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_development_dependency(%q<rspec>, [">=2"])
|
23
|
+
s.add_runtime_dependency(%q<bunny>)
|
24
|
+
s.add_runtime_dependency(%q<erubis>)
|
25
|
+
end
|
data/lib/leecher.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
module Leecher
|
2
|
+
|
3
|
+
class Client
|
4
|
+
|
5
|
+
require "bunny"
|
6
|
+
require "leecher/shellout"
|
7
|
+
|
8
|
+
include Leecher::Shellout
|
9
|
+
|
10
|
+
class ShutdownError < RuntimeError; end
|
11
|
+
|
12
|
+
attr_accessor :username
|
13
|
+
attr_accessor :password
|
14
|
+
attr_accessor :metalink_queue_name
|
15
|
+
attr_accessor :bunny_opts
|
16
|
+
attr_accessor :aria2_opts
|
17
|
+
attr_accessor :log
|
18
|
+
|
19
|
+
def initialize(username,
|
20
|
+
password,
|
21
|
+
metalink_queue_name,
|
22
|
+
bunny_opts,
|
23
|
+
aria2_opts,
|
24
|
+
log)
|
25
|
+
self.username = username
|
26
|
+
self.password = password
|
27
|
+
self.metalink_queue_name = metalink_queue_name
|
28
|
+
self.bunny_opts = bunny_opts
|
29
|
+
self.aria2_opts = aria2_opts
|
30
|
+
self.log = log
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def run()
|
35
|
+
@bunny = make_bunny(self.bunny_opts)
|
36
|
+
@bunny.start()
|
37
|
+
|
38
|
+
@graceful_shutdown = false
|
39
|
+
@dont_kill_me = false
|
40
|
+
[:INT, :TERM].each do |sig|
|
41
|
+
trap(sig) do
|
42
|
+
@graceful_shutdown = true
|
43
|
+
log.info("Caught SIG{sig}")
|
44
|
+
raise ShutdownError.new("SIG#{sig}") unless @dont_kill_me
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
q = get_queue(@bunny, username, metalink_queue_name)
|
49
|
+
until @graceful_shutdown
|
50
|
+
begin
|
51
|
+
drain_metalink_queue(q)
|
52
|
+
rescue ShutdownError => e
|
53
|
+
log.info(["Going away because of", e.message].join(": "))
|
54
|
+
rescue Exception => e
|
55
|
+
log.error([e.class.name, e.message].join(": "))
|
56
|
+
e.backtrace.each do |line|
|
57
|
+
log.debug(line)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def make_bunny(bunny_opts)
|
64
|
+
Bunny.new(bunny_opts)
|
65
|
+
end
|
66
|
+
|
67
|
+
def get_queue(bunny, *queue_joinable_name)
|
68
|
+
bunny.queue(File.join(*queue_joinable_name))
|
69
|
+
end
|
70
|
+
|
71
|
+
def drain_metalink_queue(q)
|
72
|
+
next_queue_message(q) do |msg|
|
73
|
+
@dont_kill_me = true
|
74
|
+
|
75
|
+
aria_exec = make_aria_exec(URI.parse(msg))
|
76
|
+
log.info("Will exec #{aria_exec.inspect()}")
|
77
|
+
stdout_rd, stdout_wr = IO.pipe()
|
78
|
+
stderr_rd, stderr_wr = IO.pipe()
|
79
|
+
rc = shellout(aria_exec, nil, stdout_wr, stderr_wr)
|
80
|
+
stdout_wr.close()
|
81
|
+
stderr_wr.close()
|
82
|
+
|
83
|
+
if rc == 0
|
84
|
+
log.info("aria2c exited happily")
|
85
|
+
return true
|
86
|
+
else
|
87
|
+
log.error("aria2c exited unhappily (rc = #{rc})")
|
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
|
94
|
+
return false
|
95
|
+
end
|
96
|
+
|
97
|
+
@dont_kill_me = false
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def next_queue_message(q, &block)
|
102
|
+
q.subscribe() do |msg|
|
103
|
+
payload = msg[:payload]
|
104
|
+
case payload
|
105
|
+
when :queue_empty
|
106
|
+
next
|
107
|
+
else
|
108
|
+
if block_given?
|
109
|
+
yield(payload)
|
110
|
+
else
|
111
|
+
payload
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def make_aria_exec(uri)
|
118
|
+
[
|
119
|
+
aria2_opts[:bin],
|
120
|
+
aria2_opts[:args].join(" "),
|
121
|
+
uri.to_s(),
|
122
|
+
].join(" ")
|
123
|
+
end
|
124
|
+
end # class Client
|
125
|
+
end # module Leecher
|
data/lib/leecher/log.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
module Leecher
|
2
|
+
|
3
|
+
class Log
|
4
|
+
|
5
|
+
require "time"
|
6
|
+
|
7
|
+
LEVELS = {
|
8
|
+
:debug => 0,
|
9
|
+
:warn => 1,
|
10
|
+
:info => 2,
|
11
|
+
:error => 3,
|
12
|
+
:fatal => 4,
|
13
|
+
}
|
14
|
+
|
15
|
+
attr_accessor :log_fn
|
16
|
+
attr_reader :level
|
17
|
+
|
18
|
+
def initialize(level = :debug,
|
19
|
+
log_fn = nil)
|
20
|
+
self.level = level
|
21
|
+
self.log_fn = log_fn
|
22
|
+
open_log_fd()
|
23
|
+
end
|
24
|
+
|
25
|
+
def open_log_fd
|
26
|
+
@log_fd.close() if @log_fd and not @log_fd.closed?
|
27
|
+
if self.log_fn
|
28
|
+
@log_fd = File.open(self.log_fn, "a")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def close_log_fd
|
33
|
+
if @log_fd and not @log_fd.closed?
|
34
|
+
@log_fd.flush()
|
35
|
+
@log_fd.close()
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
LEVELS.each_key do |level|
|
40
|
+
define_method(level) do |message|
|
41
|
+
write_to_log(level, message)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def level=(level)
|
46
|
+
@level = level
|
47
|
+
@_enum_level = LEVELS[level]
|
48
|
+
end
|
49
|
+
|
50
|
+
def write_to_log(level, message)
|
51
|
+
if LEVELS[level] >= @_enum_level
|
52
|
+
do_write_to_log(level, message)
|
53
|
+
else
|
54
|
+
# Toss it
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def do_write_to_log(level, message)
|
59
|
+
iso8601_timestr = Time.now.iso8601()
|
60
|
+
log_for_humans(iso8601_timestr, level, message)
|
61
|
+
|
62
|
+
if @log_fd and not @log_fd.closed?
|
63
|
+
@log_fd.puts("%s %s %s" % [iso8601_timestr, level.to_s.upcase, message])
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Map the log level (LOG_LEVEL_*) to an array containing
|
68
|
+
# [human_name:String, colour:String(ANSI escape sequence)]
|
69
|
+
ANSI_RED = "\033[0;31m"
|
70
|
+
ANSI_RED_INVERTED = "\033[7;31m"
|
71
|
+
ANSI_BROWN = "\033[0;33m"
|
72
|
+
ANSI_MAGENTA = "\033[0;35m"
|
73
|
+
ANSI_GREEN = "\033[0;32m"
|
74
|
+
ANSI_BOLD_WHITE = "\033[0;37m"
|
75
|
+
ANSI_NORMAL = "\033[0m"
|
76
|
+
HUMAN_LOG_LEVELS = {
|
77
|
+
:fatal => ["FATAL", ANSI_RED_INVERTED],
|
78
|
+
:error => ["ERROR", ANSI_RED],
|
79
|
+
:info => ["INFO", ANSI_GREEN],
|
80
|
+
:warn => ["WARN", ANSI_MAGENTA],
|
81
|
+
:debug => ["DEBUG", ANSI_BROWN],
|
82
|
+
}
|
83
|
+
def log_for_humans(iso8601_timestr, log_level, log_msg)
|
84
|
+
level_str, level_colour = HUMAN_LOG_LEVELS[log_level]
|
85
|
+
puts("#{ANSI_BOLD_WHITE}#{iso8601_timestr}#{ANSI_NORMAL} "\
|
86
|
+
"#{level_colour}#{"%5.5s" % level_str}#{ANSI_NORMAL} #{log_msg}")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Leecher
|
2
|
+
module Shellout
|
3
|
+
|
4
|
+
# FIXME: probably want to implement timeouts here..
|
5
|
+
def shellout(cmd, stdin = nil, stdout = nil, stderr = nil)
|
6
|
+
child_pid, child_status = nil
|
7
|
+
child_pid = Kernel.fork()
|
8
|
+
|
9
|
+
if child_pid
|
10
|
+
# Parent process executes this
|
11
|
+
child_status = (Process.waitpid2(child_pid)[1]).to_i >> 8
|
12
|
+
else
|
13
|
+
Process.setsid()
|
14
|
+
STDIN.reopen(stdin || "/dev/null")
|
15
|
+
STDOUT.reopen(stdout || "/dev/null")
|
16
|
+
STDERR.reopen(stderr || "/dev/null")
|
17
|
+
3.upto(256) { |fd| IO.new(fd).close rescue nil }
|
18
|
+
|
19
|
+
Kernel.exec(cmd)
|
20
|
+
# Never reached.
|
21
|
+
Kernel.exit!
|
22
|
+
end
|
23
|
+
|
24
|
+
child_status
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/spec_helper"
|
2
|
+
|
3
|
+
require "lib/leecher/client"
|
4
|
+
|
5
|
+
describe Leecher::Client do
|
6
|
+
|
7
|
+
before do
|
8
|
+
mock_log = double("log")
|
9
|
+
%w(debug warn info error fatal).each do |log_level|
|
10
|
+
mock_log.should_receive(log_level).any_number_of_times
|
11
|
+
end
|
12
|
+
@c = Leecher::Client.new("username",
|
13
|
+
"password",
|
14
|
+
"metalink",
|
15
|
+
double("bunny_opts"),
|
16
|
+
{
|
17
|
+
:bin => "/usr/bin/aria2c",
|
18
|
+
:args => ["-V"],
|
19
|
+
},
|
20
|
+
mock_log)
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
it "should initialize" do
|
25
|
+
@c.username.should == "username"
|
26
|
+
@c.password.should == "password"
|
27
|
+
@c.metalink_queue_name.should == "metalink"
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#next_queue_message" do
|
31
|
+
|
32
|
+
it "should only return when something is on the queue" do
|
33
|
+
bunny = double("bunny")
|
34
|
+
@c.stub(:make_bunny) { bunny }
|
35
|
+
q = double("q")
|
36
|
+
q.should_receive(:subscribe).
|
37
|
+
and_yield({:payload => :queue_empty}).
|
38
|
+
and_yield({:payload => "message"})
|
39
|
+
@c.next_queue_message(q).should == "message"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#drain_metalink_queue" do
|
44
|
+
|
45
|
+
it "should reject URIs that are not URIs" do
|
46
|
+
@c.stub(:next_queue_message).
|
47
|
+
and_yield(nil)
|
48
|
+
lambda { @c.drain_metalink_queue(double("q")) }.
|
49
|
+
should raise_error(URI::InvalidURIError)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should shellout to aria2" do
|
53
|
+
@c.stub(:next_queue_message).
|
54
|
+
and_yield("http://some/url")
|
55
|
+
@c.should_receive(:shellout).
|
56
|
+
with("/usr/bin/aria2c -V http://some/url",
|
57
|
+
anything(), anything(), anything()).
|
58
|
+
and_return(0)
|
59
|
+
@c.drain_metalink_queue(double("q")).should be_true
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should return according to the rc" do
|
63
|
+
@c.stub(:next_queue_message).
|
64
|
+
and_yield("http://some/url")
|
65
|
+
@c.stub(:shellout).
|
66
|
+
and_return(0)
|
67
|
+
q = double("q")
|
68
|
+
@c.drain_metalink_queue(q).should be_true
|
69
|
+
@c.stub(:shellout).
|
70
|
+
and_return(1)
|
71
|
+
@c.drain_metalink_queue(q).should be_false
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: leecher
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Marc Bowes
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-04-22 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rspec
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 7
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
version: "2"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: bunny
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id002
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: erubis
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
type: :runtime
|
62
|
+
version_requirements: *id003
|
63
|
+
description: A server populates a queue, this client downloads those files
|
64
|
+
email:
|
65
|
+
- marcbowes+leecher@gmail.com
|
66
|
+
executables:
|
67
|
+
- leecher
|
68
|
+
extensions: []
|
69
|
+
|
70
|
+
extra_rdoc_files: []
|
71
|
+
|
72
|
+
files:
|
73
|
+
- .gitignore
|
74
|
+
- Gemfile
|
75
|
+
- Rakefile
|
76
|
+
- bin/leecher
|
77
|
+
- config/client.yml
|
78
|
+
- leecher.gemspec
|
79
|
+
- lib/leecher.rb
|
80
|
+
- lib/leecher/client.rb
|
81
|
+
- lib/leecher/log.rb
|
82
|
+
- lib/leecher/shellout.rb
|
83
|
+
- lib/leecher/version.rb
|
84
|
+
- spec/client_spec.rb
|
85
|
+
- spec/spec_helper.rb
|
86
|
+
has_rdoc: true
|
87
|
+
homepage: ""
|
88
|
+
licenses: []
|
89
|
+
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
hash: 3
|
101
|
+
segments:
|
102
|
+
- 0
|
103
|
+
version: "0"
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
hash: 3
|
110
|
+
segments:
|
111
|
+
- 0
|
112
|
+
version: "0"
|
113
|
+
requirements: []
|
114
|
+
|
115
|
+
rubyforge_project: leecher
|
116
|
+
rubygems_version: 1.6.2
|
117
|
+
signing_key:
|
118
|
+
specification_version: 3
|
119
|
+
summary: Download files off an AMQP queue
|
120
|
+
test_files: []
|
121
|
+
|