leecher 1.0.0 → 1.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.
@@ -1,12 +1,14 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- leecher (1.0.0)
4
+ leecher (1.1.0)
5
5
  amqp
6
6
  daemon-kit
7
7
  erubis
8
8
  multi_json
9
+ rest-client
9
10
  safely
11
+ sys-filesystem
10
12
  yajl-ruby
11
13
 
12
14
  GEM
@@ -26,8 +28,12 @@ GEM
26
28
  diff-lcs (1.1.3)
27
29
  erubis (2.7.0)
28
30
  eventmachine (0.12.10)
31
+ ffi (1.1.0)
32
+ mime-types (1.19)
29
33
  multi_json (1.3.6)
30
34
  rake (0.9.2.2)
35
+ rest-client (1.6.7)
36
+ mime-types (>= 1.16)
31
37
  rspec (2.11.0)
32
38
  rspec-core (~> 2.11.0)
33
39
  rspec-expectations (~> 2.11.0)
@@ -37,6 +43,8 @@ GEM
37
43
  diff-lcs (~> 1.1.3)
38
44
  rspec-mocks (2.11.1)
39
45
  safely (0.3.2)
46
+ sys-filesystem (1.0.0)
47
+ ffi (>= 1.0.0)
40
48
  yajl-ruby (1.1.0)
41
49
 
42
50
  PLATFORMS
@@ -25,6 +25,8 @@ Gem::Specification.new do |s|
25
25
  s.add_runtime_dependency "yajl-ruby"
26
26
  s.add_runtime_dependency "amqp"
27
27
  s.add_runtime_dependency "erubis"
28
+ s.add_runtime_dependency "sys-filesystem"
29
+ s.add_runtime_dependency "rest-client"
28
30
 
29
31
  s.add_development_dependency "rake"
30
32
  s.add_development_dependency "rspec"
@@ -16,17 +16,35 @@ module Leecher
16
16
 
17
17
  def call(method, *args)
18
18
  retried = false
19
- super("aria2.#{method}", *args)
19
+ response = super("aria2.#{method}", *args)
20
+ @http.finish if @http.started?
21
+ response
20
22
  rescue Errno::ECONNREFUSED
23
+ retried = true
21
24
  start_aria2
22
25
  Kernel.sleep(3) # Give it a chance to start.
23
26
  retry unless retried
27
+ raise
24
28
  end
25
29
 
26
30
  def add_uri(uris, options)
31
+ rewrite_aria_dir_opt(options)
27
32
  call("addUri", uris, options)
28
33
  end
29
34
 
35
+ # WEIRD: sometimes we get nil back here.
36
+ def global_opts
37
+ call("getGlobalOption")
38
+ end
39
+
40
+ def rewrite_aria_dir_opt(options)
41
+ # Attempt to rewrite +dir+ to a relative path
42
+ if (dir = options.delete("dir"))
43
+ global_dir = global_opts.delete("dir")
44
+ options["dir"] = File.join(global_dir, dir)
45
+ end
46
+ end
47
+
30
48
  def tell_status(gid, keys = nil)
31
49
  if keys.nil? or keys.empty?
32
50
  call("tellStatus", gid.to_s)
@@ -35,6 +53,18 @@ module Leecher
35
53
  end
36
54
  end
37
55
 
56
+ def get_version
57
+ call("getVersion")
58
+ end
59
+
60
+ def tell_active
61
+ call("tellActive")
62
+ end
63
+
64
+ def tell_waiting(offset, limit)
65
+ call("tellWaiting", offset, limit)
66
+ end
67
+
38
68
  def make_aria_exec
39
69
  args = config.args.dup
