deluge-rpc 0.1.3

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: 8fdcfe0656ef321dd54ac8d8f5a8f788bcd17cd6
4
+ data.tar.gz: 1157ad39d1fbfe536fb1d35ed4c72c8dc16cad6f
5
+ SHA512:
6
+ metadata.gz: c2d795c706720dff717af01890ec926fb258444353456fe725dde872ba83c9a22e7d724760719daab364975f2dbd8136dfa6f605e338c798894e7b9e5b4aeb30
7
+ data.tar.gz: 4225adb085e5dceab4e3e7268335cfaddad2b53c9b62ada57df808cf69f1ca0644f000cc4ebc3636f512b24e7e597686b49456dd7e1d011692c2d0eb2f25d15c
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ *.gem
@@ -0,0 +1 @@
1
+ 2.1.4
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in deluge-api.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Igor Yamolov
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,122 @@
1
+ # Deluge::RPC
2
+
3
+ Ruby RPC client library for Deluge torrent client.
4
+ Provides dynamic API bindings depending on RPC server.
5
+ Multi-threading friendly, thanks to ``concurrent-ruby`` gem.
6
+
7
+ Official RPC protocol documentation:
8
+ http://deluge.readthedocs.org/en/develop/core/rpc.html
9
+
10
+ Deluge RPC API reference:
11
+ http://deluge.readthedocs.org/en/develop/core/rpc.html#remote-api
12
+
13
+ ## Usage
14
+
15
+ ```ruby
16
+ require 'deluge'
17
+
18
+ # Initialize client
19
+ client = Deluge::Rpc::Client.new(
20
+ host: 'localhost', port: 58846,
21
+ login: 'username', password: 'password'
22
+ )
23
+
24
+ # Start connection and authenticate
25
+ client.connect
26
+
27
+ # Get auth level
28
+ client.auth_level
29
+ # => 5
30
+
31
+ # Get available methods
32
+ client.api_methods
33
+ # => ['daemon.add_torrent_file', 'core.shutdown', ...]
34
+
35
+ # Get deluge version
36
+ client.daemon.info
37
+ # => "1.3.10"
38
+
39
+ # Get torrents list
40
+ client.core.get_torrents_status({}, ['name', 'hash'])
41
+ # => [{name: 'Hot Chicks Action', hash: '<torrent_hash>'}, ...]
42
+
43
+ # Get namespace
44
+ core = client.core
45
+ # => <Deluge::Rpc::Namespace name="core">
46
+
47
+ # Get namespace methods
48
+ core.api_methods
49
+ # => ['core.get_session_status', 'core.get_upload_rate', ....]
50
+
51
+ # Invoke namespace method
52
+ core.get_config
53
+ # => {"info_sent"=>0.0, "lsd"=>true, "send_info"=>false, ... }
54
+
55
+ # Close connection
56
+ client.close
57
+ ```
58
+
59
+ ## Events
60
+
61
+ Receiving events is dead simple:
62
+
63
+ ```ruby
64
+ client.register_event('TorrentAddedEvent') do |torrent_id|
65
+ puts "Torrent #{torrent_id} was added! YAY!"
66
+ end
67
+
68
+ # You can pass symbols as event name,
69
+ # they would be converted to proper camelcase event names aka "EventNameEvent"
70
+ client.register_event(:torrent_removed) do |torrent_id|
71
+ puts "Torrent #{torrent_id} was removed ;_;"
72
+ end
73
+ ```
74
+
75
+ Unfortunately there is no way to listen ALL events, due to Deluge architecture.
76
+ You have to register each event you need.
77
+
78
+ **Keep in mind event blocks would be executed in connection thread, NOT main thread!**
79
+ Avoid time-consuming code!
80
+
81
+ ### Known events
82
+
83
+ Event Name | Arguments | Description
84
+ --------------------------|-------------------------|------------------------------------------------------
85
+ ``TorrentAddedEvent`` | torrent_id | New torrent is successfully added to the session.
86
+ ``TorrentRemovedEvent`` | torrent_id | Torrent has been removed from the session.
87
+ ``PreTorrentRemovedEvent`` | torrent_id | Torrent is about to be removed from the session.
88
+ ``TorrentStateChangedEvent`` | torrent_id, state | Torrent changes state.
89
+ ``TorrentQueueChangedEvent`` | &nbsp; | The queue order has changed.
90
+ ``TorrentFolderRenamedEvent`` | torrent_id, old, new | Folder within a torrent has been renamed.
91
+ ``TorrentFileRenamedEvent`` | torrent_id, index, name | File within a torrent has been renamed.
92
+ ``TorrentFinishedEvent`` | torrent_id | Torrent finishes downloading.
93
+ ``TorrentResumedEvent`` | torrent_id | Torrent resumes from a paused state.
94
+ ``TorrentFileCompletedEvent`` | torrent_id, index | File completes.
95
+ ``NewVersionAvailableEvent`` | new_release | More recent version of Deluge is available.
96
+ ``SessionStartedEvent`` | &nbsp; | Session has started. This typically only happens once when the daemon is initially started.
97
+ ``SessionPausedEvent`` | &nbsp; | Session has been paused.
98
+ ``SessionResumedEvent`` | &nbsp; | Session has been resumed.
99
+ ``ConfigValueChangedEvent`` | key, value | Config value changes in the Core.
100
+ ``PluginEnabledEvent`` | name | Plugin is enabled in the Core.
101
+ ``PluginDisabledEvent`` | name | Plugin is disabled in the Core.
102
+
103
+ This list was extracted from Deluge 1.3.11 sources. Events for your version could be different.
104
+ There is no official documentation.
105
+
106
+ Current events could be found here: http://git.deluge-torrent.org/deluge/tree/deluge/event.py
107
+
108
+ ## Installation
109
+
110
+ Add this line to your application's Gemfile:
111
+
112
+ ```ruby
113
+ gem 'deluge-rpc'
114
+ ```
115
+
116
+ And then execute:
117
+
118
+ $ bundle
119
+
120
+ Or install it yourself as:
121
+
122
+ $ gem install deluge-rpc
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'deluge/rpc/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "deluge-rpc"
8
+ spec.version = Deluge::Rpc::VERSION
9
+ spec.authors = ["Igor Yamolov"]
10
+ spec.email = ["clouster@yandex.ru"]
11
+ spec.summary = %q{Deluge RPC protocol wrapper}
12
+ spec.description = %q{Communicate with Deluge torrent client via RPC protocol}
13
+ spec.homepage = "https://github.com/t3hk0d3/deluge-rpc"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'concurrent-ruby', '~> 0.7'
22
+ spec.add_dependency 'rencoder', '~> 0.1'
23
+ spec.add_development_dependency 'bundler', '~> 1.7'
24
+ spec.add_development_dependency 'rspec', '~> 3.1'
25
+ end
@@ -0,0 +1 @@
1
+ require_relative 'deluge/rpc'
@@ -0,0 +1,8 @@
1
+ module Deluge
2
+ module Rpc
3
+ require_relative 'rpc/version'
4
+ require_relative 'rpc/namespace'
5
+ require_relative 'rpc/connection'
6
+ require_relative 'rpc/client'
7
+ end
8
+ end
@@ -0,0 +1,89 @@
1
+ module Deluge
2
+ module Rpc
3
+ class Client
4
+ attr_reader :namespaces, :api_methods, :auth_level
5
+
6
+ def initialize(options = {})
7
+ @connection = Deluge::Rpc::Connection.new(options)
8
+ @login = options.fetch(:login)
9
+ @password = options.fetch(:password)
10
+
11
+ @namespaces = {}
12
+ @api_methods = []
13
+ end
14
+
15
+ def connect
16
+ @connection.start
17
+
18
+ @auth_level = @connection.authenticate(@login, @password)
19
+
20
+ register_methods!
21
+
22
+ true
23
+ end
24
+
25
+ def register_event(event_name, &block)
26
+ raise "Provide block for event" unless block
27
+
28
+ if event_name.is_a?(Symbol)
29
+ event_name = "#{event_name}_event"
30
+ # convert to CamelCase
31
+ event_name.gsub!(/(?:_|^)(.)/) { |match| $1.upcase }
32
+ end
33
+
34
+ @connection.register_event(event_name, &block)
35
+ end
36
+
37
+ def close
38
+ @connection.close
39
+ @auth_level = nil
40
+ @api_methods = []
41
+ @namespaces.each_key do |ns|
42
+ self.singleton_class.send :undef_method, ns
43
+ end
44
+ @namespaces = {}
45
+ end
46
+
47
+ private
48
+
49
+ def register_methods!
50
+ methods = @connection.method_list
51
+
52
+ methods.each do |method|
53
+ *namespaces, method_name = method.split('.')
54
+
55
+ register_method!(namespaces, method_name)
56
+ @api_methods << method
57
+ end
58
+ end
59
+
60
+ def register_method!(namespaces, method)
61
+ namespace = register_namespace(namespaces)
62
+
63
+ namespace.register_method(method)
64
+ end
65
+
66
+ def register_namespace(namespaces)
67
+ ns = namespaces.shift
68
+
69
+ root = @namespaces[ns]
70
+
71
+ unless root
72
+ root = Rpc::Namespace.new(ns, @connection)
73
+ @namespaces[ns] = root
74
+
75
+ define_singleton_method(ns.to_sym) do
76
+ @namespaces[ns]
77
+ end
78
+ end
79
+
80
+ namespaces.each do |namespace|
81
+ root = root.register_namespace(namespace)
82
+ end
83
+
84
+ root
85
+ end
86
+
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,234 @@
1
+ require 'rencoder'
2
+ require 'socket'
3
+ require 'openssl'
4
+ require 'thread'
5
+ require 'zlib'
6
+ require 'stringio'
7
+
8
+ require 'concurrent'
9
+
10
+ module Deluge
11
+ module Rpc
12
+ class Connection
13
+ class RPCError < StandardError; end
14
+ class InvokeTimeoutError < StandardError; end
15
+ class ConnectionClosedError < StandardError; end
16
+
17
+ DAEMON_LOGIN = 'daemon.login'
18
+ DAEMON_METHOD_LIST = 'daemon.get_method_list'
19
+ DAEMON_REGISTER_EVENT = 'daemon.set_event_interest'
20
+
21
+ DEFAULT_CALL_TIMEOUT = 5.0 # seconds
22
+
23
+ DEFAULT_PORT = 58846
24
+
25
+ RPC_RESPONSE = 1
26
+ RPC_ERROR = 2
27
+ RPC_EVENT = 3
28
+
29
+ attr_reader :host, :port
30
+
31
+ def initialize(options = {})
32
+ @host = options.delete(:host) || 'localhost'
33
+ @port = (options.delete(:port) || DEFAULT_PORT).to_i
34
+
35
+ @call_timeout = options.delete(:call_timeout) || DEFAULT_CALL_TIMEOUT
36
+
37
+ @request_id = Concurrent::AtomicFixnum.new
38
+ @running = Concurrent::AtomicBoolean.new
39
+
40
+ @messages = {}
41
+ @events = {}
42
+
43
+ @write_mutex = Mutex.new
44
+ end
45
+
46
+ def start
47
+ raise 'Connection already opened' if @connection
48
+
49
+ @connection = OpenSSL::SSL::SSLSocket.new(create_socket, ssl_context)
50
+
51
+ @connection.connect
52
+
53
+ @running.make_true
54
+
55
+ @main_thread = Thread.current
56
+ @thread = Thread.new(&self.method(:read_loop))
57
+
58
+ # register present events
59
+ recover_events! if @events.size > 0
60
+
61
+ true
62
+ end
63
+
64
+ def authenticate(login, password)
65
+ self.call(DAEMON_LOGIN, login, password)
66
+ end
67
+
68
+ def method_list
69
+ self.call(DAEMON_METHOD_LIST)
70
+ end
71
+
72
+ def register_event(event_name, force = false, &block)
73
+ unless @events[event_name] # Register event only ONCE!
74
+ self.call(DAEMON_REGISTER_EVENT, [event_name]) if @connection # Let events be initialized lazily
75
+ end
76
+
77
+ @events[event_name] ||= []
78
+ @events[event_name] << block
79
+
80
+ true
81
+ end
82
+
83
+ def close
84
+ @running.make_false
85
+ end
86
+
87
+ def call(method, *args)
88
+ raise "Not connected!" unless @connection
89
+
90
+ kwargs = {}
91
+ kwargs = args.pop if args.size == 1 && args.last.is_a?(Hash)
92
+
93
+ future = Concurrent::IVar.new
94
+
95
+ request_id = @request_id.increment
96
+ @messages[request_id] = future
97
+
98
+ message = [[request_id, method, args, kwargs]]
99
+
100
+ write_packet(message)
101
+
102
+ result = future.value!(@call_timeout)
103
+
104
+ if result.nil? && future.pending?
105
+ raise InvokeTimeoutError.new("Failed to retreive response for '#{method}' in #{@call_timeout} seconds. Probably method not exists.")
106
+ end
107
+
108
+ result
109
+ end
110
+
111
+ private
112
+
113
+ def read_loop
114
+ while(@running.true?)
115
+ io_poll = IO.select([@connection], nil, [@connection], 0.1)
116
+
117
+ next unless io_poll
118
+
119
+ read_sockets, _, error_sockets = io_poll
120
+
121
+ if @connection.eof?
122
+ # TODO: implement auto-recovery
123
+ raise ConnectionClosedError
124
+ end
125
+
126
+ read_sockets.each do |socket|
127
+ packets = read_packets(socket)
128
+
129
+ packets.each do |packet|
130
+ dispatch_packet(packet)
131
+ end
132
+ end
133
+ end
134
+ rescue => e
135
+ @main_thread.raise(e)
136
+ ensure
137
+ @connection.close if @connection
138
+ @connection = nil
139
+ @messages.clear
140
+ end
141
+
142
+ def dispatch_packet(packet)
143
+ type, response_id, value = packet
144
+
145
+ case type
146
+ when RPC_RESPONSE, RPC_ERROR
147
+ future = @messages.delete(response_id)
148
+
149
+ return unless future # TODO: Handle unknown messages
150
+
151
+ if type == RPC_RESPONSE
152
+ future.set(value)
153
+ else
154
+ future.fail(RPCError.new(value))
155
+ end
156
+ when RPC_EVENT
157
+ handlers = @events[response_id]
158
+ return unless handlers # TODO: Handle unknown events
159
+
160
+ handlers.each do |block|
161
+ block.call(*value)
162
+ end
163
+ else
164
+ raise "Unknown packet type #{type.inspect}"
165
+ end
166
+ end
167
+
168
+ def write_packet(packet)
169
+ raw = Zlib::Deflate.deflate Rencoder.dump(packet)
170
+
171
+ @write_mutex.synchronize do
172
+ if IO.select([], [@connection], nil, nil)
173
+ @connection.write(raw)
174
+ end
175
+ end
176
+ end
177
+
178
+ def read_packets(socket)
179
+ raw = ""
180
+ begin
181
+ buffer = socket.readpartial(1024)
182
+ raw += buffer
183
+ end until(buffer.size < 1024)
184
+
185
+ raw = Zlib::Inflate.inflate(raw)
186
+
187
+ parse_packets(raw)
188
+ end
189
+
190
+ def recover_events!
191
+ present_events = @events
192
+ @events = {}
193
+
194
+ present_events.each do |event, handlers|
195
+ handlers.each do |handler|
196
+ self.register_event(event, &handler)
197
+ end
198
+ end
199
+ end
200
+
201
+ def create_socket
202
+ socket = TCPSocket.new(host, port)
203
+
204
+ if ::Socket.constants.include?('TCP_NODELAY') || ::Socket.constants.include?(:TCP_NODELAY)
205
+ socket.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true)
206
+ end
207
+ socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true)
208
+
209
+ socket
210
+ end
211
+
212
+ def ssl_context
213
+ # SSLv3 is not allowed (http://dev.deluge-torrent.org/ticket/2555)
214
+ context = OpenSSL::SSL::SSLContext.new('SSLv23')
215
+ # TODO: Consider allowing server certificate validation
216
+ context.set_params(verify_mode: OpenSSL::SSL::VERIFY_NONE)
217
+
218
+ context
219
+ end
220
+
221
+ def parse_packets(raw)
222
+ io = StringIO.new(raw)
223
+
224
+ packets = []
225
+
226
+ until(io.eof?)
227
+ packets << Rencoder.load(io)
228
+ end
229
+
230
+ packets
231
+ end
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,45 @@
1
+ module Deluge
2
+ module Rpc
3
+ class Namespace
4
+ attr_reader :name, :connection, :namespaces, :api_methods
5
+
6
+ def initialize(name, connection)
7
+ @name, @connection = name, connection
8
+ @namespaces = {}
9
+ @api_methods = []
10
+ end
11
+
12
+ def register_namespace(namespace)
13
+ namespace = namespace.to_sym
14
+
15
+ return namespaces[namespace] if namespaces.include?(namespace)
16
+
17
+ ns = Namespace.new("#{self.name}.#{namespace}", connection)
18
+
19
+ namespaces[namespace] = ns
20
+
21
+ define_singleton_method(namespace) do
22
+ ns
23
+ end
24
+
25
+ ns
26
+ end
27
+
28
+ def register_method(method)
29
+ method = method.to_sym
30
+
31
+ api_methods << "#{name}.#{method}"
32
+
33
+ define_singleton_method(method) do |*args|
34
+ call(method, *args)
35
+ end
36
+ end
37
+
38
+ def call(method, *args)
39
+ method_name = "#{name}.#{method}"
40
+
41
+ @connection.call(method_name, *args)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,5 @@
1
+ module Deluge
2
+ module Rpc
3
+ VERSION = "0.1.3"
4
+ end
5
+ end
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+
3
+ describe Deluge::Rpc::Client do
4
+
5
+ let(:connection) do
6
+ double('RpcConnection').tap do |connection|
7
+ allow(connection).to receive(:start)
8
+ allow(connection).to receive(:authenticate).with('test', 'password').and_return(5)
9
+ allow(connection).to receive(:method_list).and_return(['test.api.method'])
10
+ allow(connection).to receive(:call).with('test.api.method').and_return('winning')
11
+ allow(connection).to receive(:close)
12
+ end
13
+ end
14
+
15
+ before do
16
+ allow(Deluge::Rpc::Connection).to receive(:new).with(kind_of(Hash)).and_return(connection)
17
+ end
18
+
19
+ subject { described_class.new(host: 'localhost', login: 'test', password: 'password') }
20
+
21
+ describe '#connect' do
22
+ before do
23
+ subject.connect
24
+ end
25
+
26
+ it 'starts connection' do
27
+ expect(connection).to have_received(:start)
28
+ end
29
+
30
+ it 'authenticate' do
31
+ expect(connection).to have_received(:authenticate).with('test', 'password')
32
+ end
33
+
34
+ it 'set auth_level' do
35
+ expect(subject.auth_level).to eq(5)
36
+ end
37
+
38
+ it 'register methods' do
39
+ expect(subject.api_methods).to include('test.api.method')
40
+ end
41
+
42
+ it 'create namespace access methods' do
43
+ expect(subject.test).to be_a(Deluge::Rpc::Namespace).and have_attributes(name: 'test')
44
+ end
45
+
46
+ it 'create api access methods' do
47
+ expect(subject.test.api.method).to eq('winning')
48
+ end
49
+ end
50
+
51
+ describe '#close' do
52
+ before do
53
+ subject.connect
54
+
55
+ subject.close
56
+ end
57
+
58
+ it 'closes connection' do
59
+ expect(connection).to have_received(:close)
60
+ end
61
+
62
+ it 'clear namespaces' do
63
+ expect(subject.namespaces).to be_empty
64
+ end
65
+
66
+ it 'clear methods' do
67
+ expect(subject.api_methods).to be_empty
68
+ end
69
+
70
+ it 'remove namespace methods' do
71
+ expect(subject).not_to respond_to(:test)
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe Deluge::Rpc::Namespace do
4
+
5
+ let(:connection) do
6
+ double('RpcConnection').tap do |connection|
7
+ allow(connection).to receive(:call)
8
+ end
9
+ end
10
+
11
+ let(:instance) { described_class.new('root', connection) }
12
+
13
+ describe '#register_namespace' do
14
+ let(:result) { instance.register_namespace('test') }
15
+
16
+ it 'register new namespace' do
17
+ expect(result).to eq(instance.namespaces[:test])
18
+ end
19
+
20
+ it 'returns registered namespace' do
21
+ expect(result).to be_a(described_class).and have_attributes(name: 'root.test')
22
+ end
23
+
24
+ it 'returns existing namespace if its already registered' do
25
+ expect(instance.register_namespace('test')).to eql(result)
26
+ end
27
+
28
+ it 'creates namespace access instance method' do
29
+ expect(result).to eq(instance.test)
30
+ end
31
+ end
32
+
33
+ describe '#register_method' do
34
+ before do
35
+ instance.register_method('test')
36
+ end
37
+
38
+ it 'register new api method' do
39
+ expect(instance.api_methods).to include('root.test')
40
+ end
41
+
42
+ it 'create instance method' do
43
+ expect(instance).to respond_to(:test)
44
+ end
45
+ end
46
+
47
+ describe 'api access instance method' do
48
+ before do
49
+ instance.register_method('test')
50
+
51
+ instance.test('hello', 'world')
52
+ end
53
+
54
+ it 'invoke api call' do
55
+ expect(connection).to have_received(:call).with('root.test', 'hello', 'world')
56
+ end
57
+ end
58
+
59
+ end
@@ -0,0 +1,5 @@
1
+ require 'rspec'
2
+
3
+ $:.unshift File.expand_path('../../lib', __FILE__)
4
+
5
+ require 'deluge/rpc'
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: deluge-rpc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.3
5
+ platform: ruby
6
+ authors:
7
+ - Igor Yamolov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-06-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: concurrent-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rencoder
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.7'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.7'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.1'
69
+ description: Communicate with Deluge torrent client via RPC protocol
70
+ email:
71
+ - clouster@yandex.ru
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".ruby-version"
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - deluge-rpc.gemspec
83
+ - lib/deluge.rb
84
+ - lib/deluge/rpc.rb
85
+ - lib/deluge/rpc/client.rb
86
+ - lib/deluge/rpc/connection.rb
87
+ - lib/deluge/rpc/namespace.rb
88
+ - lib/deluge/rpc/version.rb
89
+ - spec/deluge/api/client_spec.rb
90
+ - spec/deluge/api/namespace_spec.rb
91
+ - spec/spec_helper.rb
92
+ homepage: https://github.com/t3hk0d3/deluge-rpc
93
+ licenses:
94
+ - MIT
95
+ metadata: {}
96
+ post_install_message:
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubyforge_project:
112
+ rubygems_version: 2.2.2
113
+ signing_key:
114
+ specification_version: 4
115
+ summary: Deluge RPC protocol wrapper
116
+ test_files:
117
+ - spec/deluge/api/client_spec.rb
118
+ - spec/deluge/api/namespace_spec.rb
119
+ - spec/spec_helper.rb
120
+ has_rdoc: