smartfox 0.2.1 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -13,6 +13,8 @@ begin
13
13
  gem.add_development_dependency "rspec", ">= 1.2.9"
14
14
  gem.add_dependency 'json'
15
15
  gem.add_dependency 'builder'
16
+ gem.add_dependency 'libxml-ruby'
17
+ gem.files.exclude 'nbproject/**'
16
18
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
19
  end
18
20
  Jeweler::GemcutterTasks.new
@@ -30,6 +32,7 @@ Spec::Rake::SpecTask.new(:rcov) do |spec|
30
32
  spec.libs << 'lib' << 'spec'
31
33
  spec.pattern = 'spec/**/*_spec.rb'
32
34
  spec.rcov = true
35
+ spec.rcov_opts = ['--exclude', "features,kernel,load-diff-lcs\.rb,instance_exec\.rb,lib/spec.rb,lib/spec/runner.rb,^spec/*,bin/spec,examples,/gems,/Library/Ruby,\.autotest,#{ENV['GEM_HOME']}"]
33
36
  end
34
37
 
35
38
  task :spec => :check_dependencies
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.1
1
+ 0.3.1
@@ -8,6 +8,9 @@ module SmartFox
8
8
  autoload :Socket, 'smartfox/socket'
9
9
  autoload :BlueBox, 'smartfox/blue_box'
10
10
  autoload :Packet, 'smartfox/packet'
11
+ autoload :Room, 'smartfox/room'
12
+ autoload :User, 'smartfox/user'
13
+ autoload :Message, 'smartfox/message'
11
14
 
12
15
  Logger = Logger.new(STDOUT)
13
16
 
@@ -8,7 +8,7 @@ class SmartFox::Client
8
8
  class TransportTimeoutError < SmartFox::SmartFoxError; end
9
9
 
10
10
  attr_reader :connected, :room_list, :buddy_list, :server, :port
11
- attr_reader :current_room
11
+ attr_reader :current_room, :users, :user_id
12
12
  alias_method :connected?, :connected
13
13
 
14
14
  CLIENT_VERSION = "1.5.8"
@@ -27,6 +27,8 @@ class SmartFox::Client
27
27
  ACTION_AUTO_JOIN = 'autoJoin'
28
28
  ACTION_UPDATE_ROOMS = 'getRmList'
29
29
  ACTION_ROOM_LIST = 'rmList'
30
+ ACTION_JOIN_OK = 'joinOK'
31
+ ACTION_JOIN_FAIL = 'joinKO'
30
32
 
31
33
  EXTENDED_RESPONSE = 'xtRes'
32
34
 
@@ -39,7 +41,8 @@ class SmartFox::Client
39
41
  @server = options[:server] || 'localhost'
40
42
  @port = options[:port]
41
43
  @events = {}
42
- @rooms = []
44
+ @rooms = {}
45
+ @users = {}
43
46
  end
44
47
 
45
48
  def connect()
@@ -138,6 +141,14 @@ class SmartFox::Client
138
141
  end
139
142
  end
140
143
 
144
+ def parse_user(node)
145
+ if user = @users[node['i'].to_i]
146
+ user.parse(node)
147
+ else
148
+ @users[node['i'].to_i] = SmartFox::User.parse(node)
149
+ end
150
+ end
151
+
141
152
  private
142
153
  def send_packet(header, action, room_id = -1)
143
154
  xml = Builder::XmlMarkup.new()
@@ -181,18 +192,32 @@ class SmartFox::Client
181
192
  def handle_system_packet(packet)
182
193
  case packet.action
183
194
  when ACTION_LOGIN_OK
184
- @username = packet.data['n']
185
- @moderator = packet.data['mod'] != "0"
186
- @id = packet.data['id']
187
- SmartFox::Logger.info "SmartFox::Client logged in as #{@username}"
195
+ @username = packet.data.first['n']
196
+ @moderator = packet.data.first['mod'] != "0"
197
+ @user_id = packet.data.first['id'].to_i
198
+ SmartFox::Logger.info "SmartFox::Client logged in as #{@username} (ID:#{@user_id})"
188
199
  raise_event(:logged_in, self)
189
200
  when ACTION_ROOM_LIST
190
201
  @rooms.clear
191
- packet.data.children.each do |room|
192
- @rooms << SmartFox::Room.parse(room)
202
+ packet.data.first.children.each do |room|
203
+ room_object = SmartFox::Room.parse(self, room)
204
+ @rooms[room_object.id] = room_object
193
205
  end
