msngr 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/msngr.rb +18 -0
- data/lib/msngr/clients/redis.rb +47 -0
- data/lib/msngr/messenger.rb +103 -0
- data/lib/msngr/receiver.rb +61 -0
- data/lib/msngr/version.rb +4 -0
- data/spec/lib/msngr/clients/redis_spec.rb +42 -0
- data/spec/lib/msngr/messenger_spec.rb +78 -0
- data/spec/lib/msngr/receiver_spec.rb +37 -0
- data/spec/lib/msngr_spec.rb +10 -0
- data/spec/spec_helper.rb +9 -0
- metadata +90 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 80f389f5be19c06e918bbabc6758063ebc871298
|
4
|
+
data.tar.gz: d152dc1d6e25696fa09fc917248dfbb599483c1e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 249d9724441d3a927679ed6e8c88a7e3944c7812efe0a787f656bab7878796452d6d6c46d96a69f1f793680521b2989ab972c30251eff5e38bbf54d0aedde2a9
|
7
|
+
data.tar.gz: 53dc43080979cd573aae3ca906fe15abc4f61a07bcef3cdf15f6bde205766eabdeac76cb5b64aaa188af654012499161c40932708e788818a9050fd0e681e961
|
data/lib/msngr.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "msngr/version"
|
2
|
+
require "msngr/receiver"
|
3
|
+
require "msngr/messenger"
|
4
|
+
|
5
|
+
module Msngr
|
6
|
+
module Clients; end
|
7
|
+
extend self
|
8
|
+
|
9
|
+
# Shorthand for writing Msngr::Messenger.new(*args).
|
10
|
+
#
|
11
|
+
# @param [Array] *args
|
12
|
+
# @return [Msngr::Messenger]
|
13
|
+
#
|
14
|
+
def new(*args)
|
15
|
+
Messenger.new(*args)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "redis"
|
2
|
+
|
3
|
+
class Msngr::Clients::Redis
|
4
|
+
|
5
|
+
# (Connectivity) Arguments to initialize the Redis instance with.
|
6
|
+
#
|
7
|
+
# @return [Array]
|
8
|
+
#
|
9
|
+
attr_reader :args
|
10
|
+
|
11
|
+
# Instantiances an instance of Msngr::Clients::Redis.
|
12
|
+
#
|
13
|
+
# @param [Array] *args the arguments to pass in to the Redis client.
|
14
|
+
#
|
15
|
+
def initialize(*args)
|
16
|
+
@args = args
|
17
|
+
end
|
18
|
+
|
19
|
+
# Yields all events/messages from the Redis server.
|
20
|
+
#
|
21
|
+
# @yield [event, message]
|
22
|
+
# @yieldparam [String] event the name of the received event.
|
23
|
+
# @yieldparam [String] message the message of the received event.
|
24
|
+
#
|
25
|
+
# @note This is an interface for Msngr::Messenger.
|
26
|
+
#
|
27
|
+
def on_message
|
28
|
+
connection.psubscribe("*") do |on|
|
29
|
+
on.pmessage { |_, event, message| yield event, message }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Creates and returns a new instance of Redis using @args if present.
|
36
|
+
#
|
37
|
+
# @return [Redis]
|
38
|
+
#
|
39
|
+
def connection
|
40
|
+
if args.any?
|
41
|
+
Redis.new(*args)
|
42
|
+
else
|
43
|
+
Redis.new
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require "thread"
|
2
|
+
|
3
|
+
class Msngr::Messenger
|
4
|
+
|
5
|
+
# A client object interface to receive messages from.
|
6
|
+
#
|
7
|
+
# @return [Msngr::Clients::*]
|
8
|
+
#
|
9
|
+
attr_reader :client
|
10
|
+
|
11
|
+
# An Array of Receiver objects to dispatch callbacks/messages to.
|
12
|
+
#
|
13
|
+
# @return [Array<Receiver>]
|
14
|
+
#
|
15
|
+
attr_reader :receivers
|
16
|
+
|
17
|
+
# Initializes a new Messenger.
|
18
|
+
#
|
19
|
+
# @param [Msngr::Clients::*] client the to receive messages from.
|
20
|
+
# @return [Receiver]
|
21
|
+
#
|
22
|
+
def initialize(client)
|
23
|
+
@client = client
|
24
|
+
@receivers = []
|
25
|
+
@mutex = Mutex.new
|
26
|
+
end
|
27
|
+
|
28
|
+
# Creates and returns a new Receiver and adds it to @receivers so
|
29
|
+
# it'll receive messages matching the provided pattern.
|
30
|
+
#
|
31
|
+
# @param [Regexp] pattern
|
32
|
+
# @return [Receiver]
|
33
|
+
#
|
34
|
+
def subscribe(pattern)
|
35
|
+
Msngr::Receiver.new(pattern).tap do |receiver|
|
36
|
+
@mutex.synchronize { @receivers << receiver }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Removes the Receiver from @receivers and invokes the receiver's
|
41
|
+
# @on_unsubscribe_callbacks.
|
42
|
+
#
|
43
|
+
# @param [Receiver] receiver
|
44
|
+
#
|
45
|
+
def unsubscribe(receiver)
|
46
|
+
@mutex.synchronize { @receivers.delete(receiver) }
|
47
|
+
dispatch(receiver.on_unsubscribe_callbacks, self)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Listens on a new thread. Will auto-restart when crashed
|
51
|
+
# in an attempt to recover from exceptions.
|
52
|
+
#
|
53
|
+
def listen!
|
54
|
+
Thread.new do
|
55
|
+
loop do
|
56
|
+
begin
|
57
|
+
listen
|
58
|
+
rescue => e
|
59
|
+
puts "Messenger error occurred:"
|
60
|
+
puts "#{e.class.name}"
|
61
|
+
puts "#{e.backtrace.join("\n")}"
|
62
|
+
puts "Restarting.."
|
63
|
+
sleep 1
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# Instructs the @client to yield events/messages when it receives them. Received
|
72
|
+
# events are matched with each Receiver's pattern and will dispatch the event's message
|
73
|
+
# to all subscribing Receiver instances that match the event's pattern.
|
74
|
+
#
|
75
|
+
def listen
|
76
|
+
client.on_message do |event, message|
|
77
|
+
subscribing_receivers(event) do |receiver|
|
78
|
+
dispatch(receiver.on_message_callbacks, message)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Yields Receiver objects who's pattern matches the event.
|
84
|
+
#
|
85
|
+
# @param [String] event
|
86
|
+
# @yield [Receiver]
|
87
|
+
#
|
88
|
+
def subscribing_receivers(event)
|
89
|
+
receivers.each do |receiver|
|
90
|
+
yield receiver if receiver.pattern.match(event)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Dispatches args to the provided callbacks.
|
95
|
+
#
|
96
|
+
# @param [Array<Proc>] callbacks
|
97
|
+
# @param [Array] *args an Array of arguments to call each Proc with.
|
98
|
+
#
|
99
|
+
def dispatch(callbacks, *args)
|
100
|
+
callbacks.each { |callback| callback.call(*args) }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "thread"
|
2
|
+
|
3
|
+
class Msngr::Receiver
|
4
|
+
|
5
|
+
# Event pattern to match.
|
6
|
+
#
|
7
|
+
# @return [String]
|
8
|
+
#
|
9
|
+
attr_reader :pattern
|
10
|
+
|
11
|
+
# Array of Procs to invoke when a message is received.
|
12
|
+
#
|
13
|
+
# @return [Array<Proc>]
|
14
|
+
#
|
15
|
+
attr_reader :on_message_callbacks
|
16
|
+
|
17
|
+
# Array of Procs to invoke when unsubscribed from a Messenger.
|
18
|
+
#
|
19
|
+
# @return [Array<Proc>]
|
20
|
+
#
|
21
|
+
attr_reader :on_unsubscribe_callbacks
|
22
|
+
|
23
|
+
# Initializes a new Receiver.
|
24
|
+
#
|
25
|
+
# @param [Regexp] pattern the pattern to listen for.
|
26
|
+
# @return [Receiver]
|
27
|
+
#
|
28
|
+
def initialize(pattern = /.+/)
|
29
|
+
@pattern = pattern
|
30
|
+
@on_message_callbacks = []
|
31
|
+
@on_unsubscribe_callbacks = []
|
32
|
+
@mutex = Mutex.new
|
33
|
+
end
|
34
|
+
|
35
|
+
# Define a callback that invokes on each received message.
|
36
|
+
#
|
37
|
+
# @param [Proc] block
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# receiver.on_message do |message|
|
41
|
+
# puts "Message Received: #{message}"
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
def on_message(&block)
|
45
|
+
@mutex.synchronize { @on_message_callbacks << block }
|
46
|
+
end
|
47
|
+
|
48
|
+
# Define a callback that invokes when unsubscribed from a Messenger.
|
49
|
+
#
|
50
|
+
# @param [Proc] block
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
# receiver.on_unsubscribe do |messenger|
|
54
|
+
# puts "Unsubscribed From: #{messenger}"
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
def on_unsubscribe(&block)
|
58
|
+
@mutex.synchronize { @on_unsubscribe_callbacks << block }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "msngr/clients/redis"
|
3
|
+
|
4
|
+
describe Msngr::Clients::Redis do
|
5
|
+
|
6
|
+
let(:client) { Msngr::Clients::Redis.new }
|
7
|
+
|
8
|
+
it "should initialize without redis options" do
|
9
|
+
client.should have(0).args
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should initialize with arguments" do
|
13
|
+
client = Msngr::Clients::Redis.new(host: "127.0.0.1", port: 6379)
|
14
|
+
client.args.should == [{host: "127.0.0.1", port: 6379}]
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should establish a connection without any args" do
|
18
|
+
Redis.expects(:new)
|
19
|
+
client.send(:connection)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should establish a connection with args" do
|
23
|
+
options = { host: "127.0.0.1", port: 6379 }
|
24
|
+
client = Msngr::Clients::Redis.new(options)
|
25
|
+
Redis.expects(:new).with(options)
|
26
|
+
client.send(:connection)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should yield a message" do
|
30
|
+
connection, on = mock, mock
|
31
|
+
|
32
|
+
client.stubs(:connection).returns(connection)
|
33
|
+
connection.expects(:psubscribe).with("*").yields(on)
|
34
|
+
on.expects(:pmessage).yields("", "room.1", "John joined the room!")
|
35
|
+
|
36
|
+
client.on_message do |event, message|
|
37
|
+
event.should == "room.1"
|
38
|
+
message.should == "John joined the room!"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "msngr/receiver"
|
3
|
+
require "msngr/messenger"
|
4
|
+
|
5
|
+
describe Msngr::Messenger do
|
6
|
+
|
7
|
+
let(:client) { mock }
|
8
|
+
let(:messenger) { Msngr::Messenger.new(client) }
|
9
|
+
|
10
|
+
it "should initialize with an empty array of receivers" do
|
11
|
+
messenger.receivers.should be_empty
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should create a new Receiver and add it to @receivers" do
|
15
|
+
receiver = messenger.subscribe(/.+/)
|
16
|
+
messenger.should have(1).receivers
|
17
|
+
messenger.receivers.first.should == receiver
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should unsubscribe a receiver" do
|
21
|
+
receiver = messenger.subscribe(/.+/)
|
22
|
+
messenger.expects(:dispatch).
|
23
|
+
with(receiver.on_unsubscribe_callbacks, messenger)
|
24
|
+
messenger.unsubscribe(receiver)
|
25
|
+
messenger.should have(0).receivers
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should start listening on a separate thread" do
|
29
|
+
Thread.expects(:new).yields
|
30
|
+
messenger.expects(:loop).yields
|
31
|
+
messenger.expects(:listen).once
|
32
|
+
messenger.listen!
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should display a backtrace on error and restart" do
|
36
|
+
Thread.expects(:new).yields
|
37
|
+
messenger.expects(:loop).yields
|
38
|
+
messenger.expects(:listen).raises.then.returns
|
39
|
+
messenger.expects(:puts).times(4)
|
40
|
+
messenger.expects(:sleep)
|
41
|
+
messenger.listen!
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should listen to the client but not dispatch" do
|
45
|
+
messenger.subscribe(/room\.2/)
|
46
|
+
client.expects(:on_message).yields("room.1", "Hi John")
|
47
|
+
messenger.expects(:dispatch).never
|
48
|
+
messenger.send(:listen)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should listen to the client and dispatch a message" do
|
52
|
+
messenger.subscribe(/room\.1/).on_message { |m| m.should == "Hi John" }
|
53
|
+
client.expects(:on_message).yields("room.1", "Hi John")
|
54
|
+
messenger.send(:listen)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should listen to the client and dispatch 3 messages" do
|
58
|
+
3.times do
|
59
|
+
messenger.subscribe(/room\.1/).tap do |receiver|
|
60
|
+
cb = proc {}
|
61
|
+
cb.expects(:call).with("Hi John")
|
62
|
+
receiver.on_message(&cb)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
2.times do
|
67
|
+
messenger.subscribe(/room\.2/).tap do |receiver|
|
68
|
+
cb = proc {}
|
69
|
+
cb.expects(:call).never
|
70
|
+
receiver.on_message(&cb)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
client.expects(:on_message).yields("room.1", "Hi John")
|
75
|
+
messenger.send(:listen)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "msngr/receiver"
|
3
|
+
|
4
|
+
describe Msngr::Receiver do
|
5
|
+
|
6
|
+
let(:receiver) { Msngr::Receiver.new }
|
7
|
+
|
8
|
+
it "should initialize with a default wildcard pattern" do
|
9
|
+
receiver.pattern.should == /.+/
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should initialize with a custom pattern" do
|
13
|
+
pattern = /rooms\.1/
|
14
|
+
Msngr::Receiver.new(pattern).pattern.should == pattern
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should define an message callback" do
|
18
|
+
callback = proc { |msg| "invoked: #{msg}" }
|
19
|
+
receiver.on_message(&callback)
|
20
|
+
|
21
|
+
callbacks = receiver.on_message_callbacks
|
22
|
+
callbacks.size.should == 1
|
23
|
+
callbacks.first.call("message").should == "invoked: message"
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should define an unsubscribe callback" do
|
27
|
+
callback = proc { |msngr| "unsubscribed from: #{msngr}" }
|
28
|
+
receiver.on_unsubscribe(&callback)
|
29
|
+
|
30
|
+
callbacks = receiver.on_unsubscribe_callbacks
|
31
|
+
callbacks.size.should == 1
|
32
|
+
|
33
|
+
object = Object.new
|
34
|
+
callbacks.first.call(object).should == "unsubscribed from: #{object}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: msngr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael van Rooijen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-12-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 10.1.0
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 10.1.0
|
37
|
+
type: :development
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 10.1.0
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 10.1.0
|
47
|
+
description: A light-weight Ruby library for multi-threaded Ruby applications that
|
48
|
+
allows threads to share a single service connection for more efficient messaging.
|
49
|
+
email:
|
50
|
+
- meskyanichi@gmail.com
|
51
|
+
executables: []
|
52
|
+
extensions: []
|
53
|
+
extra_rdoc_files: []
|
54
|
+
files:
|
55
|
+
- "./lib/msngr.rb"
|
56
|
+
- "./lib/msngr/clients/redis.rb"
|
57
|
+
- "./lib/msngr/messenger.rb"
|
58
|
+
- "./lib/msngr/receiver.rb"
|
59
|
+
- "./lib/msngr/version.rb"
|
60
|
+
- "./spec/lib/msngr/clients/redis_spec.rb"
|
61
|
+
- "./spec/lib/msngr/messenger_spec.rb"
|
62
|
+
- "./spec/lib/msngr/receiver_spec.rb"
|
63
|
+
- "./spec/lib/msngr_spec.rb"
|
64
|
+
- "./spec/spec_helper.rb"
|
65
|
+
homepage: https://github.com/meskyanichi/msngr/
|
66
|
+
licenses:
|
67
|
+
- MIT
|
68
|
+
metadata: {}
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
requirements: []
|
84
|
+
rubyforge_project:
|
85
|
+
rubygems_version: 2.2.0
|
86
|
+
signing_key:
|
87
|
+
specification_version: 4
|
88
|
+
summary: A light-weight Ruby library for multi-threaded Ruby applications that allows
|
89
|
+
threads to share a single service connection for more efficient messaging.
|
90
|
+
test_files: []
|