active_mailbox 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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