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.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +62 -0
- data/LICENSE +19 -0
- data/README.md +173 -0
- data/Rakefile +5 -0
- data/lib/transmission.rb +11 -0
- data/lib/transmission/arguments.rb +50 -0
- data/lib/transmission/arguments/location_set.rb +11 -0
- data/lib/transmission/arguments/session_set.rb +56 -0
- data/lib/transmission/arguments/torrent_add.rb +24 -0
- data/lib/transmission/arguments/torrent_set.rb +32 -0
- data/lib/transmission/config.rb +18 -0
- data/lib/transmission/extensions.rb +4 -0
- data/lib/transmission/extensions/torrent_actions.rb +15 -0
- data/lib/transmission/extensions/torrent_attributes.rb +103 -0
- data/lib/transmission/extensions/torrent_status.rb +42 -0
- data/lib/transmission/extensions/transmission.rb +82 -0
- data/lib/transmission/fields.rb +41 -0
- data/lib/transmission/fields/session_get.rb +61 -0
- data/lib/transmission/fields/session_stats.rb +15 -0
- data/lib/transmission/fields/torrent_get.rb +76 -0
- data/lib/transmission/model.rb +8 -0
- data/lib/transmission/model/session.rb +48 -0
- data/lib/transmission/model/session_stats.rb +28 -0
- data/lib/transmission/model/torrent.rb +152 -0
- data/lib/transmission/rpc.rb +116 -0
- data/lib/transmission/rpc/connector.rb +67 -0
- data/lib/transmission/utils.rb +24 -0
- data/spec/helpers/stubs.rb +82 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/transmission/arguments_spec.rb +78 -0
- data/spec/transmission/fields_spec.rb +60 -0
- data/spec/transmission/model/session_spec.rb +85 -0
- data/spec/transmission/model/torrent_spec.rb +345 -0
- data/spec/transmission/rpc/connector_spec.rb +82 -0
- data/spec/transmission/rpc_spec.rb +33 -0
- data/spec/transmission/utils_spec.rb +69 -0
- data/transmission-rpc-ruby-ext.gemspec +17 -0
- 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,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
|