faye-huboard 1.0.4

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.
@@ -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