voicemail 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +26 -0
  3. data/.rspec +3 -0
  4. data/Gemfile +3 -0
  5. data/Guardfile +5 -0
  6. data/LICENSE +20 -0
  7. data/README.md +62 -0
  8. data/Rakefile +11 -0
  9. data/lib/voicemail/application_controller.rb +27 -0
  10. data/lib/voicemail/mailbox_controller.rb +56 -0
  11. data/lib/voicemail/mailbox_main_menu_controller.rb +41 -0
  12. data/lib/voicemail/mailbox_messages_controller.rb +55 -0
  13. data/lib/voicemail/mailbox_play_message_controller.rb +69 -0
  14. data/lib/voicemail/mailbox_set_greeting_controller.rb +75 -0
  15. data/lib/voicemail/mailbox_set_pin_controller.rb +45 -0
  16. data/lib/voicemail/plugin.rb +86 -0
  17. data/lib/voicemail/storage.rb +7 -0
  18. data/lib/voicemail/storage_pstore.rb +93 -0
  19. data/lib/voicemail/version.rb +3 -0
  20. data/lib/voicemail/voicemail_controller.rb +32 -0
  21. data/lib/voicemail.rb +14 -0
  22. data/spec/spec_helper.rb +14 -0
  23. data/spec/support/voicemail_controller_spec_helper.rb +39 -0
  24. data/spec/voicemail/mailbox_controller_spec.rb +79 -0
  25. data/spec/voicemail/mailbox_main_menu_controller_spec.rb +35 -0
  26. data/spec/voicemail/mailbox_messages_controller_spec.rb +71 -0
  27. data/spec/voicemail/mailbox_play_message_controller_spec.rb +70 -0
  28. data/spec/voicemail/mailbox_set_greeting_controller_spec.rb +61 -0
  29. data/spec/voicemail/mailbox_set_pin_controller_spec.rb +53 -0
  30. data/spec/voicemail/storage_pstore_spec.rb +45 -0
  31. data/spec/voicemail/storage_spec.rb +13 -0
  32. data/spec/voicemail/voicemail_controller_spec.rb +65 -0
  33. data/tmp/.gitignore +2 -0
  34. data/voicemail.gemspec +29 -0
  35. metadata +200 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 85483d596c2b6e25016b05a3ea29885e8c243851
