electric_slide 0.0.2

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: 6b6b7652ebb0629af9310a93d7c6ac0352fd92c8
4
+ data.tar.gz: f5fd82735d7d8949b14a4ea8ee318c096820092f
5
+ SHA512:
6
+ metadata.gz: 9b9bafc665557361a51d504d71d94d2775a8e68924a22600e23a68f8b53acb7a2fcb85a3197e797e0c8af7cccc272f606d63f651c9a654f0aa9e2dc2aa69ac7a
7
+ data.tar.gz: 85da4abe01db3cd099748cc11ada5c59383c38b8ac1e1994adaa01f98d9d6af05db659e767e6dae6cdef3c4fbae42c414689d9af2d09a670142b1355ec820caa
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ *.swp
2
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+ gemspec
3
+
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2011 The Adhearsion Foundation, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,3 @@
1
+ AhnQueue - Automatic Call Distribution (ACD) Services for Adhearsion
2
+ ====================================================================
3
+
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+ # -*- ruby -*-
4
+ ENV['RUBY_FLAGS'] = "-I#{%w(lib ext bin spec).join(File::PATH_SEPARATOR)}"
5
+
6
+ require 'rubygems'
7
+ require 'bundler/gem_tasks'
8
+ require 'bundler/setup'
9
+
10
+ require 'rspec/core/rake_task'
11
+ RSpec::Core::RakeTask.new
12
+
13
+ require 'ci/reporter/rake/rspec'
14
+ task :ci => ['ci:setup:rspec', :spec]
15
+ task :default => :spec
16
+
@@ -0,0 +1 @@
1
+ # You can use this file for component-specific configuration.
@@ -0,0 +1,32 @@
1
+ require 'date'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "electric_slide"
5
+ s.version = "0.0.2"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Ben Klang"]
9
+
10
+ s.date = Date.today.to_s
11
+ s.description = "Automatic Call Distributor (ACD) for Adhearsion. Currently implements only Round Robin distribution strategies."
12
+ s.email = "dev&adhearsion.com"
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+
16
+ s.has_rdoc = true
17
+ s.homepage = "http://github.com/adhearsion/electric_slide"
18
+ s.require_paths = ["lib"]
19
+ s.rubygems_version = "1.2.0"
20
+ s.summary = "Automatic Call Distributor for Adhearsion"
21
+
22
+ s.add_runtime_dependency 'adhearsion'
23
+ s.add_runtime_dependency 'countdownlatch'
24
+ s.add_runtime_dependency 'activesupport'
25
+ s.add_development_dependency 'rspec', ['>= 2.5.0']
26
+ s.add_development_dependency 'flexmock', ['>= 0.9.0']
27
+ s.add_development_dependency 'ci_reporter'
28
+ s.add_development_dependency 'simplecov'
29
+ s.add_development_dependency 'simplecov-rcov'
30
+
31
+ s.specification_version = 2
32
+ end
@@ -0,0 +1,65 @@
1
+ require 'singleton'
2
+ require 'active_support/dependencies/autoload'
3
+ require 'adhearsion/foundation/thread_safety'
4
+
5
+ class ElectricSlide < Adhearsion::Plugin
6
+ extend ActiveSupport::Autoload
7
+
8
+ autoload :QueueStrategy
9
+ autoload :RoundRobin
10
+ autoload :RoundRobinMeetme
11
+
12
+ include Singleton
13
+
14
+ def initialize
15
+ @queues = {}
16
+ end
17
+
18
+ def create(name, queue_type, agent_type = Agent)
19
+ synchronize do
20
+ @queues[name] = queue_type.new unless @queues.has_key?(name)
21
+ @queues[name].extend agent_type
22
+ end
23
+ end
24
+
25
+ def get_queue(name)
26
+ synchronize do
27
+ @queues[name]
28
+ end
29
+ end
30
+
31
+ def self.method_missing(method, *args, &block)
32
+ instance.send method, *args, &block
33
+ end
34
+
35
+ module Agent
36
+ def work(agent_call)
37
+ loop do
38
+ agent_call.execute 'Bridge', @queue.next_call
39
+ end
40
+ end
41
+ end
42
+
43
+ class CalloutAgent
44
+ def work(agent_channel)
45
+ @queue.next_call.each do |next_call|
46
+ next_call.dial agent_channel
47
+ end
48
+ end
49
+ end
50
+
51
+ class MeetMeAgent
52
+ include Agent
53
+
54
+ def work(agent_call)
55
+ loop do
56
+ agent_call.join agent_conf, @queue.next_call
57
+ end
58
+ end
59
+ end
60
+
61
+ class BridgeAgent
62
+ include Agent
63
+ end
64
+ end
65
+
@@ -0,0 +1,18 @@
1
+ class ElectricSlide
2
+ module QueueStrategy
3
+ def wrap_call(call)
4
+ call = QueuedCall.new(call) unless call.respond_to?(:queued_time)
5
+ call
6
+ end
7
+
8
+ def priority_enqueue(call)
9
+ # TODO: Add this call to the front of the line
10
+ enqueue call
11
+ end
12
+
13
+ def enqueue(call)
14
+ call.hold
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,24 @@
1
+ require 'countdownlatch'
2
+
3
+ class ElectricSlide
4
+ class QueuedCall
5
+ attr_accessor :call, :queued_time
6
+
7
+ def initialize(call)
8
+ @call = call
9
+ @queued_time = Time.now
10
+ end
11
+
12
+ def hold
13
+ call.execute 'StartMusicOnHold'
14
+ @latch = CountDownLatch.new 1
15
+ @latch.wait
16
+ call.execute 'StopMusicOnHold'
17
+ end
18
+
19
+ def make_ready!
20
+ @latch.countdown!
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,46 @@
1
+ require 'thread'
2
+ require 'electric_slide/queue_strategy'
3
+
4
+ class ElectricSlide
5
+ class RoundRobin
6
+ include QueueStrategy
7
+ attr_reader :queue
8
+
9
+ def initialize
10
+ @queue = []
11
+ @agents_waiting = []
12
+ @conditional = ConditionVariable.new
13
+ end
14
+
15
+ def next_call
16
+ call = nil
17
+ synchronize do
18
+ @agents_waiting << Thread.current
19
+ @conditional.wait(@mutex) if @queue.length == 0
20
+ @agents_waiting.delete Thread.current
21
+ call = @queue.shift
22
+ end
23
+
24
+ call.make_ready!
25
+ call
26
+ end
27
+
28
+ def agents_waiting
29
+ synchronize do
30
+ @agents_waiting.dup
31
+ end
32
+ end
33
+
34
+ # TODO: Add mechanism to add calls with higher priority to the front of the queue.
35
+
36
+ def enqueue(call)
37
+ call = wrap_call(call)
38
+ synchronize do
39
+ @queue << call
40
+ @conditional.signal if @queue.length == 1
41
+ end
42
+ super
43
+ end
44
+ end
45
+ end
46
+
@@ -0,0 +1,34 @@
1
+ class ElectricSlide
2
+ class RoundRobinMeetme
3
+ include QueueStrategy
4
+
5
+ def initialize(call)
6
+ @queue = []
7
+ end
8
+
9
+ def next_call
10
+ call = synchronize do
11
+ @queue.pop
12
+ end
13
+
14
+ call.make_ready!
15
+ call
16
+ end
17
+
18
+ def priority_enqueue(call)
19
+ call = wrap_call(call)
20
+
21
+ synchronize do
22
+ @queue.unshift call
23
+ end
24
+ super
25
+ end
26
+
27
+ def enqueue(call)
28
+ synchronize do
29
+ @queue << call
30
+ end
31
+ end
32
+ end
33
+ end
34
+
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe ElectricSlide::QueueStrategy do
4
+ include ElectricSlide::QueueStrategy
5
+
6
+ describe '#wrap_call' do
7
+ it 'should pass through a QueuedCall object' do
8
+ obj = ElectricSlide::QueuedCall.new dummy_call
9
+ wrap_call(obj).should be obj
10
+ end
11
+
12
+ it 'should wrap any object that does not respond to #queued_time' do
13
+ wrap_call(dummy_call).should be_a ElectricSlide::QueuedCall
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe ElectricSlide::QueuedCall do
4
+ it 'should initialize the queued_time to the current time' do
5
+ now = Time.now
6
+ flexmock(Time).should_receive(:now).once.and_return now
7
+ qcall = ElectricSlide::QueuedCall.new dummy_call
8
+ qcall.instance_variable_get(:@queued_time).should == now
9
+ end
10
+
11
+ it 'should start and stop music on hold when put on hold and released' do
12
+ # Both tests are combined here so we do not leave too many suspended threads lying about
13
+ queued_caller = dummy_call
14
+ flexmock(queued_caller).should_receive(:execute).once.with('StartMusicOnHold')
15
+ flexmock(queued_caller).should_receive(:execute).once.with('StopMusicOnHold')
16
+ qcall = ElectricSlide::QueuedCall.new queued_caller
17
+
18
+ # Place the call on hold and wait for it to enqueue
19
+ Thread.new { qcall.hold }
20
+ sleep 0.5
21
+
22
+ # Release the call from being on hold and sleep to ensure we get the Stop MOH signal
23
+ qcall.make_ready!
24
+ sleep 0.5
25
+ end
26
+
27
+ it 'should block the call when put on hold' do
28
+ queued_caller = dummy_call
29
+ flexmock(queued_caller).should_receive(:execute).once.with('StartMusicOnHold')
30
+ flexmock(queued_caller).should_receive(:execute).once.with('StopMusicOnHold')
31
+ qcall = ElectricSlide::QueuedCall.new queued_caller
32
+
33
+ hold_thread = Thread.new { qcall.hold }
34
+
35
+ # Give the holding thread a chance to block...
36
+ sleep 0.5
37
+ hold_thread.status.should == "sleep"
38
+ qcall.make_ready!
39
+ sleep 0.5
40
+ hold_thread.status.should be false
41
+ end
42
+ end
@@ -0,0 +1,87 @@
1
+ require 'spec_helper'
2
+
3
+ describe ElectricSlide::RoundRobin do
4
+ def dummy_queued_call
5
+ dqc = ElectricSlide::QueuedCall.new dummy_call
6
+ flexmock(dqc).should_receive(:hold).once
7
+ flexmock(dqc).should_receive(:make_ready!).once
8
+ dqc
9
+ end
10
+
11
+ before :each do
12
+ @queue = ElectricSlide::RoundRobin.new
13
+ end
14
+
15
+ describe "Queue is empty at start" do
16
+ subject { ElectricSlide::RoundRobin.new }
17
+ its(:queue) { should have(0).items }
18
+ end
19
+
20
+ it 'should properly enqueue a call' do
21
+ call = ElectricSlide::QueuedCall.new dummy_call
22
+ flexmock(call).should_receive(:hold).once
23
+ @queue.enqueue call
24
+ @queue.instance_variable_get(:@queue).first.should be call
25
+ end
26
+
27
+ it 'should return the call object that is passed in' do
28
+ call = dummy_queued_call
29
+ @queue.enqueue call
30
+ @queue.next_call.should be call
31
+ end
32
+
33
+ it 'should block an agent requesting a call until a call becomes available' do
34
+ call = dummy_queued_call
35
+ agent_thread = Thread.new { @queue.next_call }
36
+
37
+ # Give the agent thread a chance to block...
38
+ sleep 0.5
39
+
40
+ @queue.agents_waiting.count.should == 1
41
+
42
+ @queue.enqueue call
43
+
44
+ # Give the agent thread a chance to retrieve the call...
45
+ sleep 0.5
46
+ @queue.agents_waiting.count.should == 0
47
+ agent_thread.kill
48
+ end
49
+
50
+ it 'should unblock only one agent per call entering the queue' do
51
+ call = dummy_queued_call
52
+ agent1_thread = Thread.new { @queue.next_call }
53
+ agent2_thread = Thread.new { @queue.next_call }
54
+
55
+ # Give the agent threads a chance to block...
56
+ sleep 0.5
57
+
58
+ @queue.agents_waiting.count.should == 2
59
+
60
+ @queue.enqueue call
61
+
62
+ # Give the agent thread a chance to retrieve the call...
63
+ sleep 0.5
64
+ @queue.agents_waiting.count.should == 1
65
+ agent1_thread.kill
66
+ agent2_thread.kill
67
+ end
68
+
69
+ it 'should properly enqueue calls and return them in the same order' do
70
+ call1 = dummy_queued_call
71
+ call2 = dummy_queued_call
72
+ call3 = dummy_queued_call
73
+ threads = {}
74
+
75
+ threads[:call1] = Thread.new { @queue.enqueue call1 }
76
+ sleep 0.5
77
+ threads[:call2] = Thread.new { @queue.enqueue call2 }
78
+ sleep 0.5
79
+ threads[:call3] = Thread.new { @queue.enqueue call3 }
80
+ sleep 0.5
81
+
82
+
83
+ @queue.next_call.should be call1
84
+ @queue.next_call.should be call2
85
+ @queue.next_call.should be call3
86
+ end
87
+ end
@@ -0,0 +1 @@
1
+ require 'spec_helper'
@@ -0,0 +1,25 @@
1
+ $:.push File.join(File.dirname(__FILE__), '..', 'lib')
2
+ Thread.abort_on_exception = true
3
+
4
+ %w{
5
+ adhearsion
6
+ electric_slide
7
+ electric_slide/queued_call
8
+ electric_slide/queue_strategy
9
+ electric_slide/round_robin
10
+ rspec/core
11
+ flexmock
12
+ flexmock/rspec
13
+ }.each { |r| require r }
14
+
15
+ RSpec.configure do |config|
16
+ config.mock_framework = :flexmock
17
+ config.filter_run :focus => true
18
+ config.run_all_when_everything_filtered = true
19
+ config.color_enabled = true
20
+ end
21
+
22
+ def dummy_call
23
+ Object.new
24
+ end
25
+
metadata ADDED
@@ -0,0 +1,173 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: electric_slide
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Ben Klang
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: adhearsion
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: countdownlatch
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 2.5.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: 2.5.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: flexmock
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: 0.9.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: 0.9.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: ci_reporter
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: simplecov-rcov
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Automatic Call Distributor (ACD) for Adhearsion. Currently implements
126
+ only Round Robin distribution strategies.
127
+ email: dev&adhearsion.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - .gitignore
133
+ - Gemfile
134
+ - LICENSE
135
+ - README.markdown
136
+ - Rakefile
137
+ - config/ahn_acd.yml
138
+ - electric_slide.gemspec
139
+ - lib/electric_slide.rb
140
+ - lib/electric_slide/queue_strategy.rb
141
+ - lib/electric_slide/queued_call.rb
142
+ - lib/electric_slide/round_robin.rb
143
+ - lib/electric_slide/round_robin_meetme.rb
144
+ - spec/electric_slide/queue_strategy_spec.rb
145
+ - spec/electric_slide/queued_call_spec.rb
146
+ - spec/electric_slide/round_robin_spec.rb
147
+ - spec/electric_slide_spec.rb
148
+ - spec/spec_helper.rb
149
+ homepage: http://github.com/adhearsion/electric_slide
150
+ licenses: []
151
+ metadata: {}
152
+ post_install_message:
153
+ rdoc_options: []
154
+ require_paths:
155
+ - lib
156
+ required_ruby_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - '>='
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ required_rubygems_version: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - '>='
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ requirements: []
167
+ rubyforge_project:
168
+ rubygems_version: 2.0.3
169
+ signing_key:
170
+ specification_version: 2
171
+ summary: Automatic Call Distributor for Adhearsion
172
+ test_files: []
173
+ has_rdoc: true