194
206
 
195
207
  raise_event(:rooms_updated, self, @rooms)
208
+ when ACTION_JOIN_OK
209
+ @current_room = @rooms[packet.room]
210
+ SmartFox::Logger.info "SmartFox::Client joined room #{packet.room}"
211
+ SmartFox::Logger.debug "SmartFox::Client#handle_system_packet ACTION_JOIN_OK data => #{packet.data}"
212
+ @current_room.joined
213
+ @current_room.parse_users(packet.data.find{|n| n.name == 'uLs'})
214
+ raise_event(:room_joined, self, @current_room)
215
+ when ACTION_JOIN_FAIL
216
+ raise_event(:room_join_failed, self)
217
+ else
218
+ if packet.room > 0
219
+ @rooms[packet.room].handle_system_packet(packet)
220
+ end
196
221
  end
197
222
  end
198
223
 
@@ -0,0 +1,7 @@
1
+ class SmartFox::Message
2
+ attr_accessor :user, :message, :room
3
+
4
+ def initialize(user, message, room)
5
+ @user, @message, @room = user, message, room
6
+ end
7
+ end
@@ -1,12 +1,13 @@
1
1
  require 'libxml'
2
2
 
3
3
  class SmartFox::Packet
4
- attr_reader :action, :header, :room, :data
4
+ attr_reader :action, :header, :room, :data, :body
5
5
 
6
- def initialize(header, action, room = 0, extra = nil)
6
+ def initialize(header, action, body, room = 0, extra = nil)
7
7
  @header = header
8
8
  @action = action
9
9
  @room = room.to_i
10
+ @body = body
10
11
  @data = (@header == SmartFox::Client::HEADER_EXTENDED ? SmartFox::Packet.parse_extended(extra) : extra)
11
12
  end
12
13
 
@@ -27,8 +28,8 @@ class SmartFox::Packet
27
28
  header = document.root['t']
28
29
  action = document.root.child['action']
29
30
  room = document.root.child['r']
30
- extra = document.root.child.child
31
- new(header, action, room, extra)
31
+ extra = document.root.child.children? ? document.root.child.children : nil
32
+ new(header, action, document.root.child, room, extra)
32
33
  end
33
34
 
34
35
  def self.parse_json(data)
@@ -41,7 +42,7 @@ class SmartFox::Packet
41
42
 
42
43
  def self.parse_extended(data)
43
44
  SmartFox::Logger.debug "SmartFox::Packet.parse_extended('#{data}')"
44
- document = LibXML::XML::Parser.string(data.content).parse
45
+ document = LibXML::XML::Parser.string(data.first.content).parse
45
46
  parse_extended_object document.root
46
47
  end
47
48
 
@@ -1,3 +1,115 @@
1
1
  class SmartFox::Room
2
+ attr_reader :id, :name, :private, :temporary, :game, :max_users, :users, :transcript
2
3
 
