pomelo-citrus 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.
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