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.
Files changed (46) hide show
  1. data/.gitignore +5 -4
  2. data/.travis.yml +2 -0
  3. data/Gemfile +1 -2
  4. data/Gemfile.lock +48 -0
  5. data/README +58 -0
  6. data/Rakefile +5 -7
  7. data/bin/leecher +2 -265
  8. data/bin/leecher-aria2-event-hook +12 -0
  9. data/config/amqp.example.yml +28 -0
  10. data/config/arguments.rb +12 -0
  11. data/config/aria2.example.yml +6 -0
  12. data/config/boot.rb +64 -0
  13. data/config/environment.rb +22 -0
  14. data/config/environments/development.rb +2 -0
  15. data/config/environments/production.rb +5 -0
  16. data/config/environments/test.rb +2 -0
  17. data/config/gheed.example.yml +2 -0
  18. data/config/leecher.example.yml +1 -0
  19. data/config/post-daemonize/readme +5 -0
  20. data/config/pre-daemonize/amqp.rb +6 -0
  21. data/config/pre-daemonize/json.rb +3 -0
  22. data/config/pre-daemonize/readme +12 -0
  23. data/config/pre-daemonize/safely.rb +13 -0
  24. data/leecher.gemspec +13 -4
  25. data/lib/leecher.rb +24 -2
  26. data/lib/leecher/aria2/client.rb +65 -0
  27. data/lib/leecher/client.rb +31 -211
  28. data/lib/leecher/gheed/client.rb +35 -0
  29. data/lib/leecher/metalink_queue.rb +68 -0
  30. data/lib/leecher/version.rb +1 -1
  31. data/libexec/leecher-daemon.rb +34 -0
  32. data/script/console +4 -0
  33. data/script/destroy +4 -0
  34. data/script/generate +4 -0
  35. data/spec/aria2/client_spec.rb +26 -0
  36. data/spec/client_spec.rb +42 -83
  37. data/spec/metalink_queue_spec.rb +25 -0
  38. data/spec/spec.opts +2 -0
  39. data/spec/spec_helper.rb +0 -2
  40. data/tasks/rspec.rake +6 -0
  41. metadata +134 -90
  42. data/config/aria2.conf +0 -15
  43. data/config/client.yml +0 -32
  44. data/lib/leecher/log.rb +0 -91
  45. data/lib/leecher/shellout.rb +0 -27
  46. data/spec/shellout_spec.rb +0 -26
data/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
- *.gem
2
- .bundle
3
- Gemfile.lock
4
- pkg/*
1
+ log
2
+ config/gheed.yml
3
+ config/amqp.yml
4
+ config/aria2.yml
5
+ config/leecher.yml
@@ -0,0 +1,2 @@
1
+ rvm:
2
+ - 1.9.3
data/Gemfile CHANGED
@@ -1,4 +1,3 @@
1
- source "http://rubygems.org"
1
+ source :rubygems
2
2
 
3
- # Specify your gem's dependencies in leecher.gemspec
4
3
  gemspec
@@ -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 'bundler'
1
+ require "bundler"
2
2
  Bundler::GemHelper.install_tasks
3
3
 
4
- require "rspec/core/rake_task"
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
@@ -1,268 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- module Leecher
4
- class Cli
3
+ require File.expand_path('../../config/environment', __FILE__)
5
4
 
6
- require "optparse"
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