transmission-client 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.
@@ -1,5 +1,7 @@
1
1
  # transmission-client: A Transmission RPC Client
2
2
 
3
+ **Please note, with the current release i dropped support for the blocking api. Eventmachine is now required.**
4
+
3
5
  The goal is to support all requests described in the Transmission [RPC Specifications](http://trac.transmissionbt.com/browser/trunk/doc/rpc-spec.txt).
4
6
 
5
7
  ## Installing
@@ -16,24 +18,10 @@ To install transmission-client:
16
18
 
17
19
  sudo gem install transmission-client
18
20
 
19
- If you want to use EventMachine (optional) you need to install the eventmachine gem and igrigorik's em-http-request:
20
-
21
- sudo gem install eventmachine
22
- sudo gem install em-http-request
23
-
24
21
  ## Usage
25
22
  Get a list of torrents and print its file names:
26
23
 
27
24
  require 'transmission-client'
28
- t = Transmission::Client.new('127.0.0.1', 9091)
29
- t.torrents.each do |torrent|
30
- puts torrent.name
31
- end
32
-
33
- To use the EventMachine driven interface:
34
-
35
- require 'eventmachine'
36
- require 'transmission-client'
37
25
 
38
26
  EventMachine.run do
39
27
  t = Transmission::Client.new
@@ -45,5 +33,28 @@ To use the EventMachine driven interface:
45
33
  end
46
34
  end
47
35
  end
36
+
37
+ Authentication support (thanks hornairs):
38
+
39
+ t = Transmission::Client.new('127.0.0.1', 9091, 'username', 'password')
40
+
41
+ Callbacks:
48
42
 
43
+ EventMachine.run do
44
+ t = Transmission::Client.new
45
+
46
+ t.on_download_finished do |torrent|
47
+ puts "Wha torrent finished"
48
+ end
49
+ t.on_torrent_stopped do |torrent|
50
+ puts "Oooh torrent stopped"
51
+ end
52
+ t.on_torrent_started do |torrent|
53
+ puts "Torrent started."
54
+ end
55
+ t.on_torrent_removed do |torrent|
56
+ puts "Darn torrent deleted."
57
+ end
58
+ end
59
+
49
60
  RDoc is still to be written, at the meantime have a look at the code to find out which methods are supported.
data/Rakefile CHANGED
@@ -10,6 +10,7 @@ begin
10
10
  gem.email = "git@dsander.de"
11
11
  gem.homepage = "http://github.com/dsander/transmission-client"
12
12
  gem.authors = ["Dominik Sander"]
13
+ gem.add_dependency "em-http-request"
13
14
  gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
14
15
  gem.files += Dir['lib/**/*.rb','README.markdown']
15
16
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.1.0
@@ -5,17 +5,8 @@ require 'rubygems'
5
5
  $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
6
6
 
7
7
  require 'transmission-client/client'
8
-
9
- if defined? EM
10
- begin
11
- require 'em-http'
12
- require 'transmission-client/em-connection'
13
- rescue LoadError
14
- require 'transmission-client/connection'
15
- end
16
- else
17
- require 'transmission-client/connection'
18
- end
8
+ require 'em-http'
9
+ require 'transmission-client/em-connection'
19
10
  require 'transmission-client/torrent'
20
11
  require 'transmission-client/session'
21
12
 
@@ -1,71 +1,106 @@
1
1
  module Transmission
2
2
  class Client
3
- def initialize(host='localhost',port=9091)
4
- Connection.instance.init(host, port)
3
+ def on_download_finished(&blk); @on_download_finished = blk; callback_initialized; end
4
+ def on_torrent_added(&blk); @on_torrent_added = blk; callback_initialized; end
5
+ def on_torrent_stopped(&blk); @on_torrent_stopped = blk; callback_initialized; end
6
+ def on_torrent_started(&blk); @on_torrent_started = blk; callback_initialized; end
7
+ def on_torrent_removed(&blk); @on_torrent_removed = blk; callback_initialized; end
8
+
9
+ def initialize(host='localhost',port=9091, username = nil, password = nil)
10
+ Connection.init(host, port, username, password)
11
+ @torrents = nil
5
12
  end
6
13
 
7
14
  def start_all &cb
8
- Connection.instance.send('torrent-start', &cb)
15
+ Connection.send('torrent-start')
9
16
  end
10
17
 
11
- def start(id, &cb)
12
- Connection.instance.send('torrent-start', {'ids' => id.class == Array ? id : [id]}, &cb)
18
+ def start(id)
19
+ Connection.send('torrent-start', {'ids' => id.class == Array ? id : [id]})
13
20
  end
14
21
 
15
- def stop(id, &cb)
16
- Connection.instance.send('torrent-stop', {'ids' => id.class == Array ? id : [id]}, &cb)
22
+ def stop(id)
23
+ Connection.send('torrent-stop', {'ids' => id.class == Array ? id : [id]})
17
24
  end
18
25
 
19
26
  def stop_all &cb
20
- Connection.instance.send('torrent-stop', &cb)
27
+ Connection.send('torrent-stop')
21
28
  end
22
29
 
23
- def remove(id, delete_data = false, &cb)
24
- Connection.instance.send('torrent-remove', {'ids' => id.class == Array ? id : [id], 'delete-local-data' => delete_data }, &cb)
30
+ def remove(id, delete_data = false)
31
+ Connection.send('torrent-remove', {'ids' => id.class == Array ? id : [id], 'delete-local-data' => delete_data })
25
32
  end
26
33
 
27
- def remove_all(delete_data = false, &cb)
28
- Connection.instance.send('torrent-remove', {'delete-local-data' => delete_data }, &cb)
34
+ def remove_all(delete_data = false)
35
+ Connection.send('torrent-remove', {'delete-local-data' => delete_data })
29
36
  end
30
37
 
31
- def add_torrent(a, &cb)
38
+ def add_torrent(a)
32
39
  if a['filename'].nil? && a['metainfo'].nil?
33
40
  raise "You need to provide either a 'filename' or 'metainfo'."
34
41
  end
35
- Connection.instance.send('torrent-add', a, &cb)
42
+ Connection.send('torrent-add', a)
36
43
  end
37
44
 
38
- def add_torrent_by_file(filename, &cb)
39
- add_torrent({'filename' => filename}, &cb)
45
+ def add_torrent_by_file(filename)
46
+ add_torrent({'filename' => filename})
40
47
  end
41
48
 
42
- def add_torrent_by_data(data, &cb)
43
- add_torrent({'metainfo' => data}, &cb)
49
+ def add_torrent_by_data(data)
50
+ add_torrent({'metainfo' => data})
44
51
  end
45
52
 
46
- def session &cb
47
- if cb
48
- Connection.instance.request('session-get') { |resp| cb.call Session.new resp }
49
- else
50
- Session.new Connection.instance.request('session-get')
51
- end
53
+ def session
54
+ Connection.request('session-get') { |resp| yield Session.new resp }
52
55
  end
53
56
 
54
- def torrents(fields = nil, &cb)
55
- torrs = []
56
- if cb
57
- Connection.instance.request('torrent-get', {'fields' => fields ? fields : Transmission::Torrent::ATTRIBUTES}) { |resp|
58
- resp['torrents'].each do |t|
59
- torrs << Torrent.new(t)
60
- end
61
- cb.call torrs
62
- }
63
- else
64
- Connection.instance.request('torrent-get', {'fields' => fields ? fields : Transmission::Torrent::ATTRIBUTES})['torrents'].each do |t|
65
- torrs << Torrent.new(t)
66
- end
67
- torrs
68
- end
57
+ def torrents(fields = nil)
58
+ Connection.request('torrent-get', {'fields' => fields ? fields : Transmission::Torrent::ATTRIBUTES}) { |resp|
59
+ torrs = []
60
+ resp['torrents'].each do |t|
61
+ torrs << Torrent.new(t)
62
+ end
63
+ yield torrs
64
+ }
69
65
  end
66
+
67
+ private
68
+ def callback_initialized
69
+ return if @torrent_poller
70
+ @torrent_poller = EM.add_periodic_timer(1) do
71
+ updated_torrents = {}
72
+ self.torrents do |tors|
73
+ tors.each do |torrent|
74
+ updated_torrents[torrent.id] = torrent
75
+ end
76
+ compare_torrent_status updated_torrents
77
+ @torrents = updated_torrents.dup
78
+ end
79
+
80
+
81
+ end
82
+ end
83
+
84
+ def compare_torrent_status updated_torrents
85
+ return false unless @torrents
86
+ updated_torrents.each_pair do |id, t|
87
+ old = @torrents[t.id] if @torrents[t.id]
88
+ if old == nil
89
+ @on_torrent_started.call t if @on_torrent_started
90
+ elsif old.downloading? && t.seeding?
91
+ @on_download_finished.call t if @on_download_finished
92
+ elsif old.stopped? && !t.stopped?
93
+ @on_torrent_started.call t if @on_torrent_started
94
+ elsif !old.stopped? && t.stopped?
95
+ @on_torrent_stopped.call t if @on_torrent_stopped
96
+ end
97
+ @torrents.delete t.id
98
+ end
99
+ if @torrents.length > 0 && @on_torrent_removed
100
+ @torrents.values.each do |t|
101
+ @on_torrent_removed.call t
102
+ end
103
+ end
104
+ end
70
105
  end
71
106
  end
@@ -1,51 +1,53 @@
1
1
  module Transmission
2
2
  class Connection
3
- include Singleton
4
- #include EM::Deferrable
5
-
6
- def init(host, port)
7
- @host = host
8
- @port = port
9
- uri = URI.parse("http://#{@host}:#{@port}/transmission/rpc")
10
- #@conn = Net::HTTP.start(uri.host, uri.port)
11
- @conn = EventMachine::HttpRequest.new(uri)
12
- @header = {} #{"Accept-Encoding" => "deflate"} deflate is broken somewhere
13
- end
14
-
15
- def request(method, attributes={}, &cb)
16
- req = @conn.post(:body => build_json(method,attributes), :head => @header )
17
- req.callback {
18
- if req.response_header.status == 409 #&& @header['x-transmission-session-id'].nil?
19
- @header['x-transmission-session-id'] = req.response_header['X_TRANSMISSION_SESSION_ID']
20
- request(method,attributes, &cb)
21
- elsif req.response_header.status == 200
22
- resp = JSON.parse(req.response)
23
- if resp["result"] == 'success'
24
- cb.call resp['arguments'] if cb
25
- else
26
- cb.call resp if cb
3
+ class <<self
4
+ def init(host, port, username = nil, password = nil)
5
+ @host = host
6
+ @port = port
7
+ @header = username.nil? ? {} : {'authorization' => [username, password]}
8
+ uri = URI.parse("http://#{@host}:#{@port}/transmission/rpc")
9
+ @conn = EventMachine::HttpRequest.new(uri)
10
+ end
11
+
12
+ def request(method, attributes={})
13
+ req = @conn.post(:body => build_json(method,attributes), :head => @header )
14
+ req.callback {
15
+ case req.response_header.status
16
+ when 401
17
+ raise SecurityError, 'The client was not able to authenticate, is your username or password wrong?'
18
+ when 409 #&& @header['x-transmission-session-id'].nil?
19
+ @header['x-transmission-session-id'] = req.response_header['X_TRANSMISSION_SESSION_ID']
20
+ request(method,attributes) do |resp|
21
+ yield resp
22
+ end
23
+ when 200
24
+ resp = JSON.parse(req.response)
25
+ if resp["result"] == 'success'
26
+ yield resp['arguments']
27
+ else
28
+ yield resp
29
+ end
27
30
  end
31
+ }
32
+ req.errback {
33
+ raise "Unknown response."
34
+ }
35
+ end
36
+
37
+ def send(method, attributes={})
38
+ request(method, attributes) do |resp|
39
+ yield resp
28
40
  end
29
- }
30
- req.errback {
31
- puts 'errback'
32
- pp req
33
- }
34
- end
35
-
36
- def send(method, attributes={}, &cb)
37
- request(method, attributes) do |resp|
38
- cb.call resp if cb
39
41
  end
40
- end
41
-
42
- def build_json(method,attributes = {})
43
- if attributes.length == 0
44
- {'method' => method}.to_json
45
- else
46
- {'method' => method, 'arguments' => attributes }.to_json
42
+
43
+ def build_json(method,attributes = {})
44
+ if attributes.length == 0
45
+ {'method' => method}.to_json
46
+ else
47
+ {'method' => method, 'arguments' => attributes }.to_json
48
+ end
47
49
  end
50
+
48
51
  end
49
-
50
52
  end
51
53
  end
@@ -3,31 +3,56 @@ module Transmission
3
3
  ATTRIBUTES = ['activityDate', 'addedDate', 'bandwidthPriority', 'comment', 'corruptEver', 'creator', 'dateCreated', 'desiredAvailable', 'doneDate', 'downloadDir', 'downloadedEver', 'downloadLimit', 'downloadLimited', 'error', 'errorString', 'eta', 'hashString', 'haveUnchecked', 'haveValid', 'honorsSessionLimits', 'id', 'isPrivate', 'leftUntilDone', 'manualAnnounceTime', 'maxConnectedPeers', 'name', 'peer-limit', 'peersConnected', 'peersGettingFromUs', 'peersKnown', 'peersSendingToUs', 'percentDone', 'pieces', 'pieceCount', 'pieceSize', 'rateDownload', 'rateUpload', 'recheckProgress', 'seedRatioLimit', 'seedRatioMode', 'sizeWhenDone', 'startDate', 'status', 'swarmSpeed', 'totalSize', 'torrentFile', 'uploadedEver', 'uploadLimit', 'uploadLimited', 'uploadRatio', 'webseedsSendingToUs']
4
4
  ADV_ATTRIBUTES = ['files', 'fileStats', 'peers', 'peersFrom', 'priorities', 'trackers', 'trackerStats', 'wanted', 'webseeds']
5
5
  SETABLE_ATTRIBUTES = ['bandwidthPriority', 'downloadLimit', 'downloadLimited', 'files-wanted', 'files-unwanted', 'honorsSessionLimits', 'ids', 'location', 'peer-limit', 'priority-high', 'priority-low', 'priority-normal', 'seedRatioLimit', 'seedRatioMode', 'uploadLimit', 'uploadLimited']
6
+ CHECK_WAIT = 1
7
+ CHECK = 2
8
+ DOWNLOAD = 4
9
+ SEED = 8
10
+ STOPPED = 16
6
11
 
7
12
  def initialize(attributes)
8
13
  @attributes = attributes
9
14
  end
10
15
 
11
16
  def start
12
- Connection.instance.send('torrent-start', {'ids' => @attributes['id']})
17
+ Connection.send('torrent-start', {'ids' => @attributes['id']})
13
18
  end
14
19
 
15
20
  def stop
16
- Connection.instance.send('torrent-stop', {'ids' => @attributes['id']})
21
+ Connection.send('torrent-stop', {'ids' => @attributes['id']})
17
22
  end
18
23
 
19
24
  def verify
20
- Connection.instance.send('torrent-verify', {'ids' => @attributes['id']})
25
+ Connection.send('torrent-verify', {'ids' => @attributes['id']})
21
26
  end
22
27
 
23
28
  def reannounce
24
- Connection.instance.send('torrent-reannounce', {'ids' => @attributes['id']})
29
+ Connection.send('torrent-reannounce', {'ids' => @attributes['id']})
25
30
  end
26
31
 
27
32
  def remove(delete_data = false)
28
- Connection.instance.send('torrent-remove', {'ids' => @attributes['id'], 'delete-local-data' => delete_data })
33
+ Connection.send('torrent-remove', {'ids' => @attributes['id'], 'delete-local-data' => delete_data })
29
34
  end
30
35
 
36
+ def downloading?
37
+ self.status == DOWNLOAD
38
+ end
39
+
40
+ def stopped?
41
+ self.status == STOPPED
42
+ end
43
+
44
+ def checking?
45
+ self.status == CHECK || self.status == CHECK_WAIT
46
+ end
47
+
48
+ def seeding?
49
+ self.status == SEED
50
+ end
51
+
52
+ def id
53
+ @attributes['id']
54
+ end
55
+
31
56
  def method_missing(m, *args, &block)
32
57
  if ATTRIBUTES.include? m.to_s
33
58
  return @attributes[m.to_s]
@@ -35,7 +60,7 @@ module Transmission
35
60
  raise "Attribute not yet supported."
36
61
  elsif m[-1..-1] == '='
37
62
  if SETABLE_ATTRIBUTES.include? m[0..-2]
38
- Connection.instance.send('torrent-set', {'ids' => [@attributes['id']], m[0..-2] => args.first})
63
+ Connection.send('torrent-set', {'ids' => [@attributes['id']], m[0..-2] => args.first})
39
64
  else
40
65
  raise "Invalid Attribute."
41
66
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: transmission-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dominik Sander
@@ -9,9 +9,19 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-10-27 00:00:00 +01:00
12
+ date: 2010-02-11 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: em-http-request
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
15
25
  - !ruby/object:Gem::Dependency
16
26
  name: thoughtbot-shoulda
17
27
  type: :development
@@ -31,18 +41,15 @@ extensions: []
31
41
  extra_rdoc_files:
32
42
  - LICENSE
33
43
  - README.markdown
34
- - README.rdoc
35
44
  files:
36
45
  - .document
37
46
  - .gitignore
38
47
  - LICENSE
39
48
  - README.markdown
40
- - README.rdoc
41
49
  - Rakefile
42
50
  - VERSION
43
51
  - lib/transmission-client.rb
44
52
  - lib/transmission-client/client.rb
45
- - lib/transmission-client/connection.rb
46
53
  - lib/transmission-client/em-connection.rb
47
54
  - lib/transmission-client/session.rb
48
55
  - lib/transmission-client/torrent.rb
@@ -77,5 +84,5 @@ signing_key:
77
84
  specification_version: 3
78
85
  summary: A Transmission RPC Client
79
86
  test_files:
80
- - test/test_transmission-rpc.rb
81
87
  - test/helper.rb
88
+ - test/test_transmission-rpc.rb
@@ -1,49 +0,0 @@
1
- # transmission-client: A Transmission RPC Client
2
-
3
- The goal is to support all requests described in the Transmission [RPC Specifications](http://trac.transmissionbt.com/browser/trunk/doc/rpc-spec.txt).
4
-
5
- ## Installing
6
- You need to have http://gemcutter.org in you gem sources. To add it you can execute either
7
-
8
- sudo gem install gemcutter
9
- sudo gem tumble
10
-
11
- or
12
-
13
- sudo gem source -a http://gemcutter.org
14
-
15
- To install transmission-client:
16
-
17
- sudo gem install transmission-client
18
-
19
- If you want to use EventMachine (optional) you need to install the eventmachine gem and igrigorik's em-http-request:
20
-
21
- sudo gem install eventmachine
22
- sudo gem install em-http-request
23
-
24
- ## Usage
25
- Get a list of torrents and print its file names:
26
-
27
- require 'transmission-client'
28
- t = Transmission::Client.new('127.0.0.1', 9091)
29
- t.torrents.each do |torrent|
30
- puts torrent.name
31
- end
32
-
33
- To use the EventMachine driven interface:
34
-
35
- require 'eventmachine'
36
- require 'transmission-client'
37
-
38
- EventMachine.run do
39
- t = Transmission::Client.new
40
- EM.add_periodic_timer(1) do
41
- t.torrents do |torrents|
42
- torrents.each do |tor|
43
- puts tor.percentDone
44
- end
45
- end
46
- end
47
- end
48
-
49
- RDoc is still to be written, at the meantime have a look at the code to find out which methods are supported.
@@ -1,42 +0,0 @@
1
- module Transmission
2
- class Connection
3
- include Singleton
4
-
5
- def init(host, port)
6
- @host = host
7
- @port = port
8
- uri = URI.parse("http://#{@host}:#{@port}")
9
- @conn = Net::HTTP.start(uri.host, uri.port)
10
- @header = {}
11
- end
12
-
13
- def request(method, attributes={})
14
- res = @conn.post('/transmission/rpc',build_json(method,attributes),@header)
15
- if res.class == Net::HTTPConflict && @header['x-transmission-session-id'].nil?
16
- @header['x-transmission-session-id'] = res['x-transmission-session-id']
17
- request(method,attributes)
18
- elsif res.class == Net::HTTPOK
19
- resp = JSON.parse(res.body)
20
- if resp["result"] == 'success'
21
- #pp resp
22
- resp['arguments']
23
- else
24
- resp
25
- end
26
- end
27
- end
28
-
29
- def send(method, attributes={})
30
- request(method, attributes)['result'].nil?
31
- end
32
-
33
- def build_json(method,attributes = {})
34
- if attributes.length == 0
35
- {'method' => method}.to_json
36
- else
37
- {'method' => method, 'arguments' => attributes }.to_json
38
- end
39
- end
40
-
41
- end
42
- end