ruby-iarm 0.0.1
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.
- 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
|