leecher 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|