Skip to content

Commit

Permalink
Merge branch 'release/0.3.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
bklang committed May 21, 2015
2 parents ea92526 + 200daab commit 270e594
Show file tree
Hide file tree
Showing 13 changed files with 217 additions and 43 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Expand Up @@ -3,6 +3,8 @@ rvm:
- 1.9.2
- 1.9.3
- 2.0.0
- 2.1.0
- 2.2.0
- jruby-19mode
- rbx-19mode
- ruby-head
Expand Down
4 changes: 3 additions & 1 deletion CHANGELOG.md
@@ -1,4 +1,6 @@
# develop
# Version 0.3.0
* Feature: added multi-digit patterns
* Feature: added inter-digit timeout to support multi-digit patterns

# Version 0.2.1
* Don't require manual loading of `DialWithApps`
Expand Down
1 change: 1 addition & 0 deletions lib/matrioska.rb
Expand Up @@ -3,3 +3,4 @@
require "matrioska/plugin"
require "matrioska/app_runner"
require "matrioska/dial_with_apps"
require "matrioska/core_ext/adhearsion_call"
70 changes: 49 additions & 21 deletions lib/matrioska/app_runner.rb
Expand Up @@ -2,28 +2,36 @@ module Matrioska
class AppRunner
include Adhearsion::CallController::Utility