4
+ EVENTS = [ :message_sent, :message_received, :message_received_self, :message_received_other, :user_joined, :user_left, :user_count_updated ]
5
+ ACTION_PUBLISH_MESSAGE = 'pubMsg'
6
+ ACTION_USER_ENTER = 'uER'
7
+ ACTION_UPDATE_USER_COUNT = 'uCount'
8
+
9
+ def initialize(client, node)
10
+ @client = client
11
+ @id = node['id'].to_i
12
+ @name = node.first.content
13
+ @private = node['priv'] == '1'
14
+ @temporary = node['temp'] == '1'
15
+ @current_users = node['ucnt'].to_i
16
+ @max_users = node['maxu'].to_i
17
+ @game = node['game'] == '1'
18
+ @events = {}
19
+ @transcript = []
20
+
21
+ @users = nil
22
+ SmartFox::Logger.debug "SmartFox::Room.new -> #{inspect}"
23
+ end
24
+
25
+ def current_users
26
+ @users ? @users.length : @current_users
27
+ end
28
+
29
+ def self.parse(client, node)
30
+ new(client, node)
31
+ end
32
+
33
+ def inspect
34
+ "#<#{self.class.name}:#{object_id} id:#{@id} name:#{@name}#{' temporary' if @temporary}#{' private' if @private} current_users:#{@current_users} max_users:#{@max_users}>"
35
+ end
36
+
37
+ def join
38
+ @client.join_room self
39
+ end
40
+
41
+ def joined
42
+ @transcript.clear
43
+ end
44
+
45
+ def parse_users(users)
46
+ @users = {}
47
+
48
+ users.children.each do |child|
49
+ user = @client.parse_user(child)
50
+ @users[user.id] = user
51
+ end
52
+ end
53
+
54
+ def send_message(message)
55
+ SmartFox::Logger.info "SmartFox::Room#send_message('#{message}')"
56
+ @client.send :send_packet, SmartFox::Client::HEADER_SYSTEM, ACTION_PUBLISH_MESSAGE, self.id do |packet|
57
+ packet.txt do |txt|
58
+ txt.cdata! message
59
+ end
60
+ end
61
+
62
+ @transcript << SmartFox::Message.new(@client.users[@client.user_id], message, self)
63
+
64
+ raise_event :message_sent, @client, self, message
65
+ end
66
+
67
+ def handle_system_packet(packet)
68
+ case packet.action
69
+ when ACTION_PUBLISH_MESSAGE
70
+ text = packet.data.find{|c| c.name == 'txt'}.child.content
71
+ user_id = packet.data.find{|c| c.name == 'user'}['id'].to_i
72
+ user = @client.users[user_id]
73
+ message = SmartFox::Message.new(user, text, self)
74
+ SmartFox::Logger.info "SmartFox::Room did receive message #{message.inspect}"
75
+ @transcript << message unless user.id == @client.user_id
76
+
77
+ SmartFox::Logger.info "SmartFox::Room#handle_system_packet ACTION_PUBLISH_MESSAGE #{user.id} == #{@client.user_id}"
78
+ if user.id == @client.user_id
79
+ SmartFox::Logger.debug "SmartFox::Room#handle_system_packet ACTION_PUBLISH_MESSAGE from self"
80
+ raise_event :message_received_self, @client, @room, message
81
+ else
82
+ SmartFox::Logger.debug "SmartFox::Room#handle_system_packet ACTION_PUBLISH_MESSAGE from #{user.name}"
83
+ raise_event :message_received_other, @client, @room, message
84
+ end
85
+ raise_event :message_received, @client, @room, message
86
+ when ACTION_USER_ENTER
87
+ packet.data.each do |u|
88
+ user = @client.parse_user(u)
89
+ @users[user.id] = user
90
+ raise_event :user_joined, @client, self, user
91
+ end
92
+ when ACTION_UPDATE_USER_COUNT
93
+ old_count = @current_users
94
+ @current_users = packet.body['u'].to_i
95
+ raise_event :user_count_updated, @client, self, { :old => old_count, :new => @current_users }
96
+ end
97
+ end
98
+
99
+ def add_handler(event, &proc)
100
+ @events[event.to_sym] = [] unless @events[event.to_sym]
101
+ @events[event.to_sym] << proc
102
+ end
103
+
104
+ private
105
+ def raise_event(event_name, *params)
106
+ event = @events[event_name.to_sym]
107
+ return unless event
108
+ event.each do |event_handler|
109
+ event_handler.call(*params)
110
+ end
111
+
112
+ # Pass the message upstream to the client as well
113
+ @client.send :raise_event, event_name, self, *params
114
+ end
3
115
  end
@@ -34,10 +34,11 @@ class SmartFox::Socket::Connection
34
34
 
35
35
  def disconnect
36
36
  @disconnecting = true
37
+ @socket.close if @socket
37
38
  while @connected
38
39
  Thread.pass
39
40
  end
40
- @socket.close if @socket
41
+
41
42
  end
42
43
 
43
44
  def send_data(data)
@@ -52,7 +53,7 @@ class SmartFox::Socket::Connection
52
53
 
53
54
  buffer = String.new
54
55
  begin
55
- @socket.read_nonblock(4096, buffer)
56
+ buffer = @socket.readpartial(4096)
56
57
 
57
58
  @buffer += buffer if buffer
58
59
 
@@ -0,0 +1,14 @@
1
+ class SmartFox::User
2
+ attr_accessor :id, :moderator, :name
3
+
4
+ def parse(node)
5
+ @id = node['i'].to_i
6
+ @name = node.find_first('n').first.content
7
+
8
+ self
9
+ end
10
+
11
+ def self.parse(node)
12
+ new.parse(node)
13
+ end
14
+ end
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{smartfox}
8
- s.version = "0.2.1"
8
+ s.version = "0.3.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Richard Penwell"]
@@ -27,18 +27,12 @@ Gem::Specification.new do |s|
27
27
  "lib/smartfox/blue_box.rb",
