bernstein 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 28bd90208deb3660afce5ecadda52636d9c31e8c
4
+ data.tar.gz: e84c2e6717ed39563921b813f676e678b91e79f5
5
+ SHA512:
6
+ metadata.gz: 1c5605a448740f7e0940175e9c72ada85f6f6ea36e6d1c24fb53982cca179e8de9891cea93ed71da27294e12058eb022f3d8fdccc9dc2f66b25ae0c5236d97f9
7
+ data.tar.gz: 1e52a953484c378fc81638411255d5a6b1a4896995658639af604d9450ea770ee7a104c1581302126befaf76be1676d2c9880cd74e9697a8163f59213bf5a510
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.*~
2
+ *.swp
3
+ *~
4
+ *.rdb
5
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rspec'
4
+ gem 'ruby-osc'
5
+ gem 'eventmachine'
6
+ gem 'redis'
7
+ gem 'redis-namespace'
8
+ gem 'json'
9
+ gem 'daemons'
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Anthony Plekhov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # Bernstein
2
+
3
+ Bernstein is a simple Ruby message queue for [OSC](http://en.wikipedia.org/wiki/Open_Sound_Control) messages
4
+ that gives Ruby the ability to asynchronously send messages to OSC-enabled software and hardware.
5
+ It is built on top of the [ruby-osc](https://github.com/maca/ruby-osc) libray and currently uses Redis to
6
+ queue the messages. Bernstein provides support for float,integer and string datatypes. In addition, it offers an
7
+ awk-mode that a client can use to be notified that an OSC message was delivered and awknowledged.
8
+
9
+ ## Why is it useful?
10
+
11
+ While typical OSC communication is 1:1 between a client and an OSC consumer, Bernstein allows for many clients
12
+ to control an OSC device or process. For example, there could be a web interface adapting requests from
13
+ many users and sending them as OSC messages to a sound generator in an installation or performance.
14
+ See [here](https://github.com/aplekhov/bernstein-web).
15
+ Due to the queuing there is an inherent latency, however that might be neglible in certain situations.
16
+
17
+
18
+ ## Basic Usage
19
+
20
+ gem install bernstein
21
+
22
+ Make sure that Redis is running. Start the background poller daemon:
23
+
24
+ bernstein start -- -c bernstein.yml
25
+
26
+ ```ruby
27
+ require 'bernstein'
28
+
29
+ # see sample yml file for options
30
+ Bernstein.configure_from_yaml!('bernstein.yml')
31
+
32
+ # send a message string, all parameters will be converted to floats
33
+ msg_id = Bernstein::Client.send_message_by_string "/synths/4/filter_cutoff 0.5"
34
+
35
+ # send a message with specific types
36
+ msg_id = Bernstein::Client.send_message '/synth/params', 'sinewave', 440, 556.3, 334.0
37
+
38
+ # get status ('queued','sending','sent')
39
+ Bernstein::Client.message_status(msg_id)
40
+ ```
41
+
42
+ Stop the background poller like this:
43
+
44
+ bernstein stop
45
+
46
+ ## Default Configuration
47
+ See bernstein.sample.yml.
48
+ Note: if no redis options are passed, then the redis connection defaults will be used.
49
+
50
+ ## Awk mode
51
+ By default, bernstein has awk mode enabled which means that it will send an internal message id along with every
52
+ message and expect the OSC receiver to respond back with an OSC 'awk' that contains the same message id. For many software
53
+ OSC implementations this is pretty easy to setup, however it is not likely to work with hardware. Make sure to disable it
54
+ using the `require_awks` config key.
55
+
56
+ ## License
57
+ The MIT License. Copyright (c) 2014 Anthony Plekhov. See LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'rdoc/task'
3
+
4
+ desc "Run specs"
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ desc "Generate code coverage"
8
+ RSpec::Core::RakeTask.new(:coverage) do |t|
9
+ t.rcov = true
10
+ t.rcov_opts = ['--exclude', 'spec']
11
+ end
12
+
13
+ desc 'Generate documentation for plugin.'
14
+ Rake::RDocTask.new(:rdoc) do |rdoc|
15
+ rdoc.rdoc_dir = 'rdoc'
16
+ rdoc.title = 'Timeliness'
17
+ rdoc.options << '--line-numbers' << '--inline-source'
18
+ rdoc.rdoc_files.include('README')
19
+ rdoc.rdoc_files.include('lib/**/*.rb')
20
+ end
21
+
22
+ desc 'Default: run specs.'
23
+ task :default => :spec
data/bernstein.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/bernstein/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Anthony Plekhov"]
6
+ gem.email = ["anthony.plekhov@gmail.com"]
7
+ gem.description = gem.summary = "Ruby OSC message queue"
8
+ gem.license = "MIT"
9
+ gem.executables = ['bernstein']
10
+ gem.files = `git ls-files`.split("\n")
11
+ gem.test_files = `git ls-files -- spec/*`.split("\n")
12
+ gem.name = "bernstein"
13
+ gem.require_paths = ["lib"]
14
+ gem.version = Bernstein::VERSION
15
+ gem.add_dependency 'redis', '>= 3.1.0'
16
+ gem.add_dependency 'redis-namespace', '>= 1.5.1'
17
+ gem.add_dependency 'eventmachine', '>= 1.0.3'
18
+ gem.add_dependency 'json', '>= 1.8.1'
19
+ gem.add_dependency 'daemons', '>= 1.1.9'
20
+ gem.add_dependency 'ruby-osc', '>= 0.31.0'
21
+ gem.add_development_dependency 'rspec', '>= 3.1.0'
22
+ end
@@ -0,0 +1,14 @@
1
+ # osc config for where osc messages need to be delivered
2
+ :osc_client:
3
+ :port: 9090
4
+ :host: '127.0.0.1'
5
+ # osc config that bernstein listens on for awk OSC messages
6
+ :osc_server:
7
+ :port: 9000
8
+ :host: '127.0.0.1'
9
+ :require_awks: true
10
+ # redis config, accepts all Redis gem options
11
+ :redis_queue:
12
+ :key_expiry: 300
13
+ :host: '127.0.0.1'
14
+ :port: 6379
data/bin/bernstein ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bernstein'
4
+ require 'daemons'
5
+
6
+ #options = {
7
+ # :backtrace => true,
8
+ # :ontop => true,
9
+ # :log_output => true,
10
+ #}
11
+
12
+ working_directory = Dir.pwd
13
+ config_opt_index = ARGV.find_index('-c')
14
+ if ARGV[0] == "start" && config_opt_index.nil?
15
+ puts "Usage: bundle exec bernstein [start|stop|restart] -- -c <bernstein yaml file>"
16
+ exit
17
+ else
18
+ init_proc = Proc.new do
19
+ Bernstein.configure_from_yaml!(File.join( working_directory, ARGV[config_opt_index + 1]))
20
+ end
21
+ end
22
+
23
+ Daemons.run_proc('bernstein') do
24
+ init_proc.call
25
+ Bernstein::Server.start
26
+ end
@@ -0,0 +1,34 @@
1
+ module Bernstein
2
+ class Client
3
+ ##
4
+ # Example: Bernstein::Client.send_message("/synths/4/filter_cutoff .5")
5
+ # note: only accepts float arguments
6
+ #
7
+ def self.send_message_by_string(message_string)
8
+ msg = Message.build_from_string(message_string)
9
+ save_and_return_id(msg)
10
+ end
11
+
12
+ ##
13
+ # Example: Bernstein::Client.send_message("/synths/frequencies", 440, 556.3 334.0")
14
+ # note: only accepts float arguments
15
+ #
16
+ def self.send_message(address = '/', *args)
17
+ msg = Message.build(address, *args)
18
+ save_and_return_id(msg)
19
+ end
20
+
21
+ ##
22
+ # Example: Bernstein::Client.message_status("34246456458856")
23
+ #
24
+ def self.message_status(message_id)
25
+ Message.get_status(message_id)
26
+ end
27
+
28
+ private
29
+ def self.save_and_return_id(msg)
30
+ msg.save!
31
+ msg.id
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,78 @@
1
+ require 'ruby-osc'
2
+ require 'json'
3
+
4
+ module Bernstein
5
+ class Message
6
+ attr_reader :id, :osc_message
7
+ @@persister = RedisQueue
8
+ @@osc_connection = OSCConnection
9
+
10
+ def initialize(osc_message, id = nil)
11
+ @osc_message = osc_message
12
+ @id = id || new_id
13
+ @is_saved = false
14
+ end
15
+
16
+ def self.build(address = '', *args)
17
+ Message.new(OSC::Message.new(address, *args))
18
+ end
19
+
20
+ # only supports float arguments
21
+ def self.build_from_string(message_string)
22
+ address, args = parse_message_string(message_string)
23
+ Message.new(OSC::Message.new(address, *args))
24
+ end
25
+
26
+ def self.deserialize(serialized_msg)
27
+ data = JSON.parse(serialized_msg)
28
+ Message.new OSC::Message.new(data['address'], *data['args']), data['id']
29
+ end
30
+
31
+ def serialize
32
+ {'id' => @id, 'address' => @osc_message.address, 'args' => @osc_message.args}.to_json
33
+ end
34
+
35
+ def self.get_status(id)
36
+ @@persister.status(id)
37
+ end
38
+
39
+ def status
40
+ @@persister.status(@id)
41
+ end
42
+
43
+ def self.get_queued_messages
44
+ @@persister.queued_messages
45
+ end
46
+
47
+ def self.set_as_sent!(id)
48
+ @@persister.mark_as_sent(id)
49
+ end
50
+
51
+ def save!
52
+ unless @is_saved
53
+ @@persister.add(self)
54
+ @is_saved = true
55
+ end
56
+ end
57
+
58
+ def send!(expect_awk = true)
59
+ @@osc_connection.send_message self, expect_awk
60
+ @@persister.dequeue @id, !expect_awk
61
+ end
62
+
63
+ def ==(other)
64
+ (self.class == other.class) && (self.osc_message == other.osc_message) &&
65
+ (self.id == other.id)
66
+ end
67
+
68
+ protected
69
+ def new_id
70
+ Time.now.to_f.to_s.delete('.')
71
+ end
72
+
73
+ def self.parse_message_string message_string
74
+ message_array = message_string.split
75
+ [message_array.shift, message_array.map{|arg| arg.to_f}]
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,20 @@
1
+ require 'ruby-osc'
2
+
3
+ module Bernstein
4
+ class OSCConnection
5
+ include OSC
6
+
7
+ @options = {port: 8000, host: '127.0.0.1'}
8
+
9
+ def self.configure!(options = {})
10
+ @options.merge!(options || {})
11
+ @connection = OSC::Client.new @options[:port], @options[:host]
12
+ end
13
+
14
+ def self.send_message(message, with_message_id = true)
15
+ osc_message = message.osc_message
16
+ osc_message = OSC::Bundle.new(nil, osc_message, OSC::Message.new('/message_id', message.id)) if with_message_id
17
+ @connection.send osc_message
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,86 @@
1
+ require 'redis'
2
+ require 'redis-namespace'
3
+
4
+ module Bernstein
5
+ class RedisQueue
6
+ include States
7
+
8
+ QUEUE_SET = "queued_messages"
9
+ @options = {key_expiry: 300, redis: {}}
10
+
11
+ def self.configure!(options = {})
12
+ @options.merge!(options || {})
13
+ @redis = Redis::Namespace.new(:bernstein, :redis => Redis.new(@options[:redis]))
14
+ end
15
+
16
+ def self.add(message)
17
+ @redis.multi do
18
+ @redis.sadd QUEUE_SET, message.id
19
+ @redis.setex message.id, @options[:key_expiry], message.serialize
20
+ @redis.setex status_key(message.id), @options[:key_expiry], STATES[:queued]
21
+ end
22
+ end
23
+
24
+ def self.status(id)
25
+ @redis.get(status_key(id)) || STATES[:not_yet_queued]
26
+ end
27
+
28
+ def self.queued_messages
29
+ queued_message_ids = @redis.smembers QUEUE_SET
30
+ messages = []
31
+ unless queued_message_ids.empty?
32
+ messages = @redis.mget(queued_message_ids).compact
33
+ unless messages.empty?
34
+ messages.map!{|m| Message.deserialize(m)}
35
+ end
36
+ if messages.size < queued_message_ids.size
37
+ clean_up_queue(queued_message_ids - messages.map{|m| m.id})
38
+ end
39
+ end
40
+ messages
41
+ end
42
+
43
+ def self.clear
44
+ @redis.del QUEUE_SET
45
+ end
46
+
47
+ def self.dequeue(id, mark_as_sent = false)
48
+ remove_and_change_status(id, (mark_as_sent ? STATES[:sent] : STATES[:sending]))
49
+ end
50
+
51
+ def self.mark_as_sent(id)
52
+ set_status(id, STATES[:sent])
53
+ end
54
+
55
+ private
56
+ def self.status_key(id)
57
+ "#{id}_status"
58
+ end
59
+
60
+ def self.remove_and_change_status(id, status)
61
+ remove(id){ set_status(id, status) }
62
+ end
63
+
64
+ def self.set_status(id, status)
65
+ @redis.setex status_key(id), @options[:key_expiry], status
66
+ end
67
+
68
+ def self.remove(id)
69
+ if block_given?
70
+ @redis.multi do
71
+ @redis.srem QUEUE_SET, id
72
+ yield
73
+ end
74
+ else
75
+ @redis.srem QUEUE_SET, id
76
+ end
77
+ @redis.del id
78
+ end
79
+
80
+ def self.clean_up_queue(ids_to_remove)
81
+ @redis.pipelined do
82
+ ids_to_remove.each{|id| @redis.srem QUEUE_SET, id}
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,41 @@
1
+ require 'eventmachine'
2
+ require 'ruby-osc'
3
+
4
+ module Bernstein
5
+ class Server
6
+ @options = {port: 9000, host: '127.0.0.1', require_awks: true, poll_interval: 5, awk_address: '/awk_id'}
7
+ def self.configure!(options = {})
8
+ @options.merge!(options || {})
9
+ end
10
+
11
+ def self.start
12
+ OSC.run do
13
+ @server = OSC::Server.new(@options[:port],@options[:host])
14
+ if @options[:require_awks]
15
+ @server.add_pattern @options[:awk_address] do |*args|
16
+ handle_awknowledgement(args[1])
17
+ end
18
+ end
19
+
20
+ @timer = EventMachine::PeriodicTimer.new(@options[:poll_interval]) do
21
+ process_queued_messages
22
+ end
23
+ yield if block_given?
24
+ end
25
+ end
26
+
27
+ def self.stop
28
+ @server.stop unless @server.nil?
29
+ @timer.cancel unless @timer.nil?
30
+ end
31
+
32
+ private
33
+ def self.process_queued_messages
34
+ Message.get_queued_messages.each{|m| m.send!(@options[:require_awks])}
35
+ end
36
+
37
+ def self.handle_awknowledgement(id)
38
+ Message.set_as_sent!(id)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,5 @@
1
+ module Bernstein
2
+ module States
3
+ STATES = {not_yet_queued: 'not_yet_queued', queued: 'queued', sending: 'sending', sent: 'sent'}
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module Bernstein
2
+ VERSION = "0.0.1"
3
+ end
data/lib/bernstein.rb ADDED
@@ -0,0 +1,20 @@
1
+ require 'bernstein/states'
2
+ require 'bernstein/redis_queue'
3
+ require 'bernstein/osc_connection'
4
+ require 'bernstein/message'
5
+ require 'bernstein/client'
6
+ require 'bernstein/server'
7
+ require 'bernstein/version'
8
+ require 'yaml'
9
+
10
+ module Bernstein
11
+ def self.configure_from_yaml!(file_path)
12
+ configure!(YAML.load_file(file_path))
13
+ end
14
+
15
+ def self.configure!(options = {})
16
+ RedisQueue.configure!(options[:redis_queue])
17
+ OSCConnection.configure!(options[:osc_client])
18
+ Server.configure!(options[:osc_server])
19
+ end
20
+ end
@@ -0,0 +1,35 @@
1
+ require 'helper'
2
+
3
+ describe Bernstein::Client do
4
+ subject { Bernstein::Client }
5
+
6
+ describe "sending a message" do
7
+ it "should build the message object and return its id" do
8
+ id = subject.send_message_by_string("/synths/4/chord/notes 25 30 10")
9
+ expect(id).to_not be_nil
10
+ queued_messages = Bernstein::Message.get_queued_messages
11
+ message = queued_messages.find{|m| m.id == id}
12
+ expect(message).to_not be_nil
13
+ expect(message.osc_message.address).to eq("/synths/4/chord/notes")
14
+ expect(message.osc_message.args).to eq([25.0, 30.0, 10.0])
15
+ end
16
+
17
+ it "should accept arguments to build the message" do
18
+ id = subject.send_message("/synths/4/parameters", 20, 34.2, 'squarewave')
19
+ expect(id).to_not be_nil
20
+ queued_messages = Bernstein::Message.get_queued_messages
21
+ message = queued_messages.find{|m| m.id == id}
22
+ expect(message).to_not be_nil
23
+ expect(message.osc_message.address).to eq("/synths/4/parameters")
24
+ expect(message.osc_message.args).to eq([20, 34.2, 'squarewave'])
25
+ end
26
+ end
27
+
28
+ describe "querying a message's status by id" do
29
+ it "should return the current status or not yet queued" do
30
+ id = subject.send_message("/synths/4/chord/notes 25 30 10")
31
+ expect_state(subject.message_status(id), :queued)
32
+ expect_state(subject.message_status('123'), :not_yet_queued)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,221 @@
1
+ require 'helper'
2
+ require 'ruby-osc'
3
+
4
+ class DummyPersister
5
+ attr_accessor :queue, :sent_messages, :message_states
6
+ def initialize
7
+ @queue, @sent_messages, @message_states = [],[], {}
8
+ end
9
+
10
+ def add(message)
11
+ @queue << message
12
+ @message_states[message] = :queued
13
+ end
14
+
15
+ def queued_messages
16
+ @queue
17
+ end
18
+
19
+ def dequeue(id, mark_as_sent = false)
20
+ message = @queue.find{|m| m.id == id}
21
+ if !message.nil?
22
+ @message_states[message] = (mark_as_sent ? :sent : :sending)
23
+ @sent_messages << message
24
+ @queue.delete(message)
25
+ end
26
+ end
27
+
28
+ def mark_as_sent(id)
29
+ message = @message_states.keys.find{|m| m.id == id}
30
+ @message_states[message] = :sent unless message.nil?
31
+ end
32
+
33
+ def status(id)
34
+ message = @message_states.keys.find{|m| m.id == id}
35
+ Bernstein::States::STATES[@message_states[message]]
36
+ end
37
+
38
+ def clear
39
+ @queue.clear
40
+ @sent_messages.clear
41
+ @message_states.clear
42
+ end
43
+ end
44
+
45
+ class DummyOSCConnection
46
+ attr_accessor :fail_send, :sent_messages, :sent_ids
47
+ def initialize
48
+ @fail_send, @sent_messages, @sent_ids = false, [], []
49
+ end
50
+
51
+ def send_message message, with_message_id = true
52
+ if @fail_send
53
+ throw "something went wrong"
54
+ else
55
+ @sent_messages << message
56
+ @sent_ids << message.id if with_message_id
57
+ end
58
+ end
59
+ end
60
+
61
+ describe Bernstein::Message do
62
+ subject { Bernstein::Message }
63
+
64
+ before(:all) do
65
+ @mock_queue = DummyPersister.new
66
+ @mock_osc_connection = DummyOSCConnection.new
67
+ @real_persister = Bernstein::Message.class_variable_get('@@persister')
68
+ @real_osc_connection = Bernstein::Message.class_variable_get('@@osc_connection')
69
+ Bernstein::Message.class_variable_set('@@persister', @mock_queue)
70
+ Bernstein::Message.class_variable_set('@@osc_connection', @mock_osc_connection)
71
+ end
72
+
73
+ after(:all) do
74
+ Bernstein::Message.class_variable_set('@@persister', @real_persister)
75
+ Bernstein::Message.class_variable_set('@@osc_connection', @real_osc_connection)
76
+ end
77
+
78
+ before(:each) do
79
+ @mock_queue.clear
80
+ @message = Bernstein::Message.build_from_string("/test 1 2 3")
81
+ end
82
+
83
+ describe "building a new message" do
84
+ it "should be able to be built from a message string and turn all parameters into floats" do
85
+ address = "/test/this/out"
86
+ args = ['1', '2', '3.5']
87
+ message = subject.build_from_string("#{address} #{args.join(' ')}")
88
+ expect(message.osc_message.address).to eq(address)
89
+ expect(message.osc_message.args).to eq(args.map{|a| a.to_f})
90
+ end
91
+
92
+ it "should be able to built from address and args, preserving types" do
93
+ address = "/test/this/out"
94
+ args = [5, 'pizza', 9.9, 2.0]
95
+ message = subject.build(address,*args)
96
+ expect(message.osc_message.address).to eq(address)
97
+ expect(message.osc_message.args).to eq(args)
98
+ end
99
+
100
+ it "should be able to be built from an already built osc message" do
101
+ msg = OSC::Message.new('/hi/msg','2',4)
102
+ message1 = subject.new(msg)
103
+ expect(message1.osc_message).to eq(msg)
104
+ end
105
+
106
+ it "should return a unique id" do
107
+ messages = []
108
+ 5.times {|i| messages << subject.build_from_string("/test #{i}")}
109
+ messages.each do |message|
110
+ expect(message.id).to_not be_nil
111
+ (messages - [message]).each{|other_message| expect(other_message.id).to_not eq(message.id)}
112
+ end
113
+ end
114
+
115
+ it "should be able to be built with a set id" do
116
+ address = "/test/this/out"
117
+ args = ['1', '2', '3']
118
+ id = "456"
119
+ osc_msg = OSC::Message.new(address, *args)
120
+ message = subject.new osc_msg, id
121
+ expect(message.id).to eq(id)
122
+ expect(message.osc_message.address).to eq(address)
123
+ expect(message.osc_message.args).to eq(args)
124
+ end
125
+
126
+ it "should be equal to another message with the same id, args and address" do
127
+ message1 = subject.build_from_string("/test 1 2 3")
128
+ message2 = subject.new(OSC::Message.new("/test", 1,2,3), message1.id)
129
+ message3 = subject.new(message2.osc_message, '999')
130
+ expect(message1).to eq(message2)
131
+ expect(message1).to_not eq(message3)
132
+ end
133
+ end
134
+
135
+ describe "serialization" do
136
+ it "should serialize and deserialize the osc message and id" do
137
+ @message = subject.build_from_string("/test 1 2 3.2345")
138
+ serialized_msg = @message.serialize
139
+ expect(@message).to eq(subject.deserialize(serialized_msg))
140
+ end
141
+
142
+ it "should handle float, integer and string arguments" do
143
+ @message = subject.build("/test", 3.4567, 10, 'a_string')
144
+ serialized_msg = @message.serialize
145
+ expect(@message).to eq(subject.deserialize(serialized_msg))
146
+ end
147
+ end
148
+
149
+ describe "saving a message" do
150
+ it "should add message onto the queue upon save" do
151
+ @message.save!
152
+ expect(@mock_queue.queue).to include(@message)
153
+ end
154
+
155
+ it "should not be able to add the same message onto the queue again" do
156
+ @message.save!
157
+ expect{@message.save!}.to_not change(@mock_queue.queue, :size)
158
+ end
159
+ end
160
+
161
+ describe "sending a message" do
162
+ before(:each) do
163
+ @message.save!
164
+ end
165
+
166
+ after(:each) do
167
+ @mock_osc_connection.fail_send = false
168
+ end
169
+
170
+ it "should send the message and message id on the OSC connection and remove from queue" do
171
+ @message.send!
172
+ expect(@mock_osc_connection.sent_messages).to include(@message)
173
+ expect(@mock_osc_connection.sent_ids).to include(@message.id)
174
+ expect(@mock_queue.sent_messages).to include(@message)
175
+ end
176
+
177
+ it "should not mark the message as sent on the queue if there is an error" do
178
+ @mock_osc_connection.fail_send = true
179
+ @message.send rescue
180
+ expect(@mock_queue.sent_messages).to_not include(@message)
181
+ expect(@mock_osc_connection.sent_messages).to_not include(@message)
182
+ expect(@mock_osc_connection.sent_ids).to_not include(@message.id)
183
+ end
184
+
185
+ it "should send the message and mark as sent and not ask to send message id along" do
186
+ @message.send!(false)
187
+ expect_state(@message.status, :sent)
188
+ expect(@mock_osc_connection.sent_messages).to include(@message)
189
+ expect(@mock_osc_connection.sent_ids).to_not include(@message.id)
190
+ end
191
+ end
192
+
193
+ describe "querying status" do
194
+ before(:each) do
195
+ @message.save!
196
+ end
197
+
198
+ it "should return the current status for a message object" do
199
+ expect_state(@message.status, :queued)
200
+ @message.send!
201
+ expect_state(@message.status, :sending)
202
+ @mock_queue.mark_as_sent(@message.id)
203
+ expect_state(@message.status, :sent)
204
+ end
205
+
206
+ it "should return the current status by message id" do
207
+ expect_state(subject.get_status(@message.id), :queued)
208
+ @message.send!(false)
209
+ expect_state(subject.get_status(@message.id), :sent)
210
+ end
211
+ end
212
+
213
+ describe "getting queued messages" do
214
+ it "should return all queued messages" do
215
+ @message.save!
216
+ @message2 = subject.build_from_string("/test/2 4 5 6")
217
+ @message2.save!
218
+ expect(subject.get_queued_messages).to eq([@message, @message2])
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,31 @@
1
+ require 'helper'
2
+
3
+ def current_client
4
+ Bernstein::OSCConnection.instance_variable_get('@connection')
5
+ end
6
+
7
+ describe Bernstein::OSCConnection do
8
+ describe "configuring" do
9
+ it "should instantiate a new osc client based on the port and host that was given" do
10
+ expect(OSC::Client).to receive(:new).with(9000, '10.10.10.1')
11
+ Bernstein::OSCConnection.configure!({port: 9000, host: '10.10.10.1'})
12
+ end
13
+ end
14
+
15
+ describe "sending a message" do
16
+ it "should create a new osc message from the passed in message and call send on the client" do
17
+ Bernstein::OSCConnection.configure!({port: 9000, host: '10.10.10.1'})
18
+ expect(OSC::Message).to receive(:new).with('/test', 1.0,2.0,3.0).and_return("mock")
19
+ message1 = Bernstein::Message.build_from_string("/test 1 2 3")
20
+ expect(current_client).to receive(:send).with("mock")
21
+ Bernstein::OSCConnection.send_message(message1, false)
22
+ end
23
+
24
+ it "should send a bundle when asked to send message id" do
25
+ Bernstein::OSCConnection.configure!({port: 9000, host: '10.10.10.1'})
26
+ message1 = Bernstein::Message.build_from_string("/test 1 2 3")
27
+ expect(current_client).to receive(:send).with(kind_of(OSC::Bundle))
28
+ Bernstein::OSCConnection.send_message(message1, true)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,125 @@
1
+ require 'helper'
2
+
3
+ def redis_connection
4
+ Bernstein::RedisQueue.instance_variable_get('@redis')
5
+ end
6
+
7
+ def redis_queue
8
+ redis_connection.smembers(Bernstein::RedisQueue::QUEUE_SET)
9
+ end
10
+
11
+ describe Bernstein::RedisQueue do
12
+ subject { Bernstein::RedisQueue }
13
+
14
+ before(:each) do
15
+ @message = Bernstein::Message.build_from_string("/test/3 1 2.55 4")
16
+ end
17
+
18
+ describe "initialization and configuration" do
19
+ after(:all){Bernstein::RedisQueue.configure!({redis:{}})}
20
+
21
+ it "should pass redis options to initialize a new redis connection" do
22
+ redis_opts = {host: "10.0.1.1", port: 6380, db: 15}
23
+ expect(Redis).to receive(:new).with(redis_opts).and_call_original
24
+ Bernstein::RedisQueue.configure!({key_expiry: 600, redis: redis_opts})
25
+ end
26
+
27
+ it "should be namespaced" do
28
+ expect(redis_connection.class).to eq(Redis::Namespace)
29
+ end
30
+ end
31
+
32
+ describe "adding a new message" do
33
+ it "should serialize the message and add it to the proper sets" do
34
+ subject.add(@message)
35
+ data = redis_connection.get(@message.id)
36
+ expect(Bernstein::Message.deserialize(data)).to eq(@message)
37
+ expect(redis_queue).to include(@message.id)
38
+ end
39
+ end
40
+
41
+ describe "expired messages" do
42
+ before(:all) do
43
+ @key_expiry = 1
44
+ Bernstein::RedisQueue.configure! key_expiry: @key_expiry
45
+ end
46
+
47
+ after(:all) do
48
+ Bernstein::RedisQueue.configure! key_expiry: 300
49
+ end
50
+
51
+ it "should expire messages based on set expiry times" do
52
+ subject.add(@message)
53
+ expect_state(subject.status(@message.id), :queued)
54
+ expect(redis_connection.get(@message.id)).to_not be_nil
55
+ sleep @key_expiry + 1
56
+ expect(redis_connection.get(@message.id)).to be_nil
57
+ expect_state(subject.status(@message.id), :not_yet_queued)
58
+ end
59
+
60
+ it "should expire messages that have had their status changed" do
61
+ subject.add(@message)
62
+ subject.mark_as_sent(@message.id)
63
+ expect_state(subject.status(@message.id), :sent)
64
+ sleep @key_expiry + 1
65
+ expect_state(subject.status(@message.id), :not_yet_queued)
66
+ end
67
+
68
+ it "should clean up request_queue of unhandled expired messages" do
69
+ subject.add(@message)
70
+ expect(subject.queued_messages).to include(@message)
71
+ sleep @key_expiry + 1
72
+ @another_message = Bernstein::Message.build_from_string("/test/current 5 6 7")
73
+ subject.add(@another_message)
74
+ queued_messages = subject.queued_messages
75
+ queue_set = redis_queue
76
+ expect(queued_messages).to_not include(@message)
77
+ expect(queued_messages).to include(@another_message)
78
+ expect(queue_set).to_not include(@message.id)
79
+ expect(queue_set).to include(@another_message.id)
80
+ end
81
+ end
82
+
83
+ describe "marking and requesting status" do
84
+ it "should return not yet sent queued for unknown messages" do
85
+ expect_state(subject.status('123456'), :not_yet_queued)
86
+ end
87
+
88
+ it "should set new messages' status to queued" do
89
+ subject.add(@message)
90
+ expect_state(subject.status(@message.id), :queued)
91
+ end
92
+
93
+ it "should mark a message as sent" do
94
+ subject.add(@message)
95
+ subject.mark_as_sent(@message.id)
96
+ expect_state(subject.status(@message.id), :sent)
97
+ end
98
+
99
+ it "should dequeue messages and set them to sending state by default" do
100
+ subject.add(@message)
101
+ subject.dequeue(@message.id)
102
+ expect_state(subject.status(@message.id), :sending)
103
+ expect(redis_queue).to_not include(@message.id)
104
+ end
105
+
106
+ it "should dequeue messages and set their state straight to sent" do
107
+ subject.add(@message)
108
+ subject.dequeue(@message.id, true)
109
+ expect_state(subject.status(@message.id), :sent)
110
+ expect(redis_queue).to_not include(@message.id)
111
+ end
112
+ end
113
+
114
+ describe "getting queued messages" do
115
+ it "should pull queued messages and deserialize them" do
116
+ subject.clear
117
+ message = Bernstein::Message.build_from_string("/test/1 1")
118
+ message2 = Bernstein::Message.build_from_string("/test/2 1 2")
119
+ message3 = Bernstein::Message.build_from_string("/test/3 1 2 3")
120
+ messages = [message, message2, message3]
121
+ messages.each{|m| subject.add(m)}
122
+ expect(subject.queued_messages.sort{|a,b| a.id <=> b.id}).to eq(messages.sort{|a,b| a.id <=> b.id})
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,91 @@
1
+ require 'helper'
2
+ require 'ruby-osc'
3
+ require 'eventmachine'
4
+
5
+ describe Bernstein::Server do
6
+ describe "initialization and configuration" do
7
+ it "should pass options to initialize a new OSC server" do
8
+ opts = {port: 9004, host: '127.0.0.1'}
9
+ expect(OSC::Server).to receive(:new).with(opts[:port], opts[:host]).and_call_original
10
+ Bernstein::Server.configure!(opts)
11
+ Bernstein::Server.start do
12
+ Bernstein::Server.stop
13
+ EventMachine.stop_event_loop
14
+ end
15
+ end
16
+ end
17
+
18
+ describe "processing queued messages" do
19
+ before(:each) do
20
+ connection = Bernstein::Message.class_variable_get('@@osc_connection')
21
+ allow(connection).to receive(:send_message)
22
+ Bernstein::RedisQueue.clear
23
+ @message = Bernstein::Client.send_message_by_string("/test/1 1")
24
+ @message2 = Bernstein::Client.send_message_by_string("/test/2 1 2")
25
+ @message3 = Bernstein::Client.send_message_by_string("/test/3 1 2 3")
26
+ [@message, @message2, @message3].each do |message_id|
27
+ expect_state(Bernstein::Client.message_status(message_id), :queued)
28
+ end
29
+ end
30
+
31
+
32
+ it "should call send on all queued messages" do
33
+ Bernstein::Server.configure!({poll_interval:1, require_awks:true})
34
+ Bernstein::Server.start do
35
+ EventMachine.add_timer 4 do
36
+ Bernstein::Server.stop
37
+ EventMachine.stop_event_loop
38
+ end
39
+ end
40
+ [@message, @message2, @message3].each do |message_id|
41
+ expect_state(Bernstein::Client.message_status(message_id), :sending)
42
+ end
43
+ end
44
+ end
45
+
46
+ describe "awks" do
47
+ before(:each) do
48
+ connection = Bernstein::Message.class_variable_get('@@osc_connection')
49
+ allow(connection).to receive(:send_message)
50
+ Bernstein::RedisQueue.clear
51
+ @message_id = Bernstein::Client.send_message_by_string("/test/1 1")
52
+ expect_state(Bernstein::Client.message_status(@message_id), :queued)
53
+ end
54
+
55
+ it "should listen for awks and set messages with corresponding ids to sent if configured" do
56
+ Bernstein::Server.configure!({port: 9090, host: '127.0.0.1', poll_interval: 1, require_awks: true})
57
+ Bernstein::Server.start do
58
+ client = OSC::Client.new 9090
59
+ EventMachine.add_timer 2 do
60
+ expect_state(Bernstein::Client.message_status(@message_id), :sending)
61
+ end
62
+ EventMachine.add_timer 3 do
63
+ client.send OSC::Message.new('/awk_id', @message_id)
64
+ end
65
+ EventMachine.add_timer 4 do
66
+ Bernstein::Server.stop
67
+ EventMachine.stop_event_loop
68
+ end
69
+ end
70
+ expect_state(Bernstein::Client.message_status(@message_id), :sent)
71
+ end
72
+
73
+ it "should not listen for awks and set messages directly to sent if configured" do
74
+ Bernstein::Server.configure!({port: 9090, host: '127.0.0.1', require_awks: false, poll_interval: 1})
75
+ Bernstein::Server.start do
76
+ client = OSC::Client.new 9090
77
+ EventMachine.add_timer 2 do
78
+ expect_state(Bernstein::Client.message_status(@message_id), :sent)
79
+ end
80
+ EventMachine.add_timer 3 do
81
+ client.send OSC::Message.new('/awk_id', @message_id)
82
+ end
83
+ EventMachine.add_timer 4 do
84
+ Bernstein::Server.stop
85
+ EventMachine.stop_event_loop
86
+ end
87
+ end
88
+ expect_state(Bernstein::Client.message_status(@message_id), :sent)
89
+ end
90
+ end
91
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'rspec'
2
+ $:.unshift( File.join( File.dirname( __FILE__), '..', 'lib' ) )
3
+ require 'bernstein'
4
+
5
+ RSpec.configure do |c|
6
+ c.mock_with :rspec
7
+ end
8
+
9
+ Bernstein.configure!
10
+
11
+ include Bernstein::States
12
+
13
+ def expect_state(something, state)
14
+ expect(something).to eq(STATES[state])
15
+ end
metadata ADDED
@@ -0,0 +1,171 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bernstein
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Anthony Plekhov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redis
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 3.1.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 3.1.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: redis-namespace
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: 1.5.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: 1.5.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: eventmachine
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: 1.0.3
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: 1.0.3
55
+ - !ruby/object:Gem::Dependency
56
+ name: json
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 1.8.1
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: 1.8.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: daemons
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: 1.1.9
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: 1.1.9
83
+ - !ruby/object:Gem::Dependency
84
+ name: ruby-osc
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: 0.31.0
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: 0.31.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: 3.1.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: 3.1.0
111
+ description: Ruby OSC message queue
112
+ email:
113
+ - anthony.plekhov@gmail.com
114
+ executables:
115
+ - bernstein
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - .gitignore
120
+ - Gemfile
121
+ - LICENSE
122
+ - README.md
123
+ - Rakefile
124
+ - bernstein.gemspec
125
+ - bernstein.sample.yml
126
+ - bin/bernstein
127
+ - lib/bernstein.rb
128
+ - lib/bernstein/client.rb
129
+ - lib/bernstein/message.rb
130
+ - lib/bernstein/osc_connection.rb
131
+ - lib/bernstein/redis_queue.rb
132
+ - lib/bernstein/server.rb
133
+ - lib/bernstein/states.rb
134
+ - lib/bernstein/version.rb
135
+ - spec/bernstein/client_spec.rb
136
+ - spec/bernstein/message_spec.rb
137
+ - spec/bernstein/osc_connection_spec.rb
138
+ - spec/bernstein/redis_queue_spec.rb
139
+ - spec/bernstein/server_spec.rb
140
+ - spec/helper.rb
141
+ homepage:
142
+ licenses:
143
+ - MIT
144
+ metadata: {}
145
+ post_install_message:
146
+ rdoc_options: []
147
+ require_paths:
148
+ - lib
149
+ required_ruby_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - '>='
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ required_rubygems_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - '>='
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ requirements: []
160
+ rubyforge_project:
161
+ rubygems_version: 2.4.2
162
+ signing_key:
163
+ specification_version: 4
164
+ summary: Ruby OSC message queue
165
+ test_files:
166
+ - spec/bernstein/client_spec.rb
167
+ - spec/bernstein/message_spec.rb
168
+ - spec/bernstein/osc_connection_spec.rb
169
+ - spec/bernstein/redis_queue_spec.rb
170
+ - spec/bernstein/server_spec.rb
171
+ - spec/helper.rb