alondra 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. data/.gitignore +9 -0
  2. data/Gemfile +22 -0
  3. data/Gemfile.lock +166 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +177 -0
  6. data/Rakefile +34 -0
  7. data/alondra.gemspec +23 -0
  8. data/app/assets/javascripts/alondra-client.js.coffee.erb +71 -0
  9. data/app/assets/javascripts/moz_websocket.js +5 -0
  10. data/app/assets/javascripts/vendor/jquery.json-2.2.js +178 -0
  11. data/app/assets/javascripts/vendor/json2.js +480 -0
  12. data/app/assets/javascripts/vendor/swfobject.js +4 -0
  13. data/app/assets/javascripts/vendor/web_socket.js +379 -0
  14. data/app/assets/swf/WebSocketMain.swf +0 -0
  15. data/app/helpers/alondra_helper.rb +24 -0
  16. data/lib/alondra/changes_callbacks.rb +52 -0
  17. data/lib/alondra/changes_push.rb +23 -0
  18. data/lib/alondra/channel.rb +84 -0
  19. data/lib/alondra/command.rb +38 -0
  20. data/lib/alondra/command_dispatcher.rb +22 -0
  21. data/lib/alondra/connection.rb +52 -0
  22. data/lib/alondra/event.rb +74 -0
  23. data/lib/alondra/event_listener.rb +86 -0
  24. data/lib/alondra/event_router.rb +27 -0
  25. data/lib/alondra/listener_callback.rb +37 -0
  26. data/lib/alondra/message.rb +35 -0
  27. data/lib/alondra/message_queue.rb +70 -0
  28. data/lib/alondra/message_queue_client.rb +71 -0
  29. data/lib/alondra/push_controller.rb +53 -0
  30. data/lib/alondra/pushing.rb +14 -0
  31. data/lib/alondra/server.rb +44 -0
  32. data/lib/alondra/session_parser.rb +57 -0
  33. data/lib/alondra.rb +80 -0
  34. data/lib/generators/alondra/USAGE +8 -0
  35. data/lib/generators/alondra/alondra_generator.rb +7 -0
  36. data/lib/generators/alondra/templates/alondra +33 -0
  37. data/lib/tasks/alondra_tasks.rake +4 -0
  38. data/script/rails +6 -0
  39. data/test/dummy/Rakefile +7 -0
  40. data/test/dummy/app/assets/javascripts/application.js +9 -0
  41. data/test/dummy/app/assets/stylesheets/application.css +7 -0
  42. data/test/dummy/app/controllers/application_controller.rb +14 -0
  43. data/test/dummy/app/controllers/chats_controller.rb +85 -0
  44. data/test/dummy/app/controllers/messages_controller.rb +12 -0
  45. data/test/dummy/app/controllers/sessions_controller.rb +20 -0
  46. data/test/dummy/app/controllers/users_controller.rb +30 -0
  47. data/test/dummy/app/helpers/application_helper.rb +2 -0
  48. data/test/dummy/app/helpers/chats_helper.rb +6 -0
  49. data/test/dummy/app/helpers/error_messages_helper.rb +23 -0
  50. data/test/dummy/app/helpers/layout_helper.rb +22 -0
  51. data/test/dummy/app/mailers/.gitkeep +0 -0
  52. data/test/dummy/app/models/.gitkeep +0 -0
  53. data/test/dummy/app/models/chat.rb +5 -0
  54. data/test/dummy/app/models/message.rb +4 -0
  55. data/test/dummy/app/models/user.rb +37 -0
  56. data/test/dummy/app/views/chats/_form.html.erb +21 -0
  57. data/test/dummy/app/views/chats/edit.html.erb +6 -0
  58. data/test/dummy/app/views/chats/index.html.erb +23 -0
  59. data/test/dummy/app/views/chats/new.html.erb +5 -0
  60. data/test/dummy/app/views/chats/show.html.erb +49 -0
  61. data/test/dummy/app/views/layouts/application.html.erb +19 -0
  62. data/test/dummy/app/views/sessions/new.html.erb +15 -0
  63. data/test/dummy/app/views/shared/_message.js.erb +1 -0
  64. data/test/dummy/app/views/users/_form.html.erb +20 -0
  65. data/test/dummy/app/views/users/edit.html.erb +3 -0
  66. data/test/dummy/app/views/users/index.html.erb +25 -0
  67. data/test/dummy/app/views/users/new.html.erb +5 -0
  68. data/test/dummy/app/views/users/show.html.erb +15 -0
  69. data/test/dummy/config/application.rb +42 -0
  70. data/test/dummy/config/boot.rb +10 -0
  71. data/test/dummy/config/database.yml +31 -0
  72. data/test/dummy/config/environment.rb +5 -0
  73. data/test/dummy/config/environments/development.rb +27 -0
  74. data/test/dummy/config/environments/production.rb +54 -0
  75. data/test/dummy/config/environments/test.rb +39 -0
  76. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  77. data/test/dummy/config/initializers/inflections.rb +10 -0
  78. data/test/dummy/config/initializers/mime_types.rb +5 -0
  79. data/test/dummy/config/initializers/secret_token.rb +7 -0
  80. data/test/dummy/config/initializers/session_store.rb +9 -0
  81. data/test/dummy/config/initializers/wrap_parameters.rb +12 -0
  82. data/test/dummy/config/locales/en.yml +5 -0
  83. data/test/dummy/config/routes.rb +19 -0
  84. data/test/dummy/config.ru +4 -0
  85. data/test/dummy/db/migrate/20110719090458_create_chats.rb +9 -0
  86. data/test/dummy/db/migrate/20110719090538_create_messages.rb +10 -0
  87. data/test/dummy/db/migrate/20110720193249_create_users.rb +16 -0
  88. data/test/dummy/db/schema.rb +38 -0
  89. data/test/dummy/lib/controller_authentication.rb +48 -0
  90. data/test/dummy/log/.gitkeep +0 -0
  91. data/test/dummy/public/404.html +26 -0
  92. data/test/dummy/public/422.html +26 -0
  93. data/test/dummy/public/500.html +26 -0
  94. data/test/dummy/public/favicon.ico +0 -0
  95. data/test/dummy/public/stylesheets/application.css +75 -0
  96. data/test/dummy/script/rails +6 -0
  97. data/test/integration/push_changes_test.rb +41 -0
  98. data/test/integration/push_messages_test.rb +40 -0
  99. data/test/models/channel_test.rb +49 -0
  100. data/test/models/command_test.rb +29 -0
  101. data/test/models/configuration_test.rb +22 -0
  102. data/test/models/connection_test.rb +18 -0
  103. data/test/models/event_listener_test.rb +217 -0
  104. data/test/models/event_router_test.rb +7 -0
  105. data/test/models/message_queue_client_test.rb +26 -0
  106. data/test/models/message_queue_test.rb +70 -0
  107. data/test/models/pushing_test.rb +34 -0
  108. data/test/performance/message_queue_performance.rb +66 -0
  109. data/test/support/factories.rb +19 -0
  110. data/test/support/integration_helper.rb +18 -0
  111. data/test/support/integration_test.rb +6 -0
  112. data/test/support/mocks/bogus_event.rb +15 -0
  113. data/test/support/mocks/mock_connection.rb +24 -0
  114. data/test/support/mocks/mock_event_router.rb +12 -0
  115. data/test/support/mocks/mock_listener.rb +9 -0
  116. data/test/test_helper.rb +14 -0
  117. metadata +316 -0