28
28
  "lib/smartfox/blue_box/connection.rb",
29
29
  "lib/smartfox/client.rb",
30
+ "lib/smartfox/message.rb",
30
31
  "lib/smartfox/packet.rb",
31
32
  "lib/smartfox/room.rb",
32
33
  "lib/smartfox/socket.rb",
33
34
  "lib/smartfox/socket/connection.rb",
34
- "nbproject/configs/Spec.properties",
35
- "nbproject/private/config.properties",
36
- "nbproject/private/configs/Spec.properties",
37
- "nbproject/private/private.properties",
38
- "nbproject/private/private.xml",
39
- "nbproject/private/rake-d.txt",
40
- "nbproject/project.properties",
41
- "nbproject/project.xml",
35
+ "lib/smartfox/user.rb",
42
36
  "smartfox.gemspec",
43
37
  "spec/packet_spec.rb",
44
38
  "spec/smartfox_spec.rb",
@@ -66,15 +60,18 @@ Gem::Specification.new do |s|
66
60
  s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
67
61
  s.add_runtime_dependency(%q<json>, [">= 0"])
68
62
  s.add_runtime_dependency(%q<builder>, [">= 0"])
63
+ s.add_runtime_dependency(%q<libxml-ruby>, [">= 0"])
69
64
  else
70
65
  s.add_dependency(%q<rspec>, [">= 1.2.9"])
71
66
  s.add_dependency(%q<json>, [">= 0"])
72
67
  s.add_dependency(%q<builder>, [">= 0"])
68
+ s.add_dependency(%q<libxml-ruby>, [">= 0"])
73
69
  end
74
70
  else
75
71
  s.add_dependency(%q<rspec>, [">= 1.2.9"])
76
72
  s.add_dependency(%q<json>, [">= 0"])
77
73
  s.add_dependency(%q<builder>, [">= 0"])
74
+ s.add_dependency(%q<libxml-ruby>, [">= 0"])
78
75
  end
79
76
  end
80
77
 
@@ -80,8 +80,68 @@ describe SmartFox::Client do
80
80
  @connection.refresh_rooms
81
81
  updated_waiter.wait
82
82
 
83
- room_list.should_not be_blank
83
+ room_list.should_not be_empty
84
+ end
85
+
86
+ it "should join a room with auto join" do
87
+ login_to_connection
88
+ room_joined_waiter = Waiter.new
89
+ @connection.add_handler(:room_joined) { |client, room| room_joined_waiter.fire }
90
+ @connection.add_handler(:rooms_updated) { |client, rooms| @connection.auto_join }
91
+ @connection.refresh_rooms
92
+ room_joined_waiter.wait
93
+
94
+ @connection.current_room.current_users.should == 1
95
+ end
96
+
97
+ it "should see it's own messages sent from the room" do
98
+ login_to_connection
99
+ message_sent_waiter = Waiter.new
100
+ @connection.add_handler(:room_joined) do |client, room|
101
+ room.add_handler(:message_sent) { message_sent_waiter.fire }
102
+ room.send_message "Hello world"
103
+ end
104
+ @connection.add_handler(:rooms_updated) { |client, rooms| @connection.auto_join }
105
+ @connection.refresh_rooms
106
+ message_sent_waiter.wait
107
+
108
+ @connection.current_room.transcript.count.should == 1
109
+ end
110
+
111
+ it "should see it's own messages received from the room" do
112
+ login_to_connection
113
+ message_received_waiter = Waiter.new
114
+ @connection.add_handler(:room_joined) do |client, room|
115
+ room.add_handler(:message_received_self) { message_received_waiter.fire }
116
+ room.send_message "Hello world"
117
+ end
118
+ @connection.add_handler(:rooms_updated) { |client, rooms| @connection.auto_join }
119
+ @connection.refresh_rooms
120
+ message_received_waiter.wait
121
+
122
+ @connection.current_room.transcript.count.should == 1
123
+ end
124
+
125
+ it "should see others messages in the room, and play nicely if there are two instances" do
126
+ login_to_connection
127
+ message_received_waiter = Waiter.new
128
+ @connection.add_handler(:room_joined) do |client, room|
129
+ room.add_handler(:message_received_other) { message_received_waiter.fire }
130
+ end
131
+ @connection.add_handler(:rooms_updated) { |client, rooms| @connection.auto_join }
132
+ @connection.refresh_rooms
133
+
134
+ second_connection = SmartFox::Client.new()
135
+ second_connection.add_handler(:room_joined) do |client, room|
136
+ room.send_message "From two"
137
+ end
138
+ second_connection.add_handler(:connected) { |client| client.login('simpleChat', 'other') }
139
+ second_connection.add_handler(:logged_in) { |client| client.refresh_rooms }
140
+ second_connection.add_handler(:rooms_updated) {|client, rooms| client.auto_join }
141
+ second_connection.connect
142
+
143
+ message_received_waiter.wait
144
+
145
+ @connection.current_room.transcript.count.should == 1
84
146
  end
