shingara-blather 0.4.8
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README.md +162 -0
- data/examples/echo.rb +18 -0
- data/examples/execute.rb +16 -0
- data/examples/ping_pong.rb +37 -0
- data/examples/print_hierarchy.rb +76 -0
- data/examples/rosterprint.rb +14 -0
- data/examples/stream_only.rb +27 -0
- data/examples/xmpp4r/echo.rb +35 -0
- data/lib/blather/client/client.rb +310 -0
- data/lib/blather/client/dsl/pubsub.rb +170 -0
- data/lib/blather/client/dsl.rb +264 -0
- data/lib/blather/client.rb +87 -0
- data/lib/blather/core_ext/nokogiri.rb +40 -0
- data/lib/blather/errors/sasl_error.rb +43 -0
- data/lib/blather/errors/stanza_error.rb +107 -0
- data/lib/blather/errors/stream_error.rb +82 -0
- data/lib/blather/errors.rb +69 -0
- data/lib/blather/jid.rb +142 -0
- data/lib/blather/roster.rb +111 -0
- data/lib/blather/roster_item.rb +122 -0
- data/lib/blather/stanza/disco/disco_info.rb +176 -0
- data/lib/blather/stanza/disco/disco_items.rb +132 -0
- data/lib/blather/stanza/disco.rb +25 -0
- data/lib/blather/stanza/iq/query.rb +53 -0
- data/lib/blather/stanza/iq/roster.rb +179 -0
- data/lib/blather/stanza/iq.rb +138 -0
- data/lib/blather/stanza/message.rb +332 -0
- data/lib/blather/stanza/presence/status.rb +212 -0
- data/lib/blather/stanza/presence/subscription.rb +101 -0
- data/lib/blather/stanza/presence.rb +163 -0
- data/lib/blather/stanza/pubsub/affiliations.rb +79 -0
- data/lib/blather/stanza/pubsub/create.rb +65 -0
- data/lib/blather/stanza/pubsub/errors.rb +18 -0
- data/lib/blather/stanza/pubsub/event.rb +123 -0
- data/lib/blather/stanza/pubsub/items.rb +103 -0
- data/lib/blather/stanza/pubsub/publish.rb +103 -0
- data/lib/blather/stanza/pubsub/retract.rb +92 -0
- data/lib/blather/stanza/pubsub/subscribe.rb +68 -0
- data/lib/blather/stanza/pubsub/subscription.rb +134 -0
- data/lib/blather/stanza/pubsub/subscriptions.rb +81 -0
- data/lib/blather/stanza/pubsub/unsubscribe.rb +68 -0
- data/lib/blather/stanza/pubsub.rb +129 -0
- data/lib/blather/stanza/pubsub_owner/delete.rb +52 -0
- data/lib/blather/stanza/pubsub_owner/purge.rb +52 -0
- data/lib/blather/stanza/pubsub_owner.rb +51 -0
- data/lib/blather/stanza.rb +149 -0
- data/lib/blather/stream/client.rb +31 -0
- data/lib/blather/stream/component.rb +38 -0
- data/lib/blather/stream/features/resource.rb +63 -0
- data/lib/blather/stream/features/sasl.rb +187 -0
- data/lib/blather/stream/features/session.rb +44 -0
- data/lib/blather/stream/features/tls.rb +28 -0
- data/lib/blather/stream/features.rb +53 -0
- data/lib/blather/stream/parser.rb +102 -0
- data/lib/blather/stream.rb +231 -0
- data/lib/blather/xmpp_node.rb +218 -0
- data/lib/blather.rb +78 -0
- data/spec/blather/client/client_spec.rb +559 -0
- data/spec/blather/client/dsl/pubsub_spec.rb +462 -0
- data/spec/blather/client/dsl_spec.rb +143 -0
- data/spec/blather/core_ext/nokogiri_spec.rb +83 -0
- data/spec/blather/errors/sasl_error_spec.rb +33 -0
- data/spec/blather/errors/stanza_error_spec.rb +129 -0
- data/spec/blather/errors/stream_error_spec.rb +108 -0
- data/spec/blather/errors_spec.rb +33 -0
- data/spec/blather/jid_spec.rb +87 -0
- data/spec/blather/roster_item_spec.rb +96 -0
- data/spec/blather/roster_spec.rb +103 -0
- data/spec/blather/stanza/discos/disco_info_spec.rb +226 -0
- data/spec/blather/stanza/discos/disco_items_spec.rb +148 -0
- data/spec/blather/stanza/iq/query_spec.rb +64 -0
- data/spec/blather/stanza/iq/roster_spec.rb +140 -0
- data/spec/blather/stanza/iq_spec.rb +45 -0
- data/spec/blather/stanza/message_spec.rb +132 -0
- data/spec/blather/stanza/presence/status_spec.rb +132 -0
- data/spec/blather/stanza/presence/subscription_spec.rb +105 -0
- data/spec/blather/stanza/presence_spec.rb +66 -0
- data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
- data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
- data/spec/blather/stanza/pubsub/event_spec.rb +84 -0
- data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
- data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
- data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
- data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
- data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
- data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
- data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +61 -0
- data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
- data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
- data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
- data/spec/blather/stanza/pubsub_spec.rb +67 -0
- data/spec/blather/stanza_spec.rb +116 -0
- data/spec/blather/stream/client_spec.rb +1011 -0
- data/spec/blather/stream/component_spec.rb +95 -0
- data/spec/blather/stream/parser_spec.rb +145 -0
- data/spec/blather/xmpp_node_spec.rb +231 -0
- data/spec/fixtures/pubsub.rb +311 -0
- data/spec/spec_helper.rb +43 -0
- metadata +249 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Blather
|
2
|
+
|
3
|
+
Copyright (c) 2009 Jeff Smick
|
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 NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
# Blather
|
2
|
+
|
3
|
+
XMPP DSL (and more) for Ruby written on EventMachine and Nokogiri.
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
* evented architecture
|
8
|
+
* uses Nokogiri
|
9
|
+
* simplified starting point
|
10
|
+
|
11
|
+
## Project Pages
|
12
|
+
|
13
|
+
* [Docs](http://blather.squishtech.com)
|
14
|
+
* [GitHub](https://github.com/sprsquish/blather)
|
15
|
+
* [Gemcutter](http://gemcutter.org/gems/blather)
|
16
|
+
* [Google Group](http://groups.google.com/group/xmpp-blather)
|
17
|
+
|
18
|
+
# Usage
|
19
|
+
|
20
|
+
## Installation
|
21
|
+
|
22
|
+
sudo gem install blather
|
23
|
+
|
24
|
+
## Example
|
25
|
+
|
26
|
+
See the examples directory for more advanced examples.
|
27
|
+
|
28
|
+
This will auto-accept any subscription requests and echo back any chat messages.
|
29
|
+
|
30
|
+
require 'rubygems'
|
31
|
+
require 'blather/client'
|
32
|
+
|
33
|
+
setup 'echo@jabber.local', 'echo'
|
34
|
+
|
35
|
+
# Auto approve subscription requests
|
36
|
+
subscription :request? do |s|
|
37
|
+
write_to_stream s.approve!
|
38
|
+
end
|
39
|
+
|
40
|
+
# Echo back what was said
|
41
|
+
message :chat?, :body do |m|
|
42
|
+
write_to_stream m.reply
|
43
|
+
end
|
44
|
+
|
45
|
+
## Handlers
|
46
|
+
|
47
|
+
Setup handlers by calling their names as methods.
|
48
|
+
|
49
|
+
# Will only be called for messages where #chat? responds positively
|
50
|
+
# and #body == 'exit'
|
51
|
+
message :chat?, :body => 'exit'
|
52
|
+
|
53
|
+
### Non-Stanza Handlers
|
54
|
+
|
55
|
+
So far there are two non-stanza related handlers.
|
56
|
+
|
57
|
+
# Called after the connection has been connected. It's good for initializing
|
58
|
+
# your system.
|
59
|
+
# DSL:
|
60
|
+
when_ready {}
|
61
|
+
# Client:
|
62
|
+
client.register_handler(:ready) {}
|
63
|
+
|
64
|
+
# Called after the connection has been terminated. Good for teardown or
|
65
|
+
# automatic reconnection.
|
66
|
+
# DSL:
|
67
|
+
disconnected {}
|
68
|
+
# Client
|
69
|
+
client.register_handler(:disconnected) {}
|
70
|
+
# The following will reconnect every time the connection is lost:
|
71
|
+
disconnected { client.connect }
|
72
|
+
|
73
|
+
### Handler Guards
|
74
|
+
|
75
|
+
Guards act like AND statements. Each condition must be met if the handler is to
|
76
|
+
be used.
|
77
|
+
|
78
|
+
# Equivalent to saying (stanza.chat? && stanza.body)
|
79
|
+
message :chat?, :body
|
80
|
+
|
81
|
+
The different types of guards are:
|
82
|
+
|
83
|
+
# Symbol
|
84
|
+
# Checks for a non-false reply to calling the symbol on the stanza
|
85
|
+
# Equivalent to stanza.chat?
|
86
|
+
message :chat?
|
87
|
+
|
88
|
+
# Hash with any value (:body => 'exit')
|
89
|
+
# Calls the key on the stanza and checks for equality
|
90
|
+
# Equivalent to stanza.body == 'exit'
|
91
|
+
message :body => 'exit'
|
92
|
+
|
93
|
+
# Hash with regular expression (:body => /exit/)
|
94
|
+
# Calls the key on the stanza and checks for a match
|
95
|
+
# Equivalent to stanza.body.match /exit/
|
96
|
+
message :body => /exit/
|
97
|
+
|
98
|
+
# Hash with array (:name => [:gone, :forbidden])
|
99
|
+
# Calls the key on the stanza and check for inclusion in the array
|
100
|
+
# Equivalent to [:gone, :forbidden].include?(stanza.name)
|
101
|
+
stanza_error :name => [:gone, :fobidden]
|
102
|
+
|
103
|
+
# Proc
|
104
|
+
# Calls the proc passing in the stanza
|
105
|
+
# Checks that the ID is modulo 3
|
106
|
+
message proc { |m| m.id % 3 == 0 }
|
107
|
+
|
108
|
+
# Array
|
109
|
+
# Use arrays with the previous types effectively turns the guard into
|
110
|
+
# an OR statement.
|
111
|
+
# Equivalent to stanza.body == 'foo' || stanza.body == 'baz'
|
112
|
+
message [{:body => 'foo'}, {:body => 'baz'}]
|
113
|
+
|
114
|
+
# XPath
|
115
|
+
# Runs the xpath query on the stanza and checks for results
|
116
|
+
# This guard type cannot be combined with other guards
|
117
|
+
# Equivalent to !stanza.find('/iq/ns:pubsub', :ns => 'pubsub:namespace').empty?
|
118
|
+
iq '/iq/ns:pubsub', :ns => 'pubsub:namespace'
|
119
|
+
|
120
|
+
### Filters
|
121
|
+
|
122
|
+
Blather provides before and after filters that work much the way regular
|
123
|
+
handlers work. Filters come in a before and after flavor. They're called in
|
124
|
+
order of definition and can be guarded like handlers.
|
125
|
+
|
126
|
+
before { |s| "I'm run before any handler" }
|
127
|
+
before { |s| "I'm run next" }
|
128
|
+
|
129
|
+
before(:message) { |s| "I'm only run in front of message stanzas" }
|
130
|
+
before(nil, :id => 1) { |s| "I'll only be run when the stanza's ID == 1" }
|
131
|
+
|
132
|
+
# ... handlers
|
133
|
+
|
134
|
+
after { |s| "I'm run after everything" }
|
135
|
+
|
136
|
+
## On the Command Line:
|
137
|
+
|
138
|
+
Default usage is:
|
139
|
+
|
140
|
+
[blather_script] [options] node@domain.com/resource password [host] [port]
|
141
|
+
|
142
|
+
Command line options:
|
143
|
+
|
144
|
+
-D, --debug Run in debug mode (you will see all XMPP communication)
|
145
|
+
-d, --daemonize Daemonize the process
|
146
|
+
--pid=[PID] Write the PID to this file
|
147
|
+
--log=[LOG] Write to the [LOG] file instead of stdout/stderr
|
148
|
+
-h, --help Show this message
|
149
|
+
-v, --version Show version
|
150
|
+
|
151
|
+
|
152
|
+
# Author
|
153
|
+
|
154
|
+
[Jeff Smick](http://github.com/sprsquish)
|
155
|
+
|
156
|
+
### Contributors
|
157
|
+
|
158
|
+
[Nolan Darilek](http://github.com/thewordnerd)
|
159
|
+
|
160
|
+
# Copyright
|
161
|
+
|
162
|
+
Copyright (c) 2009 Jeff Smick. See LICENSE for details.
|
data/examples/echo.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'blather/client'
|
4
|
+
|
5
|
+
when_ready { puts "Connected ! send messages to #{jid.stripped}." }
|
6
|
+
|
7
|
+
subscription :request? do |s|
|
8
|
+
write_to_stream s.approve!
|
9
|
+
end
|
10
|
+
|
11
|
+
message :chat?, :body => 'exit' do |m|
|
12
|
+
say m.from, 'Exiting ...'
|
13
|
+
shutdown
|
14
|
+
end
|
15
|
+
|
16
|
+
message :chat?, :body do |m|
|
17
|
+
say m.from, "You sent: #{m.body}"
|
18
|
+
end
|
data/examples/execute.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'blather/client'
|
4
|
+
|
5
|
+
message :chat?, :body => 'exit' do |m|
|
6
|
+
say m.from, 'Exiting ...'
|
7
|
+
shutdown
|
8
|
+
end
|
9
|
+
|
10
|
+
message :chat?, :body do |m|
|
11
|
+
begin
|
12
|
+
say m.from, eval(m.body)
|
13
|
+
rescue => e
|
14
|
+
say m.from, e.inspect
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'blather/client/dsl'
|
2
|
+
$stdout.sync = true
|
3
|
+
|
4
|
+
module Ping
|
5
|
+
extend Blather::DSL
|
6
|
+
def self.run; client.run; end
|
7
|
+
|
8
|
+
setup 'echo@jabber.local/ping', 'echo'
|
9
|
+
|
10
|
+
status :from => Blather::JID.new('echo@jabber.local/pong') do |s|
|
11
|
+
puts "serve!"
|
12
|
+
say s.from, 'ping'
|
13
|
+
end
|
14
|
+
|
15
|
+
message :chat?, :body => 'pong' do |m|
|
16
|
+
puts "ping!"
|
17
|
+
say m.from, 'ping'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Pong
|
22
|
+
extend Blather::DSL
|
23
|
+
def self.run; client.run; end
|
24
|
+
|
25
|
+
setup 'echo@jabber.local/pong', 'echo'
|
26
|
+
message :chat?, :body => 'ping' do |m|
|
27
|
+
puts "pong!"
|
28
|
+
say m.from, 'pong'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
trap(:INT) { EM.stop }
|
33
|
+
trap(:TERM) { EM.stop }
|
34
|
+
EM.run do
|
35
|
+
Ping.run
|
36
|
+
Pong.run
|
37
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'blather'
|
2
|
+
|
3
|
+
class Object
|
4
|
+
begin
|
5
|
+
ObjectSpace.each_object(Class.new) {}
|
6
|
+
|
7
|
+
# Exclude this class unless it's a subclass of our supers and is defined.
|
8
|
+
# We check defined? in case we find a removed class that has yet to be
|
9
|
+
# garbage collected. This also fails for anonymous classes -- please
|
10
|
+
# submit a patch if you have a workaround.
|
11
|
+
def subclasses_of(*superclasses)
|
12
|
+
subclasses = []
|
13
|
+
|
14
|
+
superclasses.each do |sup|
|
15
|
+
ObjectSpace.each_object(class << sup; self; end) do |k|
|
16
|
+
if k != sup && (k.name.blank? || eval("defined?(::#{k}) && ::#{k}.object_id == k.object_id"))
|
17
|
+
subclasses << k
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
subclasses
|
23
|
+
end
|
24
|
+
rescue RuntimeError
|
25
|
+
# JRuby and any implementations which cannot handle the objectspace traversal
|
26
|
+
# above fall back to this implementation
|
27
|
+
def subclasses_of(*superclasses)
|
28
|
+
subclasses = []
|
29
|
+
|
30
|
+
superclasses.each do |sup|
|
31
|
+
ObjectSpace.each_object(Class) do |k|
|
32
|
+
if superclasses.any? { |superclass| k < superclass } &&
|
33
|
+
(k.name.blank? || eval("defined?(::#{k}) && ::#{k}.object_id == k.object_id"))
|
34
|
+
subclasses << k
|
35
|
+
end
|
36
|
+
end
|
37
|
+
subclasses.uniq!
|
38
|
+
end
|
39
|
+
subclasses
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Hash
|
45
|
+
def deep_merge(second)
|
46
|
+
# From: http://www.ruby-forum.com/topic/142809
|
47
|
+
# Author: Stefan Rusterholz
|
48
|
+
merger = proc { |key,v1,v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
|
49
|
+
self.merge(second, &merger)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
handlers = {}
|
54
|
+
(Object.subclasses_of(Blather::Stanza) + Object.subclasses_of(Blather::BlatherError)).each do |klass|
|
55
|
+
handlers = handlers.deep_merge klass.handler_hierarchy.inject('klass' => klass.to_s.gsub('Blather::', '')) { |h,k| {k.to_s => h} }
|
56
|
+
end
|
57
|
+
|
58
|
+
level = 0
|
59
|
+
runner = proc do |k,v|
|
60
|
+
next if k == 'klass'
|
61
|
+
|
62
|
+
str = ''
|
63
|
+
if level > 0
|
64
|
+
(level - 1).times { str << '| ' }
|
65
|
+
str << '|- '
|
66
|
+
end
|
67
|
+
|
68
|
+
puts str+k
|
69
|
+
if Hash === v
|
70
|
+
level += 1
|
71
|
+
v.sort.each &runner
|
72
|
+
level -= 1
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
handlers.sort.each &runner
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Prints out each roster entry
|
4
|
+
|
5
|
+
require 'blather/client'
|
6
|
+
|
7
|
+
when_ready do
|
8
|
+
roster.grouped.each do |group, items|
|
9
|
+
puts "#{'*'*3} #{group || 'Ungrouped'} #{'*'*3}"
|
10
|
+
items.each { |item| puts "- #{item.name} (#{item.jid})" }
|
11
|
+
puts
|
12
|
+
end
|
13
|
+
shutdown
|
14
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'blather'
|
4
|
+
|
5
|
+
trap(:INT) { EM.stop }
|
6
|
+
trap(:TERM) { EM.stop }
|
7
|
+
EM.run do
|
8
|
+
Blather::Stream::Client.start(Class.new {
|
9
|
+
attr_accessor :jid
|
10
|
+
|
11
|
+
def post_init(stream, jid = nil)
|
12
|
+
@stream = stream
|
13
|
+
self.jid = jid
|
14
|
+
|
15
|
+
@stream.send_data Blather::Stanza::Presence::Status.new
|
16
|
+
puts "Stream started!"
|
17
|
+
end
|
18
|
+
|
19
|
+
def receive_data(stanza)
|
20
|
+
@stream.send_data stanza.reply!
|
21
|
+
end
|
22
|
+
|
23
|
+
def unbind
|
24
|
+
puts "Stream ended!"
|
25
|
+
end
|
26
|
+
}.new, 'echo@jabber.local', 'echo')
|
27
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
# This bot will reply to every message it receives. To end the game, send 'exit'
|
4
|
+
|
5
|
+
require 'xmpp4r/client'
|
6
|
+
include Jabber
|
7
|
+
|
8
|
+
# settings
|
9
|
+
if ARGV.length != 2
|
10
|
+
puts "Run with ./echo_thread.rb user@server/resource password"
|
11
|
+
exit 1
|
12
|
+
end
|
13
|
+
myJID = JID.new(ARGV[0])
|
14
|
+
myPassword = ARGV[1]
|
15
|
+
cl = Client.new(myJID)
|
16
|
+
cl.connect
|
17
|
+
cl.auth(myPassword)
|
18
|
+
cl.send(Presence.new)
|
19
|
+
puts "Connected ! send messages to #{myJID.strip.to_s}."
|
20
|
+
mainthread = Thread.current
|
21
|
+
cl.add_message_callback do |m|
|
22
|
+
if m.type != :error
|
23
|
+
m2 = Message.new(m.from, "You sent: #{m.body}")
|
24
|
+
m2.type = m.type
|
25
|
+
cl.send(m2)
|
26
|
+
if m.body == 'exit'
|
27
|
+
m2 = Message.new(m.from, "Exiting ...")
|
28
|
+
m2.type = m.type
|
29
|
+
cl.send(m2)
|
30
|
+
mainthread.wakeup
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
Thread.stop
|
35
|
+
cl.close
|