bernstein 0.0.1

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