active_mailbox 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Joshua Priddle
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.
@@ -0,0 +1,215 @@
1
+ # ActiveMailbox
2
+
3
+ ActiveMailbox provides a simple API and command line utility to work with
4
+ voicemail boxes and voicemails generated by Asterisk.
5
+
6
+
7
+ ## Command line usage via active_mailbox
8
+
9
+ ActiveMailbox includes a command line tool which administrators can use to
10
+ perform maintenance on a user's mailbox.
11
+
12
+ The command takes **three** arguments: the action (OPTION), the context
13
+ defined in voicemail.conf (CONTEXT), and the user's phone number (MAILBOX)
14
+
15
+ Usage: active_mailbox [OPTION] MAILBOX
16
+
17
+ Mailbox Options:
18
+ --context Look for [MAILBOX] in [CONTEXT]
19
+ --delete Delete [MAILBOX] and all messages
20
+ --sort Sort messages in [MAILBOX] (recursive)
21
+
22
+ Cleanup Options:
23
+ --clean-ghosts Cleanup 'ghost' messages
24
+ --clean-stale Cleanup messages older than 30 days
25
+ --clean-mp3s Cleanup [MAILBOX]/.mp3 directory
26
+ --purge Remove all messages, but leave [MAILBOX] folders intact
27
+
28
+ Greeting Options:
29
+ --delete-temp Delete [MAILBOX]/temp.wav
30
+ --delete-unavail Delete [MAILBOX]/unavail.wav
31
+ --delete-busy Delete [MAILBOX]/busy.wav
32
+
33
+ General Options:
34
+ -h, --help Show this message
35
+ -v, --version Show version
36
+
37
+
38
+ ### Example Command line usage
39
+
40
+ #### Delete unavailable greeting
41
+
42
+ active_mailbox --delete-unavail 15183332220
43
+
44
+
45
+ #### Delete temp greeting
46
+
47
+ active_mailbox --delete-temp 15183332220
48
+
49
+
50
+ #### Purge mailbox (deletes all messages, leaves folders intact)
51
+
52
+ active_mailbox --purge 15183332220
53
+
54
+
55
+ #### Delete messages older than 30 days
56
+
57
+ active_mailbox --clean-stale 15183332220
58
+
59
+
60
+ #### Delete mailbox
61
+
62
+ active_mailbox --delete 15183332222
63
+
64
+
65
+ #### Delete 'ghost' messages
66
+
67
+ Ghost voicemails happen if a message's txt file exists, but the
68
+ corresponding wav file does not. Example:
69
+
70
+ tree 518/15183332225/INBOX
71
+ 518/15183332225/INBOX
72
+ |-- msg0000.txt
73
+ |-- msg0000.wav
74
+ `-- msg0002.txt
75
+
76
+ Run `active_mailbox --clean-ghosts` to clear the offending txt files.
77
+
78
+ active_mailbox --clean-ghosts 518 15183332220
79
+
80
+ Observe the results:
81
+
82
+ tree 518/15183332225/INBOX
83
+ 518/15183332225/INBOX
84
+ |-- msg0000.txt
85
+ `-- msg0000.wav
86
+
87
+ #### Sorting
88
+
89
+ If voicemails are manually deleted, the INBOX order can be out of sync, as
90
+ depicted below:
91
+
92
+ tree 518/15183332225/INBOX
93
+ 518/15183332225/INBOX
94
+ |-- msg0000.txt
95
+ |-- msg0000.wav
96
+ |-- msg0002.txt
97
+ |-- msg0002.wav
98
+ |-- msg0006.txt
99
+ |-- msg0006.wav
100
+ |-- msg0009.txt
101
+ `-- msg0009.wav
102
+
103
+ Sort the messages so they play correctly in Asterisk using
104
+ `active_mailbox --sort`:
105
+
106
+ active_mailbox --sort 15183332225
107
+
108
+ Messages are renamed in INBOX after sorting
109
+
110
+ tree 518/15183332225/INBOX
111
+ 518/15183332225/INBOX
112
+ |-- msg0000.txt
113
+ |-- msg0000.wav
114
+ |-- msg0001.txt
115
+ |-- msg0001.wav
116
+ |-- msg0002.txt
117
+ |-- msg0002.wav
118
+ |-- msg0003.txt
119
+ `-- msg0003.wav
120
+
121
+
122
+ ## Library Usage
123
+
124
+ ActiveMailbox can also be used in ruby scripts to work with mailboxes
125
+ and voicemail messages.
126
+
127
+
128
+ ### Working with mailboxes
129
+
130
+ # Create a mailbox object
131
+ mailbox = ActiveMailbox::Mailbox.find('office_a', '1234')
132
+
133
+ # Destroy mailbox and all messages/greetings
134
+ mailbox.destroy!
135
+
136
+ # Determine the greeting Asterisk will playback
137
+ mailbox.current_greeting
138
+
139
+ # Destroy ``unavail.wav``
140
+ mailbox.destroy_greeting!
141
+
142
+ # Destroy ``temp.wav``
143
+ mailbox.destroy_temp!
144
+
145
+ # A Hash of the mailbox's messages
146
+ #
147
+ # Keys are the subdirectory, lowercased, as
148
+ # a symbol. Eg: INBOX = :inbox, Old = :old
149
+ #
150
+ # Values are arrays of Message objects
151
+ mailbox.folders
152
+
153
+ # Fetch array of messages in :inbox
154
+ inbox = mailbox.folders[:inbox]
155
+
156
+ # Or some sugar
157
+ inbox = mailbox.inbox
158
+
159
+ # Or :old
160
+ mailbox.folders[:old]
161
+ mailbox.old
162
+
163
+
164
+ ### Working with messages
165
+
166
+ # Set mailbox
167
+ mailbox = ActiveMailbox::Mailbox.find('office_a', '1234')
168
+
169
+ # Get first message in INBOX
170
+ vm = mailbox.inbox.first
171
+
172
+ # Path to Asterisk generated msg####.txt
173
+ vm.txt
174
+
175
+ # Path to Asterisk generated msg####.wav
176
+ vm.wav
177
+
178
+ # Caller ID name
179
+ vm.callerid_name
180
+
181
+ # Caller ID number
182
+ vm.callerid_number
183
+
184
+ # Message duration (in seconds)
185
+ vm.duration
186
+
187
+ # Delete this message
188
+ # Deletes wav, txt, mp3, xml files
189
+ vm.destroy!
190
+
191
+
192
+ ## Notes
193
+
194
+ If Asterisk does **not** keep voicemail in
195
+ `/var/spool/asterisk/voicemail` on this server, add the following
196
+ to `~/.bashrc` (or the appropriate shell config file):
197
+
198
+ export ASTERISK_VOICEMAIL_ROOT='/my/alternate/voicemail/path'
199
+
200
+ Note that you can specify this as an ENV variable when running the
201
+ `active_mailbox` executable.
202
+
203
+ ASTERISK_VOICEMAIL_ROOT='/root' active_mailbox [OPTION] MAILBOX
204
+
205
+
206
+ ## Development
207
+
208
+ ActiveMailbox is still in development, as such, don't use it yet unless you're
209
+ brave. Then again, if you're using Asterisk, you're already a cowboy so have
210
+ fun!
211
+
212
+
213
+ ## License
214
+
215
+ MIT License, see LICENSE.
@@ -0,0 +1,33 @@
1
+ require 'rake/testtask'
2
+
3
+ $:.unshift 'lib'
4
+
5
+ desc "Start an IRB session preloaded with this library"
6
+ task :console do
7
+ sh "ASTERISK_VOICEMAIL_ROOT='./test/fixtures/' irb -rlib/active_mailbox.rb -I./lib"
8
+ end
9
+
10
+ require 'sdoc_helpers'
11
+ desc "Push a new version to Gemcutter"
12
+ task :publish do
13
+ require 'active_mailbox/version'
14
+
15
+ ver = ActiveMailbox::Version
16
+
17
+ sh "gem build active_mailbox.gemspec"
18
+ sh "gem push active_mailbox-#{ver}.gem"
19
+ sh "git tag -a -m 'ActiveMailbox v#{ver}' v#{ver}"
20
+ sh "git push origin v#{ver}"
21
+ sh "git push origin master"
22
+ sh "git clean -fd"
23
+ sh "rake pages"
24
+ end
25
+
26
+ task :default => :test
27
+
28
+ require 'rake/testtask'
29
+ Rake::TestTask.new(:test) do |test|
30
+ test.libs << 'lib' << 'test' << '.'
31
+ test.pattern = 'test/**/*_test.rb'
32
+ test.verbose = true
33
+ end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'active_mailbox'
4
+ require 'active_mailbox/cli'
5
+
6
+ ActiveMailbox::CLI.run!(ARGV)
@@ -0,0 +1,16 @@
1
+ require 'active_mailbox/version'
2
+ require 'active_mailbox/errors'
3
+ require 'active_mailbox/mailbox'
4
+ require 'active_mailbox/folder'
5
+ require 'active_mailbox/message'
6
+
7
+ module ActiveMailbox
8
+ # The directory Asterisk records voicemail in
9
+ #
10
+ # By default, this is `/var/spool/asterisk/voicemail`. If it isn't,
11
+ # you did something fancy when building Asterisk. Set
12
+ # ENV['ASTERISK_VOICEMAIL_ROOT'] to that directory in your library
13
+ # or add 'export ASTERISK_VOICEMAIL_ROOT="/my/voicemail"' to your
14
+ # ~/.bashrc
15
+ VOICEMAIL_ROOT = ENV['ASTERISK_VOICEMAIL_ROOT'] || "/var/spool/asterisk/voicemail"
16
+ end
@@ -0,0 +1,112 @@
1
+ require 'optparse'
2
+
3
+ module ActiveMailbox
4
+ #
5
+ # This class facilites ActiveMailbox's command line functions
6
+ #
7
+ class CLI
8
+
9
+ #
10
+ # Invoke the CLI
11
+ #
12
+ def self.run!(argv)
13
+ options = ActiveMailbox::CLI.parse(argv)
14
+
15
+ args = [options[:mailbox]]
16
+ args.unshift(options[:context]) if options[:context]
17
+
18
+ mailbox = ActiveMailbox::Mailbox.find(*args)
19
+
20
+ if options[:arg]
21
+ mailbox.send(options[:command], options[:arg])
22
+ else
23
+ mailbox.send(options[:command])
24
+ end
25
+ end
26
+
27
+ #
28
+ # Parse CLI arguments (ARGV)
29
+ #
30
+ def self.parse(argv)
31
+ options = {}
32
+
33
+ ::OptionParser.new do |opts|
34
+ opts.banner = "Usage: active_mailbox [OPTION] MAILBOX\n"
35
+
36
+ opts.separator ""
37
+ opts.separator "Mailbox Options:"
38
+
39
+ opts.on('--context', 'Look for [MAILBOX] in [CONTEXT]') do |context|
40
+ options[:context] = conext
41
+ end
42
+
43
+ opts.on('--delete', 'Delete [MAILBOX] and all messages') do
44
+ options[:command] = :delete!
45
+ end
46
+
47
+ opts.on('--sort', 'Sort messages in [MAILBOX] (recursive)') do
48
+ options[:command] = :sort!
49
+ end
50
+
51
+ opts.separator ""
52
+ opts.separator "Cleanup Options:"
53
+
54
+ opts.on('--clean-ghosts', "Cleanup 'ghost' messages") do
55
+ options[:command] = :clean_ghosts!
56
+ end
57
+
58
+ opts.on('--clean-stale', 'Cleanup messages older than 30 days') do
59
+ options[:command] = :clean_stale!
60
+ end
61
+
62
+ opts.on('--clean-mp3s', 'Cleanup [MAILBOX]/.mp3 directory') do
63
+ options[:command] = :clean_mp3s!
64
+ end
65
+
66
+ opts.on('--purge', 'Remove all messages, but leave [MAILBOX] folders intact') do
67
+ options[:command] = :purge!
68
+ end
69
+
70
+ opts.separator ""
71
+ opts.separator "Greeting Options:"
72
+
73
+ opts.on('--delete-temp', 'Delete [MAILBOX]/temp.wav') do
74
+ options[:command] = :delete_temp_greeting!
75
+ end
76
+
77
+ opts.on('--delete-unavail', 'Delete [MAILBOX]/unavail.wav') do
78
+ options[:command] = :delete_unavail_greeting!
79
+ end
80
+
81
+ opts.on('--delete-busy', 'Delete [MAILBOX]/busy.wav') do
82
+ options[:command] = :delete_busy_greeting!
83
+ end
84
+
85
+ opts.separator ""
86
+ opts.separator "General Options:"
87
+
88
+ opts.on('-h', '--help', 'Show this message') do
89
+ options[:command] = :help
90
+ puts opts
91
+ exit
92
+ end
93
+
94
+ opts.on('-v', '--version', 'Show version') do
95
+ options[:command] = :version
96
+ puts ActiveMailbox::Version
97
+ exit
98
+ end
99
+
100
+ begin
101
+ argv = ['-h'] if argv.empty?
102
+ opts.parse!(argv)
103
+ options[:mailbox] = argv.shift
104
+ options
105
+ rescue ::OptionParser::ParseError => err
106
+ STDERR.puts err.message, "\n", opts
107
+ exit(-1)
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,8 @@
1
+ module ActiveMailbox
2
+ module Errors #:nodoc: all
3
+ class MailboxNotFound < StandardError; end
4
+ class MessageNotFound < StandardError; end
5
+ class FolderNotFound < StandardError; end
6
+ class GreetingNotFound < StandardError; end
7
+ end
8
+ end
@@ -0,0 +1,166 @@
1
+ module ActiveMailbox
2
+ #
3
+ # ActiveMailbox::Folder represents an Asterisk mailbox Folder, such as
4
+ # INBOX, or Old. They contain the actuall messages for a mailbox.
5
+ #
6
+ class Folder
7
+
8
+ include Comparable
9
+
10
+ attr_reader :path, :mailbox
11
+
12
+ attr_accessor :reload_messages
13
+
14
+ #
15
+ # Create a new Folder object
16
+ #
17
+ def initialize(path, mailbox)
18
+ unless File.exists?(path)
19
+ raise ActiveMailbox::Errors::FolderNotFound, "#{path} does not exist"
20
+ end
21
+ @mailbox = mailbox
22
+ @path = path
23
+ end
24
+
25
+ #
26
+ # Compare base on path
27
+ #
28
+ def <=>(other)
29
+ path <=> other.path
30
+ end
31
+
32
+ #
33
+ # An array of Message objects in this folder
34
+ #
35
+ def messages(reload = false)
36
+ if reload or @reload_messages or ! defined?(@messages)
37
+ @messages = []
38
+ Dir.chdir(@path) do
39
+ Dir["*.txt"].each do |txt|
40
+ @messages << Message.new("#{Dir.pwd}/#{txt}", self)
41
+ end
42
+ end
43
+ end
44
+ @messages
45
+ ensure
46
+ @reload_messages = false
47
+ end
48
+
49
+ #
50
+ # The name of this folder
51
+ #
52
+ def name
53
+ @name ||= path.split('/').last
54
+ end
55
+
56
+ #
57
+ # Returns number of messages in this folder
58
+ #
59
+ def count
60
+ messages.count
61
+ end
62
+ alias :size :count
63
+
64
+ #
65
+ # Sort messages in this folder
66
+ #
67
+ # Eg:
68
+ # Before Sort: msg0002, msg0005, msg0010, msg0020
69
+ # After Sort: msg0000, msg0001, msg0002, msg0003
70
+ #
71
+ def sort!
72
+ unless messages.size == messages.last.number + 1
73
+
74
+ renamer = lambda do |old_file, new_file|
75
+ %w[wav txt].each do |ext|
76
+ File.rename("#{old_file}.#{ext}", "#{new_file}.#{ext}")
77
+ end
78
+ end
79
+
80
+ messages.each_with_index.map do |message, index|
81
+ old_file = message.path
82
+ tmp_file = old_file.sub(/msg[0-9]{4}/, 'temp_msg%04d' % index)
83
+ renamer.call(old_file, tmp_file)
84
+ [old_file, tmp_file]
85
+ end.each do |old_file, tmp_file|
86
+ new_file = tmp_file.sub('temp_msg', 'msg')
87
+ renamer.call(tmp_file, new_file)
88
+ end
89
+ end
90
+ ensure
91
+ @reload_messages = true
92
+ end
93
+
94
+ #
95
+ # Destroy all messages/info files in this folder
96
+ #
97
+ def purge!
98
+ Dir.chdir(@path) do
99
+ Dir["*"].each do |file|
100
+ File.unlink(file)
101
+ end
102
+ end
103
+ ensure
104
+ @reload_messages = true
105
+ end
106
+
107
+ #
108
+ # Clean all 'ghost' Messages in this Folder
109
+ #
110
+ # A 'ghost' voicemail occurs when the .wav file does not exist.
111
+ # Asterisk sees the info (txt) file and thinks a voicemail exists,
112
+ # then plays nothing as the wav is not found.
113
+ #
114
+ # At the same time, you might run into issues if a wav exists, but
115
+ # not the txt file.
116
+ #
117
+ def clean_ghosts!(autosort = true)
118
+ Dir.chdir(@path) do
119
+ message_list.each do |file|
120
+ txt = "#{Dir.pwd}/#{file}.txt"
121
+ wav = "#{Dir.pwd}/#{file}.wav"
122
+
123
+ txt_exists = File.exists?(txt)
124
+ wav_exists = File.exists?(wav)
125
+
126
+ unless txt_exists && wav_exists
127
+ File.unlink(txt) if txt_exists
128
+ File.unlink(wav) if wav_exists
129
+ end
130
+ end
131
+ end
132
+ sort! if autosort
133
+ end
134
+
135
+ #
136
+ # Destroy Messages in this Folder that are older than 30 days
137
+ #
138
+ def clean_stale!(autosort = true)
139
+ messages.each do |message|
140
+ message.destroy! if message.stale?
141
+ end
142
+ sort! if autosort
143
+ end
144
+
145
+ #
146
+ # Destroy this Folder
147
+ #
148
+ def destroy!
149
+ FileUtils.rm_rf(@path)
150
+ end
151
+
152
+ private
153
+ #
154
+ # Grabs a list of valid message files in this folder
155
+ #
156
+ def message_list(strip_extension = true)
157
+ Dir['msg[0-9][0-9][0-9][0-9]*'].map do |file|
158
+ if strip_extension
159
+ file.split('.').first
160
+ else
161
+ file
162
+ end
163
+ end.uniq
164
+ end
165
+ end
166
+ end