deluge 1.0.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.
@@ -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