ruby-iarm 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +1 -0
- data/LICENSE +26 -0
- data/Manifest +21 -0
- data/README +3 -0
- data/README.textile +52 -0
- data/Rakefile +3 -0
- data/bin/IARMserver.rb +10 -0
- data/bin/chattest.rb +57 -0
- data/bin/chattest_sniffer.rb +14 -0
- data/lib/iarm.rb +5 -0
- data/lib/iarm/client.rb +11 -0
- data/lib/iarm/msg.rb +8 -0
- data/lib/iarm/msg/channel_member.rb +1 -0
- data/lib/iarm/msg/join.rb +2 -0
- data/lib/iarm/msg/part.rb +1 -0
- data/lib/iarm/msg/timeout.rb +1 -0
- data/lib/iarm/msg/topic.rb +1 -0
- data/lib/iarm/server.rb +241 -0
- data/lib/iarm/timer.rb +50 -0
- data/ruby-iarm.gemspec +32 -0
- data/test/performance_test.rb +1 -0
- data/test/test_iarm.rb +134 -0
- metadata +113 -0
data/CHANGELOG
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
v0.0.1. First version
|
data/LICENSE
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Copyright (c) 2010, Andrew Snow
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice,
|
8
|
+
* this list of conditions and the following disclaimer.
|
9
|
+
* Redistributions in binary form must reproduce the above copyright
|
10
|
+
* notice, this list of conditions and the following disclaimer in the
|
11
|
+
* documentation and/or other materials provided with the distribution.
|
12
|
+
* Neither the name of the author nor the names of its
|
13
|
+
* contributors may be used to endorse or promote products derived from
|
14
|
+
* this software without specific prior written permission.
|
15
|
+
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
17
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
18
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
19
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
20
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
21
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
22
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
23
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
24
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
25
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
26
|
+
POSSIBILITY OF SUCH DAMAGE.
|
data/Manifest
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
CHANGELOG
|
2
|
+
LICENSE
|
3
|
+
README
|
4
|
+
README.textile
|
5
|
+
Rakefile
|
6
|
+
bin/IARMserver.rb
|
7
|
+
bin/chattest.rb
|
8
|
+
bin/chattest_sniffer.rb
|
9
|
+
lib/iarm.rb
|
10
|
+
lib/iarm/client.rb
|
11
|
+
lib/iarm/msg.rb
|
12
|
+
lib/iarm/msg/channel_member.rb
|
13
|
+
lib/iarm/msg/join.rb
|
14
|
+
lib/iarm/msg/part.rb
|
15
|
+
lib/iarm/msg/timeout.rb
|
16
|
+
lib/iarm/msg/topic.rb
|
17
|
+
lib/iarm/server.rb
|
18
|
+
lib/iarm/timer.rb
|
19
|
+
test/performance_test.rb
|
20
|
+
test/test_iarm.rb
|
21
|
+
Manifest
|
data/README
ADDED
data/README.textile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
|
2
|
+
h2. IARM(Intra-Application Relay Messaging for Ruby): IRC-inspired Messaging Server for Ruby
|
3
|
+
|
4
|
+
bc. require 'iarm'
|
5
|
+
|
6
|
+
h3. Start a Server:
|
7
|
+
|
8
|
+
bc. Iarm::Server.start('drbunix:/tmp/.s.iarm_socket')
|
9
|
+
|
10
|
+
h3. Connect, join a channel, say something:
|
11
|
+
|
12
|
+
Clients connect to a central server via DRb, join channels, and send
|
13
|
+
messages to the channel.
|
14
|
+
|
15
|
+
bc. c = Iarm::Client.connect('drbunix:/tmp/.s.iarm_socket')
|
16
|
+
c.join('nickname', 'channelname')
|
17
|
+
c.say('nickname', 'channelname', 'Hello world')
|
18
|
+
|
19
|
+
You supply your current nickname to every call, which allows use of multiple
|
20
|
+
nicks in a session.
|
21
|
+
|
22
|
+
h3. Read messages
|
23
|
+
|
24
|
+
bc. msg = c.getmsg('nickname', timeout)
|
25
|
+
puts "Message received #{msg.class}: #{msg.data}
|
26
|
+
from: #{msg.from}
|
27
|
+
on channel: #{msg.channel}"
|
28
|
+
|
29
|
+
As well as regular @Iarm::Msg@ data messages, there are informational types
|
30
|
+
about the channel, generated by the server: @Join@, @Part@, @Timeout@, and @Topic@.
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
h2. Features
|
35
|
+
|
36
|
+
* Join and depart channels
|
37
|
+
* Channels exist while there are >0 members
|
38
|
+
* Get list of channels
|
39
|
+
* Get list of channel members
|
40
|
+
* Get/set channel topic
|
41
|
+
* Auto-notified on join, depart, timeout of other members. Notified on topic changes.
|
42
|
+
* Messages are any type of marshalable ruby data
|
43
|
+
* Configurable timeout: messages are saved upon disconnect until timeout
|
44
|
+
* Poll for messages, or wait for a new message with optional timeout. To help with refresh loops in web apps, clients can disconnect and reconnect within a configurable timeout, and not lose any messages. This makes it trivial to implement, say, the back-end of a (poor!) Campfire clone in Rails.
|
45
|
+
* There is no persistence mechanism
|
46
|
+
* There is no security except channels may have passwords. Also, DRb supports SSL (untested).
|
47
|
+
|
48
|
+
h3. Contact
|
49
|
+
|
50
|
+
Andrew Snow <andrew@modulus.org>
|
51
|
+
Andys^ on irc.freenode.net
|
52
|
+
|
data/Rakefile
ADDED
data/bin/IARMserver.rb
ADDED
data/bin/chattest.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require '../iarm'
|
2
|
+
|
3
|
+
Thread.abort_on_exception = true
|
4
|
+
|
5
|
+
puts "Name?"
|
6
|
+
nick = gets
|
7
|
+
nick.chomp!
|
8
|
+
|
9
|
+
reader = Thread.new do
|
10
|
+
puts "reader: Connecting"
|
11
|
+
iarm = Iarm::Client.connect('drbunix:/tmp/.s.iarm')
|
12
|
+
msg = nil
|
13
|
+
loop do
|
14
|
+
#sleep 1 if(msg.nil?)
|
15
|
+
puts "reader: waiting for message"
|
16
|
+
msg = iarm.getmsg(nick, 30)
|
17
|
+
if(msg)
|
18
|
+
if(msg.kind_of?(Iarm::Msg::Join))
|
19
|
+
puts "#{msg.channel}: *** #{msg.from} #{msg.kind_of?(Iarm::Msg::ChannelMember) ? 'is in' : 'has joined'} the channel"
|
20
|
+
elsif(msg.kind_of?(Iarm::Msg::Part))
|
21
|
+
puts "#{msg.channel}: *** #{msg.from} has #{msg.kind_of?(Iarm::Msg::Timeout) ? 'timed out of' : 'departed'} the channel"
|
22
|
+
else
|
23
|
+
puts "#{msg.channel}: <#{msg.from}> #{msg.data}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
puts "writer: connecting"
|
30
|
+
ia = Iarm::Client.connect('drbunix:/tmp/.s.iarm')
|
31
|
+
ch = nil
|
32
|
+
|
33
|
+
loop do
|
34
|
+
puts "Input?"
|
35
|
+
input = gets
|
36
|
+
if(input)
|
37
|
+
input.chomp!
|
38
|
+
puts "writer: posting"
|
39
|
+
if(input =~ /\/join (.*)$/)
|
40
|
+
ch = $1
|
41
|
+
puts "*** #{nick} joining #{ch}"
|
42
|
+
ia.join(nick, ch)
|
43
|
+
elsif(input =~ /\/part (.*)$/)
|
44
|
+
ia.depart(nick, $1)
|
45
|
+
puts "*** #{nick} departing #{$1}"
|
46
|
+
ch = nil if(ch == $1)
|
47
|
+
elsif(input =~ /\/quit$/)
|
48
|
+
ia.depart(nick)
|
49
|
+
reader.kill
|
50
|
+
exit
|
51
|
+
elsif(ch)
|
52
|
+
puts "<#{nick}:#{ch}> #{input}"
|
53
|
+
ia.post(Iarm::Msg.new(ch, nick, input))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'iarm'
|
2
|
+
iarm = Iarm::Client.connect('drbunix:/tmp/.s.iarm')
|
3
|
+
loop do
|
4
|
+
msg = iarm.getmsg(nil, 50)
|
5
|
+
if(msg)
|
6
|
+
if(msg.kind_of?(Iarm::Msg::Join))
|
7
|
+
puts "#{msg.channel}: *** #{msg.from} has joined the channel"
|
8
|
+
elsif(msg.kind_of?(Iarm::Msg::Part))
|
9
|
+
puts "#{msg.channel}: *** #{msg.from} has #{msg.kind_of?(Iarm::Msg::Timeout) ? 'timed out of' : 'departed'} the channel"
|
10
|
+
else
|
11
|
+
puts "#{msg.channel}: <#{msg.from}> #{msg.data}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/iarm.rb
ADDED
data/lib/iarm/client.rb
ADDED
data/lib/iarm/msg.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
class Iarm::Msg::ChannelMember < Iarm::Msg::Join ; end
|
@@ -0,0 +1 @@
|
|
1
|
+
class Iarm::Msg::Part < Iarm::Msg ; end
|
@@ -0,0 +1 @@
|
|
1
|
+
class Iarm::Msg::Timeout < Iarm::Msg::Part ; end
|
@@ -0,0 +1 @@
|
|
1
|
+
class Iarm::Msg::Topic < Iarm::Msg ; end
|
data/lib/iarm/server.rb
ADDED
@@ -0,0 +1,241 @@
|
|
1
|
+
|
2
|
+
require 'thread'
|
3
|
+
require 'drb'
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
module Iarm
|
8
|
+
class Server
|
9
|
+
|
10
|
+
def ping
|
11
|
+
'pong'
|
12
|
+
end
|
13
|
+
def ttl(ttl_secs)
|
14
|
+
@ttl_secs = ttl_secs
|
15
|
+
end
|
16
|
+
|
17
|
+
def list(pattern=nil)
|
18
|
+
pattern ? @channels.keys.grep(pattern) : @channels.keys
|
19
|
+
end
|
20
|
+
|
21
|
+
def who(channel)
|
22
|
+
if(@channels.has_key?(channel))
|
23
|
+
@channel_members[channel] #.each {|w,time| post_msg(who, Msg::ChannelMember.new(channel, w, time)) }
|
24
|
+
else
|
25
|
+
{}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def join(who, channel, key=nil) # returns true if joined, false if denied, and nil if new channel formed
|
30
|
+
retval = nil
|
31
|
+
touch_nickname(who)
|
32
|
+
@mutex.synchronize do
|
33
|
+
if(@channels.has_key?(channel))
|
34
|
+
retval = (@channels[channel] == key)
|
35
|
+
else
|
36
|
+
@channels[channel] = key
|
37
|
+
end
|
38
|
+
|
39
|
+
if(retval != false) # if retval is true (joined existing) or nil (new channel formed)
|
40
|
+
if(!@channel_members[channel].has_key?(who)) # don't re-join them if they've already joined before
|
41
|
+
@channel_members[channel][who] = clockval
|
42
|
+
@channels_joined[who] << channel
|
43
|
+
send_msg(Msg::Join.new(channel, who, @channel_members[channel][who]))
|
44
|
+
post_msg(who, @topics[channel]) if @topics.has_key?(channel)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
retval
|
49
|
+
end
|
50
|
+
|
51
|
+
def depart(who, channel=nil) # nil=depart ALL channels and log out client
|
52
|
+
@mutex.synchronize do
|
53
|
+
(channel.nil? ? @channels_joined[who] : [ channel ]).each do |ch|
|
54
|
+
@channels_joined[who].delete(ch)
|
55
|
+
if @channel_members[ch].delete(who)
|
56
|
+
send_msg(Msg::Part.new(ch, who))
|
57
|
+
end
|
58
|
+
check_channel_empty(ch)
|
59
|
+
end
|
60
|
+
kill_client(who) if(channel.nil?)
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
# getmsg(): NOTES
|
66
|
+
# returns msg or nil if no messages and timed out.
|
67
|
+
# also serves as a keep-alive to avoid getting killed by ttl
|
68
|
+
# if who=nil then it listens on all channels, but only one client can do this at once
|
69
|
+
# if another client is already listening with the same who-id, it has the effect of making them return immediately (before their timeout is up)
|
70
|
+
def getmsg(who, timeout=0)
|
71
|
+
if(@msgs[who].empty? && timeout != 0)
|
72
|
+
wait_existing = false
|
73
|
+
msg = @mutex.synchronize do
|
74
|
+
wait_existing = Iarm::Timer.poke(@listeners[who])
|
75
|
+
next_msg(who)
|
76
|
+
end
|
77
|
+
return msg if(msg)
|
78
|
+
|
79
|
+
if(wait_existing)
|
80
|
+
Thread.pass while(@mutex.synchronize { @listeners.has_key?(who) })
|
81
|
+
end
|
82
|
+
|
83
|
+
#puts "Timer.wait: timeout=#{timeout}"
|
84
|
+
Iarm::Timer.wait(timeout) do |mode|
|
85
|
+
@mutex.synchronize do
|
86
|
+
mode ? @listeners[who] = Thread.current : @listeners.delete(who)
|
87
|
+
end
|
88
|
+
#puts "IARM getmsg: #{who} #{mode ? 'entering' : 'exiting'} wait with msgcount=#{@msgs[who].length}"
|
89
|
+
Iarm::Timer.poke(Thread.current) if mode && @msgs[who].length>0 # don't bother sleeping if we already have a new message waiting
|
90
|
+
end
|
91
|
+
end
|
92
|
+
@mutex.synchronize { next_msg(who) }
|
93
|
+
end
|
94
|
+
|
95
|
+
def getmsgs(who, timeout=0)
|
96
|
+
res = [ getmsg(who, timeout) ]
|
97
|
+
while(!res.empty? && (msg = getmsg(who, 0)))
|
98
|
+
res << msg
|
99
|
+
end
|
100
|
+
res
|
101
|
+
end
|
102
|
+
|
103
|
+
def say(who, channel, data)
|
104
|
+
post(Iarm::Msg.new(channel, who, data))
|
105
|
+
end
|
106
|
+
|
107
|
+
def set_topic(who, channel, data)
|
108
|
+
touch_nickname(who)
|
109
|
+
if @channels.has_key?(channel)
|
110
|
+
data = Msg::Topic.new(channel, who, data) unless data.kind_of?(Msg::Topic)
|
111
|
+
if(@topics[channel] != data)
|
112
|
+
@mutex.synchronize { @topics[channel] = data }
|
113
|
+
post(data)
|
114
|
+
data
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def get_topic(channel)
|
120
|
+
@topics[channel]
|
121
|
+
end
|
122
|
+
|
123
|
+
def post(msg)
|
124
|
+
@mutex.synchronize { send_msg(msg) } if(msg.kind_of?(Msg))
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.start(uri=nil)
|
128
|
+
DRb.start_service(uri, self.new)
|
129
|
+
DRb.thread
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
REAPER_GRANULARITY = 5 #seconds
|
134
|
+
|
135
|
+
def initialize
|
136
|
+
@mutex = Mutex.new()
|
137
|
+
@reaper_mutex = Mutex.new()
|
138
|
+
@ttl_secs = 60
|
139
|
+
@listeners = Hash.new() # { who => Thread }
|
140
|
+
@msgs = Hash.new() {|hsh,key| hsh[key] = [ ] } # { who => [ msg1, msg2, ...] }
|
141
|
+
@clients = Hash.new() # { who => time_of_last_activity }
|
142
|
+
@channel_members = Hash.new() {|hsh,key| hsh[key] = { } } # { channelname => { who1 => join_time }, who2 => ...] }
|
143
|
+
@channels_joined = Hash.new() {|hsh,key| hsh[key] = [ ] } # { who => [ channel1, channel2 ] }
|
144
|
+
@channels = Hash.new() # { channelname => password }
|
145
|
+
@topics = Hash.new() # { channelname => topic }
|
146
|
+
@timeout_queue = []
|
147
|
+
reaper_thread
|
148
|
+
end
|
149
|
+
|
150
|
+
def touch_nickname(nickname) #TODO: call this
|
151
|
+
# UPTO THERE
|
152
|
+
timeout_box = @ttl_secs / REAPER_GRANULARITY #/
|
153
|
+
@reaper_mutex.synchronize do
|
154
|
+
@timeout_queue[timeout_box] ||= []
|
155
|
+
@timeout_queue[timeout_box] << nickname
|
156
|
+
@clients[nickname] = clockval
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
=begin
|
162
|
+
reaper ideas
|
163
|
+
------------
|
164
|
+
|
165
|
+
have a linked list which is in order of things to timeout
|
166
|
+
when taking something off the list, check its actual timeout value and put it back to sleep if needed
|
167
|
+
this could be a binary search down the track, for performance
|
168
|
+
|
169
|
+
=end
|
170
|
+
|
171
|
+
def timed_out?(nickname)
|
172
|
+
(tla = @clients[nickname]) && (tla + @ttl_secs) < clockval
|
173
|
+
end
|
174
|
+
|
175
|
+
def reaper_thread
|
176
|
+
@reaper ||= Thread.new do
|
177
|
+
loop do
|
178
|
+
kill_list = []
|
179
|
+
sleep REAPER_GRANULARITY
|
180
|
+
@reaper_mutex.synchronize do
|
181
|
+
timeoutlist = @timeout_queue.shift
|
182
|
+
if timeoutlist
|
183
|
+
timeoutlist.each do |who|
|
184
|
+
kill_list << who if timed_out?(who)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
@mutex.synchronize do
|
189
|
+
kill_list.each do |who|
|
190
|
+
if(@channels_joined.has_key?(who))
|
191
|
+
@channels_joined[who].each do |ch|
|
192
|
+
@channel_members[ch].delete(who)
|
193
|
+
send_msg(Msg::Timeout.new(ch, who))
|
194
|
+
check_channel_empty(ch)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
kill_client(who)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def clockval
|
205
|
+
Time.new.to_i
|
206
|
+
end
|
207
|
+
|
208
|
+
def send_msg(msg)
|
209
|
+
@channel_members[msg.channel].each_key {|w| post_msg(w, msg) }
|
210
|
+
post_msg(nil, msg) if(@clients.has_key?(nil))
|
211
|
+
end
|
212
|
+
def post_msg(who, msg)
|
213
|
+
if(msg.kind_of?(Msg::Topic) || who != msg.from)
|
214
|
+
@msgs[who] << msg
|
215
|
+
Iarm::Timer.poke(@listeners[who]) if(@listeners.has_key?(who))
|
216
|
+
end
|
217
|
+
end
|
218
|
+
def next_msg(who) # returns msg or nil
|
219
|
+
touch_nickname(who)
|
220
|
+
@msgs[who].shift
|
221
|
+
end
|
222
|
+
def check_channel_empty(channel)
|
223
|
+
if(@channel_members[channel].empty?)
|
224
|
+
@channels.delete(channel)
|
225
|
+
@channel_members.delete(channel)
|
226
|
+
@topics.delete(channel)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
def kill_client(who)
|
230
|
+
@channels_joined[who].each do |ch|
|
231
|
+
@channel_members[ch].delete(who)
|
232
|
+
check_channel_empty(ch)
|
233
|
+
end
|
234
|
+
@channels_joined.delete(who)
|
235
|
+
@clients.delete(who)
|
236
|
+
@msgs.delete(who)
|
237
|
+
@listeners.delete(who)
|
238
|
+
end
|
239
|
+
|
240
|
+
end
|
241
|
+
end
|
data/lib/iarm/timer.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module Iarm
|
5
|
+
class Timer
|
6
|
+
class Timeout < Exception; end
|
7
|
+
class Poke < Exception; end
|
8
|
+
|
9
|
+
def self.poke(thr)
|
10
|
+
crit do
|
11
|
+
if(thr)# && thr.stop?)
|
12
|
+
thr.raise(Poke.new)
|
13
|
+
true
|
14
|
+
else
|
15
|
+
false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.wait(timeout)
|
21
|
+
timer = create_timer(timeout)
|
22
|
+
yield(true) if block_given?
|
23
|
+
Thread.stop
|
24
|
+
rescue Timeout
|
25
|
+
return false
|
26
|
+
rescue Poke
|
27
|
+
return true
|
28
|
+
ensure
|
29
|
+
Thread.kill(timer) if(timer && timer.alive?)
|
30
|
+
yield(false) if block_given?
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.crit
|
34
|
+
yield
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def self.create_timer(timeout)
|
39
|
+
return nil if(timeout.nil?)
|
40
|
+
|
41
|
+
waiter = Thread.current
|
42
|
+
Thread.start do
|
43
|
+
Thread.pass
|
44
|
+
sleep(timeout)
|
45
|
+
# Thread.critical = true
|
46
|
+
waiter.raise(Timeout.new)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/ruby-iarm.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{ruby-iarm}
|
5
|
+
s.version = "0.0.1"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = [""]
|
9
|
+
s.date = %q{2010-06-07}
|
10
|
+
s.description = %q{}
|
11
|
+
s.email = %q{}
|
12
|
+
s.executables = ["IARMserver.rb", "chattest.rb", "chattest_sniffer.rb"]
|
13
|
+
s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "README", "README.textile", "bin/IARMserver.rb", "bin/chattest.rb", "bin/chattest_sniffer.rb", "lib/iarm.rb", "lib/iarm/client.rb", "lib/iarm/msg.rb", "lib/iarm/msg/channel_member.rb", "lib/iarm/msg/join.rb", "lib/iarm/msg/part.rb", "lib/iarm/msg/timeout.rb", "lib/iarm/msg/topic.rb", "lib/iarm/server.rb", "lib/iarm/timer.rb"]
|
14
|
+
s.files = ["CHANGELOG", "LICENSE", "README", "README.textile", "Rakefile", "bin/IARMserver.rb", "bin/chattest.rb", "bin/chattest_sniffer.rb", "lib/iarm.rb", "lib/iarm/client.rb", "lib/iarm/msg.rb", "lib/iarm/msg/channel_member.rb", "lib/iarm/msg/join.rb", "lib/iarm/msg/part.rb", "lib/iarm/msg/timeout.rb", "lib/iarm/msg/topic.rb", "lib/iarm/server.rb", "lib/iarm/timer.rb", "test/performance_test.rb", "test/test_iarm.rb", "Manifest", "ruby-iarm.gemspec"]
|
15
|
+
s.homepage = %q{}
|
16
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Ruby-iarm", "--main", "README"]
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.rubyforge_project = %q{ruby-iarm}
|
19
|
+
s.rubygems_version = %q{1.3.7}
|
20
|
+
s.summary = %q{}
|
21
|
+
s.test_files = ["test/test_iarm.rb", "test/performance_test.rb"]
|
22
|
+
|
23
|
+
if s.respond_to? :specification_version then
|
24
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
25
|
+
s.specification_version = 3
|
26
|
+
|
27
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
28
|
+
else
|
29
|
+
end
|
30
|
+
else
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
|
data/test/test_iarm.rb
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
|
2
|
+
require '../iarm'
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
Thread.abort_on_exception = true
|
6
|
+
|
7
|
+
def socket_path
|
8
|
+
'drbunix:/tmp/.s.testiarm'
|
9
|
+
end
|
10
|
+
|
11
|
+
Iarm::Server.start(socket_path)
|
12
|
+
|
13
|
+
module TestIarmServer
|
14
|
+
|
15
|
+
|
16
|
+
def setup
|
17
|
+
@client1 = new_client
|
18
|
+
@client2 = new_client
|
19
|
+
end
|
20
|
+
|
21
|
+
def teardown
|
22
|
+
@client1.depart('client1')
|
23
|
+
@client1 = nil
|
24
|
+
@client2.depart('client2')
|
25
|
+
@client2 = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
def new_client
|
30
|
+
Iarm::Client.connect(socket_path) or raise 'Cannot connect'
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
class TestIarm < Test::Unit::TestCase
|
37
|
+
include TestIarmServer
|
38
|
+
|
39
|
+
def test_join_and_speak
|
40
|
+
@client1.join('client1', 'test_channel')
|
41
|
+
@client2.join('client2', 'test_channel')
|
42
|
+
|
43
|
+
join_msg = @client1.getmsg('client1', 1)
|
44
|
+
assert_instance_of Iarm::Msg::Join, join_msg
|
45
|
+
assert_equal 'client2', join_msg.from
|
46
|
+
assert_equal 'test_channel', join_msg.channel
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_topic
|
50
|
+
@client1.join('client1', 'test_channel')
|
51
|
+
|
52
|
+
|
53
|
+
topic = @client1.get_topic('test_channel')
|
54
|
+
assert_nil topic
|
55
|
+
|
56
|
+
@client1.set_topic('client1', 'test_channel', 'Channel Topic')
|
57
|
+
topic = @client1.get_topic('test_channel')
|
58
|
+
assert_kind_of Iarm::Msg::Topic, topic
|
59
|
+
assert_equal 'Channel Topic', topic.data
|
60
|
+
assert_equal 'client1', topic.from
|
61
|
+
assert_equal 'test_channel', topic.channel
|
62
|
+
|
63
|
+
@client2.join('client2', 'test_channel')
|
64
|
+
topic = @client2.getmsg('client2', 1)
|
65
|
+
assert_kind_of Iarm::Msg::Topic, topic
|
66
|
+
assert_equal 'Channel Topic', topic.data
|
67
|
+
assert_equal 'client1', topic.from
|
68
|
+
assert_equal 'test_channel', topic.channel
|
69
|
+
|
70
|
+
@client2.set_topic('client2', 'test_channel', 'New Topic')
|
71
|
+
topic = @client1.get_topic('test_channel')
|
72
|
+
assert_kind_of Iarm::Msg::Topic, topic
|
73
|
+
assert_equal 'New Topic', topic.data
|
74
|
+
assert_equal 'client2', topic.from
|
75
|
+
assert_equal 'test_channel', topic.channel
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_queued_msg
|
79
|
+
@client1.join('client1', 'test_channel')
|
80
|
+
@client2.join('client2', 'test_channel')
|
81
|
+
@client1.say('client1', 'test_channel', 'test message')
|
82
|
+
|
83
|
+
new_connection = new_client
|
84
|
+
msg = new_connection.getmsg('client2', 1)
|
85
|
+
assert_instance_of Iarm::Msg, msg
|
86
|
+
assert_equal 'client1', msg.from
|
87
|
+
assert_equal 'test_channel', msg.channel
|
88
|
+
assert_equal 'test message', msg.data
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_who
|
92
|
+
@client1.join('client1', 'test_channel')
|
93
|
+
channel_members = @client2.who('test_channel')
|
94
|
+
assert_equal ['client1'], channel_members.keys.sort
|
95
|
+
|
96
|
+
@client2.join('client2', 'test_channel')
|
97
|
+
channel_members = @client2.who('test_channel')
|
98
|
+
assert_equal ['client1', 'client2'], channel_members.keys.sort
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_depart
|
102
|
+
@client1.join('client1', 'test_channel')
|
103
|
+
@client2.join('client2', 'test_channel')
|
104
|
+
@client1.depart('client1', 'test_channel')
|
105
|
+
msg = @client2.getmsg('client2', 1)
|
106
|
+
assert_instance_of Iarm::Msg::Part, msg
|
107
|
+
|
108
|
+
channel_members = @client2.who('test_channel')
|
109
|
+
assert_equal ['client2'], channel_members.keys.sort
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_depart_all
|
113
|
+
@client1.join('client1', 'test_channel')
|
114
|
+
@client1.join('client1', 'test_channel2')
|
115
|
+
@client1.depart('client1')
|
116
|
+
|
117
|
+
assert_equal [], @client2.who('test_channel').keys
|
118
|
+
assert_equal [], @client2.who('test_channel2').keys
|
119
|
+
|
120
|
+
assert_equal [], @client2.list
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_server_running
|
124
|
+
assert_equal 'pong', @client1.ping
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_timeout
|
128
|
+
@client1.ttl(2)
|
129
|
+
@client1.join('client1', 'test_channel')
|
130
|
+
assert_equal ['client1'], @client2.who('test_channel').keys
|
131
|
+
sleep(Iarm::Server::REAPER_GRANULARITY + 1)
|
132
|
+
assert_equal [], @client2.who('test_channel').keys
|
133
|
+
end
|
134
|
+
end
|
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-iarm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- ""
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-06-07 00:00:00 +10:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: ""
|
23
|
+
email: ""
|
24
|
+
executables:
|
25
|
+
- IARMserver.rb
|
26
|
+
- chattest.rb
|
27
|
+
- chattest_sniffer.rb
|
28
|
+
extensions: []
|
29
|
+
|
30
|
+
extra_rdoc_files:
|
31
|
+
- CHANGELOG
|
32
|
+
- LICENSE
|
33
|
+
- README
|
34
|
+
- README.textile
|
35
|
+
- bin/IARMserver.rb
|
36
|
+
- bin/chattest.rb
|
37
|
+
- bin/chattest_sniffer.rb
|
38
|
+
- lib/iarm.rb
|
39
|
+
- lib/iarm/client.rb
|
40
|
+
- lib/iarm/msg.rb
|
41
|
+
- lib/iarm/msg/channel_member.rb
|
42
|
+
- lib/iarm/msg/join.rb
|
43
|
+
- lib/iarm/msg/part.rb
|
44
|
+
- lib/iarm/msg/timeout.rb
|
45
|
+
- lib/iarm/msg/topic.rb
|
46
|
+
- lib/iarm/server.rb
|
47
|
+
- lib/iarm/timer.rb
|
48
|
+
files:
|
49
|
+
- CHANGELOG
|
50
|
+
- LICENSE
|
51
|
+
- README
|
52
|
+
- README.textile
|
53
|
+
- Rakefile
|
54
|
+
- bin/IARMserver.rb
|
55
|
+
- bin/chattest.rb
|
56
|
+
- bin/chattest_sniffer.rb
|
57
|
+
- lib/iarm.rb
|
58
|
+
- lib/iarm/client.rb
|
59
|
+
- lib/iarm/msg.rb
|
60
|
+
- lib/iarm/msg/channel_member.rb
|
61
|
+
- lib/iarm/msg/join.rb
|
62
|
+
- lib/iarm/msg/part.rb
|
63
|
+
- lib/iarm/msg/timeout.rb
|
64
|
+
- lib/iarm/msg/topic.rb
|
65
|
+
- lib/iarm/server.rb
|
66
|
+
- lib/iarm/timer.rb
|
67
|
+
- test/performance_test.rb
|
68
|
+
- test/test_iarm.rb
|
69
|
+
- Manifest
|
70
|
+
- ruby-iarm.gemspec
|
71
|
+
has_rdoc: true
|
72
|
+
homepage: ""
|
73
|
+
licenses: []
|
74
|
+
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options:
|
77
|
+
- --line-numbers
|
78
|
+
- --inline-source
|
79
|
+
- --title
|
80
|
+
- Ruby-iarm
|
81
|
+
- --main
|
82
|
+
- README
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
hash: 3
|
91
|
+
segments:
|
92
|
+
- 0
|
93
|
+
version: "0"
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
hash: 11
|
100
|
+
segments:
|
101
|
+
- 1
|
102
|
+
- 2
|
103
|
+
version: "1.2"
|
104
|
+
requirements: []
|
105
|
+
|
106
|
+
rubyforge_project: ruby-iarm
|
107
|
+
rubygems_version: 1.3.7
|
108
|
+
signing_key:
|
109
|
+
specification_version: 3
|
110
|
+
summary: ""
|
111
|
+
test_files:
|
112
|
+
- test/test_iarm.rb
|
113
|
+
- test/performance_test.rb
|