rbus 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.txt +9 -0
- data/COPYING.txt +341 -0
- data/HACKING.txt +104 -0
- data/Manifest.txt +58 -0
- data/README.txt +17 -0
- data/Rakefile +97 -0
- data/TUTORIAL.txt +303 -0
- data/bin/rbus-send +165 -0
- data/examples/async_rb_and_notify.rb +56 -0
- data/examples/async_rb_loop.rb +52 -0
- data/examples/glib_async_rb_loop.rb +57 -0
- data/examples/glib_rhythmbox.rb +54 -0
- data/examples/hal_device_info.rb +54 -0
- data/examples/listnames.rb +37 -0
- data/examples/network_manager_get_properties.rb +50 -0
- data/examples/notification_bubble.rb +46 -0
- data/examples/rhythmbox_print_playing_uri.rb +41 -0
- data/examples/rhythmbox_signal_print_playing.rb +54 -0
- data/examples/rhythmbox_start_service.rb +39 -0
- data/examples/rhythmbox_toggle_playing.rb +46 -0
- data/lib/rbus.rb +25 -0
- data/lib/rbus/auth/auth.rb +53 -0
- data/lib/rbus/auth/dbus_cookie_sha1.rb +66 -0
- data/lib/rbus/auth/dummy.rb +37 -0
- data/lib/rbus/auth/external.rb +34 -0
- data/lib/rbus/auth/state_machine.rb +168 -0
- data/lib/rbus/bus/bus.rb +101 -0
- data/lib/rbus/bus/proxy.rb +137 -0
- data/lib/rbus/bus/transport.rb +125 -0
- data/lib/rbus/default.rb +29 -0
- data/lib/rbus/etc/exception.rb +44 -0
- data/lib/rbus/etc/log.rb +100 -0
- data/lib/rbus/etc/types.rb +56 -0
- data/lib/rbus/etc/version.rb +34 -0
- data/lib/rbus/glib.rb +29 -0
- data/lib/rbus/mainloop/glib.rb +77 -0
- data/lib/rbus/mainloop/mainloop.rb +84 -0
- data/lib/rbus/mainloop/observers.rb +149 -0
- data/lib/rbus/mainloop/thread.rb +78 -0
- data/lib/rbus/message/constants.rb +51 -0
- data/lib/rbus/message/marshal.rb +139 -0
- data/lib/rbus/message/message.rb +110 -0
- data/lib/rbus/message/reader.rb +108 -0
- data/lib/rbus/message/serial_generator.rb +48 -0
- data/lib/rbus/message/unmarshal.rb +171 -0
- data/lib/rbus/message/writer.rb +69 -0
- data/setup.rb +1608 -0
- data/spec/auth_spec.rb +123 -0
- data/spec/bus_spec.rb +178 -0
- data/spec/helpers/bus_mocks.rb +64 -0
- data/spec/helpers/spec_helper.rb +24 -0
- data/spec/mainloop_spec.rb +74 -0
- data/spec/marshal_spec.rb +91 -0
- data/spec/message_spec.rb +61 -0
- data/spec/observers_spec.rb +28 -0
- data/spec/proxy_spec.rb +120 -0
- data/spec/transport_spec.rb +187 -0
- data/spec/unmarshal_spec.rb +186 -0
- metadata +118 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
#--
|
2
|
+
#
|
3
|
+
# R-Bus is a native Ruby implementation of the D-Bus protocol.
|
4
|
+
# Copyright (C) 2007 Kristoffer Lundén (kristoffer.lunden@gmail.com)
|
5
|
+
#
|
6
|
+
# This program is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program; if not, write to the Free Software
|
18
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
19
|
+
# MA 02110-1301, USA. A copy of the GNU General Public License is
|
20
|
+
# also available at http://www.gnu.org/copyleft/gpl.html.
|
21
|
+
#
|
22
|
+
#++
|
23
|
+
#
|
24
|
+
module RBus
|
25
|
+
module Auth
|
26
|
+
|
27
|
+
# Implements the AUTH EXTERNAL mechanism.
|
28
|
+
class External
|
29
|
+
def auth
|
30
|
+
['EXTERNAL', Process.uid, :OK]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
#--
|
2
|
+
#
|
3
|
+
# R-Bus is a native Ruby implementation of the D-Bus protocol.
|
4
|
+
# Copyright (C) 2007 Kristoffer Lundén (kristoffer.lunden@gmail.com)
|
5
|
+
#
|
6
|
+
# This program is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program; if not, write to the Free Software
|
18
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
19
|
+
# MA 02110-1301, USA. A copy of the GNU General Public License is
|
20
|
+
# also available at http://www.gnu.org/copyleft/gpl.html.
|
21
|
+
#
|
22
|
+
#++
|
23
|
+
#
|
24
|
+
module RBus
|
25
|
+
module Auth
|
26
|
+
|
27
|
+
# Implements a statemachine for Authorization like the one described in
|
28
|
+
# the D-Bus specification.
|
29
|
+
class StateMachine
|
30
|
+
def initialize(transport)
|
31
|
+
@transport = transport
|
32
|
+
@mechanism = FIRST_MECHANISM.new
|
33
|
+
end
|
34
|
+
|
35
|
+
# Main entry point, returns guid if succesful, or raises otherwise.
|
36
|
+
def authorize
|
37
|
+
state = :AUTH
|
38
|
+
|
39
|
+
loop do
|
40
|
+
case state
|
41
|
+
when :AUTH
|
42
|
+
command, data, state = @mechanism.auth
|
43
|
+
send_auth(command, data)
|
44
|
+
|
45
|
+
when :REJECTED
|
46
|
+
response = Command.new(@transport.readline)
|
47
|
+
case response.command
|
48
|
+
when 'REJECTED'
|
49
|
+
next_mechanism(response.data)
|
50
|
+
state = :AUTH
|
51
|
+
else
|
52
|
+
raise AuthException
|
53
|
+
end
|
54
|
+
|
55
|
+
when :OK
|
56
|
+
response = Command.new(@transport.readline)
|
57
|
+
case response.command
|
58
|
+
when 'OK'
|
59
|
+
send_begin
|
60
|
+
return response.data
|
61
|
+
when 'REJECT'
|
62
|
+
next_mechanism(response.data)
|
63
|
+
state = :AUTH
|
64
|
+
when 'DATA'
|
65
|
+
send_cancel
|
66
|
+
state = :REJECT
|
67
|
+
when 'ERROR'
|
68
|
+
send_cancel
|
69
|
+
state = :REJECT
|
70
|
+
else
|
71
|
+
send_error
|
72
|
+
state = :OK
|
73
|
+
end
|
74
|
+
|
75
|
+
when :DATA
|
76
|
+
response = Command.new(@transport.readline)
|
77
|
+
case response.command
|
78
|
+
when 'DATA'
|
79
|
+
begin
|
80
|
+
message, state = @mechanism.data(response.data)
|
81
|
+
rescue AuthException => e
|
82
|
+
send_cancel
|
83
|
+
state = :REJECT
|
84
|
+
else
|
85
|
+
send_data(message)
|
86
|
+
end
|
87
|
+
when 'REJECT'
|
88
|
+
next_mechanism(response.data)
|
89
|
+
state = :AUTH
|
90
|
+
when 'ERROR'
|
91
|
+
send_cancel
|
92
|
+
state = :REJECT
|
93
|
+
when 'OK'
|
94
|
+
send_begin
|
95
|
+
return response.data
|
96
|
+
else
|
97
|
+
send_error
|
98
|
+
state = :DATA
|
99
|
+
end
|
100
|
+
else
|
101
|
+
raise
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Move to next mechanism
|
107
|
+
def next_mechanism(data)
|
108
|
+
if @mechanisms.nil?
|
109
|
+
populate_mechanisms(data)
|
110
|
+
end
|
111
|
+
if @mechanisms.empty?
|
112
|
+
raise AuthException
|
113
|
+
else
|
114
|
+
@mechanism = @mechanisms.shift.new
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Build initial mechanism list
|
119
|
+
def populate_mechanisms(data)
|
120
|
+
server_mechanisms = data.split(' ')
|
121
|
+
|
122
|
+
# Find common mechanisms, or try them all if server didn't tell us
|
123
|
+
if server_mechanisms.empty?
|
124
|
+
mechanisms = MECHANISMS.keys
|
125
|
+
else
|
126
|
+
mechanisms = (server_mechanisms & MECHANISMS.keys)
|
127
|
+
end
|
128
|
+
|
129
|
+
@mechanisms = mechanisms.map{|name| MECHANISMS[name]}
|
130
|
+
end
|
131
|
+
|
132
|
+
# Send AUTH ...
|
133
|
+
def send_auth(command, data)
|
134
|
+
line = ['AUTH', command, hex_encode(data)].compact.join(' ')
|
135
|
+
@transport.sendline(line)
|
136
|
+
end
|
137
|
+
|
138
|
+
# send_begin => BEGIN, etc
|
139
|
+
def method_missing(symbol, *args)
|
140
|
+
command = symbol.to_s
|
141
|
+
if command.sub!('send_', '')
|
142
|
+
line = [command.upcase, hex_encode(args[0])].compact.join(' ')
|
143
|
+
@transport.sendline(line)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Server expects all data to be encoded in hex
|
148
|
+
def hex_encode(plain)
|
149
|
+
return nil if plain.nil?
|
150
|
+
plain.to_s.unpack('H*')[0]
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Parses server response and splits it into component parts
|
155
|
+
# Decodes where appropriate (only DATA)
|
156
|
+
class Command
|
157
|
+
attr_reader :command, :data
|
158
|
+
def initialize(string)
|
159
|
+
@command, data = string.split(/ /, 2)
|
160
|
+
@data = (@command == 'DATA') ? hex_decode(data) : data
|
161
|
+
end
|
162
|
+
|
163
|
+
def hex_decode(encoded)
|
164
|
+
encoded.scan(/[[:xdigit:]]{2}/).map{|h|h.hex.chr}.join
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
data/lib/rbus/bus/bus.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
#--
|
2
|
+
#
|
3
|
+
# R-Bus is a native Ruby implementation of the D-Bus protocol.
|
4
|
+
# Copyright (C) 2007 Kristoffer Lundén (kristoffer.lunden@gmail.com)
|
5
|
+
#
|
6
|
+
# This program is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program; if not, write to the Free Software
|
18
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
19
|
+
# MA 02110-1301, USA. A copy of the GNU General Public License is
|
20
|
+
# also available at http://www.gnu.org/copyleft/gpl.html.
|
21
|
+
#
|
22
|
+
#++
|
23
|
+
#
|
24
|
+
|
25
|
+
require File.dirname(__FILE__) + '/proxy'
|
26
|
+
require 'singleton'
|
27
|
+
|
28
|
+
module RBus
|
29
|
+
|
30
|
+
# Get the session bus
|
31
|
+
def self.session_bus
|
32
|
+
SessionBus.instance
|
33
|
+
end
|
34
|
+
|
35
|
+
# Get the system bus
|
36
|
+
def self.system_bus
|
37
|
+
SystemBus.instance
|
38
|
+
end
|
39
|
+
|
40
|
+
# Get Bus with specific address
|
41
|
+
def self.get_bus(address)
|
42
|
+
Bus.new(address)
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
# Represents a connection to a Bus. It is also a Proxy object for
|
47
|
+
# sending messages to the bus itself.
|
48
|
+
class Bus < Proxy
|
49
|
+
NAMES = []
|
50
|
+
|
51
|
+
attr_reader :message_loop
|
52
|
+
|
53
|
+
# Connect to Bus with +server_address+, which can contain multiple
|
54
|
+
# addresses to try, separated by semicolons.
|
55
|
+
def initialize(server_address)
|
56
|
+
raise InvalidAddressException, 'Empty address' if server_address.empty?
|
57
|
+
|
58
|
+
# Can be several addresses, separated by ;
|
59
|
+
server_address.split(';').each do |address|
|
60
|
+
@transport = Transport.connect(address)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Not used for anything, save at all?
|
64
|
+
@guid = Auth.authorize(@transport)
|
65
|
+
|
66
|
+
# Start main message loop.
|
67
|
+
#@message_loop = Message::Loop.new(@transport).run
|
68
|
+
@message_loop = Mainloop.new(@transport)
|
69
|
+
|
70
|
+
@well_known_name = 'org.freedesktop.DBus'
|
71
|
+
@object_path = '/org/freedesktop/DBus'
|
72
|
+
@interface = 'org.freedesktop.DBus'
|
73
|
+
NAMES << Hello()
|
74
|
+
Introspect()
|
75
|
+
|
76
|
+
Log.debug('Bus names:', NAMES)
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns a remote object for well-known name and object path.
|
81
|
+
def get_object(well_known_name, object_path)
|
82
|
+
Proxy.new(self, well_known_name, object_path)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Represents the connection to the Session Bus.
|
87
|
+
class SessionBus < Bus
|
88
|
+
include Singleton
|
89
|
+
def initialize
|
90
|
+
super(ENV['DBUS_SESSION_BUS_ADDRESS'])
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Represents the connection to the System Bus.
|
95
|
+
class SystemBus < Bus
|
96
|
+
include Singleton
|
97
|
+
def initialize
|
98
|
+
super(ENV['DBUS_SYSTEM_BUS_ADDRESS'] || 'unix:path=/var/run/dbus/system_bus_socket')
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
#--
|
2
|
+
#
|
3
|
+
# R-Bus is a native Ruby implementation of the D-Bus protocol.
|
4
|
+
# Copyright (C) 2007 Kristoffer Lundén (kristoffer.lunden@gmail.com)
|
5
|
+
#
|
6
|
+
# This program is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program; if not, write to the Free Software
|
18
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
19
|
+
# MA 02110-1301, USA. A copy of the GNU General Public License is
|
20
|
+
# also available at http://www.gnu.org/copyleft/gpl.html.
|
21
|
+
#
|
22
|
+
#++
|
23
|
+
#
|
24
|
+
require 'rexml/document'
|
25
|
+
|
26
|
+
module RBus
|
27
|
+
|
28
|
+
VALID_METHOD_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,254}$/
|
29
|
+
|
30
|
+
# BlankSlate is a superclass that empties (almost) all instance methods
|
31
|
+
# from it's subclasses.
|
32
|
+
# See: http://onestepback.org/index.cgi/Tech/Ruby/BlankSlate.rdoc
|
33
|
+
class BlankSlate
|
34
|
+
instance_methods.each { |m| undef_method m unless m =~ /^__|[!\?]$/ }
|
35
|
+
end
|
36
|
+
|
37
|
+
# The object that represents the remote bus object.
|
38
|
+
class Proxy < BlankSlate
|
39
|
+
|
40
|
+
# Parses Introspect() XML into a hash with
|
41
|
+
# {method => signature}
|
42
|
+
def self.parse_introspect(xml)
|
43
|
+
methods = {}
|
44
|
+
doc = REXML::Document.new(xml)
|
45
|
+
doc.elements.each("//method") do |method|
|
46
|
+
name = method.attribute('name').to_s
|
47
|
+
signature = ''
|
48
|
+
method.elements.each("*[@direction!='out']") do |arg|
|
49
|
+
signature << arg.attribute('type').to_s
|
50
|
+
end
|
51
|
+
methods[name] = signature
|
52
|
+
end
|
53
|
+
methods
|
54
|
+
end
|
55
|
+
|
56
|
+
def initialize(bus, well_known_name, object_path)
|
57
|
+
@bus = bus
|
58
|
+
@message_loop = @bus.message_loop
|
59
|
+
@well_known_name = well_known_name
|
60
|
+
@object_path = object_path
|
61
|
+
Introspect()
|
62
|
+
end
|
63
|
+
|
64
|
+
def Introspect # :nodoc:
|
65
|
+
# TODO: store interfaces as well, if ever needed
|
66
|
+
interface = @interface
|
67
|
+
@interface = 'org.freedesktop.DBus.Introspectable'
|
68
|
+
@methods = Proxy.parse_introspect(method_missing(:Introspect))
|
69
|
+
Log.debug("Methods from introspect: " + @methods.inspect)
|
70
|
+
@interface = interface
|
71
|
+
end
|
72
|
+
|
73
|
+
# Set interface and return or yield self.
|
74
|
+
# If a block is given, a copy of the object will be
|
75
|
+
# the new interface will only be set for that block, and reset
|
76
|
+
# afterwards.
|
77
|
+
def interface!(interface)
|
78
|
+
old_i = @interface
|
79
|
+
@interface = interface
|
80
|
+
if block_given?
|
81
|
+
yield self
|
82
|
+
@interface = old_i
|
83
|
+
end
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
87
|
+
# Connect block to a signal
|
88
|
+
def connect!(signal, options = {}, &block)
|
89
|
+
raise MessageException, "connect! needs a block" unless block_given?
|
90
|
+
|
91
|
+
rules = {
|
92
|
+
:type => 'signal',
|
93
|
+
:member => signal.to_s,
|
94
|
+
:path => @object_path,
|
95
|
+
#:sender => @well_known_name
|
96
|
+
}
|
97
|
+
rules['interface'] = @interface if @interface
|
98
|
+
|
99
|
+
rules.merge!(options)
|
100
|
+
|
101
|
+
Log.debug(rules)
|
102
|
+
@bus.AddMatch(rules, &block)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Define a method with signature.
|
106
|
+
# +name+ can be a string or a symbol.
|
107
|
+
def method!(name, signature)
|
108
|
+
@methods[name.to_s] = signature
|
109
|
+
end
|
110
|
+
|
111
|
+
# Routes all method calls to remote object
|
112
|
+
def method_missing(name, *args, &block) # :nodoc:
|
113
|
+
|
114
|
+
method = name.to_s
|
115
|
+
|
116
|
+
Log.debug('Proxy method called: ' + method)
|
117
|
+
|
118
|
+
unless method.match(VALID_METHOD_NAME)
|
119
|
+
raise InvalidNameException, "Invalid method name '#{name}'"
|
120
|
+
end
|
121
|
+
|
122
|
+
call = MethodCall.new do |m|
|
123
|
+
m.destination = @well_known_name
|
124
|
+
m.object_path = @object_path
|
125
|
+
m.interface = @interface if @interface
|
126
|
+
m.member = method
|
127
|
+
m.arguments = args
|
128
|
+
if !args.empty? && @methods.has_key?(method)
|
129
|
+
Log.debug('Signature: ' + @methods[method])
|
130
|
+
m.signature = @methods[method]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
Log.debug(block_given? ? 'Block given' : 'Block NOT given')
|
134
|
+
@message_loop.send_message(call, block_given? ? block : nil)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
#--
|
2
|
+
#
|
3
|
+
# R-Bus is a native Ruby implementation of the D-Bus protocol.
|
4
|
+
# Copyright (C) 2007 Kristoffer Lundén (kristoffer.lunden@gmail.com)
|
5
|
+
#
|
6
|
+
# This program is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program; if not, write to the Free Software
|
18
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
19
|
+
# MA 02110-1301, USA. A copy of the GNU General Public License is
|
20
|
+
# also available at http://www.gnu.org/copyleft/gpl.html.
|
21
|
+
#
|
22
|
+
#++
|
23
|
+
#
|
24
|
+
require 'socket'
|
25
|
+
|
26
|
+
module RBus
|
27
|
+
|
28
|
+
# Transport represents the connection to the bus,
|
29
|
+
# and supports sending and recieving messages
|
30
|
+
# The class itself is a factory that returns the
|
31
|
+
# appropriate transport for an address
|
32
|
+
class Transport
|
33
|
+
|
34
|
+
# Abstract base implementation of a transport.
|
35
|
+
class AbstractTransport
|
36
|
+
# Newline
|
37
|
+
CRLF = "\015\012"
|
38
|
+
attr_reader :socket
|
39
|
+
|
40
|
+
def initialize(connection_info)
|
41
|
+
@connection_info = connection_info
|
42
|
+
create_socket
|
43
|
+
end
|
44
|
+
|
45
|
+
def send(message)
|
46
|
+
@socket.write(message)
|
47
|
+
end
|
48
|
+
|
49
|
+
def read(len = 2**27)
|
50
|
+
@socket.read(len)
|
51
|
+
#@socket.recv(len)
|
52
|
+
end
|
53
|
+
|
54
|
+
def sendline(message)
|
55
|
+
@socket.write(message + CRLF)
|
56
|
+
end
|
57
|
+
|
58
|
+
def readline
|
59
|
+
@socket.readline.chomp
|
60
|
+
end
|
61
|
+
|
62
|
+
def close
|
63
|
+
@socket.close
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
# Represents a connection to a UNIX Socket, abstract or regular.
|
69
|
+
class UNIXTransport < AbstractTransport
|
70
|
+
def create_socket
|
71
|
+
if @connection_info.has_key?('abstract')
|
72
|
+
@socket = Socket.new(Socket::AF_UNIX, Socket::SOCK_STREAM, 0)
|
73
|
+
@socket.connect("\1\0\0#{@connection_info['abstract']}")
|
74
|
+
elsif @connection_info.has_key?('path')
|
75
|
+
@socket = UNIXSocket.open(@connection_info['path'])
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Represents a connection to a TCP Socket.
|
81
|
+
class TCPTransport < AbstractTransport
|
82
|
+
def create_socket
|
83
|
+
@socket = TCPSocket.new(@connection_info['host'], @connection_info['port'])
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Unescape simple hex encoding (%XX) for values
|
88
|
+
def self.unescape(value)
|
89
|
+
value.gsub(/%([[:xdigit:]]{2})/) {$1.hex.chr}
|
90
|
+
end
|
91
|
+
|
92
|
+
# Parse a valid connection address
|
93
|
+
def self.parse_address(address)
|
94
|
+
connection_info = {}
|
95
|
+
connection_info['transport'], info = address.split(':', 2)
|
96
|
+
|
97
|
+
raise InvalidAddressException if info.nil? || info.empty?
|
98
|
+
|
99
|
+
info.split(',').each do |pair|
|
100
|
+
key, value = pair.split('=')
|
101
|
+
connection_info[key] = unescape(value)
|
102
|
+
end
|
103
|
+
connection_info
|
104
|
+
end
|
105
|
+
|
106
|
+
# Define factory method connect
|
107
|
+
class << self
|
108
|
+
def connect(address)
|
109
|
+
connection_info = parse_address(address)
|
110
|
+
|
111
|
+
klass =
|
112
|
+
case connection_info['transport']
|
113
|
+
when 'unix'
|
114
|
+
UNIXTransport
|
115
|
+
when 'tcp'
|
116
|
+
TCPTransport
|
117
|
+
else
|
118
|
+
raise InvalidTransportException, "Unknown transport #{connection_info['transport']}"
|
119
|
+
end
|
120
|
+
klass::new(connection_info)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|