VALID_DIGITS = /[^0-9*#]/

def initialize(call)
@call = call
register_runner_with_call
@app_map = {}
@running = false
end

def start
if started? && running?
logger.warn "Already-active runner #{self} received start event."
return
end

@state = :started
logger.debug "MATRIOSKA START CALLED"
unless @running
@component = Punchblock::Component::Input.new mode: :dtmf, grammar: { value: grammar_accept }
logger.debug "MATRIOSKA STARTING LISTENER"
@component.register_event_handler Punchblock::Event::Complete do |event|
handle_input_complete event
end
@call.write_and_await_response @component if @call.active?
logger.debug "MATRIOSKA STARTING LISTENER"
@component = Punchblock::Component::Input.new mode: :dtmf, inter_digit_timeout: Adhearsion.config[:matrioska].timeout * 1_000, grammar: { value: build_grammar }
@component.register_event_handler Punchblock::Event::Complete do |event|
handle_input_complete event
end
@call.write_and_await_response @component if @call.alive? && @call.active?
end

def stop!
@state = :stopped
@component.stop! if @component && @component.executing?
@component.stop! if running?
end

def running?
!!(@component && @component.executing?)
end

def status
Expand All @@ -38,12 +46,12 @@ def stopped?
@state == :stopped
end

def map_app(digit, controller = nil, &block)
digit = digit.to_s
def map_app(pattern, controller = nil, &block)
pattern = pattern.to_s
range = "1234567890*#"

unless range.include?(digit) && digit.size == 1
raise ArgumentError, "The first argument should be a single digit String or number in the range 1234567890*#"
if VALID_DIGITS.match(pattern)
raise ArgumentError, "The first argument should be a String or number containing only 1234567890*#"
end

payload = if block_given?
Expand All @@ -54,18 +62,18 @@ def map_app(digit, controller = nil, &block)
controller
end

@app_map[digit] = payload
@app_map[pattern] = payload
end

def handle_input_complete(event)
if @state == :stopped
logger.warn "Stopped runner #{self} received event."
logger.warn "Stopped runner #{self} received stop event."
return
end
logger.debug "MATRIOSKA HANDLING INPUT"
result = event.reason.respond_to?(:utterance) ? event.reason.utterance : nil
digit = parse_dtmf result
match_and_run digit unless @running
match_and_run digit
end

private
Expand All @@ -77,12 +85,11 @@ def app_map
def match_and_run(digit)
if match = @app_map[digit]
logger.debug "MATRIOSKA #match_and_run called with #{digit}"
@running = true
callback = lambda do |call|
@running = false
logger.debug "MATRIOSKA CALLBACK RESTARTING LISTENER"
if call.active?
start unless stopped?
if call.alive? && call.active? && started?
logger.debug "MATRIOSKA CALLBACK RESTARTING LISTENER"
start
else
logger.debug "MATRIOSKA CALLBACK NOT DOING ANYTHING BECAUSE CALL IS DEAD"
end
Expand All @@ -104,5 +111,26 @@ def match_and_run(digit)
rescue Adhearsion::Call::Hangup
logger.debug "Matrioska terminated because the call was disconnected"
end

def build_grammar
current_app_map = app_map
RubySpeech::GRXML.draw mode: :dtmf, root: 'options' do
rule id: 'options', scope: 'public' do
one_of do
current_app_map.keys.each do |index|
item do
index
end
end
end
end
end
end

def register_runner_with_call
@call[:matrioska_runners] ||= []
@call[:matrioska_runners] << self
end

end
end
15 changes: 15 additions & 0 deletions lib/matrioska/core_ext/adhearsion_call.rb
@@ -0,0 +1,15 @@
require 'adhearsion'

class Adhearsion::Call
def stop_all_runners!
return unless variables[:matrioska_runners]

variables[:matrioska_runners].each do |runner|
runner.stop!
end
end

def runners
Array(variables[:matrioska_runners])
end
end
16 changes: 10 additions & 6 deletions lib/matrioska/dial_with_apps.rb
Expand Up @@ -38,15 +38,19 @@ def dial_with_apps(to, options = {}, &block)
dial = Adhearsion::CallController::Dial::ParallelConfirmationDial.new to, options, call
yield dial

local_runner = Matrioska::AppRunner.new call
@local_runner_block.call local_runner
local_runner.start
if @local_runner_block
local_runner = Matrioska::AppRunner.new call
@local_runner_block.call local_runner
local_runner.start
end

dial.prep_calls do |new_call|
new_call.on_joined call do
remote_runner = Matrioska::AppRunner.new new_call
@remote_runner_block.call remote_runner
remote_runner.start
if @remote_runner_block
remote_runner = Matrioska::AppRunner.new new_call
@remote_runner_block.call remote_runner
remote_runner.start
end
end
end

Expand Down
4 changes: 4 additions & 0 deletions lib/matrioska/plugin.rb
Expand Up @@ -8,5 +8,9 @@ class Plugin < Adhearsion::Plugin
end
end
end

config :matrioska do
timeout 2, desc: "Time (in seconds) to wait between each digit before trying to resolve match", transform: Proc.new { |v| v.to_i }
end
end
end
2 changes: 1 addition & 1 deletion lib/matrioska/version.rb
@@ -1,3 +1,3 @@
module Matrioska
VERSION = "0.2.1"
VERSION = "0.3.0"
end
2 changes: 1 addition & 1 deletion matrioska.gemspec
Expand Up @@ -7,7 +7,7 @@ Gem::Specification.new do |s|
s.version = Matrioska::VERSION
s.authors = ["Luca Pradovera"]
s.email = ["lpradovera@mojolingo.com"]
s.homepage = "https://github.com/polysics/matrioska"
s.homepage = "https://github.com/adhearsion/matrioska"
s.summary = %q{Adhearsion plugin for in-call apps}
s.description = %q{Adhearsion plugin for in-call apps. Provides a features-style interface to run applications in calls.}
s.license = 'MIT'
Expand Down
43 changes: 30 additions & 13 deletions spec/matrioska/app_runner_spec.rb
Expand Up @@ -18,27 +18,39 @@ def run
end

describe "#start" do
before do
subject.map_app("34*") { call.do_stuff_from_a_block }
subject.map_app(5, MockController)
end

let(:grxml) {
RubySpeech::GRXML.draw mode: 'dtmf', root: 'inputdigits' do
rule id: 'inputdigits', scope: 'public' do
RubySpeech::GRXML.draw mode: :dtmf, root: 'options' do
rule id: 'options', scope: 'public' do
one_of do
0.upto(9) { |d| item { d.to_s } }
item { "#" }
item { "*" }
item { "34*" }
item { "5" }
end
end
end
}

let(:input_component) {
Punchblock::Component::Input.new mode: :dtmf, grammar: { value: grxml }
Punchblock::Component::Input.new mode: :dtmf, inter_digit_timeout: Adhearsion.config[:matrioska].timeout.to_i * 1_000, grammar: { value: grxml }
}

it "should start the appropriate component" do
call.should_receive(:write_and_await_response).with(input_component)
subject.start
subject.status.should == :started
end

it "should do nothing if #start is called twice" do
call.should_receive(:write_and_await_response).once.with(input_component)
subject.should_receive(:running?).once.and_return(true)
subject.start
subject.start
end

end

describe "#stop!" do
Expand All @@ -60,12 +72,15 @@ def run

describe "#map_app" do
context "with invalid input" do
let(:too_long) { "ab" }
let(:wrong) { "a" }
let(:long_pattern) { "*99" }
let(:wrong) { "a12" }

it "should raise if the first argument has invalid characters" do
expect { subject.map_app(wrong) {} }.to raise_error ArgumentError, "The first argument should be a String or number containing only 1234567890*#"
end

it "should raise if the first argument is not a single digit string in the range" do
expect { subject.map_app(too_long) {} }.to raise_error ArgumentError, "The first argument should be a single digit String or number in the range 1234567890*#"
expect { subject.map_app(wrong) {} }.to raise_error ArgumentError, "The first argument should be a single digit String or number in the range 1234567890*#"
it "should not raise if the first argument has multiple valid digits" do
expect { subject.map_app(long_pattern) {} }.to_not raise_error
end

it "raises if called without either a class or a block" do
Expand All @@ -84,7 +99,7 @@ def mock_event(digit)
end

before do
subject.map_app(3) { call.do_stuff_from_a_block }
subject.map_app("34*") { call.do_stuff_from_a_block }
subject.map_app(5, MockController)
end

Expand All @@ -106,13 +121,15 @@ def mock_event(digit)

it "executes the block if the payload is a Proc" do
call.should_receive(:do_stuff_from_a_block).once
subject.should_receive(:started?).and_return true
subject.should_receive(:start).once
subject.handle_input_complete mock_event("3")
subject.handle_input_complete mock_event("34*")
sleep 0.1 # Give the controller time to finish and the callback to fire
end

it "executes the controller if the payload is a Class" do
call.should_receive(:do_stuff_from_a_class).once
subject.should_receive(:started?).and_return true
subject.should_receive(:start).once
subject.handle_input_complete mock_event("5")
sleep 0.1 # Give the controller time to finish and the callback to fire
Expand Down
26 changes: 26 additions & 0 deletions spec/matrioska/core_ext/adhearsion_call_spec.rb
@@ -0,0 +1,26 @@
require 'adhearsion'
require 'matrioska'

describe Adhearsion::Call do
let(:call) { Adhearsion::Call.new }


it 'should track all runners launched with it' do
runner = Matrioska::AppRunner.new call
call.runners.should include(runner)
end

it 'should be possible to stop all runners on a call' do
r1 = Matrioska::AppRunner.new call
r2 = Matrioska::AppRunner.new call
r3 = Matrioska::AppRunner.new call

r1.should_receive(:stop!).once
r2.should_receive(:stop!).once
r3.should_receive(:stop!).once

call.stop_all_runners!
end


end

0 comments on commit 270e594

Please sign in to comment.