40
70
  [
@@ -3,7 +3,9 @@ module Leecher
3
3
 
4
4
  require "fileutils"
5
5
  require "multi_json"
6
+ require "sys/filesystem"
6
7
  require "leecher/metalink_queue"
8
+ require "leecher/status"
7
9
 
8
10
  attr_reader :config, :aria2_client, :gheed_client
9
11
 
@@ -40,7 +42,17 @@ module Leecher
40
42
 
41
43
  def notify_gheed(uri, state)
42
44
  metalink, deliveries = gheed_client.state_change(uri, state)
43
- queue.set_store_entry(metalink["metalink_url"], :state => :completed)
45
+ queue.set_store_entry(metalink["metalink_url"], :state => :complete)
46
+ end
47
+
48
+ def status_update
49
+ status = Status.new(aria2_client)
50
+ if status.changed?(@status)
51
+ @status = status
52
+ gheed_client.status_update(@status.to_params)
53
+ else
54
+ false
55
+ end
44
56
  end
45
57
  end
46
58
  end
@@ -3,9 +3,7 @@ module Leecher
3
3
 
4
4
  class Client
5
5
 
6
- require "uri"
7
- require "net/http"
8
- require "net/https"
6
+ require "rest_client"
9
7
 
10
8
  attr_reader :config
11
9
 
@@ -13,22 +11,22 @@ module Leecher
13
11
  @config = config
14
12
  end
15
13
 
14
+
15
+ def new_resource(path)
16
+ uri = URI.join(config.uri, path).to_s
17
+ RestClient::Resource.new(uri, config.username, config.password)
18
+ end
16
19
 
17
20
  def state_change(download, state)
18
- uri = URI.parse(config.uri)
19
- req = Net::HTTP::Put.new("/leecher/state_change")
20
- req.set_form_data("uri" => download, "state" => state)
21
- req.basic_auth(config.username, config.password)
22
- res = Net::HTTP.start(uri.hostname, uri.port) do |http|
23
- http.request(req)
24
- end
21
+ req = new_resource("/leecher/state_change")
22
+ res = req.put({ "uri" => download, "state" => state })
23
+ MultiJson.decode(res)
24
+ end
25
25
 
26
- case res
27
- when Net::HTTPSuccess
28
- MultiJson.decode(res.body)
29
- else
30
- res.error!
31
- end
26
+ def status_update(params)
27
+ req = new_resource("/leecher/status_update")
28
+ res = req.post({ "status" => params })
29
+ MultiJson.decode(res)
32
30
  end
33
31
  end
34
32
  end
@@ -0,0 +1,81 @@
1
+ module Leecher
2
+ class Status
3
+
4
+ attr_accessor :aria2_client
5
+
6
+ def initialize(aria2_client)
7
+ @aria2_client = aria2_client
8
+ end
9
+
10
+
11
+ def aria2_version
12
+ @aria2_version ||= aria2_client.get_version["version"]
13
+ end
14
+
15
+ def active
16
+ @active ||= aria2_client.tell_active
17
+ end
18
+
19
+ def waiting
20
+ @waiting ||= aria2_client.tell_waiting(0, 100)
21
+ end
22
+
23
+ def aria2_status
24
+ return "downloading" if active.any?
25
+ if waiting.empty?
26
+ "idle"
27
+ else
28
+ "paused"
29
+ end
30
+ end
31
+
32
+ def downspeed
33
+ @downspeed ||= calc_downspeed
34
+ end
35
+
36
+ def calc_downspeed
37
+ active.inject(0) do |sum, download|
38
+ sum += Integer(download["downloadSpeed"])
39
+ end
40
+ end
41
+
42
+ def diskfree
43
+ @diskfree ||= calc_diskfree
44
+ end
45
+
46
+ def calc_diskfree
47
+ dir = aria2_client.global_opts["dir"]
48
+ stat = Sys::Filesystem.stat(dir)
49
+ stat.block_size * stat.blocks_available
50
+ end
51
+
52
+ def to_params
53
+ {
54
+ "leecher_version" => Leecher::VERSION,
55
+ "aria2_version" => aria2_version,
56
+ "aria2_status" => aria2_status,
57
+ "downspeed" => downspeed,
58
+ "n_downloads" => active.size + waiting.size,
59
+ "n_active_downloads" => active.size,
60
+ "diskfree" => diskfree,
61
+ }
62
+ end
63
+
64
+ def changed?(previous, diskchange_threshold = 1048576)
65
+ return true if previous.nil?
66
+
67
+ # compare params, ignoring diskfree
68
+ now = self.to_params
69
+ now_disk = now.delete("diskfree")
70
+ before = previous.to_params
71
+ before_disk = before.delete("diskfree")
72
+ return true unless now == before
73
+
74
+ significant_diskchange?(now_disk, before_disk, diskchange_threshold)
75
+ end
76
+
77
+ def significant_diskchange?(now, before, diskchange_threshold = 1048576)
78
+ (now - before).abs > diskchange_threshold
79
+ end
80
+ end
81
+ end
@@ -1,3 +1,3 @@
1
1
  module Leecher
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -15,18 +15,27 @@ leecher = Leecher.new
15
15
 
16
16
  # Run an event-loop for processing
17
17
  DaemonKit::AMQP.run do |connection|
18
- # Inside this block we're running inside the reactor setup by the
19
- # amqp gem. Any code in the examples (from the gem) would work just
20
- # fine here.
21
-
22
18
  connection.on_tcp_connection_loss do |client, settings|
23
19
  DaemonKit.logger.debug("AMQP connection status changed: #{status}")
24
20
  client.reconnect(false, 1)
25
21
  end
26
22
 
23
+ EventMachine.add_periodic_timer(60) do
24
+ DaemonKit.logger.debug "Checking status update"
25
+ begin
26
+ if (update = leecher.status_update)
27
+ DaemonKit.logger.info "Status updated: #{update.inspect}"
28
+ else
29
+ DaemonKit.logger.debug "No status change"
30
+ end
31
+ rescue => e
32
+ DaemonKit.logger.error "Unable to post status update: #{e.message}"
33
+ end
34
+ end
35
+
27
36
  amq = AMQP::Channel.new
28
37
  amq.queue(leecher.config.metalink_queue_name).subscribe(:ack => true) do |metadata, payload|
29
- DaemonKit.logger.debug "Received [#{leecher.config.metalink_queue_name}]: #{payload.inspect}"
38
+ DaemonKit.logger.info "Received [#{leecher.config.metalink_queue_name}]: #{payload.inspect}"
30
39
  DaemonKit.logger.debug leecher.process_message_on_metalink_queue(payload)
31
40
  metadata.ack
32
41
  DaemonKit.logger.debug "ack"
@@ -11,10 +11,20 @@ module Leecher::Aria2
11
11
  client.should_receive(:call2).with("aria2.method").and_return([true, :param])
12
12
  client.call("method")
13
13
  end
14
-
15
- it "responds to #add_uri by delegating to 'aria2.addUri'" do
16
- client.should_receive(:call).with("addUri", ["uri"], { option: "value" })
17
- client.add_uri(["uri"], { option: "value" })
14
+
15
+ context "#addUri" do
16
+
17
+ it "responds to #add_uri by delegating to 'aria2.addUri'" do
18
+ client.should_receive(:call).with("addUri", ["uri"], { option: "value" })
19
+ client.add_uri(["uri"], { option: "value" })
20
+ end
21
+
22
+ it "rewrites the 'dir' option to be relative to the global dir" do
23
+ client.should_receive(:call).with("getGlobalOption").
24
+ and_return({"dir"=>"/tmp/aria2"})
25
+ client.should_receive(:call).with("addUri", ["uri"], { "dir" => "/tmp/aria2/foo" })
26
+ client.add_uri(["uri"], { "dir" => "foo" })
27
+ end
18
28
  end
19
29
 
20
30
  it "knows where the callback command is" do
@@ -46,7 +46,9 @@ module Leecher
46
46
  it "updates the store status" do
47
47
  response = {"bitfield"=>"80", "completedLength"=>"726", "connections"=>"0", "dir"=>"movies/720p/example", "downloadSpeed"=>"0", "errorCode"=>"0", "files"=>[{"completedLength"=>"726", "index"=>"1", "length"=>"726", "path"=>"movies/720p/example/nfo.metalink", "selected"=>"true", "uris"=>[{"status"=>"used", "uri"=>"http://example/download/metalinks/nfo.metalink"}, {"status"=>"waiting", "uri"=>"http://example/download/metalinks/nfo.metalink"}, {"status"=>"waiting", "uri"=>"http://example/download/metalinks/nfo.metalink"}, {"status"=>"waiting", "uri"=>"http://example/download/metalinks/nfo.metalink"}, {"status"=>"waiting", "uri"=>"http://example/download/metalinks/nfo.metalink"}]}], "followedBy"=>["2"], "gid"=>"1", "numPieces"=>"1", "pieceLength"=>"1048576", "status"=>"complete", "totalLength"=>"726", "uploadLength"=>"0", "uploadSpeed"=>"0"}
48
48
  aria2_client.should_receive(:tell_status).with(1).and_return(response)
49
- leecher.status_change(1)
49
+ gheed_client.should_receive(:state_change).with("http://example/download/metalinks/nfo.metalink", :complete).
50
+ and_return([{"metalink_url" => "http://example/download/metalinks/nfo.metalink"}, []])
51
+ leecher.state_change(1)
50
52
  entry = leecher.queue.get_queue["http://example/download/metalinks/nfo.metalink"]
51
53
  entry.should_not be_nil
52
54
  entry[:gid].should eq(1)
@@ -0,0 +1,34 @@
1
+ require "leecher/status"
2
+
3
+ module Leecher
4
+ describe Status do
5
+
6
+ let(:aria2_client) { double("aria2_client") }
7
+ let(:status) { Status.new(aria2_client) }
8
+
9
+ context "changed?" do
10
+
11
+ it "finds changes if there was no previous status" do
12
+ status.changed?(nil).should be_true
13
+ end
14
+
15
+ it "finds changes if the params have changed" do
16
+ other = status.dup
17
+ status.stub(:to_params).and_return("foo" => "bar")
18
+ other.stub(:to_params).and_return("foo" => "changed")
19
+ status.changed?(other).should be_true
20
+ end
21
+ end
22
+
23
+ context "disk space changes" do
24
+
25
+ it "ignores disk free changes under a threshold" do
26
+ status.significant_diskchange?(100, 200).should be_false
27
+ end
28
+
29
+ it "detects significant changes" do
30
+ status.significant_diskchange?(100, 200, 1).should be_true
31
+ end
32
+ end
33
+ end
34
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: leecher
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-15 00:00:00.000000000 Z
12
+ date: 2012-07-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: daemon-kit
16
- requirement: &7773340 !ruby/object:Gem::Requirement
16
+ requirement: &13644920 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *7773340
24
+ version_requirements: *13644920
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: safely
27
- requirement: &7788340 !ruby/object:Gem::Requirement
27
+ requirement: &13644100 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *7788340
35
+ version_requirements: *13644100
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: multi_json
38
- requirement: &7786200 !ruby/object:Gem::Requirement
38
+ requirement: &13643400 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *7786200
46
+ version_requirements: *13643400
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: yajl-ruby
49
- requirement: &7784880 !ruby/object:Gem::Requirement
49
+ requirement: &13642640 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *7784880
57
+ version_requirements: *13642640
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: amqp
60
- requirement: &7784000 !ruby/object:Gem::Requirement
60
+ requirement: &13641980 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0'
66
66
  type: :runtime
67
67
  prerelease: false
68
- version_requirements: *7784000
68
+ version_requirements: *13641980
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: erubis
71
- requirement: &7782780 !ruby/object:Gem::Requirement
71
+ requirement: &13639820 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,32 @@ dependencies:
76
76
  version: '0'
77
77
  type: :runtime
78
78
  prerelease: false
79
- version_requirements: *7782780
79
+ version_requirements: *13639820
80
+ - !ruby/object:Gem::Dependency
81
+ name: sys-filesystem
82
+ requirement: &13638180 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :runtime
89
+ prerelease: false
90
+ version_requirements: *13638180
91
+ - !ruby/object:Gem::Dependency
92
+ name: rest-client
93
+ requirement: &13637180 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ type: :runtime
100
+ prerelease: false
101
+ version_requirements: *13637180
80
102
  - !ruby/object:Gem::Dependency
81
103
  name: rake
82
- requirement: &7781680 !ruby/object:Gem::Requirement
104
+ requirement: &13690700 !ruby/object:Gem::Requirement
83
105
  none: false
84
106
  requirements:
85
107
  - - ! '>='
@@ -87,10 +109,10 @@ dependencies:
87
109
  version: '0'
88
110
  type: :development
89
111
  prerelease: false
90
- version_requirements: *7781680
112
+ version_requirements: *13690700
91
113
  - !ruby/object:Gem::Dependency
92
114
  name: rspec
93
- requirement: &7780840 !ruby/object:Gem::Requirement
115
+ requirement: &13689740 !ruby/object:Gem::Requirement
94
116
  none: false
95
117
  requirements:
96
118
  - - ! '>='
@@ -98,7 +120,7 @@ dependencies:
98
120
  version: '0'
99
121
  type: :development
100
122
  prerelease: false
101
- version_requirements: *7780840
123
+ version_requirements: *13689740
102
124
  description: A server populates a queue, this client downloads those files
103
125
  email:
104
126
  - marcbowes+leecher@gmail.com
@@ -137,6 +159,7 @@ files:
137
159
  - lib/leecher/client.rb
138
160
  - lib/leecher/gheed/client.rb
139
161
  - lib/leecher/metalink_queue.rb
162
+ - lib/leecher/status.rb
140
163
  - lib/leecher/version.rb
141
164
  - libexec/leecher-daemon.rb
142
165
  - script/console
@@ -147,6 +170,7 @@ files:
147
170
  - spec/metalink_queue_spec.rb
148
171
  - spec/spec.opts
149
172
  - spec/spec_helper.rb
173
+ - spec/status_spec.rb
150
174
  - tasks/rspec.rake
151
175
  homepage: ''
152
176
  licenses: []
@@ -162,7 +186,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
162
186
  version: '0'
163
187
  segments:
164
188
  - 0
165
- hash: -3150552957577293375
189
+ hash: -4395843245282034706
166
190
  required_rubygems_version: !ruby/object:Gem::Requirement
167
191
  none: false
168
192
  requirements:
@@ -171,7 +195,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
171
195
  version: '0'
172
196
  segments:
173
197
  - 0
174
- hash: -3150552957577293375
198
+ hash: -4395843245282034706
175
199
  requirements: []
176
200
  rubyforge_project: leecher
177
201
  rubygems_version: 1.8.11