transmission-rpc-ruby-ext 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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/CHANGELOG.md +3 -0
  6. data/Gemfile +10 -0
  7. data/Gemfile.lock +62 -0
  8. data/LICENSE +19 -0
  9. data/README.md +173 -0
  10. data/Rakefile +5 -0
  11. data/lib/transmission.rb +11 -0
  12. data/lib/transmission/arguments.rb +50 -0
  13. data/lib/transmission/arguments/location_set.rb +11 -0
  14. data/lib/transmission/arguments/session_set.rb +56 -0
  15. data/lib/transmission/arguments/torrent_add.rb +24 -0
  16. data/lib/transmission/arguments/torrent_set.rb +32 -0
  17. data/lib/transmission/config.rb +18 -0
  18. data/lib/transmission/extensions.rb +4 -0
  19. data/lib/transmission/extensions/torrent_actions.rb +15 -0
  20. data/lib/transmission/extensions/torrent_attributes.rb +103 -0
  21. data/lib/transmission/extensions/torrent_status.rb +42 -0
  22. data/lib/transmission/extensions/transmission.rb +82 -0
  23. data/lib/transmission/fields.rb +41 -0
  24. data/lib/transmission/fields/session_get.rb +61 -0
  25. data/lib/transmission/fields/session_stats.rb +15 -0
  26. data/lib/transmission/fields/torrent_get.rb +76 -0
  27. data/lib/transmission/model.rb +8 -0
  28. data/lib/transmission/model/session.rb +48 -0
  29. data/lib/transmission/model/session_stats.rb +28 -0
  30. data/lib/transmission/model/torrent.rb +152 -0
  31. data/lib/transmission/rpc.rb +116 -0
  32. data/lib/transmission/rpc/connector.rb +67 -0
  33. data/lib/transmission/utils.rb +24 -0
  34. data/spec/helpers/stubs.rb +82 -0
  35. data/spec/spec_helper.rb +13 -0
  36. data/spec/transmission/arguments_spec.rb +78 -0
  37. data/spec/transmission/fields_spec.rb +60 -0
  38. data/spec/transmission/model/session_spec.rb +85 -0
  39. data/spec/transmission/model/torrent_spec.rb +345 -0
  40. data/spec/transmission/rpc/connector_spec.rb +82 -0
  41. data/spec/transmission/rpc_spec.rb +33 -0
  42. data/spec/transmission/utils_spec.rb +69 -0
  43. data/transmission-rpc-ruby-ext.gemspec +17 -0
  44. metadata +141 -0
