faye-huboard 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,121 @@
1
+ module Faye
2
+ module Engine
3
+
4
+ class Memory
5
+ include Timeouts
6
+
7
+ def self.create(server, options)
8
+ new(server, options)
9
+ end
10
+
11
+ def initialize(server, options)
12
+ @server = server
13
+ @options = options
14
+ reset
15
+ end
16
+
17
+ def disconnect
18
+ reset
19
+ remove_all_timeouts
20
+ end
21
+
22
+ def reset
23
+ @namespace = Namespace.new
24
+ @clients = {}
25
+ @channels = {}
26
+ @messages = {}
27
+ end
28
+
29
+ def create_client(&callback)
30
+ client_id = @namespace.generate
31
+ @server.debug('Created new client ?', client_id)
32
+ ping(client_id)
33
+ @server.trigger(:handshake, client_id)
34
+ callback.call(client_id)
35
+ end
36
+
37
+ def destroy_client(client_id, &callback)
38
+ return unless @namespace.exists?(client_id)
39
+
40
+ if @clients.has_key?(client_id)
41
+ @clients[client_id].each { |channel| unsubscribe(client_id, channel) }
42
+ end
43
+
44
+ remove_timeout(client_id)
45
+ @namespace.release(client_id)
46
+ @messages.delete(client_id)
47
+ @server.debug('Destroyed client ?', client_id)
48
+ @server.trigger(:disconnect, client_id)
49
+ @server.trigger(:close, client_id)
50
+ callback.call if callback
51
+ end
52
+
53
+ def client_exists(client_id, &callback)
54
+ callback.call(@namespace.exists?(client_id))
55
+ end
56
+
57
+ def ping(client_id)
58
+ timeout = @server.timeout
59
+ return unless Numeric === timeout
60
+ @server.debug('Ping ?, ?', client_id, timeout)
61
+ remove_timeout(client_id)
62
+ add_timeout(client_id, 2 * timeout) { destroy_client(client_id) }
63
+ end
64
+
65
+ def subscribe(client_id, channel, &callback)
66
+ @clients[client_id] ||= Set.new
67
+ should_trigger = @clients[client_id].add?(channel)
68
+
69
+ @channels[channel] ||= Set.new
70
+ @channels[channel].add(client_id)
71
+
72
+ @server.debug('Subscribed client ? to channel ?', client_id, channel)
73
+ @server.trigger(:subscribe, client_id, channel) if should_trigger
74
+ callback.call(true) if callback
75
+ end
76
+
77
+ def unsubscribe(client_id, channel, &callback)
78
+ if @clients.has_key?(client_id)
79
+ should_trigger = @clients[client_id].delete?(channel)
80
+ @clients.delete(client_id) if @clients[client_id].empty?
81
+ end
82
+
83
+ if @channels.has_key?(channel)
84
+ @channels[channel].delete(client_id)
85
+ @channels.delete(channel) if @channels[channel].empty?
86
+ end
87
+
88
+ @server.debug('Unsubscribed client ? from channel ?', client_id, channel)
89
+ @server.trigger(:unsubscribe, client_id, channel) if should_trigger
90
+ callback.call(true) if callback
91
+ end
92
+
93
+ def publish(message, channels)
94
+ @server.debug('Publishing message ?', message)
95
+
96
+ clients = Set.new
97
+
98
+ channels.each do |channel|
99
+ next unless subs = @channels[channel]
100
+ subs.each(&clients.method(:add))
101
+ end
102
+
103
+ clients.each do |client_id|
104
+ @server.debug('Queueing for client ?: ?', client_id, message)
105
+ @messages[client_id] ||= []
106
+ @messages[client_id] << Faye.copy_object(message)
107
+ empty_queue(client_id)
108
+ end
109
+
110
+ @server.trigger(:publish, message['clientId'], message['channel'], message['data'])
111
+ end
112
+
113
+ def empty_queue(client_id)
114
+ return unless @server.has_connection?(client_id)
115
+ @server.deliver(client_id, @messages[client_id])
116
+ @messages.delete(client_id)
117
+ end
118
+ end
119
+
120
+ end
121
+ end
@@ -0,0 +1,126 @@
1
+ module Faye
2
+ module Engine
3
+
4
+ METHODS = %w[create_client client_exists destroy_client ping subscribe unsubscribe]
5
+ MAX_DELAY = 0.0
6
+ INTERVAL = 0.0
7
+ TIMEOUT = 60.0
8
+ ID_LENGTH = 160
9
+
10
+ autoload :Connection, File.expand_path('../connection', __FILE__)
11
+ autoload :Memory, File.expand_path('../memory', __FILE__)
12
+
13
+ def self.ensure_reactor_running!
14
+ Thread.new { EventMachine.run } unless EventMachine.reactor_running?
15
+ Thread.pass until EventMachine.reactor_running?
16
+ end
17
+
18
+ def self.get(options)
19
+ Proxy.new(options)
20
+ end
21
+
22
+ def self.random(bitlength = ID_LENGTH)
23
+ limit = 2 ** bitlength
24
+ max_size = (bitlength * Math.log(2) / Math.log(36)).ceil
25
+ string = SecureRandom.random_number(limit).to_s(36)
26
+ string = '0' + string while string.size < max_size
27
+ string
28
+ end
29
+
30
+ class Proxy
31
+ include Publisher
32
+ include Logging
33
+
34
+ attr_reader :interval, :timeout
35
+
36
+ extend Forwardable
37
+ def_delegators :@engine, *METHODS
38
+
39
+ def initialize(options)
40
+ super()
41
+
42
+ @options = options
43
+ @connections = {}
44
+ @interval = @options[:interval] || INTERVAL
45
+ @timeout = @options[:timeout] || TIMEOUT
46
+
47
+ engine_class = @options[:type] || Memory
48
+ @engine = engine_class.create(self, @options)
49
+
50
+ bind :close do |client_id|
51
+ EventMachine.next_tick { flush_connection(client_id) }
52
+ end
53
+
54
+ debug('Created new engine: ?', @options)
55
+ end
56
+
57
+ def connect(client_id, options = {}, &callback)
58
+ debug('Accepting connection from ?', client_id)
59
+ @engine.ping(client_id)
60
+ conn = connection(client_id, true)
61
+ conn.connect(options, &callback)
62
+ @engine.empty_queue(client_id)
63
+ end
64
+
65
+ def has_connection?(client_id)
66
+ @connections.has_key?(client_id)
67
+ end
68
+
69
+ def connection(client_id, create)
70
+ conn = @connections[client_id]
71
+ return conn if conn or not create
72
+ @connections[client_id] = Connection.new(self, client_id)
73
+ trigger('connection:open', client_id)
74
+ @connections[client_id]
75
+ end
76
+
77
+ def close_connection(client_id)
78
+ debug('Closing connection for ?', client_id)
79
+ return unless conn = @connections[client_id]
80
+ conn.socket.close if conn.socket
81
+ trigger('connection:close', client_id)
82
+ @connections.delete(client_id)
83
+ end
84
+
85
+ def open_socket(client_id, socket)
86
+ conn = connection(client_id, true)
87
+ conn.socket = socket
88
+ end
89
+
90
+ def deliver(client_id, messages)
91
+ return if !messages || messages.empty?
92
+ return false unless conn = connection(client_id, false)
93
+ messages.each(&conn.method(:deliver))
94
+ true
95
+ end
96
+
97
+ def generate_id
98
+ Engine.random
99
+ end
100
+
101
+ def flush_connection(client_id, close = true)
102
+ return unless client_id
103
+ debug('Flushing connection for ?', client_id)
104
+ return unless conn = connection(client_id, false)
105
+ conn.socket = nil unless close
106
+ conn.flush
107
+ close_connection(client_id)
108
+ end
109
+
110
+ def close
111
+ @connections.keys.each { |client_id| flush_connection(client_id) }
112
+ @engine.disconnect
113
+ end
114
+
115
+ def disconnect
116
+ @engine.disconnect if @engine.respond_to?(:disconnect)
117
+ end
118
+
119
+ def publish(message)
120
+ channels = Channel.expand(message['channel'])
121
+ @engine.publish(message, channels)
122
+ end
123
+ end
124
+
125
+ end
126
+ end
@@ -0,0 +1,48 @@
1
+ module Faye
2
+ class Error
3
+
4
+ def self.method_missing(type, *args)
5
+ code = const_get(type.to_s.upcase)
6
+ new(code[0], args, code[1]).to_s
7
+ end
8
+
9
+ def self.parse(message)
10
+ message ||= ''
11
+ return new(nil, [], message) unless Grammar::ERROR =~ message
12
+
13
+ parts = message.split(':')
14
+ code = parts[0].to_i
15
+ params = parts[1].split(',')
16
+ message = parts[2]
17
+
18
+ new(code, params, message)
19
+ end
20
+
21
+ attr_reader :code, :params, :message
22
+
23
+ def initialize(code, params, message)
24
+ @code = code
25
+ @params = params
26
+ @message = message
27
+ end
28
+
29
+ def to_s
30
+ "#{ @code }:#{ @params * ',' }:#{ @message }"
31
+ end
32
+
33
+ # http://code.google.com/p/cometd/wiki/BayeuxCodes
34
+ VERSION_MISMATCH = [300, 'Version mismatch']
35
+ CONNTYPE_MISMATCH = [301, 'Connection types not supported']
36
+ EXT_MISMATCH = [302, 'Extension mismatch']
37
+ BAD_REQUEST = [400, 'Bad request']
38
+ CLIENT_UNKNOWN = [401, 'Unknown client']
39
+ PARAMETER_MISSING = [402, 'Missing required parameter']
40
+ CHANNEL_FORBIDDEN = [403, 'Forbidden channel']
41
+ CHANNEL_UNKNOWN = [404, 'Unknown channel']
42
+ CHANNEL_INVALID = [405, 'Invalid channel']
43
+ EXT_UNKNOWN = [406, 'Unknown extension']
44
+ PUBLISH_FAILED = [407, 'Failed to publish']
45
+ SERVER_ERROR = [500, 'Internal server error']
46
+
47
+ end
48
+ end
@@ -0,0 +1,14 @@
1
+ module Faye
2
+ module Deferrable
3
+
4
+ include EventMachine::Deferrable
5
+
6
+ def set_deferred_status(status, *args)
7
+ if status == :unknown
8
+ @deferred_status = @deferred_args = @callbacks = @errbacks = nil
9
+ end
10
+ super
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,35 @@
1
+ module Faye
2
+ module Logging
3
+
4
+ LOG_LEVELS = {
5
+ :fatal => 4,
6
+ :error => 3,
7
+ :warn => 2,
8
+ :info => 1,
9
+ :debug => 0
10
+ }
11
+
12
+ LOG_LEVELS.each do |level, value|
13
+ define_method(level) { |*args| write_log(args, level) }
14
+ end
15
+
16
+ private
17
+
18
+ def write_log(message_args, level)
19
+ return unless Faye.logger
20
+
21
+ message = message_args.shift.gsub(/\?/) do
22
+ Faye.to_json(message_args.shift)
23
+ end
24
+
25
+ banner = "[#{ self.class.name }] "
26
+
27
+ if Faye.logger.respond_to?(level)
28
+ Faye.logger.__send__(level, banner + message)
29
+ elsif Faye.logger.respond_to?(:call)
30
+ Faye.logger.call(banner + message)
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,18 @@
1
+ module Faye
2
+ module Publisher
3
+
4
+ include ::WebSocket::Driver::EventEmitter
5
+
6
+ alias :bind :add_listener
7
+ alias :trigger :emit
8
+
9
+ def unbind(event, &listener)
10
+ if listener
11
+ remove_listener(event, &listener)
12
+ else
13
+ remove_all_listeners(event)
14
+ end
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,26 @@
1
+ module Faye
2
+ module Timeouts
3
+ def add_timeout(name, delay, &block)
4
+ Engine.ensure_reactor_running!
5
+ @timeouts ||= {}
6
+ return if @timeouts.has_key?(name)
7
+ @timeouts[name] = EventMachine.add_timer(delay) do
8
+ @timeouts.delete(name)
9
+ block.call
10
+ end
11
+ end
12
+
13
+ def remove_timeout(name)
14
+ @timeouts ||= {}
15
+ timeout = @timeouts[name]
16
+ return if timeout.nil?
17
+ EventMachine.cancel_timer(timeout)
18
+ @timeouts.delete(name)
19
+ end
20
+
21
+ def remove_all_timeouts
22
+ @timeouts ||= {}
23
+ @timeouts.keys.each { |name| remove_timeout(name) }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,123 @@
1
+ module Faye
2
+ class Channel
3
+
4
+ include Publisher
5
+ attr_reader :name
6
+
7
+ def initialize(name)
8
+ super()
9
+ @name = name
10
+ end
11
+
12
+ def <<(message)
13
+ trigger(:message, message)
14
+ end
15
+
16
+ def unused?
17
+ listener_count(:message).zero?
18
+ end
19
+
20
+ HANDSHAKE = '/meta/handshake'
21
+ CONNECT = '/meta/connect'
22
+ SUBSCRIBE = '/meta/subscribe'
23
+ UNSUBSCRIBE = '/meta/unsubscribe'
24
+ DISCONNECT = '/meta/disconnect'
25
+
26
+ META = 'meta'
27
+ SERVICE = 'service'
28
+
29
+ class << self
30
+ def expand(name)
31
+ segments = parse(name)
32
+ channels = ['/**', name]
33
+
34
+ copy = segments.dup
35
+ copy[copy.size - 1] = '*'
36
+ channels << unparse(copy)
37
+
38
+ 1.upto(segments.size - 1) do |i|
39
+ copy = segments[0...i]
40
+ copy << '**'
41
+ channels << unparse(copy)
42
+ end
43
+
44
+ channels
45
+ end
46
+
47
+ def valid?(name)
48
+ Grammar::CHANNEL_NAME =~ name or
49
+ Grammar::CHANNEL_PATTERN =~ name
50
+ end
51
+
52
+ def parse(name)
53
+ return nil unless valid?(name)
54
+ name.split('/')[1..-1]
55
+ end
56
+
57
+ def unparse(segments)
58
+ '/' + segments.join('/')
59
+ end
60
+
61
+ def meta?(name)
62
+ segments = parse(name)
63
+ segments ? (segments.first == META) : nil
64
+ end
65
+
66
+ def service?(name)
67
+ segments = parse(name)
68
+ segments ? (segments.first == SERVICE) : nil
69
+ end
70
+
71
+ def subscribable?(name)
72
+ return nil unless valid?(name)
73
+ not meta?(name) and not service?(name)
74
+ end
75
+ end
76
+
77
+ class Set
78
+ def initialize
79
+ @channels = {}
80
+ end
81
+
82
+ def keys
83
+ @channels.keys
84
+ end
85
+
86
+ def remove(name)
87
+ @channels.delete(name)
88
+ end
89
+
90
+ def has_subscription?(name)
91
+ @channels.has_key?(name)
92
+ end
93
+
94
+ def subscribe(names, callback)
95
+ names.each do |name|
96
+ channel = @channels[name] ||= Channel.new(name)
97
+ channel.bind(:message, &callback) if callback
98
+ end
99
+ end
100
+
101
+ def unsubscribe(name, callback)
102
+ channel = @channels[name]
103
+ return false unless channel
104
+ channel.unbind(:message, &callback)
105
+ if channel.unused?
106
+ remove(name)
107
+ true
108
+ else
109
+ false
110
+ end
111
+ end
112
+
113
+ def distribute_message(message)
114
+ channels = Channel.expand(message['channel'])
115
+ channels.each do |name|
116
+ channel = @channels[name]
117
+ channel.trigger(:message, message['data']) if channel
118
+ end
119
+ end
120
+ end
121
+
122
+ end
123
+ end