smartfox 0.2.1 → 0.3.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/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>