deluge 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +25 -0
- data/app.gemspec +15 -0
- data/lib/deluge.rb +265 -0
- data/lib/deluge/protocol.rb +3 -0
- data/lib/deluge/protocol/v3.rb +166 -0
- data/test/test_helper.rb +10 -0
- metadata +52 -0
checksums.yaml
ADDED
@@ -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
data/LICENSE.txt
ADDED
@@ -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
|
+
|
data/README.md
ADDED
@@ -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.
|
data/app.gemspec
ADDED
@@ -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
|
data/lib/deluge.rb
ADDED
@@ -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,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
|
data/test/test_helper.rb
ADDED
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
|