active_mailbox 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,250 @@
1
+ module ActiveMailbox
2
+ #
3
+ # The ActiveMailbox::Mailbox class represents a top-level Asterisk
4
+ # mailbox. A mailbox contains folders, which contain the actual
5
+ # voicemails.
6
+ #
7
+ class Mailbox
8
+ #
9
+ # Greetings that can be altered by ActiveMailbox
10
+ #
11
+ ValidGreetings = [:unavail, :temp, :greet]
12
+
13
+ include Comparable
14
+
15
+ class << self
16
+ #
17
+ # The Mailbox currently in use
18
+ #
19
+ attr_accessor :current_mailbox
20
+
21
+ #
22
+ # Find mailbox
23
+ #
24
+ # On a default Asterisk installation, this would be
25
+ # /var/spool/asterisk/voicemail/CONTEXT/MAILBOX
26
+ #
27
+ # Note: if context is not provided, it will be set to mailbox[1, 3],
28
+ # which is the area code (NPA) on 11 digit phone numbers
29
+ #
30
+ # Usage:
31
+ # find('15183332220', 'Default') # Specify context 'Default'
32
+ # find('15183332220') # Set context to '518' automatically
33
+ #
34
+ def find(mailbox, context = nil)
35
+ if context
36
+ self.current_mailbox = new(mailbox, context)
37
+ else
38
+ self.current_mailbox = new(mailbox, mailbox.to_s[1, 3])
39
+ end
40
+ yield(current_mailbox) if block_given?
41
+ current_mailbox
42
+ end
43
+ alias :[] :find
44
+ end
45
+
46
+ attr_reader :mailbox, :context
47
+
48
+ #
49
+ # Create a new Mailbox object
50
+ #
51
+ def initialize(mailbox, context)
52
+ @context = context.to_s
53
+ @mailbox = mailbox.to_s
54
+
55
+ unless File.exists?(mailbox_path)
56
+ raise ActiveMailbox::Errors::MailboxNotFound, "`#{mailbox_path}` does not exist"
57
+ end
58
+ end
59
+
60
+ #
61
+ # Compare based on path name
62
+ #
63
+ def <=>(other)
64
+ mailbox <=> other.mailbox
65
+ end
66
+
67
+ #
68
+ # Use method missing to simulate accessors for folders
69
+ #
70
+ # Eg: self.inbox is the same as self.folders[:inbox]
71
+ #
72
+ def method_missing(method_name, *args, &block)
73
+ meth = method_name.to_s.downcase.gsub(/\W/, '_').to_sym
74
+ if folders.has_key?(meth)
75
+ folders[meth]
76
+ else
77
+ super
78
+ end
79
+ end
80
+
81
+ #
82
+ # Returns true if method_name is a folder name, or calls super
83
+ #
84
+ def respond_to?(method_name)
85
+ folders.has_key?(method_name.to_s.downcase.gsub(/\W/, '_').to_sym) || super
86
+ end
87
+
88
+ #
89
+ # The full filepath to this Mailbox
90
+ #
91
+ def path
92
+ @path ||= mailbox_path
93
+ end
94
+
95
+ #
96
+ # A hash/struct of folders in this Mailbox.
97
+ # Keys are the folder name (as a symbol).
98
+ # Values are an array of Message objects
99
+ #
100
+ def folders(reload = false)
101
+ if reload or @reload_folders or ! defined?(@folders)
102
+ @folders = {}
103
+ Dir.chdir(mailbox_path) do
104
+ Dir['*'].each do |folder|
105
+ if File.directory?(folder) && ! ignore_dirs.include?(folder)
106
+ key = folder.downcase.gsub(/\W/, '_').to_sym
107
+ @folders[key] = Folder.new("#{Dir.pwd}/#{folder}", self)
108
+ end
109
+ end
110
+ end
111
+ end
112
+ @folders
113
+ ensure
114
+ @reload_folders = false
115
+ end
116
+
117
+ #
118
+ # Return total number of messages in every folder
119
+ #
120
+ def total_messages
121
+ folders.values.inject(0) { |sum, folder| sum += folder.size }
122
+ end
123
+
124
+ #
125
+ # Destroy all Messages in this mailbox, but leave Mailbox,
126
+ # Folders, and greetings intact
127
+ #
128
+ def purge!
129
+ folders.each do |name, folder|
130
+ folder.purge!
131
+ end
132
+ end
133
+
134
+ #
135
+ # Destroy this Mailbox and all messages/greetings
136
+ #
137
+ def destroy!
138
+ FileUtils.rm_rf(mailbox_path)
139
+ end
140
+
141
+ #
142
+ # Sort all Messages in all Folders
143
+ #
144
+ # See ActiveMailbox::Folder#sort! for more info
145
+ #
146
+ def sort!
147
+ folders.each do |name, folder|
148
+ folder.sort!
149
+ end
150
+ end
151
+
152
+ #
153
+ # The greeting Asterisk will play
154
+ #
155
+ def current_greeting
156
+ case
157
+ when greeting_exists?(:temp)
158
+ @current_greeting = greeting_path(:temp)
159
+ when greeting_exists?(:unavail)
160
+ @current_greeting = greeting_path(:unavail)
161
+ end
162
+ end
163
+
164
+ #
165
+ # Delete greeting
166
+ #
167
+ # Valid options: :unavail, :temp, :busy
168
+ #
169
+ def delete_greeting!(greeting = :unavail)
170
+ if ValidGreetings.include?(greeting)
171
+ greeting_exists?(greeting) && File.unlink(greeting_path(greeting))
172
+ else
173
+ raise ActiveMailbox::Errors::GreetingNotFound, "Invalid greeting `#{greeting}'"
174
+ end
175
+ end
176
+
177
+ #
178
+ # Delete temp.wav
179
+ #
180
+ def delete_temp_greeting!
181
+ greeting_exists?(:temp) && File.unlink(greeting_path(:temp))
182
+ end
183
+
184
+ #
185
+ # Delete busy.wav
186
+ #
187
+ def delete_busy_greeting!
188
+ greeting_exists?(:busy) && File.unlink(greeting_path(:busy))
189
+ end
190
+
191
+ #
192
+ # Delete unavail.wav
193
+ #
194
+ def delete_unavail_greeting!
195
+ greeting_exists?(:unavail) && File.unlink(greeting_path(:unavail))
196
+ end
197
+
198
+ #
199
+ # Deletes 'ghost' Messages from all Folders
200
+ #
201
+ # See ActiveMailbox::Folder#clean_ghosts! for
202
+ # info on 'ghost' voicemails in Asterisk
203
+ #
204
+ def clean_ghosts!(autosort = true)
205
+ folders.each do |name, folder|
206
+ folder.clean_ghosts!(autosort)
207
+ end
208
+ end
209
+
210
+ #
211
+ # Deletes Messages older than 30 days from all Folders
212
+ #
213
+ def clean_stale!(autosort = true)
214
+ folders.each do |name, folder|
215
+ folder.clean_stale!(autosort)
216
+ end
217
+ end
218
+
219
+ #
220
+ # Path to greeting
221
+ #
222
+ def greeting_path(greeting = :unavail)
223
+ "#{mailbox_path}/#{greeting}.wav"
224
+ end
225
+
226
+ private
227
+
228
+ #
229
+ # Check if greeting file exists
230
+ #
231
+ def greeting_exists?(greeting = :unavail)
232
+ ValidGreetings.include?(greeting) && File.exist?(greeting_path(greeting))
233
+ end
234
+
235
+ #
236
+ # Path to mailbox
237
+ #
238
+ def mailbox_path
239
+ "%s/%s/%s" % [ActiveMailbox::VOICEMAIL_ROOT, @context, @mailbox]
240
+ end
241
+
242
+ #
243
+ # These dirs are used by Asterisk and don't contain anything
244
+ # ActiveMailbox is interested in
245
+ #
246
+ def ignore_dirs
247
+ %w[tmp temp unavail]
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,136 @@
1
+ require 'time'
2
+
3
+ module ActiveMailbox
4
+ #
5
+ # The ActiveMailbox::Message class represents an Asterisk voicemail
6
+ #
7
+ class Message
8
+ #
9
+ # Maximum age before a Message is considered stale (30 days)
10
+ #
11
+ MaximumAge = 2592000
12
+
13
+ include Comparable
14
+
15
+ #
16
+ # Values from Asterisk's message info file (eg: msg0001.txt)
17
+ # that are used by this class
18
+ #
19
+ InfoFileKeys = %w[callerid origdate duration]
20
+
21
+ attr_reader :folder
22
+
23
+ def initialize(info_file, folder)
24
+ unless File.exists?(info_file)
25
+ raise ActiveMailbox::Errors::MessageNotFound, "#{info_file} does not exist"
26
+ end
27
+ @folder = folder
28
+ @info_file = info_file
29
+ parse_data!
30
+ end
31
+
32
+ #
33
+ # The name of this message (ex: msg0001)
34
+ #
35
+ def name
36
+ @name ||= path.split('/').last
37
+ end
38
+
39
+ #
40
+ # The number pulled from name
41
+ #
42
+ def number
43
+ @number ||= (num = name.match(/([0-9]{4})$/) and num[1].to_i)
44
+ end
45
+
46
+ #
47
+ # Compare messages by the number in the name
48
+ #
49
+ def <=>(other)
50
+ number <=> other.number
51
+ end
52
+
53
+ #
54
+ # Destroy this Message's wav and txt files
55
+ #
56
+ def destroy!
57
+ [txt, wav].each do |file|
58
+ File.unlink(file)
59
+ end
60
+ ensure
61
+ @folder.reload_messages = true
62
+ end
63
+
64
+ #
65
+ # The time the Message was left
66
+ #
67
+ def timestamp
68
+ @timestamp ||= Time.parse(@origdate)
69
+ end
70
+
71
+ #
72
+ # The caller's phone number (from CallerID)
73
+ #
74
+ def callerid_number
75
+ @callerid_number ||= @callerid.gsub(/.*\<(.*)\>/, '\1')
76
+ end
77
+
78
+ #
79
+ # The caller's name (from CallerID)
80
+ #
81
+ def callerid_name
82
+ @callerid_name ||= @callerid.gsub(/(.*)\s*\<.*\>/, '\1').strip
83
+ end
84
+
85
+ #
86
+ # The duration of this Message (in seconds)
87
+ #
88
+ def duration
89
+ @duration
90
+ end
91
+
92
+ #
93
+ # The file path to this Message's wav file
94
+ #
95
+ def wav
96
+ @wav ||= @info_file.sub(/txt$/, 'wav')
97
+ end
98
+
99
+ #
100
+ # The file path to this Message's txt file
101
+ #
102
+ def txt
103
+ @info_file
104
+ end
105
+
106
+ #
107
+ # Returns msgXXXX
108
+ #
109
+ def path
110
+ @msg ||= txt.sub('.txt', '')
111
+ end
112
+
113
+ #
114
+ # Checks if this message is stale
115
+ #
116
+ def stale?
117
+ @stale ||= Time.now.to_i - timestamp.to_i > MaximumAge
118
+ end
119
+
120
+ private
121
+
122
+ def data #:nodoc:
123
+ @data ||= File.read(@info_file)
124
+ end
125
+
126
+ def parse_data! #:nodoc:
127
+ data.lines.each do |line|
128
+ key, value = line.split('=')
129
+ if InfoFileKeys.include?(key)
130
+ instance_variable_set("@#{key}", value.chomp)
131
+ end
132
+ end
133
+ end
134
+
135
+ end
136
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveMailbox
2
+ Version = VERSION = '0.0.1' #:nodoc:
3
+ end
@@ -0,0 +1,70 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class FolderTest < Test::Unit::TestCase
4
+ context "ActiveMailbox::Folder" do
5
+ setup do
6
+ ActiveMailbox::Fixtures.create!
7
+ @mailbox = ActiveMailbox::Mailbox.find('15183332220')
8
+ @folder = @mailbox.inbox
9
+ end
10
+
11
+ teardown do
12
+ ActiveMailbox::Fixtures.destroy!
13
+ end
14
+
15
+ should "raise FolderNotFound with an invalid path" do
16
+ assert_raise ActiveMailbox::Errors::FolderNotFound do
17
+ ActiveMailbox::Folder.new('i am not real!', @mailbox)
18
+ end
19
+ end
20
+
21
+ context "instance" do
22
+ should "yield an array of Message objects" do
23
+ assert @folder.messages.is_a?(Array)
24
+ assert @folder.messages.first.is_a?(ActiveMailbox::Message)
25
+ end
26
+
27
+ should "destroy itself" do
28
+ path = @folder.path
29
+ @folder.destroy!
30
+ assert File.exists?(path) == false
31
+ end
32
+
33
+ should "sort and rename messages by filename" do
34
+ messages = @folder.messages.map(&:name)
35
+ assert messages.size == @folder.size
36
+
37
+ ActiveMailbox::Fixtures.simulate_unordered(@folder)
38
+ assert messages.size > @folder.size
39
+
40
+ names = messages.map { |m| "msg%04d" % m.match(/([0-9]{4})/)[1].to_i }
41
+ check = (0..messages.size - 1).map { |i| "msg%04d" % i }
42
+ assert_same_elements names, check
43
+ end
44
+
45
+ should "clean ghost messages" do
46
+ ActiveMailbox::Fixtures.simulate_ghosts(@folder)
47
+ count = @folder.size
48
+ @folder.clean_ghosts!
49
+ assert count > @folder.size
50
+ end
51
+
52
+ should "purge all messages" do
53
+ @folder.purge!
54
+ assert @folder.size == 0
55
+ end
56
+
57
+ should "clean stale messages" do
58
+ count = @folder.size
59
+ @folder.clean_stale!
60
+ assert @folder.size < count
61
+ end
62
+
63
+ should "be compared to another instance by path" do
64
+ mailbox = ActiveMailbox::Mailbox.find(@mailbox.mailbox)
65
+ folder = ActiveMailbox::Folder.new(@folder.path, mailbox)
66
+ assert folder == @folder
67
+ end
68
+ end
69
+ end
70
+ end