alondra 0.0.3

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 (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