@@ -0,0 +1,76 @@
1
+ module Transmission
2
+ class Fields
3
+ class TorrentGet < Transmission::Fields
4
+ ATTRIBUTES = [
5
+ { field: 'id' },
6
+ { field: 'activityDate' },
7
+ { field: 'addedDate' },
8
+ { field: 'bandwidthPriority' },
9
+ { field: 'comment' },
10
+ { field: 'corruptEver' },
11
+ { field: 'creator' },
12
+ { field: 'dateCreated' },
13
+ { field: 'desiredAvailable' },
14
+ { field: 'doneDate' },
15
+ { field: 'downloadDir' },
16
+ { field: 'downloadedEver' },
17
+ { field: 'downloadLimit' },
18
+ { field: 'downloadLimited' },
19
+ { field: 'error' },
20
+ { field: 'errorString' },
21
+ { field: 'eta' },
22
+ { field: 'etaIdle' },
23
+ { field: 'files' },
24
+ { field: 'fileStats' },
25
+ { field: 'hashString' },
26
+ { field: 'haveUnchecked' },
27
+ { field: 'haveValid' },
28
+ { field: 'honorsSessionLimits' },
29
+ { field: 'isFinished' },
30
+ { field: 'isPrivate' },
31
+ { field: 'isStalled' },
32
+ { field: 'leftUntilDone' },
33
+ { field: 'magnetLink' },
34
+ { field: 'manualAnnounceTime' },
35
+ { field: 'maxConnectedPeers' },
36
+ { field: 'metadataPercentComplete' },
37
+ { field: 'name' },
38
+ { field: 'peer-limit' },
39
+ { field: 'peers' },
40
+ { field: 'peersConnected' },
41
+ { field: 'peersFrom' },
42
+ { field: 'peersGettingFromUs' },
43
+ { field: 'peersSendingToUs' },
44
+ { field: 'percentDone' },
45
+ { field: 'pieces' },
46
+ { field: 'pieceCount' },
47
+ { field: 'pieceSize' },
48
+ { field: 'priorities' },
49
+ { field: 'queuePosition' },
50
+ { field: 'rateDownload' },
51
+ { field: 'rateUpload' },
52
+ { field: 'recheckProgress' },
53
+ { field: 'secondsDownloading' },
54
+ { field: 'secondsSeeding' },
55
+ { field: 'seedIdleLimit' },
56
+ { field: 'seedIdleMode' },
57
+ { field: 'seedRatioLimit' },
58
+ { field: 'seedRatioMode' },
59
+ { field: 'sizeWhenDone' },
60
+ { field: 'startDate' },
61
+ { field: 'status' },
62
+ { field: 'trackers' },
63
+ { field: 'trackerStats' },
64
+ { field: 'totalSize' },
65
+ { field: 'torrentFile' },
66
+ { field: 'uploadedEver' },
67
+ { field: 'uploadLimit' },
68
+ { field: 'uploadLimited' },
69
+ { field: 'uploadRatio' },
70
+ { field: 'wanted' },
71
+ { field: 'webseeds' },
72
+ { field: 'webseedsSendingToUs' }
73
+ ].freeze
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,8 @@
1
+ require_relative 'model/torrent'
2
+ require_relative 'model/session'
3
+ require_relative 'model/session_stats'
4
+
5
+ module Transmission
6
+ module Model
7
+ end
8
+ end
@@ -0,0 +1,48 @@
1
+ module Transmission
2
+ module Model
3
+ class Session
4
+ class SessionError < StandardError; end
5
+
6
+ attr_accessor :attributes, :connector
7
+
8
+ def initialize(session_object, connector)
9
+ @attributes = session_object
10
+ @connector = connector
11
+ end
12
+
13
+ def save!
14
+ filtered = Transmission::Arguments::SessionSet.filter @attributes
15
+ connector.set_session filtered
16
+ end
17
+
18
+ def to_json
19
+ @attributes
20
+ end
21
+
22
+ def method_missing(symbol, *args)
23
+ string = symbol.to_s
24
+ if string[-1] == '='
25
+ string = string[0..-2]
26
+ key = Transmission::Arguments::SessionSet.real_key string
27
+ return @attributes[key] = args.first unless key.nil?
28
+ else
29
+ key = Transmission::Fields::SessionGet.real_key string
30
+ return @attributes[key] unless key.nil?
31
+ end
32
+ super
33
+ end
34
+
35
+ class << self
36
+ def get(options = {})
37
+ rpc = options[:connector] || connector
38
+ body = rpc.get_session options[:fields]
39
+ Session.new body, rpc
40
+ end
41
+
42
+ def connector
43
+ Transmission::Config.get_connector
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,28 @@
1
+ module Transmission
2
+ module Model
3
+ class SessionStats
4
+ attr_accessor :attributes, :connector
5
+
6
+ def initialize(session_object, connector)
7
+ @attributes = session_object
8
+ @connector = connector
9
+ end
10
+
11
+ def to_json
12
+ @attributes
13
+ end
14
+
15
+ class << self
16
+ def get(options = {})
17
+ rpc = options[:connector] || connector
18
+ body = rpc.get_session_stats options[:fields]
19
+ SessionStats.new body, rpc
20
+ end
21
+
22
+ def connector
23
+ Transmission::Config.get_connector
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,152 @@
1
+ module Transmission
2
+ module Model
3
+ class Torrent
4
+ class TorrentError < StandardError; end
5
+ class TorrentNotFoundError < StandardError; end
6
+ class MissingAttributesError < StandardError; end
7
+ class DuplicateTorrentError < StandardError; end
8
+
9
+ attr_accessor :attributes, :deleted, :connector, :torrents, :ids
10
+
11
+ def initialize(torrent_object, connector)
12
+ if torrent_object.is_a? Array
13
+ is_single = torrent_object.size == 1
14
+ @attributes = is_single ? torrent_object.first : {}
15
+ @ids = is_single ? [@attributes['id'].to_i] : []
16
+ @torrents = torrent_object.inject([]) do |torrents, torrent|
17
+ @ids << torrent['id'].to_i
18
+ torrents << Torrent.new([torrent], connector)
19
+ end unless is_single
20
+ end
21
+ @connector = connector
22
+ end
23
+
24
+ def delete!(remove_local_data = false)
25
+ connector.remove_torrent @ids, remove_local_data
26
+ @deleted = true
27
+ end
28
+
29
+ def save!
30
+ filtered = Transmission::Arguments::TorrentSet.filter @attributes
31
+ connector.set_torrent @ids, filtered
32
+ end
33
+
34
+ def move_up!
35
+ connector.move_up_torrent @ids
36
+ end
37
+
38
+ def move_down!
39
+ connector.move_down_torrent @ids
40
+ end
41
+
42
+ def move_top!
43
+ connector.move_top_torrent @ids
44
+ end
45
+
46
+ def move_bottom!
47
+ connector.move_bottom_torrent @ids
48
+ end
49
+
50
+ def start!
51
+ connector.start_torrent @ids
52
+ end
53
+
54
+ def start_now!
55
+ connector.start_torrent_now @ids
56
+ end
57
+
58
+ def stop!
59
+ connector.stop_torrent @ids
60
+ end
61
+
62
+ def verify!
63
+ connector.verify_torrent @ids
64
+ end
65
+
66
+ def re_announce!
67
+ connector.re_announce_torrent @ids
68
+ end
69
+
70
+ def set_location(new_location, move = false)
71
+ connector.torrent_set_location @ids, location: new_location, move: move
72
+ end
73
+
74
+ def is_multi?
75
+ @ids.size > 1
76
+ end
77
+
78
+ # Note: this is overidden by extensions.
79
+ # def finished?
80
+ # percent_done == 1
81
+ # end
82
+
83
+ def reload!
84
+ torrents = Torrent.find @ids, connector: @connector
85
+ @ids = torrents.ids
86
+ @attributes = torrents.attributes
87
+ @torrents = torrents.torrents
88
+ self
89
+ end
90
+
91
+ def to_json
92
+ if is_multi?
93
+ @torrents.inject([]) do |torrents, torrent|
94
+ torrents << torrent.to_json
95
+ end
96
+ else
97
+ @attributes
98
+ end
99
+ end
100
+
101
+ def method_missing(symbol, *args)
102
+ string = symbol.to_s
103
+ if string[-1] == '='
104
+ string = string[0..-2]
105
+ key = Transmission::Arguments::TorrentSet.real_key string
106
+ return @attributes[key] = args.first unless key.nil?
107
+ else
108
+ key = Transmission::Fields::TorrentGet.real_key string
109
+ return @attributes[key] unless key.nil?
110
+ end
111
+ super
112
+ end
113
+
114
+ class << self
115
+ def all(options = {})
116
+ rpc = options[:connector] || connector
117
+ body = rpc.get_torrent nil, options[:fields]
118
+ Torrent.new body['torrents'], rpc
119
+ end
120
+
121
+ def find(id, options = {})
122
+ rpc = options[:connector] || connector
123
+ ids = id.is_a?(Array) ? id : [id]
124
+ body = rpc.get_torrent ids, options[:fields]
125
+ raise TorrentNotFoundError if body['torrents'].size.zero?
126
+ Torrent.new body['torrents'], rpc
127
+ end
128
+
129
+ def add(options = {})
130
+ rpc = options[:connector] || connector
131
+ body = rpc.add_torrent options[:arguments]
132
+ raise DuplicateTorrentError if body['torrent-duplicate']
133
+ find body['torrent-added']['id'], options
134
+ end
135
+
136
+ def start_all!(options = {})
137
+ rpc = options[:connector] || connector
138
+ rpc.start_torrent nil
139
+ end
140
+
141
+ def stop_all!(options = {})
142
+ rpc = options[:connector] || connector
143
+ rpc.stop_torrent nil
144
+ end
145
+
146
+ def connector
147
+ Transmission::Config.get_connector
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,116 @@
1
+ require_relative 'rpc/connector'
2
+
3
+ module Transmission
4
+ class RPC
5
+ attr_accessor :session, :connector
6
+
7
+ def initialize(options = {})
8
+ @connector = Connector.new options
9
+ end
10
+
11
+ def get_session(fields = nil)
12
+ fields = Transmission::Fields::SessionGet.new(fields)
13
+ arguments = { fields: fields.to_fields }
14
+ @connector.post method: 'session-get', arguments: arguments
15
+ end
16
+
17
+ def get_session_stats(fields = nil)
18
+ fields = Transmission::Fields::SessionStats.new(fields)
19
+ arguments = { fields: fields.to_fields }
20
+ @connector.post method: 'session-stats', arguments: arguments
21
+ end
22
+
23
+ def close_session
24
+ @connector.post method: 'session-close'
25
+ end
26
+
27
+ def test_port
28
+ @connector.post method: 'port-test'
29
+ end
30
+
31
+ def blocklist
32
+ @connector.post method: 'blocklist-update'
33
+ end
34
+
35
+ def free_space
36
+ @connector.post method: 'free-space'
37
+ end
38
+
39
+ def get_torrent(ids, fields = nil)
40
+ fields = Transmission::Fields::TorrentGet.new(fields)
41
+ arguments = { fields: fields.to_fields }
42
+ arguments[:ids] = ids if ids.is_a? Array
43
+ @connector.post method: 'torrent-get', arguments: arguments
44
+ end
45
+
46
+ def set_torrent(ids, arguments)
47
+ arguments[:ids] = ids
48
+ arguments = Transmission::Arguments::TorrentSet.new(arguments)
49
+ @connector.post method: 'torrent-set', arguments: arguments.to_arguments
50
+ end
51
+
52
+ def set_session(arguments)
53
+ arguments = Transmission::Arguments::SessionSet.new(arguments)
54
+ @connector.post method: 'session-set', arguments: arguments.to_arguments
55
+ end
56
+
57
+ def add_torrent(arguments)
58
+ arguments = Transmission::Arguments::TorrentAdd.new(arguments)
59
+ @connector.post method: 'torrent-add', arguments: arguments.to_arguments
60
+ end
61
+
62
+ def torrent_set_location(ids, arguments)
63
+ arguments[:ids] = ids
64
+ arguments = Transmission::Arguments::LocationSet.new(arguments)
65
+ @connector.post method: 'torrent-set-location', arguments: arguments.to_arguments
66
+ end
67
+
68
+ def remove_torrent(ids, delete_local_data = false)
69
+ @connector.post method: 'torrent-remove', arguments: { ids: ids, 'delete-local-data' => delete_local_data }
70
+ end
71
+
72
+ def start_torrent(ids)
73
+ @connector.post method: 'torrent-start', arguments: id_arguments(ids)
74
+ end
75
+
76
+ def start_torrent_now(ids)
77
+ @connector.post method: 'torrent-start-now', arguments: id_arguments(ids)
78
+ end
79
+
80
+ def stop_torrent(ids)
81
+ @connector.post method: 'torrent-stop', arguments: id_arguments(ids)
82
+ end
83
+
84
+ def verify_torrent(ids)
85
+ @connector.post method: 'torrent-verify', arguments: id_arguments(ids)
86
+ end
87
+
88
+ def re_announce_torrent(ids)
89
+ @connector.post method: 'torrent-reannounce', arguments: id_arguments(ids)
90
+ end
91
+
92
+ def move_up_torrent(ids)
93
+ @connector.post method: 'queue-move-up', arguments: id_arguments(ids)
94
+ end
95
+
96
+ def move_down_torrent(ids)
97
+ @connector.post method: 'queue-move-down', arguments: id_arguments(ids)
98
+ end
99
+
100
+ def move_top_torrent(ids)
101
+ @connector.post method: 'queue-move-top', arguments: id_arguments(ids)
102
+ end
103
+
104
+ def move_bottom_torrent(ids)
105
+ @connector.post method: 'queue-move-bottom', arguments: id_arguments(ids)
106
+ end
107
+
108
+ private
109
+
110
+ def id_arguments(ids)
111
+ arguments = {}
112
+ arguments[:ids] = ids if ids.is_a? Array
113
+ arguments
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,67 @@
1
+ require 'faraday'
2
+ require 'json'
3
+
4
+ module Transmission
5
+ class RPC
6
+ class Connector
7
+ class AuthError < StandardError; end
8
+ class ConnectionError < StandardError; end
9
+
10
+ attr_accessor :host, :port, :ssl, :credentials, :path, :session_id, :response
11
+
12
+ def initialize(options = {})
13
+ @host = options[:host] || 'localhost'
14
+ @port = options[:port] || 9091
15
+ @ssl = options[:ssl] || false
16
+ @credentials = options[:credentials] || nil
17
+ @path = options[:path] || '/transmission/rpc'
18
+ @session_id = options[:session_id] || ''
19
+ end
20
+
21
+ def post(params = {})
22
+ response = connection.post do |req|
23
+ req.url @path
24
+ req.headers['X-Transmission-Session-Id'] = @session_id
25
+ req.headers['Content-Type'] = 'application/json'
26
+ req.body = JSON.generate(params)
27
+ end
28
+ handle_response response, params
29
+ end
30
+
31
+ private
32
+
33
+ def json_body(response)
34
+ JSON.parse response.body
35
+ rescue
36
+ {}
37
+ end
38
+
39
+ def handle_response(response, params)
40
+ @response = response
41
+ if response.status == 409
42
+ @session_id = response.headers['x-transmission-session-id']
43
+ return post(params)
44
+ end
45
+ body = json_body response
46
+ raise AuthError if response.status == 401
47
+ raise ConnectionError, body['result'] unless response.status == 200 && body['result'] == 'success'
48
+ body['arguments']
49
+ end
50
+
51
+ def connection
52
+ @connection ||= begin
53
+ connection = Faraday.new(url: "#{scheme}://#{@host}:#{@port}", ssl: { verify: false }) do |faraday|
54
+ faraday.request :url_encoded
55
+ faraday.adapter Faraday.default_adapter
56
+ end
57
+ connection.basic_auth(@credentials[:username], @credentials[:password]) if @credentials
58
+ connection
59
+ end
60
+ end
61
+
62
+ def scheme
63
+ @ssl ? 'https' : 'http'
64
+ end
65
+ end
66
+ end
67
+ end