85
-
86
- it "should fall back on BlueBox if needed"
87
147
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smartfox
3
3
  version: !ruby/object:Gem::Version
4
- hash: 21
4
+ hash: 17
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 2
8
+ - 3
9
9
  - 1
10
- version: 0.2.1
10
+ version: 0.3.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - Richard Penwell
@@ -62,6 +62,20 @@ dependencies:
62
62
  version: "0"
63
63
  type: :runtime
64
64
  version_requirements: *id003
65
+ - !ruby/object:Gem::Dependency
66
+ name: libxml-ruby
67
+ prerelease: false
68
+ requirement: &id004 !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ hash: 3
74
+ segments:
75
+ - 0
76
+ version: "0"
77
+ type: :runtime
78
+ version_requirements: *id004
65
79
  description: Provides a client library for the SmartFox realtime communication server, including BlueBox extensions.
66
80
  email: self@richardpenwell.me
67
81
  executables: []
@@ -82,18 +96,12 @@ files:
82
96
  - lib/smartfox/blue_box.rb
83
97
  - lib/smartfox/blue_box/connection.rb
84
98
  - lib/smartfox/client.rb
99
+ - lib/smartfox/message.rb
85
100
  - lib/smartfox/packet.rb
86
101
  - lib/smartfox/room.rb
87
102
  - lib/smartfox/socket.rb
88
103
  - lib/smartfox/socket/connection.rb
89
- - nbproject/configs/Spec.properties
90
- - nbproject/private/config.properties
91
- - nbproject/private/configs/Spec.properties
92
- - nbproject/private/private.properties
93
- - nbproject/private/private.xml
94
- - nbproject/private/rake-d.txt
95
- - nbproject/project.properties
96
- - nbproject/project.xml
104
+ - lib/smartfox/user.rb
97
105
  - smartfox.gemspec
98
106
  - spec/packet_spec.rb
99
107
  - spec/smartfox_spec.rb
@@ -1 +0,0 @@
1
- main.file=spec/spec_helper.rb
@@ -1 +0,0 @@
1
- config=Spec
@@ -1 +0,0 @@
1
- work.dir=/Users/penwellr/Development/smartfox
@@ -1,3 +0,0 @@
1
- file.reference.smartfox-lib=/Users/penwellr/Development/smartfox/lib
2
- file.reference.smartfox-spec=/Users/penwellr/Development/smartfox/spec
3
- platform.active=Ruby
@@ -1,4 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project-private xmlns="http://www.netbeans.org/ns/project-private/1">
3
- <editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/1"/>
4
- </project-private>
File without changes
@@ -1,8 +0,0 @@
1
- file.reference.smartfox-lib=lib
2
- file.reference.smartfox-spec=spec
3
- javac.classpath=
4
- main.file=smartfox.rb
5
- platform.active=Ruby
6
- source.encoding=UTF-8
7
- src.dir=${file.reference.smartfox-lib}
8
- test.src.dir=${file.reference.smartfox-spec}
@@ -1,15 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project xmlns="http://www.netbeans.org/ns/project/1">
3
- <type>org.netbeans.modules.ruby.rubyproject</type>
4
- <configuration>
5
- <data xmlns="http://www.netbeans.org/ns/ruby-project/1">
6
- <name>smartfox</name>
7
- <source-roots>
8
- <root id="src.dir"/>
9
- </source-roots>
10
- <test-roots>
11
- <root id="test.src.dir"/>
12
- </test-roots>
13
- </data>
14
- </configuration>
15
- </project>