4
+ data.tar.gz: b06686ed9549ffa75661d4f3065ff0c60e21e2fc
5
+ SHA512:
6
+ metadata.gz: 21e6b40a6f88258cc4dc9f4324029d5a654da70d4feb1bc7f5513b942e9e0b72852dfb8aabbaef387b9db741c25807822c8c9760a2d04bc8d2e374fcb53f9a53
7
+ data.tar.gz: 8b7cc4401c5f949e89a4aec9ce381d2ec304efaf7c5278b6a3a4dfe55bced067fb6824fe2b590edf627d75a41d4d595385056c73d2ad3ee316d6a6430282de99
data/.gitignore ADDED
@@ -0,0 +1,26 @@
1
+ # Gem related stuff
2
+ *.gem
3
+ /pkg
4
+ /.bundle
5
+ /Gemfile.lock
6
+ /vendor
7
+
8
+ # Testing stuff
9
+ /coverage
10
+ /spec/reports
11
+ *.log
12
+ /adhearsion.pid
13
+
14
+ # RBX stuff
15
+ /.rbx/
16
+
17
+ # Editor temp/backup files
18
+ *~
19
+ .*.sw?
20
+
21
+ # General
22
+ /nbproject
23
+ .DS_Store
24
+ /.rvmrc
25
+ /.yardoc
26
+ /doc
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --colour
3
+ --tty
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,5 @@
1
+ guard 'rspec', :cli => '--format documentation' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec/" }
5
+ end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (C) 2012 Adhearsion Foundation Inc
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,62 @@
1
+ [![Build Status](https://secure.travis-ci.org/adhearsion/voicemail.png?branch=develop)](http://travis-ci.org/adhearsion/voicemail)
2
+
3
+ # Voicemail
4
+
5
+ This plugin aims to provide a basic voicemail implementation, complete with a voicemail system that allows user to listen to messages and manage them. It is currently only compatible with Asterisk, and you will need to provide the audio files and their path in the configuration. The configuration also contains many other options.
6
+
7
+ ## Usage
8
+
9
+ Every mailbox has an id and PIN that can be provisioned using a pry console.
10
+
11
+ To send a call to a mailbox use:
12
+ ```ruby
13
+ # inside a CallController method
14
+ invoke Voicemail::VoicemailController, mailbox: mailbox_id
15
+ ```
16
+
17
+ To allow access by users to the mailboxes, `invoke MailboxController`. You will have to provide the mailbox ID to load.
18
+ ```ruby
19
+ # inside a CallController method
20
+ invoke Voicemail::MailboxController, mailbox: mailbox_id
21
+ ```
22
+
23
+ ## Storage
24
+
25
+ Mailbox metadata is stored in a PStore hash on disk for easy drop-in functionality.
26
+ To implement your owm storage layer, look at the StorageMain class for method signatures.
27
+
28
+ You can set the storage layer globally in configuration.
29
+ ```ruby
30
+ config.voicemail.storage.storage_class = StorageWidget
31
+ ```
32
+
33
+ Alternatively, you can pass in a storage layer dynamically when invoking the controller.
34
+ ```ruby
35
+ if customer.voicemail_version == VERSION_ONE
36
+ invoke Voicemail::VoicemailController, mailbox: customer.mailbox_id, storage: VersionOneStorage.new
37
+ elsif customer.voicemail_version == VERSION_TWO
38
+ invoke Voicemail::VoicemailController, mailbox: customer.mailbox_id, storage: VersionTwoStorage.new
39
+ # etc.
40
+ ```
41
+
42
+ ## Author
43
+
44
+ Original author: [Luca Pradovera](https://github.com/polysics)
45
+
46
+ ## Links
47
+
48
+ * [Source](https://github.com/adhearsion/voicemail)
49
+ * [Bug Tracker](https://github.com/adhearsion/voicemail/issues)
50
+
51
+ ## Note on Patches/Pull Requests
52
+
53
+ * Fork the project.
54
+ * Make your feature addition or bug fix.
55
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
56
+ * Commit, do not mess with rakefile, version, or history.
57
+ * If you want to have your own version, that is fine but bump version in a commit by itself so I can ignore when I pull
58
+ * Send me a pull request. Bonus points for topic branches.
59
+
60
+ ## Copyright
61
+
62
+ Copyright (c) 2012 Adhearsion Foundation Inc. MIT license (see LICENSE for details).
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'bundler/setup'
2
+ require 'bundler/gem_tasks'
3
+
4
+ require 'rspec/core'
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec) do |spec|
7
+ spec.pattern = 'spec/**/*_spec.rb'
8
+ spec.rspec_opts = '--color'
9
+ end
10
+
11
+ task default: :spec
@@ -0,0 +1,27 @@
1
+ module Voicemail
2
+ class ApplicationController < ::Adhearsion::CallController
3
+ def main_menu
4
+ pass MailboxMainMenuController, mailbox: mailbox[:id]
5
+ end
6
+
7
+ private
8
+
9
+ def storage
10
+ metadata[:storage] || Storage.instance
11
+ end
12
+
13
+ def config
14
+ Voicemail::Plugin.config
15
+ end
16
+
17
+ def mailbox
18
+ @mailbox ||= fetch_mailbox
19
+ end
20
+
21
+ def fetch_mailbox
22
+ mailbox_id = metadata[:mailbox] || nil
23
+ raise ArgumentError, "Voicemail needs a mailbox specified in metadata" unless mailbox_id
24
+ storage.get_mailbox mailbox_id
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,56 @@
1
+ module Voicemail
2
+ class MailboxController < ApplicationController
3
+ def run
4
+ if mailbox
5
+ play_greeting
6
+ fail_auth unless authenticate
7
+ play_number_of_messages
8
+ main_menu
9
+ else
10
+ mailbox_not_found
11
+ end
12
+ end
13
+
14
+ def authenticate
15
+ current_tries = 0
16
+ auth_ok = false
17
+ while current_tries < config.mailbox.pin_tries
18
+ input = ask config.mailbox.please_enter_pin, terminator: "#", timeout: config.prompt_timeout
19
+ logger.info input.to_s
20
+ logger.info mailbox[:pin].to_s
21
+ auth_ok = true if input.to_s == mailbox[:pin].to_s
22
+ break if auth_ok
23
+ play config.mailbox.pin_wrong
24
+ current_tries += 1
25
+ end
26
+ auth_ok
27
+ end
28
+
29
+ def play_number_of_messages
30
+ number = storage.count_new_messages(mailbox[:id])
31
+ if number > 0
32
+ play config.mailbox.number_before
33
+ play_numeric number
34
+ play config.mailbox.number_after
35
+ else
36
+ play config.messages.no_new_messages
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def play_greeting
43
+ play config.mailbox.greeting_message
44
+ end
45
+
46
+ def mailbox_not_found
47
+ play config.mailbox_not_found
48
+ hangup
49
+ end
50
+
51
+ def fail_auth
52
+ play config.mailbox.could_not_auth
53
+ hangup
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,41 @@
1
+ module Voicemail
2
+ class MailboxMainMenuController < ApplicationController
3
+ def run
4
+ main_menu
5
+ end
6
+
7
+ def main_menu
8
+ menu config.mailbox.menu_greeting,
9
+ timeout: config.menu_timeout, tries: config.menu_tries do
10
+ match(1) { listen_to_messages }
11
+ match(2) { set_greeting }
12
+ match(3) { set_pin }
13
+
14
+ timeout do
15
+ play config.mailbox.menu_timeout_message
16
+ end
17
+
18
+ invalid do
19
+ play config.mailbox.menu_invalid_message
20
+ end
21
+
22
+ failure do
23
+ play config.mailbox.menu_failure_message
24
+ hangup
25
+ end
26
+ end
27
+ end
28
+
29
+ def set_greeting
30
+ invoke MailboxSetGreetingController, mailbox: mailbox[:id]
31
+ end
32
+
33
+ def set_pin
34
+ invoke MailboxSetPinController, mailbox: mailbox[:id]
35
+ end
36
+
37
+ def listen_to_messages
38
+ invoke MailboxMessagesController, mailbox: mailbox[:id]
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,55 @@
1
+ module Voicemail
2
+ class MailboxMessagesController < ApplicationController
3
+ def run
4
+ message_loop
5
+ end
6
+
7
+ def message_loop
8
+ number = storage.count_new_messages(mailbox[:id])
9
+ if number > 0
10
+ next_message
11
+ else
12
+ bail_out
13
+ end
14
+ end
15
+
16
+ def next_message
17
+ current_message = storage.next_new_message(mailbox[:id])
18
+ handle_message current_message
19
+ message_loop
20
+ end
21
+
22
+ def handle_message(message)
23
+ invoke MailboxPlayMessageController, message: message, mailbox: mailbox[:id]
24
+ end
25
+
26
+ private
27
+
28
+ def bail_out
29
+ play config.messages.no_new_messages
30
+ main_menu
31
+ end
32
+
33
+ def section_menu
34
+ menu config.mailbox.menu_greeting,
35
+ timeout: config.menu_timeout, tries: config.menu_tries do
36
+ match(1) { listen_to_messages }
37
+ match(2) { set_greeting }
38
+ match(3) { set_pin }
39
+
40
+ timeout do
41
+ play config.mailbox.menu_timeout_message
42
+ end
43
+
44
+ invalid do
45
+ play config.mailbox.menu_invalid_message
46
+ end
47
+
48
+ failure do
49
+ play config.mailbox.menu_failure_message
50
+ hangup
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,69 @@
1
+ module Voicemail
2
+ class MailboxPlayMessageController < ApplicationController
3
+
4
+ def run
5
+ load_message
6
+ intro_message
7
+ play_message
8
+ end
9
+
10
+ def play_message
11
+ menu current_message[:uri].gsub(/\.wav/, ''), config.messages.menu,
12
+ timeout: config.menu_timeout, tries: config.menu_tries do
13
+ match 1 do
14
+ archive_message
15
+ end
16
+
17
+ match 5 do
18
+ delete_message
19
+ end
20
+
21
+ match(7) { rewind_message }
22
+ match(9) { main_menu }
23
+
24
+ timeout do
25
+ play config.mailbox.menu_timeout_message
26
+ end
27
+
28
+ invalid do
29
+ play config.mailbox.menu_invalid_message
30
+ end
31
+
32
+ failure do
33
+ play config.mailbox.menu_failure_message
34
+ hangup
35
+ end
36
+ end
37
+ end
38
+
39
+ def intro_message
40
+ play config.messages.message_received_on
41
+ play_time current_message[:received], format: config.datetime_format
42
+ play config.messages.from
43
+ from_digits = current_message[:from].scan(/\d/).join
44
+ execute "SayDigits", from_digits unless from_digits.empty?
45
+ # say_characters '1234'
46
+ end
47
+
48
+ def rewind_message
49
+ play_message
50
+ end
51
+
52
+ def archive_message
53
+ storage.archive_message mailbox[:id], current_message[:id]
54
+ end
55
+
56
+ def delete_message
57
+ storage.delete_message mailbox[:id], current_message[:id]
58
+ end
59
+
60
+ def current_message
61
+ @message
62
+ end
63
+
64
+ def load_message
65
+ @message = metadata[:message] || nil
66
+ raise ArgumentError, "MailboxPlayMessageController needs a valid message passed to it" unless @message
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,75 @@
1
+ module Voicemail
2
+ class MailboxSetGreetingController < ApplicationController
3
+ attr_accessor :temp_recording
4
+
5
+ def run
6
+ section_menu
7
+ end
8
+
9
+ def section_menu
10
+ menu config.set_greeting.prompt,
11
+ timeout: config.menu_timeout, tries: config.menu_tries do
12
+ match(1) { listen_to_current_greeting }
13
+ match(2) { record_greeting }
14
+ match(9) { main_menu }
15
+
16
+ timeout do
17
+ play config.mailbox.menu_timeout_message
18
+ end
19
+
20
+ invalid do
21
+ play config.mailbox.menu_invalid_message
22
+ end
23
+
24
+ failure do
25
+ play config.mailbox.menu_failure_message
26
+ hangup
27
+ end
28
+ end
29
+ end
30
+
31
+ def listen_to_current_greeting
32
+ play mailbox[:greeting_message] || config.set_greeting.no_personal_greeting
33
+ section_menu
34
+ end
35
+
36
+ def record_greeting
37
+ play config.set_greeting.before_record
38
+ record_comp = record config.set_greeting.recording.to_hash.merge(interruptible: true, max_duration: 30_000)
39
+ @temp_recording = record_comp.complete_event.recording.uri.gsub(/file:\/\//, '').gsub(/\.wav/, '')
40
+ play @temp_recording
41
+
42
+ menu config.set_greeting.after_record,
43
+ timeout: config.menu_timeout, tries: config.menu_tries do
44
+ match(1) { save_greeting }
45
+ match 2 do
46
+ @temp_recording = nil
47
+ record_greeting
48
+ end
49
+
50
+ match 9 do
51
+ @temp_recording = nil
52
+ section_menu
53
+ end
54
+
55
+ timeout do
56
+ play config.mailbox.menu_timeout_message
57
+ end
58
+
59
+ invalid do
60
+ play config.mailbox.menu_invalid_message
61
+ end
62
+
63
+ failure do
64
+ play config.mailbox.menu_failure_message
65
+ hangup
66
+ end
67
+ end
68
+ end
69
+
70
+ def save_greeting
71
+ storage.save_greeting_for_mailbox mailbox[:id], temp_recording
72
+ main_menu
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,45 @@
1
+ module Voicemail
2
+ class MailboxSetPinController < ApplicationController
3
+ def run
4
+ section_menu
5
+ end
6
+
7
+ def section_menu
8
+ menu config.set_pin.menu,
9
+ timeout: config.menu_timeout, tries: config.menu_tries do
10
+ match(1) { set_pin }
11
+ match(9) { main_menu }
12
+
13
+ timeout do
14
+ play config.mailbox.menu_timeout_message
15
+ end
16
+
17
+ invalid do
18
+ play config.mailbox.menu_invalid_message
19
+ end
20
+
21
+ failure do
22
+ play config.mailbox.menu_failure_message
23
+ hangup
24
+ end
25
+ end
26
+ end
27
+
28
+ def set_pin
29
+ pin = ask config.set_pin.prompt, terminator: "#"
30
+ repeat_pin = ask config.set_pin.repeat_prompt, terminator: "#"
31
+
32
+ if pin.to_s.size < config.set_pin.pin_minimum_digits
33
+ play config.set_pin.pin_error
34
+ set_pin
35
+ elsif pin.to_s != repeat_pin.to_s
36
+ play config.set_pin.match_error
37
+ set_pin
38
+ else
39
+ play config.set_pin.change_ok
40
+ storage.change_pin_for_mailbox mailbox[:id], pin.to_s
41
+ main_menu
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,86 @@
1
+ module Voicemail
2
+ class Plugin < Adhearsion::Plugin
3
+
4
+ init :voicemail do
5
+ logger.info "Voicemail has been loaded"
6
+ end
7
+
8
+ config :voicemail do
9
+ default_greeting "You have reached voicemail", desc: "What to use to greet users"
10
+ mailbox_not_found "Mailbox not found", desc: "Message to use for a missing mailbox"
11
+ prompt_timeout 5, desc: "Timeout for the various prompts, in seconds"
12
+ menu_timeout 15.seconds, desc: "Timeout for all menus"
13
+ menu_tries 3, desc: "Tries to get matching input for all menus"
14
+ datetime_format "Q 'digits/at' IMp", desc: "Fromat to use for message date and time TTS"
15
+
16
+ desc "Voicemail recording options"
17
+ recording {
18
+ max_duration 5_000, desc: "Maximum duration for recording in milliseconds"
19
+ start_beep true, desc: "Play a beep before recording"
20
+ }
21
+
22
+ desc "Configuration for registered users"
23
+ mailbox {
24
+ greeting_message "Welcome to the mailbox system.", desc: "Message to greet voicemail users"
25
+ please_enter_pin "Please enter your PIN.", desc: "Message asking to enter PIN."
26
+ pin_tries 3, desc: "Number of tries to authenticate before failure"
27
+ pin_wrong "The PIN you entered does not match. Please try again.", desc: "Message for an user that enters the wrong PIN"
28
+ could_not_auth "We are sorry, the system could not authenticate you", desc: "Message for authentication final failure."
29
+ number_before "You have ", desc: "What to play before the number of new messages"
30
+ number_after " new messages", desc: "What to play after the number of new messages"
31
+ menu_greeting "Press 1 to listen to new messages, 2 to change your greeting, 3 to change your PIN", desc: "What to say before the main menu"
32
+ menu_timeout_message "Please enter a digit for the menu", desc: "Message to play on main menu timeout"
33
+ menu_invalid_message "Please enter valid input", desc: "Message to play on main menu invalid"
34
+ menu_failure_message "Sorry, unable to understand your input.", desc: "Message to play on main menu failure"
35
+ }
36
+
37
+ desc "Set greeting configuration"
38
+ set_greeting {
39
+ prompt "Press 1 to listen to your current greeting, 2 to record a new greeting, 9 to return to the main menu", desc: "Main prompt for setting greeting"
40
+ before_record "Please speak after the beep. The prompt will be played back after.", desc: "Recording instructions"
41
+ after_record "Press 1 to save your new greeting, 2 to discard it, 9 to go back to the menu", desc: "Menu to use after recording"
42
+ no_personal_greeting "You do not currently have a personalized greeting.", desc: "What to play if there is no specific greeting"
43
+ recording {
44
+ max_duration 5_000, desc: "Maximum duration for recording in milliseconds"
45
+ start_beep true, desc: "Play a beep before recording"
46
+ }
47
+ }
48
+
49
+ desc "Set PIN configuration"
50
+ set_pin {
51
+ menu "Press 1 to change your current PIN, or 9 to go back to the main menu", desc: "Prompt for the change PIN menu"
52
+ prompt "Enter your new PIN of at least four digits followed by the pound sign", desc: "What the user hears before entering the new PIN"
53
+ repeat_prompt "Please enter your new PIN again, followed by the pound sign", desc: "Message to ask the user to repeat his PIN"
54
+ pin_error "Please enter at least four digits followed by the pound sign.", desc: "Message in case the entered PIN is too short"
55
+ match_error "The two entered PINs don't match, please try again.", desc: "Message telling the user his new PINs don't match."
56
+ change_ok "Your PIN has been successfully changed", desc: "Message to tell the user his PIN has been changed"
57
+ pin_minimum_digits 4, desc: "Minimum number of digits for a PIN"
58
+ }
59
+
60
+ desc "Listen to messages menu configuration"
61
+ messages {
62
+ menu "Press 1 to archive the message and go to the next, press 5 to delete the message and go to the next, press 7 to hear the message again, press 9 for the main menu", desc: "Menu to use inside messages"
63
+ no_new_messages "There are no new messages", desc: "Message to inform the user he has no new messages"
64
+ message_received_on "Message received on ", desc: "Prefix to menu intro"
65
+ from " from ", desc: "Used in message intro"
66
+ }
67
+
68
+ desc "Storage configuration"
69
+ storage {
70
+ storage_class Voicemail::StoragePstore, desc: "Class that implements the Storage specification. An instance will be created."
71
+ pstore_location "voicemail.pstore", desc: "Where to store the voicemail data"
72
+ file_location "file_dir", desc: "Where to store the voicemail files"
73
+ }
74
+ end
75
+
76
+ tasks do
77
+ namespace :voicemail do
78
+ desc "Prints the Voicemail information"
79
+ task :info do
80
+ STDOUT.puts "Voicemail plugin v. #{VERSION}"
81
+ end
82
+ end
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,7 @@
1
+ module Voicemail
2
+ class Storage
3
+ def self.instance
4
+ @instance ||= Voicemail::Plugin.config.storage.storage_class.new
5
+ end
6
+ end
7
+ end