@@ -0,0 +1,74 @@
1
+ module Alondra
2
+ class Event
3
+ attr_reader :channel_name
4
+ attr_reader :type
5
+ attr_reader :resource
6
+ attr_reader :resource_type
7
+ attr_reader :connection
8
+
9
+ def initialize(event_hash, from_json = nil, connection = nil)
10
+ @connection = connection
11
+ @type = event_hash[:event].to_sym
12
+ @json_encoded = from_json
13
+
14
+ if Hash === event_hash[:resource]
15
+ @resource = fetch(event_hash[:resource_type], event_hash[:resource])
16
+ else
17
+ @resource = event_hash[:resource]
18
+ end
19
+
20
+ @resource_type = event_hash[:resource_type] || resource.class.name
21
+
22
+ if event_hash[:channel].present?
23
+ @channel_name = event_hash[:channel]
24
+ else
25
+ channel_type = type == :updated ? :member : :collection
26
+ Channel.default_name_for(resource, channel_type)
27
+ end
28
+ end
29
+
30
+ def channel
31
+ @channel ||= Channel[channel_name]
32
+ end
33
+
34
+ def fire!
35
+ if connection
36
+ # We are inside the Alondra Server
37
+ EM.schedule do
38
+ MessageQueue.instance.receive self
39
+ end
40
+ else
41
+ MessageQueueClient.push self
42
+ end
43
+ end
44
+
45
+ def as_json
46
+ {
47
+ :event => type,
48
+ :resource_type => resource_type,
49
+ :resource => resource.as_json,
50
+ :channel => channel_name
51
+ }
52
+ end
53
+
54
+ def to_json
55
+ @json_encoded ||= ActiveSupport::JSON.encode(as_json)
56
+ end
57
+
58
+ private
59
+
60
+ def fetch(resource_type_name, attributes)
61
+ attributes.symbolize_keys!
62
+ resource_class = Kernel.const_get(resource_type_name)
63
+
64
+ return attributes unless resource_class < ActiveRecord::Base
65
+
66
+ resource = resource_class.new
67
+
68
+ filtered_attributes = attributes.delete_if { |k,v| !resource.has_attribute?(k) }
69
+
70
+ resource.assign_attributes(filtered_attributes, :without_protection => true)
71
+ resource
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,86 @@
1
+ module Alondra
2
+ class EventListener
3
+ include Pushing
4
+
5
+ attr_accessor :event
6
+ attr_accessor :resource
7
+ attr_accessor :channel_name
8
+
9
+ class << self
10
+ def listened_patterns
11
+ @listened_patterns ||= [default_listened_pattern]
12
+ end
13
+
14
+ def listen_to?(channel_name)
15
+ listened_patterns.any? { |p| p =~ channel_name }
16
+ end
17
+
18
+ def listen_to(channel_name)
19
+ unless @custom_pattern_provided
20
+ listened_patterns.clear
21
+ @custom_pattern_provided = true
22
+ end
23
+
24
+ if Regexp === channel_name
25
+ listened_patterns << channel_name
26
+ else
27
+ escaped_pattern = Regexp.escape(channel_name)
28
+ listened_patterns << Regexp.new("^#{escaped_pattern}")
29
+ end
30
+ end
31
+
32
+ def on(event_type, options = {}, &block)
33
+ callbacks << ListenerCallback.new(event_type, options, block)
34
+ end
35
+
36
+ def callbacks
37
+ @callbacks ||= []
38
+ end
39
+
40
+ def default_listened_pattern
41
+ word = self.name.demodulize
42
+ word.gsub!(/Listener$/, '')
43
+ word.gsub!(/::/, '/')
44
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1\/\2')
45
+ word.gsub!(/([a-z\d])([A-Z])/,'\1/\2')
46
+ word.downcase!
47
+ Regexp.new("^/#{word}")
48
+ end
49
+
50
+ def inherited(subclass)
51
+ # In development mode Rails will load the same class many times
52
+ # Delete it first if we already have parsed it
53
+ EventRouter.listeners.delete_if { |l| l.name == subclass.name }
54
+ EventRouter.listeners << subclass
55
+ end
56
+
57
+ def matching_callbacks_for(event)
58
+ callbacks.find_all { |c| c.matches?(event) }
59
+ end
60
+
61
+ def process(event)
62
+ matching_callbacks_for(event).each do |callback|
63
+ new_instance = new(event)
64
+ begin
65
+ new_instance.instance_exec(event, &callback.proc)
66
+ rescue Exception => ex
67
+ Rails.logger.error 'Error while processing event listener callback'
68
+ Rails.logger.error ex.message
69
+ Rails.logger.error ex.stacktrace if ex.respond_to? :stacktrace
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ def session
76
+ @connection.session
77
+ end
78
+
79
+ def initialize(event)
80
+ @event = event
81
+ @resource = event.resource
82
+ @channel_name = event.channel_name
83
+ @connection = event.connection
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,27 @@
1
+ module Alondra
2
+ class EventRouter
3
+
4
+ def self.listeners
5
+ @listeners ||= []
6
+ end
7
+
8
+ def process(event)
9
+ event.channel.receive(event)
10
+
11
+ # Event listeners callback can manipulate AR objects and so can potentially
12
+ # block the EM reactor thread. To avoid that, we defer them to another thread.
13
+ EM.defer do
14
+
15
+ # Ensure the connection associated with the thread is checked in
16
+ # after the callbacks are processed
17
+ ActiveRecord::Base.connection_pool.with_connection do
18
+ listening_classes = EventRouter.listeners.select do |ob|
19
+ ob.listen_to?(event.channel_name)
20
+ end
21
+
22
+ listening_classes.each { |listening_class| listening_class.process(event) }
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,37 @@
1
+ module Alondra
2
+ class ListenerCallback
3
+ attr_reader :event_type
4
+ attr_reader :options
5
+ attr_reader :proc
6
+
7
+ CHANNEL_NAME_PATTERN = %r{\d+$}
8
+
9
+ def initialize(event_type, options = {}, proc)
10
+ @event_type = event_type
11
+ @options = options
12
+ @proc = proc
13
+ end
14
+
15
+ def matches?(event)
16
+ return false unless event.type == event_type
17
+
18
+ case options[:to]
19
+ when nil then true
20
+ when :member then
21
+ member_channel? event.channel_name
22
+ when :collection then
23
+ !member_channel?(event.channel_name)
24
+ end
25
+ end
26
+
27
+ def to_proc
28
+ proc
29
+ end
30
+
31
+ private
32
+
33
+ def member_channel?(channel_name)
34
+ channel_name =~ CHANNEL_NAME_PATTERN
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,35 @@
1
+ module Alondra
2
+ class Message
3
+ attr_reader :content
4
+ attr_reader :channel_names
5
+
6
+ def initialize(content, channel_names)
7
+ @content = content
8
+ @channel_names = channel_names
9
+ end
10
+
11
+ def enqueue
12
+ MessageQueueClient.push self
13
+ end
14
+
15
+ def send_to_channels
16
+ channels.each do |channel|
17
+ channel.receive self
18
+ end
19
+ end
20
+
21
+ def as_json
22
+ {:message => content, :channel_names => channel_names}
23
+ end
24
+
25
+ def to_json
26
+ ActiveSupport::JSON.encode(as_json)
27
+ end
28
+
29
+ private
30
+
31
+ def channels
32
+ channel_names.collect { |name| Channel[name] }
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,70 @@
1
+ require 'singleton'
2
+ require 'ffi'
3
+ require 'em-zeromq'
4
+
5
+ module Alondra
6
+ class MessageQueue
7
+ include Singleton
8
+
9
+ def start_listening
10
+ Rails.logger.info "Starting message queue"
11
+
12
+ if @connection
13
+ Rails.logger.warn 'Push connection to message queue started twice'
14
+ reset!
15
+ end
16
+
17
+ @connection = context.bind(ZMQ::SUB, Alondra.config.queue_socket, self)
18
+ @connection.setsockopt ZMQ::SUBSCRIBE, '' # receive all
19
+ end
20
+
21
+ def on_readable(socket, messages)
22
+ messages.each do |received|
23
+ begin
24
+ parse received.copy_out_string
25
+ rescue Exception => ex
26
+ Rails.logger.error "Error raised while processing message"
27
+ Rails.logger.error "#{ex.class}: #{ex.message}"
28
+ Rails.logger.error ex.backtrace.join("\n") if ex.respond_to? :backtrace
29
+ end
30
+ end
31
+ end
32
+
33
+ def parse(received_string)
34
+ received_hash = ActiveSupport::JSON.decode(received_string).symbolize_keys
35
+
36
+ if received_hash[:event]
37
+ event = Event.new(received_hash, received_string)
38
+ receive(event)
39
+ elsif received_hash[:message]
40
+ message = Message.new(received_hash[:message], received_hash[:channel_names])
41
+ message.send_to_channels
42
+ else
43
+ Rails.logger.warn "Unrecognized message type #{received_string}"
44
+ end
45
+ end
46
+
47
+ def receive(event)
48
+ event_router.process(event)
49
+ end
50
+
51
+ def reset!
52
+ @connection.close_connection()
53
+
54
+ @connection = nil
55
+ @context = nil
56
+ @push_socket = nil
57
+ end
58
+
59
+ private
60
+
61
+ def event_router
62
+ @event_router ||= EventRouter.new
63
+ end
64
+
65
+ def context
66
+ @context ||= EM::ZeroMQ::Context.new(1)
67
+ end
68
+ end
69
+ end
70
+
@@ -0,0 +1,71 @@
1
+ require 'singleton'
2
+ require 'ffi-rzmq'
3
+ require 'em-zeromq'
4
+
5
+ module Alondra
6
+ class MessageQueueClient
7
+
8
+ def self.push(message)
9
+ instance.send_message(message)
10
+ end
11
+
12
+ def self.instance
13
+ if EM.reactor_running?
14
+ async_instance
15
+ else
16
+ sync_instance
17
+ end
18
+ end
19
+
20
+ def self.async_instance
21
+ @async_instance ||= AsyncMessageQueueClient.new
22
+ end
23
+
24
+ def self.sync_instance
25
+ @sync_instance ||= SyncMessageQueueClient.new
26
+ end
27
+ end
28
+
29
+ class AsyncMessageQueueClient < MessageQueueClient
30
+ def send_message(message)
31
+ EM.schedule do
32
+ begin
33
+ push_socket.send_msg(message.to_json)
34
+ rescue Exception => ex
35
+ Rails.logger.error "Exception while sending message to message queue: #{ex.message}"
36
+ end
37
+ end
38
+ end
39
+
40
+ def push_socket
41
+ @push_socket ||= context.connect(ZMQ::PUB, Alondra.config.queue_socket)
42
+ end
43
+
44
+ def context
45
+ @context ||= EM::ZeroMQ::Context.new(1)
46
+ end
47
+ end
48
+
49
+ class SyncMessageQueueClient < MessageQueueClient
50
+
51
+ def send_message(message)
52
+ begin
53
+ push_socket.send_string(message.to_json)
54
+ rescue Exception => ex
55
+ Rails.logger.error "Exception while sending message to message queue: #{ex.message}"
56
+ end
57
+ end
58
+
59
+ def push_socket
60
+ @push_socket ||= begin
61
+ socket = context.socket(ZMQ::PUB)
62
+ socket.connect(Alondra.config.queue_socket)
63
+ socket
64
+ end
65
+ end
66
+
67
+ def context
68
+ @context ||= ZMQ::Context.new(1)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,53 @@
1
+ module Alondra
2
+ class PushController
3
+ include ActiveSupport::Configurable
4
+ include AbstractController::Logger
5
+ include AbstractController::Rendering
6
+ include AbstractController::Helpers
7
+ include AbstractController::Translation
8
+ include AbstractController::AssetPaths
9
+ include AbstractController::ViewPaths
10
+
11
+ attr_accessor :channel_names
12
+
13
+ def initialize(context, to)
14
+ @channel_names = Channel.names_for(to)
15
+
16
+ self.class.view_paths = ActionController::Base.view_paths
17
+ copy_instance_variables_from(context)
18
+ end
19
+
20
+ def render_push(options)
21
+
22
+ if EM.reactor_thread?
23
+ Rails.logger.warn('Your are rendering a view from the Event Machine reactor thread')
24
+ Rails.logger.warn('Rendering a view is a possibly blocking operation, so be careful')
25
+ end
26
+
27
+ message_content = render_to_string(*options)
28
+ msg = Message.new(message_content, channel_names)
29
+ msg.enqueue
30
+ end
31
+
32
+ def _prefixes
33
+ ['application']
34
+ end
35
+
36
+ def view_paths
37
+ @view_paths ||= ApplicationController.send '_view_paths'
38
+ end
39
+
40
+ def action_name
41
+ 'push'
42
+ end
43
+
44
+ private
45
+
46
+ def copy_instance_variables_from(context)
47
+ context.instance_variables.each do |var|
48
+ value = context.instance_variable_get(var)
49
+ instance_variable_set(var, value)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,14 @@
1
+ module Alondra
2
+
3
+ class PushingException < StandardError; end
4
+
5
+ module Pushing
6
+ def push(*args)
7
+ raise PushingException.new('You need to specify the channel to push') unless args.last[:to].present?
8
+
9
+ to = args.last.delete(:to)
10
+ controller = PushController.new(self, to)
11
+ controller.render_push(args)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,44 @@
1
+ require 'em-websocket'
2
+
3
+ module Alondra
4
+ module Server
5
+ extend self
6
+
7
+ def run
8
+ Rails.logger.info "Server starting on port #{Alondra.config.port}"
9
+
10
+ EM::WebSocket.start(:host => '0.0.0.0', :port => Alondra.config.port) do |websocket|
11
+
12
+ websocket.onopen do
13
+ session = SessionParser.parse(websocket)
14
+
15
+ Rails.logger.info "client connected."
16
+ Connection.new(websocket, session)
17
+ end
18
+
19
+ websocket.onclose do
20
+ Rails.logger.info "Connection closed"
21
+ Connections[websocket].destroy! if Connections[websocket].present?
22
+ end
23
+
24
+ websocket.onerror do |ex|
25
+ puts "Error: #{ex.message}"
26
+ Rails.logger.error "Error: #{ex.message}"
27
+ Rails.logger.error ex.backtrace.join("\n")
28
+ Connections[websocket].destroy! if Connections[websocket]
29
+ end
30
+
31
+ websocket.onmessage do |msg|
32
+ Rails.logger.info "received: #{msg}"
33
+ CommandDispatcher.dispatch(msg, Connections[websocket])
34
+ end
35
+ end
36
+
37
+ EM.error_handler do |error|
38
+ puts "Error raised during event loop: #{error.message}"
39
+ Rails.logger.error "Error raised during event loop: #{error.message}"
40
+ Rails.logger.error error.stacktrace if error.respond_to? :stacktrace
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,57 @@
1
+ require 'cgi'
2
+
3
+ module Alondra
4
+ module SessionParser
5
+ extend self
6
+
7
+ def verifier
8
+ @verifier ||= ActiveSupport::MessageVerifier.new(Rails.application.config.secret_token)
9
+ end
10
+
11
+ def parse(websocket)
12
+ cookie = websocket.request['cookie'] || websocket.request['Cookie']
13
+ token = websocket.request['query']['token']
14
+
15
+ if token.present?
16
+ SessionParser.parse_token(token)
17
+ elsif cookie.present?
18
+ SessionParser.parse_cookie(cookie)
19
+ else
20
+ Hash.new
21
+ end
22
+ end
23
+
24
+ def parse_cookie(cookie)
25
+ begin
26
+ cookies = cookie.split(';')
27
+ session_key = Rails.application.config.session_options[:key]
28
+
29
+ encoded_session = cookies.detect{|c| c.include?(session_key)}.gsub("#{session_key}=",'').strip
30
+ verifier.verify(CGI.unescape(encoded_session))
31
+ rescue ActiveSupport::MessageVerifier::InvalidSignature => ex
32
+ Rails.logger.error "invalid session cookie: #{cookie}"
33
+ Hash.new
34
+ rescue Exception => ex
35
+ Rails.logger.error "Exception parsing session from cookie: #{ex.message}"
36
+ end
37
+ end
38
+
39
+ def parse_token(token)
40
+ begin
41
+ decoded_token = verifier.verify(token)
42
+ ActiveSupport::JSON.decode(decoded_token)
43
+ rescue ActiveSupport::MessageVerifier::InvalidSignature => ex
44
+ Rails.logger.error "invalid session token: #{token}"
45
+ Hash.new
46
+ end
47
+ end
48
+
49
+ def session_key
50
+ Rails.application.config.session_options.key
51
+ end
52
+
53
+ def marshall
54
+ Rails.application.config.session_options[:coder]
55
+ end
56
+ end
57
+ end
data/lib/alondra.rb ADDED
@@ -0,0 +1,80 @@
1
+ require_relative 'alondra/message'
2
+ require_relative 'alondra/event'
3
+ require_relative 'alondra/connection'
4
+ require_relative 'alondra/channel'
5
+ require_relative 'alondra/command'
6
+ require_relative 'alondra/command_dispatcher'
7
+ require_relative 'alondra/event_router'
8
+ require_relative 'alondra/message_queue_client'
9
+ require_relative 'alondra/message_queue'
10
+ require_relative 'alondra/pushing'
11
+ require_relative 'alondra/event_listener'
12
+ require_relative 'alondra/session_parser'
13
+ require_relative 'alondra/listener_callback'
14
+ require_relative 'alondra/push_controller'
15
+ require_relative 'alondra/changes_callbacks'
16
+ require_relative 'alondra/changes_push'
17
+ require_relative 'alondra/server'
18
+
19
+ module Alondra
20
+
21
+ ActiveRecord::Base.extend ChangesPush
22
+ ActionController::Base.send :include, Pushing
23
+
24
+ class Alondra < Rails::Engine
25
+
26
+ # Setting default configuration values
27
+ config.port = Rails.env == 'test' ? 12346 : 12345
28
+ config.host = 'localhost'
29
+ config.queue_socket = 'ipc:///tmp/alondra.ipc'
30
+
31
+ initializer "enable sessions for flash websockets" do
32
+ Rails.application.config.session_store :cookie_store, httponly: false
33
+ end
34
+
35
+ initializer "load listeners" do
36
+ listeners_dir = File.join(Rails.root, 'app', 'listeners')
37
+
38
+ Rails.logger.info "Loading event listeners in #{listeners_dir}"
39
+ Dir[File.join(listeners_dir, '*.rb')].each { |file| require_dependency file }
40
+ end
41
+
42
+ def self.start_server_in_new_thread!
43
+ Thread.new do
44
+ start_server!
45
+ end
46
+ end
47
+
48
+ def self.start_server!
49
+ if EM.reactor_running?
50
+ EM.schedule do
51
+ MessageQueue.instance.start_listening
52
+ Server.run
53
+ end
54
+ else
55
+ Rails.logger.info "starting EM reactor"
56
+ EM.run do
57
+ MessageQueue.instance.start_listening
58
+ Server.run
59
+ end
60
+ die_gracefully_on_signal
61
+ end
62
+ end
63
+
64
+ def self.die_gracefully_on_signal
65
+ Signal.trap("INT") do
66
+ Rails.logger.warn "INT signal trapped. Shutting down EM reactor"
67
+ EM.stop
68
+ end
69
+
70
+ Signal.trap("TERM") do
71
+ Rails.logger.warn "TERM signal trapped. Shutting down EM reactor"
72
+ EM.stop
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+
79
+
80
+
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ rails generate alondra Thing
6
+
7
+ This will create:
8
+ what/will/it/create
@@ -0,0 +1,7 @@
1
+ class AlondraGenerator < Rails::Generators::NamedBase
2
+ source_root File.expand_path('../templates', __FILE__)
3
+
4
+ def generate_install
5
+ copy_file "alondra", "script/alondra"
6
+ end
7
+ end