pomelo-citrus 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +20 -0
  3. data/Rakefile +0 -0
  4. data/citrus.gemspec +35 -0
  5. data/lib/citrus.rb +18 -0
  6. data/lib/citrus/application.rb +237 -0
  7. data/lib/citrus/citrus.rb +27 -0
  8. data/lib/citrus/common/remote/backend/msg_remote.rb +57 -0
  9. data/lib/citrus/common/remote/frontend/channel_remote.rb +73 -0
  10. data/lib/citrus/common/remote/frontend/session_remote.rb +108 -0
  11. data/lib/citrus/common/service/backend_session_service.rb +265 -0
  12. data/lib/citrus/common/service/channel_service.rb +485 -0
  13. data/lib/citrus/common/service/connection_service.rb +71 -0
  14. data/lib/citrus/common/service/filter_service.rb +92 -0
  15. data/lib/citrus/common/service/handler_service.rb +63 -0
  16. data/lib/citrus/common/service/session_service.rb +446 -0
  17. data/lib/citrus/components/backend_session.rb +32 -0
  18. data/lib/citrus/components/channel.rb +33 -0
  19. data/lib/citrus/components/component.rb +19 -0
  20. data/lib/citrus/components/connection.rb +48 -0
  21. data/lib/citrus/components/connector.rb +265 -0
  22. data/lib/citrus/components/master.rb +40 -0
  23. data/lib/citrus/components/monitor.rb +48 -0
  24. data/lib/citrus/components/proxy.rb +195 -0
  25. data/lib/citrus/components/push_scheduler.rb +74 -0
  26. data/lib/citrus/components/remote.rb +71 -0
  27. data/lib/citrus/components/server.rb +61 -0
  28. data/lib/citrus/components/session.rb +41 -0
  29. data/lib/citrus/connectors/commands/handshake.rb +22 -0
  30. data/lib/citrus/connectors/commands/heartbeat.rb +22 -0
  31. data/lib/citrus/connectors/commands/kick.rb +22 -0
  32. data/lib/citrus/connectors/common/coder.rb +21 -0
  33. data/lib/citrus/connectors/common/handler.rb +21 -0
  34. data/lib/citrus/connectors/ws_connector.rb +110 -0
  35. data/lib/citrus/connectors/ws_socket.rb +75 -0
  36. data/lib/citrus/filters/handler/handler_filter.rb +19 -0
  37. data/lib/citrus/filters/handler/too_busy.rb +16 -0
  38. data/lib/citrus/filters/rpc/rpc_filter.rb +19 -0
  39. data/lib/citrus/filters/rpc/too_busy.rb +16 -0
  40. data/lib/citrus/master/master.rb +60 -0
  41. data/lib/citrus/master/starter.rb +73 -0
  42. data/lib/citrus/master/watchdog.rb +83 -0
  43. data/lib/citrus/modules/console.rb +45 -0
  44. data/lib/citrus/modules/console_module.rb +35 -0
  45. data/lib/citrus/modules/master_watcher.rb +88 -0
  46. data/lib/citrus/modules/monitor_watcher.rb +86 -0
  47. data/lib/citrus/monitor/monitor.rb +61 -0
  48. data/lib/citrus/push_schedulers/buffer.rb +16 -0
  49. data/lib/citrus/push_schedulers/direct.rb +76 -0
  50. data/lib/citrus/server/server.rb +327 -0
  51. data/lib/citrus/util/app_util.rb +203 -0
  52. data/lib/citrus/util/constants.rb +19 -0
  53. data/lib/citrus/util/countdown_latch.rb +42 -0
  54. data/lib/citrus/util/events.rb +14 -0
  55. data/lib/citrus/util/module_util.rb +68 -0
  56. data/lib/citrus/util/path_util.rb +50 -0
  57. data/lib/citrus/util/utils.rb +49 -0
  58. data/lib/citrus/version.rb +7 -0
  59. metadata +241 -0
