deluge 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 621e509656351a32651a96cca7575b275f5c42ed
4
+ data.tar.gz: 10b04f90dcfd5b5b9b3457138a868289a284be8c
5
+ SHA512:
6
+ metadata.gz: 603a6c8ac060f091897379b867b11edc52fc71477ffb2df708bad52693a3d8b9abd4abdb545a1e22101585f8b5f399e0e0c5f33dd501ab6c0e282f28388217b1
7
+ data.tar.gz: 1c989baf119615cf58072f27ec645f23ad22f9a57ad341ebf6742099710e95169fb5512f235d921b8aabe82d308e1dcd9e3fcda43cfd55cbd9ff59240136b998
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'turn'
4
+ gem 'shoulda'
5
+ gem 'rake'
6
+ gem 'rencode'
7
+
8
+ gemspec
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Mikael Wikman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
22
+
@@ -0,0 +1,25 @@
1
+ ## Deluge Ruby Client
2
+
3
+ This is a client library written in Ruby for communicating with Deluge daemon process. This implementation currently only supports V3 of the protocol (any version with major number 3, e.g. Deluge 3.3).
4
+
5
+ It has all core and daemon RPC functions defined, and you can access any plugin exported functions through the Deluge#call method.
6
+
7
+ ### Usage
8
+
9
+ ```ruby
10
+ gem install deluge
11
+
12
+ # defaults are 'localhost', '58846'
13
+ d = Deluge.new '192.168.1.11', '58800'
14
+
15
+ d.login 'user', 'pass'
16
+
17
+ d.add_torrent_url 'http://some-evil-tracker.com/juicy.torrent'
18
+
19
+ # example of the dynamic Deluge#call method
20
+ d.call 'webui.get_config', {}
21
+ ```
22
+
23
+ ### Methods
24
+
25
+ See _lib/deluge_ for all defined methods.
@@ -0,0 +1,15 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.authors = ['Mikael Wikman']
5
+ gem.email = ['mikael@wikman.me']
6
+ gem.description = %q{}
7
+ gem.summary = %q{Ruby implementation of the Deluge RPC API}
8
+ gem.files = `git ls-files`.split($\)
9
+ gem.homepage = 'https://github.com/mikaelwikman/deluge-ruby'
10
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
11
+ gem.test_files = gem.files.grep(%r{^(test|features)/})
12
+ gem.name = "deluge"
13
+ gem.require_paths = ["lib"]
14
+ gem.version = '1.0.0'
15
+ end
@@ -0,0 +1,265 @@
1
+ require 'deluge/protocol'
2
+ require 'base64'
3
+
4
+ class Deluge
5
+
6
+ def initialize host='localhost', port='58846', protocol=3
7
+ @protocol = protocol
8
+
9
+ if protocol == 3
10
+ @con = Protocol::V3.new(host, port)
11
+ else
12
+ raise "Unsupported protocol #{protocol}"
13
+ end
14
+ end
15
+
16
+ def call *args
17
+ @con.call *args
18
+ end
19
+
20
+ def login username, password
21
+ @con.call 'daemon.login', username, password
22
+ end
23
+
24
+ def shutdown args, kwargs={}
25
+ args = [args] unless args.is_a?(Array)
26
+ @con.call 'daemon.shutdown', args, kwargs
27
+ end
28
+
29
+ def info
30
+ @con.call 'daemon.info'
31
+ end
32
+
33
+ def get_method_list
34
+ @con.call 'daemon.get_method_list'
35
+ end
36
+
37
+ # Available options:
38
+ # max_connections max_upload_slots max_upload_speed max_download_speed prioritize_first_last_pieces file_priorities compact_allocation download_location auto_managed stop_at_ratio stop_ratio remove_at_ratio move_completed move_completed_path add_paused
39
+ def add_torrent_file filename, filedump, options={}
40
+ filedump_enc = Base64.encode64(filedump)
41
+ @con.call 'core.add_torrent_file', filename, filedump_enc, options, {}
42
+ end
43
+
44
+ # Available options:
45
+ # max_connections max_upload_slots max_upload_speed max_download_speed prioritize_first_last_pieces file_priorities compact_allocation download_location auto_managed stop_at_ratio stop_ratio remove_at_ratio move_completed move_completed_path add_paused
46
+ def add_torrent_url url, options={}
47
+ @con.call 'core.add_torrent_url', url, options, {}
48
+ end
49
+
50
+ # Available options:
51
+ # max_connections max_upload_slots max_upload_speed max_download_speed prioritize_first_last_pieces file_priorities compact_allocation download_location auto_managed stop_at_ratio stop_ratio remove_at_ratio move_completed move_completed_path add_paused
52
+ def add_torrent_magnet uri, options={}
53
+ @con.call 'core.add_torrent_magnet', uri, options, {}
54
+ end
55
+
56
+ def remove_torrent torrent_id, remove_data: false
57
+ @con.call 'core.remove_torrent', torrent_id, remove_data
58
+ end
59
+
60
+
61
+ # Gets the session status values for 'keys', these keys are taking
62
+ # from libtorrent's session status.
63
+
64
+ # See: http://www.rasterbar.com/products/libtorrent/manual.html#status
65
+ def get_session_status keys
66
+ @con.call 'core.get_session_status', keys, {}
67
+ end
68
+
69
+ def get_cache_status
70
+ @con.call 'core.get_cache_status', {}
71
+ end
72
+
73
+ def force_reannounce *torrent_ids
74
+ @con.call 'core.force_reannounce', torrent_ids, {}
75
+ end
76
+
77
+ def pause_torrent *torrent_ids
78
+ @con.call 'core.pause_torrent', torrent_ids, {}
79
+ end
80
+
81
+ def connect_peer torrent_id, ip, port
82
+ @con.call 'core.connect_peer', torrent_id, ip, port, {}
83
+ end
84
+
85
+ def move_storage *torrent_ids, dest
86
+ @con.call 'core.move_storage', torrent_ids, dest, {}
87
+ end
88
+
89
+ def pause_all_torrents
90
+ @con.call 'core.pause_all_torrents', {}
91
+ end
92
+
93
+ def resume_all_torrents
94
+ @con.call 'core.resume_all_torrents', {}
95
+ end
96
+
97
+ def resume_torrent *torrent_ids
98
+ @con.call 'core.resume_torrent', torrent_ids, {}
99
+ end
100
+
101
+ def get_torrent_status torrent_id, keys, diff=false
102
+ @con.call 'core.get_torrent_status', torrent_id, keys, diff, {}
103
+ end
104
+
105
+ def get_filter_tree show_zero_hits=true, hide_cat=nil
106
+ @con.call 'core.get_filter_tree', show_zero_hits, hide_cat, {}
107
+ end
108
+
109
+ def get_session_state
110
+ @con.call 'core.get_session_state', {}
111
+ end
112
+
113
+ def get_config
114
+ @con.call 'core.get_config', {}
115
+ end
116
+
117
+ def get_config_values *keys
118
+ @con.call 'core.get_config_values', keys, {}
119
+ end
120
+
121
+ def set_config config
122
+ @con.call 'core.set_config', config, {}
123
+ end
124
+
125
+ def get_listen_port
126
+ @con.call 'core.get_listen_port', {}
127
+ end
128
+
129
+ def get_num_connections
130
+ @con.call 'core.get_num_connections', {}
131
+ end
132
+
133
+ def get_available_plugins
134
+ @con.call 'core.get_available_plugins', {}
135
+ end
136
+
137
+ def get_enabled_plugins
138
+ @con.call 'core.get_enabled_plugins', {}
139
+ end
140
+
141
+ def enable_plugin plugin
142
+ @con.call 'core.enable_plugin', plugin, {}
143
+ end
144
+
145
+ def disable_plugin plugin
146
+ @con.call 'core.disable_plugin', plugin, {}
147
+ end
148
+
149
+ def force_recheck *torrent_ids
150
+ @con.call 'core.force_recheck', torrent_ids, {}
151
+ end
152
+
153
+ def set_torrent_options torrent_ids, options={}
154
+ torrent_ids = [torrent_ids] unless torrent_ids.kind_of?(Array)
155
+ @con.call 'core.set_torrent_options',torrent_ids, options, {}
156
+ end
157
+
158
+ def set_torrent_trackers torrent_id, *trackers
159
+ @con.call 'core.set_torrent_trackers', torrent_id, trackers, {}
160
+ end
161
+
162
+ def set_torrent_max_connections torrent_id, value
163
+ @con.call 'core.set_torrent_max_connections', torrent_id, value, {}
164
+ end
165
+
166
+ def set_torrent_max_upload_slots torrent_id, value
167
+ @con.call 'core.set_torrent_max_upload_stats', torrent_id, value, {}
168
+ end
169
+
170
+ def set_torrent_max_upload_speed torrent_id, value
171
+ @con.call 'core.set_torrent_max_upload_speed', torrent_id, value, {}
172
+ end
173
+
174
+ def set_torrent_max_download_speed torrent_id, value
175
+ @con.call 'core.set_torrent_max_download_speed', torrent_id, value, {}
176
+ end
177
+
178
+ def set_torrent_file_priorities torrent_id, priorities
179
+ @con.call 'core.set_torrent_file_priorities', torrent_id, priorities, {}
180
+ end
181
+
182
+ def set_torrent_prioritize_first_last torrent_id, value
183
+ @con.call 'core.set_torrent_prioritize_first_last', torrent_id, value, {}
184
+ end
185
+
186
+ def set_torrent_auto_managed torrent_id, value
187
+ @con.call 'core.set_torrent_auto_managed', torrent_id, value, {}
188
+ end
189
+
190
+ def set_torrent_stop_at_ratio torrent_id, value
191
+ @con.call 'core.set_torrent_stop_at_ratio', torrent_id, value, {}
192
+ end
193
+
194
+ def set_torrent_stop_ratio torrent_id, value
195
+ @con.call 'core.set_torrent_stop_ratio', torrent_id, value, {}
196
+ end
197
+
198
+ def set_torrent_remove_at_ratio torrent_id, value
199
+ @con.call 'core.set_torrent_remove_at_ratio', torrent_id, value, {}
200
+ end
201
+
202
+ def set_torrent_move_completed torrent_id, value
203
+ @con.call 'core.set_torrent_move_completed', torrent_id, value, {}
204
+ end
205
+
206
+ def set_torrent_move_completed_path torrent_id, value
207
+ @con.call 'core.set_torrent_move_completed_path', torrent_id, value, {}
208
+ end
209
+
210
+ def get_path_size path
211
+ @con.call 'core.get_path_size', path, {}
212
+ end
213
+
214
+ def create_torrent path, tracker, piece_length, comment, target, webseeds, privat, created_by, trackers, add_to_session
215
+ @con.call 'core.create_torrent', path, tracker, piece_length, comment, target, webseeds, privat, created_by, trackers, add_to_session, {}
216
+ end
217
+
218
+ def upload_plugin filename, filedump
219
+ @con.call 'core.upload_plugin', filename, filedump, {}
220
+ end
221
+
222
+ def rescan_plugins
223
+ @con.call 'core.rescan_plugins', {}
224
+ end
225
+
226
+ def rename_files torrent_id, filenames
227
+ @con.call 'core.rename_files', torrent_id, filenames, {}
228
+ end
229
+
230
+ def rename_folder torrent_id, folder, new_folder
231
+ @con.call 'core.rename_folder', torrent_id, folder, new_folder, {}
232
+ end
233
+
234
+ def queue_top *torrent_ids
235
+ @con.call 'core.queue_top', torrent_ids, {}
236
+ end
237
+
238
+ def queue_up *torrent_ids
239
+ @con.call 'core.queue_up', torrent_ids, {}
240
+ end
241
+
242
+ def queue_down *torrent_ids
243
+ @con.call 'core.queue_down', torrent_ids, {}
244
+ end
245
+
246
+ def queue_bottom *torrent_ids
247
+ @con.call 'core.queue_bottom', torrent_ids, {}
248
+ end
249
+
250
+ def glob path
251
+ @con.call 'core.glob', path, {}
252
+ end
253
+
254
+ def test_listen_port
255
+ @con.call 'core.test_listen_port', {}
256
+ end
257
+
258
+ def get_free_space path=nil
259
+ @con.call 'core.get_free_space', path, {}
260
+ end
261
+
262
+ def get_libtorrent_version
263
+ @con.call 'core.get_libtorrent_version'
264
+ end
265
+ end
@@ -0,0 +1,3 @@
1
+ class Protocol
2
+ require 'deluge/protocol/v3'
3
+ end
@@ -0,0 +1,166 @@
1
+ require 'rencode'
2
+ require 'zlib'
3
+ require 'socket'
4
+ require 'thread'
5
+ require 'openssl'
6
+
7
+ class Deluge
8
+ class Protocol::V3
9
+ # Data messages are transfered using very a simple protocol.
10
+ # Data messages are transfered with a header containing
11
+ # the length of the data to be transfered (payload).
12
+
13
+ def initialize host, port
14
+ @buffer = []
15
+ @message_length = 0
16
+ @messages = []
17
+ @mutex = Mutex.new
18
+ @host = host
19
+ @port = port
20
+ @counter = 0
21
+
22
+ connection = TCPSocket.new(host, port)
23
+ ctx = OpenSSL::SSL::SSLContext.new
24
+ ctx.set_params(verify_mode: OpenSSL::SSL::VERIFY_NONE)
25
+ ctx.ssl_version = :SSLv3
26
+
27
+ @con = OpenSSL::SSL::SSLSocket.new(connection, ctx)
28
+ @con.connect
29
+
30
+ Thread.new do
31
+ loop do
32
+ begin
33
+ data = @con.readpartial(1024)
34
+
35
+ if data.length == 0
36
+ puts "We lost the connection!"
37
+ break
38
+ end
39
+
40
+ handle_new_data(data.bytes)
41
+
42
+ rescue Exception => e
43
+ puts e.message
44
+ p e.backtrace
45
+ sleep 1
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ def call method, *args
52
+ kwargs = if args.last.is_a?(Hash)
53
+ args.delete_at(-1)
54
+ else
55
+ {}
56
+ end
57
+
58
+ request_id = @counter+=1
59
+
60
+ send([request_id, method, args, kwargs])
61
+
62
+ response = nil
63
+
64
+ sleep 0.01 until response = @messages.find{|m| m[1] == request_id}
65
+ @messages.delete(response)
66
+
67
+ if response[0] == 1
68
+ return response[2]
69
+ else
70
+ raise response.inspect
71
+ end
72
+ end
73
+
74
+ # Transfer the data.
75
+
76
+ # The data will be serialized and compressed before being sent.
77
+ # First a header is sent - containing the length of the compressed payload
78
+ # to come as a signed integer. After the header, the payload is transfered.
79
+ # :param msg: data to be transfered in a data structure serializable by rencode.
80
+ def send msg
81
+ rencoded = REncode.dump([msg]).pack('C*')
82
+ #rencoded = [193, 196, 1, 139, 100, 97, 101, 109, 111, 110, 46, 105, 110, 102, 111, 192, 102].pack('C*')
83
+ compressed = Zlib::Deflate.deflate(rencoded)
84
+ raw = compressed.bytes
85
+
86
+ # all commented out stuff is for version 4, which we do not yet support
87
+ # Store length as a signed integer (using 4 bytes), network byte order
88
+ # header = [raw.length].pack('N').bytes
89
+
90
+ #every message begins with an ASCII 'D'
91
+ # header.insert(0, 'D'.ord)
92
+
93
+ # header_str = header.pack('C*')
94
+ message_str = raw.pack('C*')
95
+
96
+ # puts "Writing header:"
97
+ # p header_str.bytes
98
+ # puts
99
+ # puts "Writing message:"
100
+ # p rencoded.bytes
101
+ # puts
102
+
103
+
104
+ # @con.write(header_str)
105
+ @con.write(message_str)
106
+ @con.flush
107
+
108
+ nil
109
+ end
110
+
111
+ def read
112
+ @mutex.synchronize do
113
+ @messages.shift
114
+ end
115
+ end
116
+
117
+ def count
118
+ @mutex.synchronize do
119
+ @messages.count
120
+ end
121
+ end
122
+
123
+ private
124
+
125
+ def handle_new_data data, &block
126
+ # commented code is for version 4, not yet supported
127
+ # @buffer.concat(data)
128
+ #
129
+ # while @buffer.length >= 5 # 'D' + 4 byte integer
130
+ # if @message_length == 0
131
+ # handle_new_message
132
+ # end
133
+ #
134
+ # if @buffer.length >= @message_length
135
+ # message = @buffer.shift(@message_length)
136
+ # @message_length = 0
137
+ #
138
+ message = data
139
+ handle_complete_message(message)
140
+ # end
141
+ # end
142
+ end
143
+
144
+ # def handle_new_message
145
+ # header = @buffer.shift(5)
146
+ # verify_d = header.shift(1)
147
+ #
148
+ # unless verify_d == 'D'.ord
149
+ # raise "Invalid header format, First byte is #{verify_d}"
150
+ # else
151
+ # @message_length = header.pack('C*').unpack('N')[0]
152
+ # raise "Message length is negative: #{@message_length}" if @message_length < 0
153
+ # end
154
+ # end
155
+
156
+ def handle_complete_message data
157
+ decompressed = Zlib::Inflate.inflate(data.pack('C*'))
158
+ derencoded = REncode.parse(decompressed)
159
+
160
+ @mutex.synchronize do
161
+ @messages << derencoded
162
+ end
163
+ end
164
+
165
+ end
166
+ end
@@ -0,0 +1,10 @@
1
+ require 'bundler/setup'
2
+ require 'test/unit'
3
+ require 'turn/autorun'
4
+ require 'shoulda'
5
+
6
+ $LOAD_PATH << 'lib'
7
+
8
+ class TestCase < Test::Unit::TestCase
9
+ end
10
+
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: deluge
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Mikael Wikman
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-13 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: ''
14
+ email:
15
+ - mikael@wikman.me
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - Gemfile
21
+ - LICENSE.txt
22
+ - README.md
23
+ - app.gemspec
24
+ - lib/deluge.rb
25
+ - lib/deluge/protocol.rb
26
+ - lib/deluge/protocol/v3.rb
27
+ - test/test_helper.rb
28
+ homepage: https://github.com/mikaelwikman/deluge-ruby
29
+ licenses: []
30
+ metadata: {}
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements: []
46
+ rubyforge_project:
47
+ rubygems_version: 2.2.2
48
+ signing_key:
49
+ specification_version: 4
50
+ summary: Ruby implementation of the Deluge RPC API
51
+ test_files:
52
+ - test/test_helper.rb