transmission-client 0.0.1 → 0.1.0

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