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 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
@@ -0,0 +1,3 @@
1
+
2
+ See README.textile
3
+
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
@@ -0,0 +1,3 @@
1
+ require 'echoe'
2
+ Echoe.new('ruby-iarm')
3
+
data/bin/IARMserver.rb ADDED
@@ -0,0 +1,10 @@
1
+
2
+ require 'drb'
3
+ require 'iarm'
4
+
5
+ Thread.abort_on_exception = true
6
+
7
+ server = Iarm::Server.new
8
+ DRb.start_service(ARGV[0].nil? ? 'drbunix:/tmp/.s.iarm' : ARGV[0], server)
9
+ DRb.thread.join
10
+
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
@@ -0,0 +1,5 @@
1
+
2
+ ['timer', 'msg', 'server', 'client'].each do |x|
3
+ require("#{File.dirname(__FILE__)}/iarm/" + x)
4
+ end
5
+
@@ -0,0 +1,11 @@
1
+
2
+ require 'drb'
3
+
4
+ module Iarm
5
+ class Client
6
+ def self.connect(server)
7
+ #DRb.start_service
8
+ DRbObject.new(nil, server)
9
+ end
10
+ end
11
+ end
data/lib/iarm/msg.rb ADDED
@@ -0,0 +1,8 @@
1
+
2
+
3
+ Iarm::Msg = Struct.new(:channel, :from, :data)
4
+
5
+ ['join', 'part', 'channel_member', 'timeout', 'topic'].each do |x|
6
+ require("#{File.dirname(__FILE__)}/msg/" + x)
7
+ end
8
+
@@ -0,0 +1 @@
1
+ class Iarm::Msg::ChannelMember < Iarm::Msg::Join ; end
@@ -0,0 +1,2 @@
1
+ class Iarm::Msg::Join < Iarm::Msg ; end # data = time joined
2
+
@@ -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
@@ -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