transmission-rpc-ruby-ext 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|