@@ -0,0 +1,108 @@
1
+ # Author:: MinixLi (gmail: MinixLi1986)
2
+ # Homepage:: http://citrus.inspawn.com
3
+ # Date:: 24 July 2014
4
+
5
+ module Citrus
6
+ # Common
7
+ #
8
+ #
9
+ module Common
10
+ # Remote
11
+ #
12
+ #
13
+ module Remote
14
+ # Frontend
15
+ #
16
+ #
17
+ module Frontend
18
+ # SessionRemote
19
+ #
20
+ #
21
+ class SessionRemote
22
+ # Create a new remote session service
23
+ #
24
+ # @param [Object] app
25
+ def initialize app
26
+ @app = app
27
+ end
28
+
29
+ # Bind the session with a user id
30
+ #
31
+ # @param [Integer] sid
32
+ # @param [String] uid
33
+ def bind sid, uid, &block
34
+ @app.session_service.bind sid, uid, &block
35
+ end
36
+
37
+ # Unbind the session with a user id
38
+ #
39
+ # @param [Integer] sid
40
+ # @param [String] uid
41
+ def unbind sid, uid, &block
42
+ @app.session_service.unbind sid, uid, &block
43
+ end
44
+
45
+ # Push the key/value into session
46
+ #
47
+ # @param [Integer] sid
48
+ # @param [String] key
49
+ # @param [Hash] value
50
+ def push sid, key, value, &block
51
+ @app.session_service.import sid, key, value, &block
52
+ end
53
+
54
+ # Push new value for the existed session
55
+ #
56
+ # @param [Integer] sid
57
+ # @param [Hash] settings
58
+ def pushAll sid, settings, &block
59
+ @app.session_service.import_all sid, settings, &block
60
+ end
61
+
62
+ # Get session information with session id
63
+ #
64
+ # @param [Integer] sid
65
+ def getBackendSessionBySid sid, &block
66
+ session = @app.session_service.get sid
67
+ unless session
68
+ block_given? and yield
69
+ return
70
+ end
71
+ block_given? and yield nil, session.to_frontend_session.export
72
+ end
73
+
74
+ # Get all the session information the specified user id
75
+ #
76
+ # @param [String] uid
77
+ def getBackendSessionByUid uid, &block
78
+ sessions = @app.session_service.get_by_uid uid
79
+ unless session
80
+ block_given? and yield
81
+ return
82
+ end
83
+
84
+ res = []
85
+ sessions.each { |session|
86
+ res << session.to_frontend_session.export
87
+ }
88
+ block_given? and yield nil, res
89
+ end
90
+
91
+ # Kick a session by session id
92
+ #
93
+ # @param [Integer] sid
94
+ def kickBySid sid, &block
95
+ @app.session_service.kick_by_sid sid, &block
96
+ end
97
+
98
+ # Kick sessions by user id
99
+ #
100
+ # @param [String] uid
101
+ def kickByUid uid, &block
102
+ @app.session_service.kick uid, &block
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,265 @@
1
+ # Author:: MinixLi (gmail: MinixLi1986)
2
+ # Homepage:: http://citrus.inspawn.com
3
+ # Date:: 28 July 2014
4
+
5
+ module Citrus
6
+ # Common
7
+ #
8
+ #
9
+ module Common
10
+ # Service
11
+ #
12
+ #
13
+ module Service
14
+ # BackendSessionService
15
+ #
16
+ #
17
+ class BackendSessionService
18
+ #
19
+ #
20
+ #
21
+ EXPORTED_FIELDS = ['id', 'frontend_id', 'uid', 'settings']
22
+
23
+ # Initialize the service
24
+ #
25
+ # @param [Object] app
26
+ def initialize app
27
+ @app = app
28
+ end
29
+
30
+ # Create a new backend session
31
+ #
32
+ # @param [Hash] args
33
+ def create args={}
34
+ if args.empty?
35
+ throw Exception.new 'args should not be empty'
36
+ end
37
+ BackendSession.new args, self
38
+ end
39
+
40
+ # Get backend session by frontend server id and session id
41
+ #
42
+ # @param [String] frontend_id
43
+ # @param [Integer] sid
44
+ def get frontend_id, sid, &block
45
+ namespace = 'sys'
46
+ service = 'sessionRemote'
47
+ method = 'getBackendSessionBySid'
48
+ args = [sid]
49
+ rpc_invoke(frontend_id, namespace, service, method,
50
+ args, &backend_session_cb.bind(nil, block))
51
+ end
52
+
53
+ # Get backend sessions by frontend server id and user id
54
+ #
55
+ # @param [String] frontend_id
56
+ # @param [String] uid
57
+ def get_by_uid frontend_id, uid, &block
58
+ namespace = 'sys'
59
+ service = 'sessionRemote'
60
+ method = 'getBackendSessionByUid'
61
+ args = [uid]
62
+ rpc_invoke(server_id, namespace, service, method,
63
+ args, &backend_session_cb.bind(nil, block))
64
+ end
65
+
66
+ # Kick a session by session id
67
+ #
68
+ # @param [String] frontend_id
69
+ # @param [Integer] sid
70
+ def kick_by_sid frontend_id, sid, &block
71
+ namespace = 'sys'
72
+ service = 'sessionRemote'
73
+ method = 'kickBySid'
74
+ args = [sid]
75
+ rpc_invoke(frontend_id, namespace, service, method, args, &block)
76
+ end
77
+
78
+ # Kick sessions by user id
79
+ #
80
+ # @param [String] frontend_id
81
+ # @param [String] uid
82
+ def kick_by_uid frontend_id, uid, &block
83
+ namespace = 'sys'
84
+ service = 'sessionRemote'
85
+ method = 'kickByUid'
86
+ args = [uid]
87
+ rpc_invoke(frontend_id, namespace, service, method, args, &block)
88
+ end
89
+
90
+ # Bind the session with the specified user id
91
+ #
92
+ # @param [String] frontend_id
93
+ # @param [Integer] sid
94
+ # @param [String] uid
95
+ def bind frontend_id, sid, uid, &block
96
+ namespace = 'sys'
97
+ service = 'sessionRemote'
98
+ method = 'bind'
99
+ args = [sid, uid]
100
+ rpc_invoke(frontend_id, namespace, service, method, args, &block)
101
+ end
102
+
103
+ # Unbind the session with the specified user id
104
+ #
105
+ # @param [String] frontend_id
106
+ # @param [Integer] sid
107
+ # @param [String] uid
108
+ def unbind frontend_id, sid, uid, &block
109
+ namespace = 'sys'
110
+ service = 'sessionRemote'
111
+ method = 'unbind'
112
+ args = [sid, uid]
113
+ rpc_invoke(frontend_id, namespace, service, method, args, &block)
114
+ end
115
+
116
+ # Push the specified customized change to the frontend internal session
117
+ #
118
+ # @param [String] frontend_id
119
+ # @param [Integer] sid
120
+ # @param [String] key
121
+ # @param [Hash] value
122
+ def push frontend_id, sid, key, value, &block
123
+ namespace = 'sys'
124
+ service = 'sessionRemote'
125
+ method = 'push'
126
+ args = [sid, key, value]
127
+ rpc_invoke(frontend_id, namespace, service, method, args, &block)
128
+ end
129
+
130
+ # Push all the customized changes to the frontend internal session
131
+ #
132
+ # @param [String] frontend_id
133
+ # @param [Integer] sid
134
+ # @param [Hash] settings
135
+ def push_all frontend_id, sid, settings, &block
136
+ namespace = 'sys'
137
+ service = 'sessionRemote'
138
+ method = 'pushAll'
139
+ args = [sid, settings]
140
+ rpc_invoke(frontend_id, namespace, service, method, args, &block)
141
+ end
142
+
143
+ private
144
+
145
+ # Backend session callback
146
+ #
147
+ # @param [#call] block
148
+ # @param [Object] err
149
+ # @param [Hash, Array] sinfos
150
+ #
151
+ # @private
152
+ def backend_session_cb block, err, sinfos
153
+ if err
154
+ block.respond_to? :call and block.call err
155
+ return
156
+ end
157
+
158
+ unless sinfos
159
+ block.respond_to? :call and block.call
160
+ return
161
+ end
162
+ sessions = []
163
+ if sinfos.instance_of? Array
164
+ # get_by_uid
165
+ sinfos.each { |sinfo| sessions << create(sinfo) }
166
+ else
167
+ # get
168
+ sessions = create sinfos
169
+ end
170
+ block.respond_to? :call and block.call nil, sessions
171
+ end
172
+
173
+ # Rpc invoke
174
+ #
175
+ # @param [String] frontend_id
176
+ # @param [String] namespace
177
+ # @param [String] service
178
+ # @param [String] method
179
+ # @param [Array] args
180
+ #
181
+ # @private
182
+ def rpc_invoke frontend_id, namespace, service, method, args, &block
183
+ @app.rpc_invoke(frontend_id, {
184
+ :namespace => namespace,
185
+ :service => service,
186
+ :method => method,
187
+ :args => args
188
+ }, &block)
189
+ end
190
+
191
+ # BackendSession
192
+ #
193
+ #
194
+ class BackendSession
195
+ # Create a new backend session
196
+ #
197
+ # @param [Hash] args
198
+ # @param [Object] service
199
+ def initialize args={}, service
200
+ args.each_pair { |key, value|
201
+ instance_eval %Q{ @#{key} = value }
202
+ }
203
+ @session_service = service
204
+ end
205
+
206
+ # Bind current session with the user id
207
+ #
208
+ # @param [String] uid
209
+ def bind uid, &block
210
+ @session_service.bind(@frontend_id, @id, uid) { |err|
211
+ @uid = uid unless err
212
+ block_given? and yield err
213
+ }
214
+ end
215
+
216
+ # Unbind current session with the user id
217
+ #
218
+ # @param [String] uid
219
+ def unbind uid, &block
220
+ @session_service.unbind(@frontend_id, @id, uid) { |err|
221
+ @uid = nil unless err
222
+ block_given? and yield err
223
+ }
224
+ end
225
+
226
+ # Set the key/value into backend session
227
+ #
228
+ # @param [String] key
229
+ # @param [Hash] value
230
+ def set key, value
231
+ @settings[key] = value
232
+ end
233
+
234
+ # Get the value from backend session by key
235
+ #
236
+ # @param [String] key
237
+ def get key
238
+ @settings[key]
239
+ end
240
+
241
+ # Push the key/value in backend session to the front internal session
242
+ #
243
+ # @param [String] key
244
+ def push key, &block
245
+ @session_service.push @frontend_id, @id, key, get(key), &block
246
+ end
247
+
248
+ # Push all the key/values in backend session to the frontend internal session
249
+ def push_all &block
250
+ @session_service.push_all @frontend_id, @id, @settings, &block
251
+ end
252
+
253
+ # Export the key/values for serialization
254
+ def export
255
+ res = {}
256
+ EXPORTED_FIELDS.each { |field|
257
+ instance_eval %Q{ res['#{field}'] = @#{field} }
258
+ }
259
+ res
260
+ end
261
+ end
262
+ end
263
+ end
264
+ end
265
+ end
@@ -0,0 +1,485 @@
1
+ # Author:: MinixLi (gmail: MinixLi1986)
2
+ # Homepage:: http://citrus.inspawn.com
3
+ # Date:: 29 July 2014
4
+
5
+ require 'citrus/common/remote/frontend/channel_remote'
6
+ require 'citrus/util/countdown_latch'
7
+
8
+ module Citrus
9
+ # Common
10
+ #
11
+ #
12
+ module Common
13
+ # Service
14
+ #
15
+ #
16
+ module Service
17
+ # ChannelService
18
+ #
19
+ #
20
+ class ChannelService
21
+ # Util
22
+ #
23
+ #
24
+ module Util
25
+ private
26
+
27
+ # Add uid and server id into group
28
+ #
29
+ # @param [String] uid
30
+ # @param [String] sid
31
+ # @param [Hash] groups
32
+ #
33
+ # @private
34
+ def add uid, sid, groups
35
+ unless sid
36
+ return false
37
+ end
38
+
39
+ group = groups[sid]
40
+ group = []; groups[sid] = group unless group
41
+
42
+ group << uid; true
43
+ end
44
+
45
+ # Delete element from array
46
+ #
47
+ # @param [String] uid
48
+ # @param [String] sid
49
+ # @param [Array] group
50
+ #
51
+ # @private
52
+ def delete_from uid, sid, group
53
+ return true unless group
54
+
55
+ group.each { |e|
56
+ group.delete e; return true if e == uid
57
+ }
58
+ return false
59
+ end
60
+
61
+ # Push message by group
62
+ #
63
+ # @param [Object] service
64
+ # @param [String] route
65
+ # @param [Hash] msg
66
+ # @param [Hash] groups
67
+ # @param [Hash] args
68
+ #
69
+ # @private
70
+ def send_message_by_group service, route, msg, groups, args, &block
71
+ app = service.app
72
+ namespace = 'sys'
73
+ service = 'channelRemote'
74
+ method = 'pushMessage'
75
+ count = groups.length
76
+ success_flag = false
77
+ fail_ids = []
78
+
79
+ block_given? and yield if count == 0
80
+
81
+ latch = Utils::CountDownLatch.new(count) {
82
+ unless success_flag
83
+ block_given? and yield Exception.new 'all uids push message fail'
84
+ return
85
+ end
86
+ block_given? and yield nil, fail_ids
87
+ }
88
+
89
+ rpc_cb = Proc.new { |server_id|
90
+ Proc.new { |err, fails|
91
+ if err
92
+ latch.done
93
+ return
94
+ end
95
+ fail_ids += fails if fails
96
+ success_flag = true
97
+ latch.done
98
+ }
99
+ }
100
+
101
+ args = { :type => 'push', :user_args => args || {} }
102
+
103
+ send_message = Proc.new { |sid|
104
+ if sid == app.server_id
105
+ service.channelRemote.send method, route, msg, groups[sid], args, &rpc_cb.call
106
+ else
107
+ app.rpc_invoke(sid, {
108
+ :namespace => namespace,
109
+ :service => service,
110
+ :method => method,
111
+ :args => [route, msg, groups[sid], args]
112
+ }, &rpc_cb.call(sid))
113
+ end
114
+ }
115
+
116
+ groups.each_with_index { |group, sid|
117
+ if group && group.length > 0
118
+ send_message sid
119
+ else
120
+ EM.next_tick { rpc_cb.call.call }
121
+ end
122
+ }
123
+ end
124
+
125
+ # Restore channel
126
+ #
127
+ # @param [Object] service
128
+ #
129
+ # @private
130
+ def restore_channel service, &block
131
+ if service.store
132
+ block_given? and yield
133
+ return
134
+ end
135
+
136
+ load_all_from_store(service, gen_key(service)) { |err, list|
137
+ if err
138
+ block_given? and yield err
139
+ return
140
+ end
141
+
142
+ unless (list.instance_of? Array) && list.lenth > 0
143
+ block_given? and yield
144
+ return
145
+ end
146
+
147
+ load_p = Proc.new { |key|
148
+ load_all_from_store(service, key) { |err, items|
149
+ items.each { |item|
150
+ sid, uid = item.split ':'
151
+ channel = service.channels[name]
152
+ if add uid, sid, channel.groups
153
+ channel.records[uid] = { :sid => sid, :uid => uid }
154
+ end
155
+ }
156
+ }
157
+ }
158
+
159
+ list.each_index { |index|
160
+ name = list[index][gen_key(service).length+1..-1]
161
+ service.channels[name] = Channel.new name, service
162
+ load_p.call list[index]
163
+ }
164
+ block_given? and yield
165
+ }
166
+ end
167
+
168
+ # Add to store
169
+ #
170
+ # @param [Object] service
171
+ # @param [String] key
172
+ # @param [Hash] value
173
+ #
174
+ # @private
175
+ def add_to_store service, key, value
176
+ if service.store
177
+ service.store.add(key, value) { |err|
178
+ if err
179
+ end
180
+ }
181
+ end
182
+ end
183
+
184
+ # Remove from store
185
+ #
186
+ # @param [Object] service
187
+ # @param [String] key
188
+ # @param [Hash] value
189
+ #
190
+ # @private
191
+ def remove_from_store service, key, value
192
+ if service.store
193
+ service.store.remove(key, value) { |err|
194
+ if err
195
+ end
196
+ }
197
+ end
198
+ end
199
+
200
+ # Load all from store
201
+ #
202
+ # @param [Object] service
203
+ # @param [String] key
204
+ #
205
+ # @private
206
+ def load_all_from_store service, key, &block
207
+ if service.store
208
+ service.store.load(key) { |err, list|
209
+ if err
210
+ block_given? and yield err
211
+ else
212
+ block_given? and yield nil, list
213
+ end
214
+ }
215
+ end
216
+ end
217
+
218
+ # Remove all from store
219
+ #
220
+ # @param [Object] service
221
+ # @param [String] key
222
+ #
223
+ # @private
224
+ def remove_all_from_store service, key
225
+ if service.store
226
+ service.store.remove_all(key) { |err|
227
+ if err
228
+ end
229
+ }
230
+ end
231
+ end
232
+
233
+ # Generate key
234
+ #
235
+ # @param [Object] service
236
+ # @param [String] name
237
+ #
238
+ # @private
239
+ def gen_key service, name=''
240
+ unless name.empty?
241
+ service.prefix + ':' + service.app.server_id + ':' + name
242
+ else
243
+ service.prefix + ':' + service.app.server_id
244
+ end
245
+ end
246
+
247
+ # Generate value
248
+ #
249
+ # @param [String] sid
250
+ # @param [String] uid
251
+ #
252
+ # @private
253
+ def gen_value sid, uid
254
+ sid + ':' + uid
255
+ end
256
+ end
257
+
258
+ include Util
259
+
260
+ attr_reader :app, :channels, :prefix, :store, :channel_remote
261
+
262
+ # Initialize the service
263
+ #
264
+ # @param [Object] app
265
+ # @param [Hash] args
266
+ def initialize app, args={}
267
+ @app = app
268
+ @channels = {}
269
+ @prefix = args[:prefix]
270
+ @store = args[:store]
271
+ @broadcast_filter = args[:broadcast_filter]
272
+ @channel_remote = Remote::Frontend::ChannelRemote.new app
273
+ end
274
+
275
+ # Start the service
276
+ def start &block
277
+ restore_channel self, &block
278
+ end
279
+
280
+ # Create channel with name
281
+ #
282
+ # @param [String] name
283
+ def create_channel name
284
+ return @channels[name] if @channels[name]
285
+
286
+ c = Channel.new name
287
+ add_to_store self, gen_key(self), gen_key(self, name)
288
+ @channels[name] = c
289
+ c
290
+ end
291
+
292
+ # Get channel by name
293
+ #
294
+ # @param [String] name
295
+ # @param [Boolean] create
296
+ def get_channel name, create=false
297
+ channel = @channels[name]
298
+ if !channel && create
299
+ channel = @channels[name] = Channel.new name
300
+ add_to_store self, gen_key(self), gen_key(self, name)
301
+ end
302
+ channel
303
+ end
304
+
305
+ # Destroy channel by name
306
+ #
307
+ # @param [String] name
308
+ def destroy_channel name
309
+ @channels.delete name
310
+ remove_from_store self, gen_key(self), gen_key(self, name)
311
+ remove_all_from_store self, gen_key(self, name)
312
+ end
313
+
314
+ # Push message by uids
315
+ #
316
+ # @param [String] route
317
+ # @param [Hash] msg
318
+ # @param [Array] uids
319
+ # @param [Hash] args
320
+ def push_message_by_uids route, msg, uids, args, &block
321
+ unless uids && uids.length != 0
322
+ block_given? and yield Exception.new 'uids should not be empty'
323
+ return
324
+ end
325
+
326
+ groups = {}
327
+ uids.each { |record| add record[:uid], record[:sid], groups }
328
+
329
+ send_message_by_group self, route, msg, groups, args, &block
330
+ end
331
+
332
+ # Broadcast message to all the connected clients
333
+ #
334
+ # @param [String] server_type
335
+ # @param [String] route
336
+ # @param [Hash] message
337
+ # @param [Hash] args
338
+ def broadcast server_type, route, msg, args, &block
339
+ namespace = 'sys'
340
+ service = 'channelRemote'
341
+ method = 'broadcast'
342
+ servers = @app.get_servers_by_type server_type
343
+
344
+ unless servers && servers.length != 0
345
+ # server list is empty
346
+ block_given? and yield
347
+ end
348
+
349
+ count = servers.length
350
+ success_flag = false
351
+
352
+ latch = Utils::CountDownLatch.new(count) {
353
+ unless success_flag
354
+ block_given? and yield Exception.new 'broadcast failed'
355
+ return
356
+ end
357
+ block_given? and yield nil
358
+ }
359
+
360
+ gen_cb = Proc.new { |server_id|
361
+ Proc.new { |err|
362
+ if err
363
+ latch.done
364
+ return
365
+ end
366
+ success_flag = true
367
+ latch.done
368
+ }
369
+ }
370
+
371
+ send_message = Proc.new { |server_id|
372
+ if server_id == @app.server_id
373
+ @channel_remote.send method, route, msg, args, &gen_cb.call
374
+ else
375
+ @app.rpc_invoke(server_id, {
376
+ :namespace => namespace,
377
+ :service => service,
378
+ :method => method,
379
+ :args => [route, msg, args]
380
+ }, &gen_cb.call(server_id))
381
+ end
382
+ }
383
+
384
+ args = { :type => 'broadcast', :user_args => args || {} }
385
+
386
+ servers.each { |server|
387
+ send_message server[:server_id]
388
+ }
389
+ end
390
+
391
+ # Channel
392
+ #
393
+ #
394
+ class Channel
395
+ include Util
396
+
397
+ attr_reader :groups, :user_amount
398
+
399
+ # Create a new channel
400
+ #
401
+ # @param [String] name
402
+ # @param [Object] service
403
+ def initialize name, service
404
+ @name = name
405
+ @groups = {}
406
+ @records = {}
407
+ @channel_service = service
408
+ @state = :state_inited
409
+ @user_amount = 0
410
+ end
411
+
412
+ # Add user to channel
413
+ #
414
+ # @param [String] uid
415
+ # @param [String] sid
416
+ def add uid, sid
417
+ return false unless @state == :state_inited
418
+
419
+ res = add uid, sid, @groups
420
+ if res
421
+ @records[uid] = { :sid => sid, :uid => uid }
422
+ @user_amount += 1
423
+ end
424
+
425
+ add_to_store @channel_service, gen_key(@channel_service, @name), gen_value(sid, uid)
426
+ res
427
+ end
428
+
429
+ # Remove user from channel
430
+ #
431
+ # @param [String] uid
432
+ # @param [String] sid
433
+ def leave uid, sid
434
+ return unless uid && sid
435
+
436
+ @records.delete uid
437
+ @user_amount -= 1
438
+ @user_amout = 0 if @user_amount < 0
439
+
440
+ remove_from_store @channel_service, gen_key(@channel_service, @name), gen_value(sid, uid)
441
+
442
+ res = delete_from uid, sid, @groups[sid]
443
+ if @groups[sid] && @groups[sid].length == 0
444
+ @groups.delete sid
445
+ end
446
+ res
447
+ end
448
+
449
+ # Get channel members
450
+ def get_members
451
+ res = []
452
+ @groups.each { |group| group.each { |e| res << e } }
453
+ res
454
+ end
455
+
456
+ # Get member info
457
+ #
458
+ # @param [String] uid
459
+ def get_member uid
460
+ @records[uid]
461
+ end
462
+
463
+ # Destroy channel
464
+ def destroy
465
+ @state = :state_destroyed
466
+ @channel_service.destroy_channel @name
467
+ end
468
+
469
+ # Push message to all the members in the channel
470
+ #
471
+ # @param [String] route
472
+ # @param [Hash] msg
473
+ # @param [Hash] args
474
+ def push_message route, msg, args, &block
475
+ unless @state == :state_inited
476
+ block_given? and yield Exception.new 'channel is not running now'
477
+ return
478
+ end
479
+ send_message_by_group @channel_service, route, msg, @groups, args, &block
480
+ end
481
+ end
482
+ end
483
+ end
484
+ end
485
+ end