trans-api 0.0.1

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.
@@ -0,0 +1,331 @@
1
+ module Trans
2
+ module Api
3
+
4
+
5
+ class Connect
6
+
7
+ require 'base64'
8
+ require 'json'
9
+ require 'nokogiri'
10
+ require 'net/http'
11
+
12
+
13
+ METHODS = {
14
+ session_get: {method: "session-get", tag: 0},
15
+ session_set: {method: "session-set", tag: 1},
16
+ session_stats: {method: "session-stats", tag: 2},
17
+ session_close: {method: "session-close", tag: 3},
18
+ torrent_get: {method: "torrent-get", tag: 4},
19
+ torrent_set: {method: "torrent-set", tag: 5},
20
+ torrent_start: {method: "torrent-start", tag: 6},
21
+ torrent_start_now: {method: "torrent-start-now", tag: 7},
22
+ torrent_stop: {method: "torrent-stop", tag: 8},
23
+ torrent_add: {method: "torrent-add", tag: 9},
24
+ torrent_remove: {method: "torrent-remove", tag: 10},
25
+ torrent_verify: {method: "torrent-verify", tag: 11},
26
+ torrent_reannounce: {method: "torrent-reannounce", tag: 12},
27
+ torrent_set_location: {method: "torrent-set-location", tag: 13},
28
+ blocklist_update: {method: "blocklist-update", tag: 14},
29
+ port_test: {method: "port-test", tag: 15},
30
+ queue_move_top: {method: "queue-move-top", tag: 16},
31
+ queue_move_up: {method: "queue-move-up", tag: 17},
32
+ queue_move_down: {method: "queue-move-down", tag: 18},
33
+ queue_move_bottom: {method: "queue-move-bottom", tag: 19}
34
+ }
35
+
36
+
37
+
38
+ def initialize(options={})
39
+ if options.empty?
40
+ @conn = Client::DEFAULT
41
+ elsif !options.nil?
42
+ @conn = options
43
+ end
44
+ self.reset_conn
45
+ end
46
+
47
+
48
+ def reset_conn
49
+ @conn[:headers] = {}
50
+ # authentication
51
+ secret = ::Base64.encode64("#{@conn[:user]}:#{@conn[:pass]}")
52
+ @conn[:headers]["Authorization"]= "Basic #{secret}" if @conn.include?(:user) && @conn.include?(:pass)
53
+ # placeholder
54
+ @conn[:headers]["X-Transmission-Session-Id"] = ""
55
+ self
56
+ end
57
+
58
+
59
+ # handles
60
+
61
+ #TODO: need to refactor these redundant functions!!
62
+
63
+ def session_get
64
+ data = METHODS[:session_get]
65
+ ret = self.do(:post, data)
66
+ # a little dirty, but works great :)
67
+ session = JSON.parse ret[:response].body.gsub("-","_"), {symbolize_names: true}
68
+ raise session[:result] unless valid? session, data[:tag]
69
+ session[:arguments]
70
+ end
71
+
72
+ def session_stats
73
+ data = METHODS[:session_stats]
74
+ ret = self.do(:post, data)
75
+ session = JSON.parse ret[:response].body.gsub("-","_"), {symbolize_names: true}
76
+ raise session[:result] unless valid? session, data[:tag]
77
+ session[:arguments]
78
+ end
79
+
80
+ def session_set(arguments={})
81
+ data = METHODS[:session_set]
82
+ data[:arguments] = argument_name_to_api arguments
83
+ ret = self.do :post, data
84
+ session = JSON.parse ret[:response].body, {symbolize_names: true}
85
+ raise session[:result] unless valid? session, data[:tag]
86
+ session[:arguments]
87
+ end
88
+
89
+ def session_close
90
+ data = METHODS[:session_close]
91
+ ret = self.do :post, data
92
+ session = JSON.parse ret[:response].body, {symbolize_names: true}
93
+ raise session[:result] unless valid? session, data[:tag]
94
+ session[:arguments]
95
+ end
96
+
97
+ def torrent_get(fields = [:id, :name, :status], ids = [])
98
+ arguments = { fields: fields }
99
+ arguments[:ids] = ids unless ids.empty?
100
+ data = METHODS[:torrent_get]
101
+ data[:arguments] = argument_name_to_api arguments
102
+ ret = self.do(:post, data)
103
+ torrents = JSON.parse ret[:response].body, {symbolize_names: true}
104
+ raise torrents[:result] unless valid? torrents, data[:tag]
105
+ torrents[:arguments][:torrents]
106
+ end
107
+
108
+ def torrent_set(arguments={}, ids = [])
109
+ arguments[:ids] = ids unless ids.empty?
110
+ data = METHODS[:torrent_set]
111
+ data[:arguments] = argument_name_to_api arguments
112
+ ret = self.do(:post, data)
113
+ torrents = JSON.parse ret[:response].body, {symbolize_names: true}
114
+ raise torrents[:result] unless valid? torrents, data[:tag]
115
+ torrents[:arguments][:torrents]
116
+ end
117
+
118
+ def torrent_start(ids = [])
119
+ arguments = {}
120
+ arguments[:ids] = ids unless ids.empty?
121
+ data = METHODS[:torrent_start]
122
+ data[:arguments] = argument_name_to_api arguments
123
+ ret = self.do(:post, data)
124
+ torrents = JSON.parse ret[:response].body, {symbolize_names: true}
125
+ raise torrents[:result] unless valid? torrents, data[:tag]
126
+ torrents[:arguments][:torrents]
127
+ end
128
+
129
+ def torrent_start_now(ids = [])
130
+ arguments = {}
131
+ arguments[:ids] = ids unless ids.empty?
132
+ data = METHODS[:torrent_start_now]
133
+ data[:arguments] = argument_name_to_api arguments
134
+ ret = self.do(:post, data)
135
+ torrents = JSON.parse ret[:response].body, {symbolize_names: true}
136
+ raise torrents[:result] unless valid? torrents, data[:tag]
137
+ torrents[:arguments][:torrents]
138
+ end
139
+
140
+ def torrent_stop(ids = [])
141
+ arguments = {}
142
+ arguments[:ids] = ids unless ids.empty?
143
+ data = METHODS[:torrent_stop]
144
+ data[:arguments] = argument_name_to_api arguments
145
+ ret = self.do(:post, data)
146
+ torrents = JSON.parse ret[:response].body, {symbolize_names: true}
147
+ raise torrents[:result] unless valid? torrents, data[:tag]
148
+ torrents[:arguments][:torrents]
149
+ end
150
+
151
+ def torrent_add(arguments={})
152
+ data = METHODS[:torrent_add]
153
+ data[:arguments] = argument_name_to_api arguments
154
+ ret = self.do(:post, data)
155
+ torrents = JSON.parse ret[:response].body.gsub("-","_"), {symbolize_names: true}
156
+ raise torrents[:result] unless valid? torrents, data[:tag]
157
+ torrents[:arguments][:torrent_added]
158
+ end
159
+
160
+ def torrent_remove(arguments={}, ids=[])
161
+ arguments[:ids] = ids unless ids.empty?
162
+ data = METHODS[:torrent_remove]
163
+ data[:arguments] = argument_name_to_api arguments
164
+ ret = self.do(:post, data)
165
+ torrents = JSON.parse ret[:response].body.gsub("-","_"), {symbolize_names: true}
166
+ raise torrents[:result] unless valid? torrents, data[:tag]
167
+ torrents[:arguments]
168
+ end
169
+
170
+ def torrent_verify(ids = [])
171
+ arguments = {}
172
+ arguments[:ids] = ids unless ids.empty?
173
+ data = METHODS[:torrent_verify]
174
+ data[:arguments] = argument_name_to_api arguments
175
+ ret = self.do(:post, data)
176
+ torrents = JSON.parse ret[:response].body, {symbolize_names: true}
177
+ raise torrents[:result] unless valid? torrents, data[:tag]
178
+ torrents[:arguments]
179
+ end
180
+
181
+ def torrent_reannounce(ids=[])
182
+ arguments = {}
183
+ arguments[:ids] = ids unless ids.empty?
184
+ data = METHODS[:torrent_reannounce]
185
+ data[:arguments] = argument_name_to_api arguments
186
+ ret = self.do(:post, data)
187
+ torrents = JSON.parse ret[:response].body, {symbolize_names: true}
188
+ raise torrents[:result] unless valid? torrents, data[:tag]
189
+ torrents[:arguments]
190
+ end
191
+
192
+ def torrent_set_location(arguments={}, ids=[])
193
+ arguments[:ids] = ids unless ids.empty?
194
+ data = METHODS[:torrent_set_location]
195
+ data[:arguments] = argument_name_to_api arguments
196
+ ret = self.do(:post, data)
197
+ torrents = JSON.parse ret[:response].body.gsub("-","_"), {symbolize_names: true}
198
+ raise torrents[:result] unless valid? torrents, data[:tag]
199
+ torrents[:arguments]
200
+ end
201
+
202
+ def blocklist_update
203
+ data = METHODS[:blocklist_update]
204
+ ret = self.do(:post, data)
205
+ torrents = JSON.parse ret[:response].body.gsub("-","_"), {symbolize_names: true}
206
+ raise torrents[:result] unless valid? torrents, data[:tag]
207
+ torrents[:arguments]
208
+ end
209
+
210
+ def port_test
211
+ data = METHODS[:port_test]
212
+ ret = self.do(:post, data)
213
+ torrents = JSON.parse ret[:response].body.gsub("-","_"), {symbolize_names: true}
214
+ raise torrents[:result] unless valid? torrents, data[:tag]
215
+ torrents[:arguments]
216
+ end
217
+
218
+
219
+ def queue_move_top(ids=[])
220
+ arguments = {}
221
+ arguments[:ids] = ids unless ids.empty?
222
+ data = METHODS[:queue_move_top]
223
+ data[:arguments] = argument_name_to_api arguments
224
+ ret = self.do(:post, data)
225
+ torrents = JSON.parse ret[:response].body.gsub("-","_"), {symbolize_names: true}
226
+ raise torrents[:result] unless valid? torrents, data[:tag]
227
+ torrents[:arguments]
228
+ end
229
+
230
+ def queue_move_up(ids=[])
231
+ arguments = {}
232
+ arguments[:ids] = ids unless ids.empty?
233
+ data = METHODS[:queue_move_up]
234
+ data[:arguments] = argument_name_to_api arguments
235
+ ret = self.do(:post, data)
236
+ torrents = JSON.parse ret[:response].body.gsub("-","_"), {symbolize_names: true}
237
+ raise torrents[:result] unless valid? torrents, data[:tag]
238
+ torrents[:arguments]
239
+ end
240
+
241
+ def queue_move_down(ids=[])
242
+ arguments = {}
243
+ arguments[:ids] = ids unless ids.empty?
244
+ data = METHODS[:queue_move_down]
245
+ data[:arguments] = argument_name_to_api arguments
246
+ ret = self.do(:post, data)
247
+ torrents = JSON.parse ret[:response].body.gsub("-","_"), {symbolize_names: true}
248
+ raise torrents[:result] unless valid? torrents, data[:tag]
249
+ torrents[:arguments]
250
+ end
251
+
252
+ def queue_move_bottom(ids=[])
253
+ arguments = {}
254
+ arguments[:ids] = ids unless ids.empty?
255
+ data = METHODS[:queue_move_bottom]
256
+ data[:arguments] = argument_name_to_api arguments
257
+ ret = self.do(:post, data)
258
+ torrents = JSON.parse ret[:response].body.gsub("-","_"), {symbolize_names: true}
259
+ raise torrents[:result] unless valid? torrents, data[:tag]
260
+ torrents[:arguments]
261
+ end
262
+
263
+
264
+
265
+
266
+ # request
267
+
268
+ def do(method = :get, data = nil)
269
+ headers = @conn[:headers]
270
+
271
+ uri = URI.parse "http://localhost/"
272
+ uri.scheme = @conn[:scheme]
273
+ uri.host = @conn[:host]
274
+ uri.port = @conn[:port]
275
+ uri.path = @conn[:path]
276
+
277
+ # request
278
+ http = Net::HTTP.new uri.host, uri.port
279
+ resp = http.get(uri.request_uri, data.to_json, headers) if method == :get
280
+ resp = http.post(uri.request_uri, data.to_json, headers) if method == :post
281
+ raise "not implemented #{method} request!!" if method != :get && method != :post
282
+
283
+ # authorize via session id
284
+ if resp.code.to_i == 409
285
+ tmp = Nokogiri::HTML resp.body
286
+ session_id = tmp.search('p code', '//X-Transmission-Session-Id').first.text.gsub("X-Transmission-Session-Id: ",'')
287
+ @conn[:headers]["X-Transmission-Session-Id"] = session_id
288
+ # recursion!!!!
289
+ return self.do(:post, data)
290
+ end
291
+
292
+ ret = {request: http, response: resp}
293
+ handle_request_error(ret, data)
294
+ ret
295
+ end
296
+
297
+
298
+ private
299
+
300
+ def valid?(body, tag)
301
+ body[:result] == "success" && body[:tag] == tag
302
+ end
303
+
304
+ def argument_name_to_api(options = {})
305
+ #TODO: fix nested arguments
306
+ ret = {}
307
+ options.each do |k,v|
308
+ ret[k.to_s.gsub('_','-')] = v
309
+ end
310
+ ret
311
+ end
312
+
313
+ # error handling
314
+
315
+ def handle_request_error(ret, data)
316
+ raise "error handling: #{data[:method]}, #{request_str ret[:response]}" if ret[:response].code.to_i != 200
317
+ end
318
+
319
+
320
+ def request_str(response)
321
+ ret = []
322
+ ret << "[code: #{response.code}]"
323
+ ret << "[body: #{response.body}]"
324
+ ret.join "\n"
325
+ end
326
+
327
+
328
+ end
329
+
330
+ end
331
+ end
@@ -0,0 +1,56 @@
1
+
2
+
3
+ module Trans
4
+ module Api
5
+
6
+ class File
7
+
8
+ def initialize(options={})
9
+ @torrent = options.delete :torrent
10
+ @torrent_fields = options.delete :fields
11
+ @fields = options[:file]
12
+
13
+ # set default stats
14
+ @torrent_fields[:files_unwanted] ||= []
15
+ @torrent_fields[:files_wanted] ||= []
16
+ # if @fields[:fileStat][:wanted] == false
17
+ # @torrent_fields[:files_unwanted] << self.id
18
+ # else
19
+ # @torrent_fields[:files_wanted] << self.id
20
+ # end
21
+
22
+ @client = Client.new
23
+ end
24
+
25
+ def id
26
+ @fields[:id]
27
+ end
28
+
29
+ def name
30
+ @fields[:name]
31
+ end
32
+
33
+ def stat
34
+ @fields[:fileStat]
35
+ end
36
+
37
+ def unwant
38
+ @torrent_fields[:files_wanted].delete self.id if @torrent_fields[:files_wanted].include? self.id
39
+ @torrent_fields[:files_unwanted] << self.id unless @torrent_fields[:files_unwanted].include? self.id
40
+ @fields[:fileStat][:wanted] = false
41
+ end
42
+
43
+ def want
44
+ @torrent_fields[:files_wanted] << self.id unless @torrent_fields[:files_wanted].include? self.id
45
+ @torrent_fields[:files_unwanted].delete self.id if @torrent_fields[:files_unwanted].include? self.id
46
+ @fields[:fileStat][:wanted] = true
47
+ end
48
+
49
+ def wanted?
50
+ @fields[:fileStat][:wanted]
51
+ end
52
+
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,83 @@
1
+ require 'singleton'
2
+
3
+
4
+ module Trans
5
+ module Api
6
+
7
+ #
8
+ # Session class
9
+ #
10
+
11
+ class Session
12
+
13
+ include Singleton
14
+
15
+ ENCRYPTION = [ :required, :preferred, :tolerated ]
16
+
17
+
18
+ def initialize
19
+ @client = Client.new
20
+ self.reset!
21
+
22
+ # map fields to accessors
23
+ @fields.each do |k, v|
24
+ # setter
25
+ self.metaclass.send(:define_method, "#{k}=") do |value|
26
+ if v.class == value.class
27
+ @fields[k] = value
28
+ else
29
+ msg = "invalid type: #{value.class}, expected: #{v.class}"
30
+ @last_error[:message] = msg
31
+ raise msg
32
+ end
33
+ end
34
+ # getter
35
+ self.metaclass.send(:define_method, "#{k}") do
36
+ @fields[k]
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ def fields
43
+ @fields.map{|k,v| k}
44
+ end
45
+
46
+ def fields_and_values
47
+ @fields
48
+ end
49
+
50
+ def errors
51
+ @last_error
52
+ end
53
+
54
+ def stats!
55
+ @client.connect.session_stats
56
+ end
57
+
58
+ def save!
59
+ # reject unchanged fields
60
+ changed = @fields.reject{|k,v| @old_fields[k] == v }
61
+ if changed.size > 0
62
+ # call api, and store changed fields
63
+ @client.connect.session_set changed
64
+ # refresh
65
+ self.reset!
66
+ end
67
+ end
68
+
69
+ # reload current object
70
+ def reset!
71
+ @fields = @client.connect.session_get
72
+ @old_fields = @fields.clone
73
+ @last_error = {error: "", message: ""}
74
+ end
75
+
76
+
77
+ def metaclass
78
+ class << self; self; end
79
+ end
80
+ end
81
+
82
+ end
83
+ end