message_channel 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "message_channel"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,18 @@
1
+ require "message_channel/version"
2
+ require "message_channel/base"
3
+
4
+ module MessageChannel
5
+
6
+ class Error < StandardError; end
7
+
8
+ class << self
9
+ include MessageChannel::Base
10
+ end
11
+
12
+ autoload :Observer, "message_channel/observer"
13
+ autoload :Druby, "message_channel/druby"
14
+ autoload :Mqtt, "message_channel/mqtt"
15
+ autoload :Redis, "message_channel/redis"
16
+ autoload :Mongodb, "message_channel/mongodb"
17
+ end
18
+
@@ -0,0 +1,54 @@
1
+ module MessageChannel
2
+ class Error < StandardError; end
3
+
4
+ module Base
5
+
6
+ # uri:
7
+ # "observer"
8
+ # "druby://127.0.0.1:8787"
9
+ # "mqtt://127.0.0.1:1883"
10
+ # "redis://127.0.0.1:6379/0"
11
+ # "mongodb://127.0.0.1:27017/test?size=4000&name=_event_queue"
12
+
13
+ def new( uri = nil, type: nil, host: nil, port: nil, db: nil, size: nil, name: nil )
14
+ if uri
15
+ uris = URI.parse( uri )
16
+ if uris.scheme.nil? && uris.host.nil? && uris.port.nil? && uris.path
17
+ type = uris.path
18
+ params = []
19
+ else
20
+ type = uris.scheme || type
21
+ host = uris.host || host
22
+ port = uris.port || port
23
+ params = uris.path.gsub(/^\//, "").split('/')
24
+ query = Hash[ URI::decode_www_form(uris.query) ] rescue {}
25
+ size = query[:size] || size
26
+ name = query[:name] || name
27
+ end
28
+ else
29
+ parans = []
30
+ end
31
+
32
+ options = { host: host, port: port }
33
+
34
+ case type
35
+ when "observer", NilClass
36
+ MessageChannel::Observer.new( **options )
37
+ when "druby"
38
+ MessageChannel::Druby.new( **options )
39
+ when "mqtt"
40
+ MessageChannel::Mqtt.new( **options )
41
+ when "redis"
42
+ options[:db] = params.shift || db
43
+ MessageChannel::Redis.new( **options )
44
+ when "mongodb"
45
+ options[:db] = params.shift || db
46
+ options[:size] = size
47
+ options[:name] = name
48
+ MessageChannel::Mongodb.new( **options )
49
+ end
50
+ end
51
+
52
+ end
53
+ end
54
+
@@ -0,0 +1,182 @@
1
+ require "drb/drb"
2
+ require "json"
3
+
4
+ module MessageChannel
5
+
6
+ class Druby
7
+
8
+ class Broker
9
+ def initialize
10
+ @mutex = Mutex.new
11
+ @patterns = {}
12
+ end
13
+
14
+ def subscribe( pattern )
15
+ queue = Queue.new
16
+ queue_id = queue.object_id
17
+ @mutex.synchronize do
18
+ @patterns[queue_id] = [pattern, queue]
19
+ end
20
+ queue_id
21
+ end
22
+
23
+ def unsubscribe( queue_id )
24
+ @mutex.synchronize do
25
+ @patterns.delete( queue_id ) rescue nil
26
+ end
27
+ rescue
28
+ nil
29
+ end
30
+
31
+ def wait( queue_id )
32
+ @patterns[queue_id].last.pop
33
+ end
34
+
35
+ def fetch( pattern )
36
+ queue = Queue.new
37
+ queue_id = queue.object_id
38
+ @mutex.synchronize do
39
+ @patterns[queue_id] = [pattern, queue]
40
+ end
41
+ topic, items = * queue.pop
42
+ rescue
43
+ nil
44
+ ensure
45
+ @mutex.synchronize do
46
+ @patterns.delete( queue_id ) rescue nil
47
+ end
48
+ end
49
+
50
+ def publish( topic, message )
51
+ @mutex.synchronize do
52
+ @patterns.each do |queue_id, items|
53
+ pattern, queue = *items
54
+ if File.fnmatch( pattern, topic, File::FNM_PATHNAME )
55
+ queue.push( [topic, message] )
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ class Agent
63
+ def initialize( host: @@host, port: @@port )
64
+ @@host = host
65
+ @@port = port
66
+ @uri = "druby://#{host}:#{port}"
67
+
68
+ if !defined?( @@Broker ) || @@Broker.nil?
69
+ @@Broker = Broker.new
70
+ DRb.start_service( @uri, @@Broker ) rescue nil
71
+ end
72
+
73
+ @drb = DRbObject.new_with_uri( @uri )
74
+ @queue_ids = {}
75
+ end
76
+
77
+ def listen_once( pattern )
78
+ topic, message = * @drb.fetch( pattern )
79
+ end
80
+
81
+ def listen_each( pattern, &block )
82
+ queue_id = @drb.subscribe( pattern )
83
+ @queue_ids[pattern] = queue_id
84
+ while true
85
+ topic, message = * @drb.wait( queue_id )
86
+ break if topic.nil?
87
+ block.call( topic, message )
88
+ end
89
+ rescue => error
90
+ nil
91
+ ensure
92
+ @drb.unsubscribe( queue_id )
93
+ @queue_ids.delete( pattern ) rescue nil
94
+ end
95
+
96
+ def unlisten( pattern )
97
+ if queue_id = @queue_ids[pattern]
98
+ @drb.unsubscribe( queue_id )
99
+ @queue_ids.delete( pattern ) rescue nil
100
+ end
101
+ end
102
+
103
+ def notify( topic, message )
104
+ @drb.publish( topic, message )
105
+ end
106
+ end
107
+
108
+ attr_reader :host, :port
109
+
110
+ def initialize( host: nil, port: nil )
111
+ @host = host || "127.0.0.1"
112
+ @port = ( port || 8787 ).to_i
113
+ @agent = Agent.new( host: @host, port: @port )
114
+ @threads = {}
115
+ end
116
+
117
+ def listen_once( *patterns )
118
+ queue = Queue.new
119
+ threads = {}
120
+ patterns.each do |pattern|
121
+ threads[pattern] = Thread.start(pattern) do |pattern|
122
+ agent = Agent.new
123
+ begin
124
+ topic, message = * agent.listen_once( pattern )
125
+ items = JSON.parse( message, symbolize_names: true )
126
+ queue.push( [topic, items] )
127
+ rescue => error
128
+ nil
129
+ end
130
+ end
131
+ end
132
+
133
+ topic, items = queue.pop
134
+ patterns.each do |pattern|
135
+ threads[pattern].kill rescue nil
136
+ threads.delete( pattern ) rescue nil
137
+ end
138
+ [topic, items]
139
+ rescue
140
+ nil
141
+ end
142
+
143
+ def listen_each( *patterns, &block )
144
+ patterns.each do |pattern|
145
+ @threads[pattern] = Thread.start(pattern) do |pattern|
146
+ begin
147
+ @agent.listen_each( pattern ) do |topic, message|
148
+ items = JSON.parse( message, symbolize_names: true )
149
+ block.call( topic, items )
150
+ end
151
+ rescue => error
152
+ nil
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ def listen( *patterns, &block )
159
+ if block.nil?
160
+ listen_once( *patterns )
161
+ else
162
+ listen_each( *patterns ) do |topic, items|
163
+ block.call( topic, items )
164
+ end
165
+ end
166
+ end
167
+
168
+ def unlisten( **patterns )
169
+ patterns.each do |pattern|
170
+ @agent.unlisten( pattern )
171
+ @threads.delete( pattern )
172
+ end
173
+ end
174
+
175
+ def notify( topic, **items )
176
+ @agent.notify( topic, items.to_json )
177
+ end
178
+
179
+ end
180
+
181
+ end
182
+
@@ -0,0 +1,131 @@
1
+ require "mongo"
2
+ require "json"
3
+
4
+ ::Mongo::Logger.logger.level = Logger::ERROR
5
+
6
+ module MessageChannel
7
+ class Mongodb
8
+ SIZE = 8000
9
+ NAME = "_event_queue"
10
+
11
+ def initialize( host: nil, port: nil, db: nil, size: nil, name: nil )
12
+ @host = host || "127.0.0.1"
13
+ @port = ( port || 27017 ).to_i
14
+ @db = db || "test"
15
+ @size = ( size || SIZE ).to_i
16
+ @name = name || NAME
17
+
18
+ @url = "mongodb://#{ @host }:#{ @port }/#{ @db }"
19
+ @client = ::Mongo::Client.new( @url )
20
+
21
+ @threads = {}
22
+ @mutex = Mutex.new
23
+
24
+ @event_queue = get_event_queue
25
+ end
26
+
27
+ def get_event_queue
28
+ event_queue = @mutex.synchronize do
29
+ if @client.database.collection_names.include?( @name )
30
+ event_queue = @client[ @name ]
31
+ else
32
+ event_queue = @client[ @name, capped: true, size: @size ]
33
+ event_queue.create
34
+ now = Time.now
35
+ doc = {
36
+ topic: "reset",
37
+ at: now.strftime("%Y%m%d.%H%M%S.%6L"),
38
+ }
39
+ event_queue.insert_one( doc )
40
+ event_queue
41
+ end
42
+ end
43
+ end
44
+
45
+ def get_event_tail( event_queue )
46
+ filter = {}
47
+ if enum = event_queue.find( {}, { sort: { "$natural" => -1 } } ).to_enum
48
+ if doc = enum.next rescue nil
49
+ filter = { "_id"=>{ "$gt"=>doc["_id"] } }
50
+ end
51
+ end
52
+ event_tail = event_queue.find( filter, { cursor_type: :tailable_await } ).to_enum
53
+ end
54
+
55
+ def listen_once( *patterns )
56
+ queue = Queue.new
57
+ threads = {}
58
+ patterns.each do |pattern|
59
+ threads[pattern] = ::Thread.start(pattern) do |pattern|
60
+ event_queue = get_event_queue
61
+ event_tail = get_event_tail( event_queue )
62
+ begin
63
+ while doc = event_tail.next
64
+ items = JSON.parse( doc.to_json, symbolize_names: true )
65
+ topic = items[:topic]
66
+ if File.fnmatch( pattern, topic, File::FNM_PATHNAME )
67
+ items.delete( :_id )
68
+ items.delete( :topic )
69
+ queue.push [topic, items]
70
+ end
71
+ end
72
+ ensure
73
+ end
74
+ end
75
+ end
76
+
77
+ topic, items = queue.pop
78
+ patterns.each do |pattern|
79
+ threads[pattern].kill rescue nil
80
+ threads.delete( pattern ) rescue nil
81
+ end
82
+ [topic, items]
83
+ end
84
+
85
+ def listen_each( *patterns, &block )
86
+ patterns.each do |pattern|
87
+ @threads[pattern] = ::Thread.start(pattern) do |pattern|
88
+ begin
89
+ event_queue = get_event_queue
90
+ event_tail = get_event_tail( event_queue )
91
+ while doc = event_tail.next
92
+ items = JSON.parse( doc.to_json, symbolize_names: true )
93
+ topic = items[:topic]
94
+ if File.fnmatch( pattern, topic, File::FNM_PATHNAME )
95
+ items.delete( :_id )
96
+ items.delete( :topic )
97
+ block.call( topic, items )
98
+ end
99
+ end
100
+ ensure
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ def listen( *patterns, &block )
107
+ if block.nil?
108
+ listen_once( *patterns )
109
+ else
110
+ listen_each( *patterns ) do |topic, items|
111
+ block.call( topic, items )
112
+ end
113
+ end
114
+ end
115
+
116
+ def unlisten( **patterns )
117
+ patterns.each do |pattern|
118
+ @threads[pattern].kill rescue nil
119
+ @threads.delete( pattern ) rescue nil
120
+ end
121
+ end
122
+
123
+ def notify( topic, **items )
124
+ items[:topic] = topic
125
+ @event_queue.insert_one( items )
126
+ end
127
+
128
+ end
129
+
130
+ end
131
+
@@ -0,0 +1,79 @@
1
+ require "mqtt"
2
+ require "json"
3
+
4
+ module MessageChannel
5
+ class Mqtt
6
+
7
+ def initialize( host: nil, port: nil )
8
+ @host = host || "127.0.0.1"
9
+ @port = ( port || 1883 ).to_i
10
+ @mqtt = MQTT::Client.connect( @host, @port )
11
+ @threads = {}
12
+ end
13
+
14
+ def listen_once( *patterns )
15
+ queue = Queue.new
16
+ threads = {}
17
+ patterns.each do |pattern|
18
+ threads[pattern] = ::Thread.start(pattern) do |pattern|
19
+ mqtt = MQTT::Client.connect( @host, @port )
20
+ begin
21
+ mqtt.get( pattern ) do |topic, message|
22
+ items = JSON.parse( message, symbolize_names: true )
23
+ mqtt.disconnect rescue nil
24
+ queue.push [topic, items]
25
+ end
26
+ ensure
27
+ end
28
+ end
29
+ end
30
+
31
+ topic, items = queue.pop
32
+ patterns.each do |pattern|
33
+ threads[pattern].kill rescue nil
34
+ threads.delete( pattern ) rescue nil
35
+ end
36
+ [topic, items]
37
+ end
38
+
39
+ def listen_each( *patterns, &block )
40
+ patterns.each do |pattern|
41
+ @threads[pattern] = ::Thread.start(pattern) do |pattern|
42
+ mqtt = MQTT::Client.connect( @host, @port )
43
+ begin
44
+ mqtt.get( pattern ) do |topic, message|
45
+ items = JSON.parse( message, symbolize_names: true )
46
+ block.call( topic, items )
47
+ end
48
+ ensure
49
+ mqtt.disconnect rescue nil
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ def listen( *patterns, &block )
56
+ if block.nil?
57
+ listen_once( *patterns )
58
+ else
59
+ listen_each( *patterns ) do |topic, items|
60
+ block.call( topic, items )
61
+ end
62
+ end
63
+ end
64
+
65
+ def unlisten( **patterns )
66
+ patterns.each do |pattern|
67
+ @threads[pattern].kill rescue nil
68
+ @threads.delete( pattern ) rescue nil
69
+ end
70
+ end
71
+
72
+ def notify( topic, **items )
73
+ @mqtt.publish( topic, items.to_json, false )
74
+ end
75
+
76
+ end
77
+
78
+ end
79
+