goshrine_bot 0.1.0

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,283 @@
1
+ module Faye
2
+ class Client
3
+ include EventMachine::Deferrable
4
+ include Timeouts
5
+
6
+ UNCONNECTED = 1
7
+ CONNECTING = 2
8
+ CONNECTED = 3
9
+ DISCONNECTED = 4
10
+
11
+ HANDSHAKE = 'handshake'
12
+ RETRY = 'retry'
13
+ NONE = 'none'
14
+
15
+ CONNECTION_TIMEOUT = 60.0
16
+
17
+ attr_reader :endpoint, :namespace, :cookie
18
+
19
+ def initialize(endpoint = nil, options = {})
20
+ @endpoint = endpoint || RackAdapter::DEFAULT_ENDPOINT
21
+ @options = options
22
+ @timeout = @options[:timeout] || CONNECTION_TIMEOUT
23
+ @cookie = @options[:cookie]
24
+
25
+ @transport = Transport.get(self)
26
+ @state = UNCONNECTED
27
+ @namespace = Namespace.new
28
+ @outbox = []
29
+ @channels = Channel::Tree.new
30
+ @current_id = 0
31
+
32
+ @advice = {'reconnect' => RETRY, 'interval' => Connection::INTERVAL}
33
+ end
34
+
35
+ # Request
36
+ # MUST include: * channel
37
+ # * version
38
+ # * supportedConnectionTypes
39
+ # MAY include: * minimumVersion
40
+ # * ext
41
+ # * id
42
+ #
43
+ # Success Response Failed Response
44
+ # MUST include: * channel MUST include: * channel
45
+ # * version * successful
46
+ # * supportedConnectionTypes * error
47
+ # * clientId MAY include: * supportedConnectionTypes
48
+ # * successful * advice
49
+ # MAY include: * minimumVersion * version
50
+ # * advice * minimumVersion
51
+ # * ext * ext
52
+ # * id * id
53
+ # * authSuccessful
54
+ def handshake(&block)
55
+ return if @advice['reconnect'] == NONE
56
+ return if @state != UNCONNECTED
57
+
58
+ @state = CONNECTING
59
+ @transport.send({
60
+ 'channel' => Channel::HANDSHAKE,
61
+ 'version' => BAYEUX_VERSION,
62
+ 'supportedConnectionTypes' => Transport.supported_connection_types
63
+
64
+ }) do |response|
65
+ unless response['successful']
66
+ EventMachine.add_timer(@advice['interval'] / 1000.0) { handshake(&block) }
67
+ return @state = UNCONNECTED
68
+ end
69
+
70
+ @state = CONNECTED
71
+ @client_id = response['clientId']
72
+ @transport = Transport.get(self, response['supportedConnectionTypes'])
73
+
74
+ block.call if block_given?
75
+ end
76
+ end
77
+
78
+ # Request Response
79
+ # MUST include: * channel MUST include: * channel
80
+ # * clientId * successful
81
+ # * connectionType * clientId
82
+ # MAY include: * ext MAY include: * error
83
+ # * id * advice
84
+ # * ext
85
+ # * id
86
+ # * timestamp
87
+ def connect(&block)
88
+
89
+ return if @advice['reconnect'] == NONE or
90
+ @state == DISCONNECTED
91
+
92
+ if @advice['reconnect'] == HANDSHAKE or @state == UNCONNECTED
93
+ begin_reconnect_timeout
94
+ return handshake { connect(&block) }
95
+ end
96
+
97
+ return callback(&block) if @state == CONNECTING
98
+ return unless @state == CONNECTED
99
+
100
+ set_deferred_status(:succeeded)
101
+ set_deferred_status(:deferred)
102
+ block.call if block_given?
103
+
104
+ return unless @connection_id.nil?
105
+
106
+ @connection_id = @namespace.generate
107
+ @transport.send({
108
+ 'channel' => Channel::CONNECT,
109
+ 'clientId' => @client_id,
110
+ 'connectionType' => @transport.connection_type,
111
+ 'id' => @connection_id
112
+
113
+ }, &verify_client_id { |response|
114
+ @namespace.release(@connection_id) if @connection_id
115
+ @connection_id = nil
116
+ remove_timeout(:reconnect)
117
+ EventMachine.add_timer(@advice['interval'] / 1000.0) { connect }
118
+ })
119
+
120
+ begin_reconnect_timeout
121
+ end
122
+
123
+ # Request Response
124
+ # MUST include: * channel MUST include: * channel
125
+ # * clientId * successful
126
+ # MAY include: * ext * clientId
127
+ # * id MAY include: * error
128
+ # * ext
129
+ # * id
130
+ def disconnect
131
+ return unless @state == CONNECTED
132
+ @state = DISCONNECTED
133
+
134
+ @transport.send({
135
+ 'channel' => Channel::DISCONNECT,
136
+ 'clientId' => @client_id
137
+ })
138
+
139
+ @channels = Channel::Tree.new
140
+ remove_timeout(:reconnect)
141
+ end
142
+
143
+ # Request Response
144
+ # MUST include: * channel MUST include: * channel
145
+ # * clientId * successful
146
+ # * subscription * clientId
147
+ # MAY include: * ext * subscription
148
+ # * id MAY include: * error
149
+ # * advice
150
+ # * ext
151
+ # * id
152
+ # * timestamp
153
+ def subscribe(channels, &block)
154
+ connect {
155
+ channels = [channels].flatten
156
+ validate_channels(channels)
157
+
158
+ @transport.send({
159
+ 'channel' => Channel::SUBSCRIBE,
160
+ 'clientId' => @client_id,
161
+ 'subscription' => channels
162
+
163
+ }, &verify_client_id { |response|
164
+ if response['successful'] and block
165
+ channels = [response['subscription']].flatten
166
+ channels.each { |channel| @channels[channel] = block }
167
+ end
168
+ })
169
+ }
170
+ end
171
+
172
+ # Request Response
173
+ # MUST include: * channel MUST include: * channel
174
+ # * clientId * successful
175
+ # * subscription * clientId
176
+ # MAY include: * ext * subscription
177
+ # * id MAY include: * error
178
+ # * advice
179
+ # * ext
180
+ # * id
181
+ # * timestamp
182
+ def unsubscribe(channels, &block)
183
+ connect {
184
+ channels = [channels].flatten
185
+ validate_channels(channels)
186
+
187
+ @transport.send({
188
+ 'channel' => Channel::UNSUBSCRIBE,
189
+ 'clientId' => @client_id,
190
+ 'subscription' => channels
191
+
192
+ }, &verify_client_id { |response|
193
+ if response['successful']
194
+ channels = [response['subscription']].flatten
195
+ channels.each { |channel| @channels[channel] = nil }
196
+ end
197
+ })
198
+ }
199
+ end
200
+
201
+ # Request Response
202
+ # MUST include: * channel MUST include: * channel
203
+ # * data * successful
204
+ # MAY include: * clientId MAY include: * id
205
+ # * id * error
206
+ # * ext * ext
207
+ def publish(channel, data, id=nil)
208
+ connect {
209
+ validate_channels([channel])
210
+
211
+ enqueue({
212
+ 'id' => (id || next_id),
213
+ 'channel' => channel,
214
+ 'data' => data,
215
+ 'clientId' => @client_id
216
+ })
217
+
218
+ add_timeout(:publish, Connection::MAX_DELAY) { flush! }
219
+ }
220
+ end
221
+
222
+ def handle_error(msg_id, error)
223
+ puts "Message #{msg_id} Error #{error}"
224
+ end
225
+
226
+ def handle_advice(advice)
227
+ @advice.update(advice)
228
+ @client_id = nil if @advice['reconnect'] == HANDSHAKE
229
+ end
230
+
231
+ def deliver_messages(messages)
232
+ messages.each do |message|
233
+ channels = @channels.glob(message['channel'])
234
+ channels.each { |callback| callback.call(message['data']) }
235
+ end
236
+ end
237
+
238
+ private
239
+
240
+ def begin_reconnect_timeout
241
+ add_timeout(:reconnect, @timeout) do
242
+ @namespace.release(@connection_id) if @connection_id
243
+ @connection_id = nil
244
+ @client_id = nil
245
+ @state = UNCONNECTED
246
+ subscribe(@channels.keys)
247
+ end
248
+ end
249
+
250
+ def enqueue(message)
251
+ @outbox << message
252
+ end
253
+
254
+ def flush!
255
+ @transport.send(@outbox)
256
+ @outbox = []
257
+ end
258
+
259
+ def next_id
260
+ @current_id += 1
261
+ end
262
+
263
+ def validate_channels(channels)
264
+ channels.each do |channel|
265
+ raise "'#{ channel }' is not a valid channel name" unless Channel.valid?(channel)
266
+ raise "Clients may not subscribe to channel '#{ channel }'" unless Channel.subscribable?(channel)
267
+ end
268
+ end
269
+
270
+ def verify_client_id(&block)
271
+ lambda do |response|
272
+ if response['clientId'] != @client_id
273
+ false
274
+ else
275
+ block.call(response)
276
+ true
277
+ end
278
+ end
279
+ end
280
+
281
+ end
282
+ end
283
+
@@ -0,0 +1,122 @@
1
+ module Faye
2
+ class Connection
3
+ include EventMachine::Deferrable
4
+ include Observable
5
+ include Timeouts
6
+
7
+ MAX_DELAY = 0.1
8
+ INTERVAL = 0.4
9
+ TIMEOUT = 60.0
10
+
11
+ attr_reader :id
12
+
13
+ def initialize(id, options = {})
14
+ @id = id
15
+ @options = options
16
+ @timeout = @options[:timeout] || TIMEOUT
17
+ @channels = Set.new
18
+ @inbox = Set.new
19
+ @connected = false
20
+ @connection_listener = options[:connection_listener]
21
+
22
+ # This is for clients that subscribe, but never re-connect to get messages
23
+ # (prevents a memory leak)
24
+ add_timeout(:deletion, 60 * INTERVAL) do
25
+ puts "Deleting orphaned connection: #{@id}"
26
+ delete
27
+ end
28
+
29
+ end
30
+
31
+ def update(message, event)
32
+ return unless message == :message
33
+ @inbox.add(event)
34
+ begin_delivery_timeout!
35
+ end
36
+
37
+ def subscribe(channel)
38
+ if @channels.add?(channel)
39
+ channel.add_observer(self)
40
+ if @options[:subscription_listener]
41
+ @options[:subscription_listener].subscribed(self, channel)
42
+ end
43
+ end
44
+ end
45
+
46
+ def unsubscribe(channel)
47
+ return @channels.each(&method(:unsubscribe)) if channel == :all
48
+ return unless @channels.member?(channel)
49
+ @channels.delete(channel)
50
+ channel.delete_observer(self)
51
+ if @options[:subscription_listener]
52
+ @options[:subscription_listener].unsubscribed(self, channel)
53
+ end
54
+ end
55
+
56
+ def connect(transport_authentication, &block)
57
+ callback(&block)
58
+ return if @connected
59
+
60
+ @connected = true
61
+
62
+ if @connection_listener
63
+ @connection_listener.connected(id, transport_authentication)
64
+ end
65
+
66
+ remove_timeout(:deletion)
67
+
68
+ begin_delivery_timeout!
69
+ begin_connection_timeout!
70
+ end
71
+
72
+ def flush!
73
+ return unless @connected
74
+ release_connection!
75
+
76
+ events = @inbox.entries
77
+ @inbox = Set.new
78
+
79
+ set_deferred_status(:succeeded, events)
80
+ set_deferred_status(:deferred)
81
+ end
82
+
83
+ def disconnect!
84
+ unsubscribe(:all)
85
+ flush!
86
+ end
87
+
88
+ private
89
+
90
+ def begin_delivery_timeout!
91
+ if @delivery_timeout.nil? && @connected && (!@inbox.empty? || @channels.empty?)
92
+ add_timeout(:delivery, MAX_DELAY) { flush! }
93
+ end
94
+ end
95
+
96
+ def begin_connection_timeout!
97
+ return unless @connection_timeout.nil? and @connected
98
+ add_timeout(:connection, @timeout) { flush! }
99
+ end
100
+
101
+ def delete
102
+ unsubscribe(:all)
103
+ if @connection_listener
104
+ @connection_listener.disconnected(id)
105
+ end
106
+ changed(true)
107
+ notify_observers(:stale_client, self)
108
+ end
109
+
110
+ def release_connection!
111
+ remove_timeout(:connection)
112
+ remove_timeout(:delivery)
113
+ @connected = false
114
+
115
+ add_timeout(:deletion, 10 * INTERVAL) do
116
+ delete
117
+ end
118
+ end
119
+
120
+ end
121
+ end
122
+
@@ -0,0 +1,44 @@
1
+ module Faye
2
+ class Error
3
+
4
+ def self.parse(string)
5
+ return nil unless Grammar::ERROR =~ string
6
+ parts = string.split(':')
7
+ args = parts[1].split(',')
8
+ new(parts[0].to_i, args, parts[2])
9
+ end
10
+
11
+ def self.method_missing(type, *args)
12
+ code = const_get(type.to_s.upcase)
13
+ new(code[0], args, code[1]).to_s
14
+ end
15
+
16
+ attr_reader :code, :args, :message
17
+
18
+ def initialize(code, args, message)
19
+ @code = code
20
+ @args = args
21
+ @message = message
22
+ end
23
+
24
+ def to_s
25
+ "#{ @code }:#{ @args * ',' }:#{ @message }"
26
+ end
27
+
28
+ # http://code.google.com/p/cometd/wiki/BayeuxCodes
29
+ VERSION_MISMATCH = [300, 'Version mismatch']
30
+ CONNTYPE_MISMATCH = [301, 'Connection types not supported']
31
+ EXT_MISMATCH = [302, 'Extension mismatch']
32
+ BAD_REQUEST = [400, 'Bad request']
33
+ CLIENT_UNKNOWN = [401, 'Unknown client']
34
+ PARAMETER_MISSING = [402, 'Missing required parameter']
35
+ CHANNEL_FORBIDDEN = [403, 'Forbidden channel']
36
+ CHANNEL_UNKNOWN = [404, 'Unknown channel']
37
+ CHANNEL_INVALID = [405, 'Invalid channel']
38
+ EXT_UNKNOWN = [406, 'Unknown extension']
39
+ PUBLISH_FAILED = [407, 'Failed to publish']
40
+ SERVER_ERROR = [500, 'Internal server error']
41
+
42
+ end
43
+ end
44
+
@@ -0,0 +1,58 @@
1
+ module Faye
2
+ module Grammar
3
+
4
+ def self.rule(&block)
5
+ source = instance_eval(&block)
6
+ %r{^#{string(source)}$}
7
+ end
8
+
9
+ def self.choice(*list)
10
+ '(' + list.map(&method(:string)) * '|' + ')'
11
+ end
12
+
13
+ def self.repeat(*pattern)
14
+ '(' + string(pattern) + ')*'
15
+ end
16
+
17
+ def self.oneormore(*pattern)
18
+ '(' + string(pattern) + ')+'
19
+ end
20
+
21
+ def self.string(item)
22
+ return item.map(&method(:string)) * '' if Array === item
23
+ String === item ? item : item.source.gsub(/^\^/, '').gsub(/\$$/, '')
24
+ end
25
+
26
+ LOWALPHA = rule {[ '[a-z]' ]}
27
+ UPALPHA = rule {[ '[A-Z]' ]}
28
+ ALPHA = rule {[ choice(LOWALPHA, UPALPHA) ]}
29
+ DIGIT = rule {[ '[0-9]' ]}
30
+ ALPHANUM = rule {[ choice(ALPHA, DIGIT) ]}
31
+ MARK = rule {[ choice(*%w[\\- \\_ \\! \\~ \\( \\) \\$ \\@]) ]}
32
+ STRING = rule {[ repeat(choice(ALPHANUM, MARK, ' ', '\\/', '\\*', '\\.')) ]}
33
+ TOKEN = rule {[ oneormore(choice(ALPHANUM, MARK)) ]}
34
+ INTEGER = rule {[ oneormore(DIGIT) ]}
35
+
36
+ CHANNEL_SEGMENT = rule {[ TOKEN ]}
37
+ CHANNEL_SEGMENTS = rule {[ CHANNEL_SEGMENT, repeat('\\/', CHANNEL_SEGMENT) ]}
38
+ CHANNEL_NAME = rule {[ '\\/', CHANNEL_SEGMENTS ]}
39
+
40
+ WILD_CARD = rule {[ '\\*{1,2}' ]}
41
+ CHANNEL_PATTERN = rule {[ repeat('\\/', CHANNEL_SEGMENT), '\\/', WILD_CARD ]}
42
+
43
+ VERSION_ELEMENT = rule {[ ALPHANUM, repeat(choice(ALPHANUM, '\\-', '\\_')) ]}
44
+ VERSION = rule {[ INTEGER, repeat('\\.', VERSION_ELEMENT) ]}
45
+
46
+ CLIENT_ID = rule {[ oneormore(ALPHANUM) ]}
47
+
48
+ ID = rule {[ oneormore(ALPHANUM) ]}
49
+
50
+ ERROR_MESSAGE = rule {[ STRING ]}
51
+ ERROR_ARGS = rule {[ STRING, repeat(',', STRING) ]}
52
+ ERROR_CODE = rule {[ DIGIT, DIGIT, DIGIT ]}
53
+ ERROR = rule {[ choice(string([ERROR_CODE, ':', ERROR_ARGS, ':', ERROR_MESSAGE]),
54
+ string([ERROR_CODE, ':', ':', ERROR_MESSAGE])) ]}
55
+
56
+ end
57
+ end
58
+
@@ -0,0 +1,20 @@
1
+ module Faye
2
+ class Namespace
3
+
4
+ def initialize
5
+ @used = {}
6
+ end
7
+
8
+ def generate
9
+ name = Faye.random
10
+ name = Faye.random while @used.has_key?(name)
11
+ @used[name] = name
12
+ end
13
+
14
+ def release(name)
15
+ @used.delete(name)
16
+ end
17
+
18
+ end
19
+ end
20
+
@@ -0,0 +1,115 @@
1
+ require 'rubygems'
2
+ require 'rack'
3
+ require 'thin'
4
+ require 'json'
5
+
6
+ module Faye
7
+ class AlwaysEmptyHash
8
+ def initialize
9
+ @h = {}
10
+ end
11
+
12
+ def method_missing(m, *args)
13
+ @h.send(m, *args)
14
+ end
15
+
16
+ def []=(key,value)
17
+ end
18
+ end
19
+
20
+ class RackAdapter
21
+
22
+ # Only supported under Thin
23
+ ASYNC_RESPONSE = [-1, AlwaysEmptyHash.new, []].freeze
24
+
25
+ DEFAULT_ENDPOINT = '/bayeux'
26
+ SCRIPT_PATH = File.join(ROOT, 'faye-client-min.js')
27
+
28
+ TYPE_JSON = {'Content-Type' => 'application/json'}
29
+ TYPE_SCRIPT = {'Content-Type' => 'text/javascript'}
30
+ TYPE_TEXT = {'Content-Type' => 'text/plain'}
31
+
32
+ def initialize(app = nil, options = nil)
33
+ @app = app if app.respond_to?(:call)
34
+ @options = [app, options].grep(Hash).first || {}
35
+
36
+ @endpoint = @options[:mount] || DEFAULT_ENDPOINT
37
+ @endpoint_re = Regexp.new('^' + @endpoint + '(/[^/]+)*(\\.js)?$')
38
+ @server = Server.new(@options)
39
+ end
40
+
41
+ def get_client
42
+ @client ||= Client.new(@server)
43
+ end
44
+
45
+ def run(port)
46
+ handler = Rack::Handler.get('thin')
47
+ handler.run(self, :Port => port)
48
+ end
49
+
50
+ def call(env)
51
+ ensure_reactor_running!
52
+ request = Rack::Request.new(env)
53
+
54
+ unless request.path_info =~ @endpoint_re
55
+ env['faye.client'] = get_client
56
+ return @app ? @app.call(env) :
57
+ [404, TYPE_TEXT, ["Sure you're not looking for #{@endpoint} ?"]]
58
+ end
59
+
60
+ if request.path_info =~ /\.js$/
61
+ return [200, TYPE_SCRIPT, File.new(SCRIPT_PATH)]
62
+ end
63
+
64
+ begin
65
+ json_msg = request.post? ? request.body.read : request.params['message']
66
+ message = JSON.parse(json_msg)
67
+ jsonp = request.params['jsonp'] || JSONP_CALLBACK
68
+
69
+ @server.flush_connection(message) if request.get?
70
+
71
+ on_response(env, message) do |replies|
72
+ response = JSON.unparse(replies)
73
+ response = "#{ jsonp }(#{ response });" if request.get?
74
+ response
75
+ end
76
+ rescue Exception => e
77
+ puts "Caught #{e.message}: " + e.backtrace.join("\n")
78
+ [400, TYPE_TEXT, 'Bad request']
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def on_response(env, message, &block)
85
+ request = Rack::Request.new(env)
86
+ type = request.get? ? TYPE_SCRIPT : TYPE_JSON
87
+ callback = env['async.callback']
88
+ transport_authentication = env['faye.transport_authentication']
89
+
90
+ if callback
91
+ body = DeferredBody.new
92
+ callback.call [200, type, body]
93
+ @server.process(message, transport_authentication, false) { |r| body.succeed block.call(r) }
94
+ return ASYNC_RESPONSE
95
+ end
96
+
97
+ response = nil
98
+ @server.process(message, transport_authentication, false) { |r| response = block.call(r) }
99
+ sleep(0.1) while response.nil?
100
+ [200, type, [response]]
101
+ end
102
+
103
+ def ensure_reactor_running!
104
+ Thread.new { EM.run } unless EM.reactor_running?
105
+ while not EM.reactor_running?; end
106
+ end
107
+
108
+ class DeferredBody
109
+ include EventMachine::Deferrable
110
+ alias :each :callback
111
+ end
112
+
113
+ end
114
+ end
115
+