Froze rails gems
[depot.git] / vendor / rails / actionmailer / lib / action_mailer / vendor / tmail-1.2.3 / tmail / mailbox.rb
1 =begin rdoc
2
3 = Mailbox and Mbox interaction class
4
5 =end
6 #--
7 # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
8 #
9 # Permission is hereby granted, free of charge, to any person obtaining
10 # a copy of this software and associated documentation files (the
11 # "Software"), to deal in the Software without restriction, including
12 # without limitation the rights to use, copy, modify, merge, publish,
13 # distribute, sublicense, and/or sell copies of the Software, and to
14 # permit persons to whom the Software is furnished to do so, subject to
15 # the following conditions:
16 #
17 # The above copyright notice and this permission notice shall be
18 # included in all copies or substantial portions of the Software.
19 #
20 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 #
28 # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
29 # with permission of Minero Aoki.
30 #++
31
32 require 'tmail/port'
33 require 'socket'
34 require 'mutex_m'
35
36
37 unless [].respond_to?(:sort_by)
38 module Enumerable#:nodoc:
39 def sort_by
40 map {|i| [yield(i), i] }.sort {|a,b| a.first <=> b.first }.map {|i| i[1] }
41 end
42 end
43 end
44
45
46 module TMail
47
48 class MhMailbox
49
50 PORT_CLASS = MhPort
51
52 def initialize( dir )
53 edir = File.expand_path(dir)
54 raise ArgumentError, "not directory: #{dir}"\
55 unless FileTest.directory? edir
56 @dirname = edir
57 @last_file = nil
58 @last_atime = nil
59 end
60
61 def directory
62 @dirname
63 end
64
65 alias dirname directory
66
67 attr_accessor :last_atime
68
69 def inspect
70 "#<#{self.class} #{@dirname}>"
71 end
72
73 def close
74 end
75
76 def new_port
77 PORT_CLASS.new(next_file_name())
78 end
79
80 def each_port
81 mail_files().each do |path|
82 yield PORT_CLASS.new(path)
83 end
84 @last_atime = Time.now
85 end
86
87 alias each each_port
88
89 def reverse_each_port
90 mail_files().reverse_each do |path|
91 yield PORT_CLASS.new(path)
92 end
93 @last_atime = Time.now
94 end
95
96 alias reverse_each reverse_each_port
97
98 # old #each_mail returns Port
99 #def each_mail
100 # each_port do |port|
101 # yield Mail.new(port)
102 # end
103 #end
104
105 def each_new_port( mtime = nil, &block )
106 mtime ||= @last_atime
107 return each_port(&block) unless mtime
108 return unless File.mtime(@dirname) >= mtime
109
110 mail_files().each do |path|
111 yield PORT_CLASS.new(path) if File.mtime(path) > mtime
112 end
113 @last_atime = Time.now
114 end
115
116 private
117
118 def mail_files
119 Dir.entries(@dirname)\
120 .select {|s| /\A\d+\z/ === s }\
121 .map {|s| s.to_i }\
122 .sort\
123 .map {|i| "#{@dirname}/#{i}" }\
124 .select {|path| FileTest.file? path }
125 end
126
127 def next_file_name
128 unless n = @last_file
129 n = 0
130 Dir.entries(@dirname)\
131 .select {|s| /\A\d+\z/ === s }\
132 .map {|s| s.to_i }.sort\
133 .each do |i|
134 next unless FileTest.file? "#{@dirname}/#{i}"
135 n = i
136 end
137 end
138 begin
139 n += 1
140 end while FileTest.exist? "#{@dirname}/#{n}"
141 @last_file = n
142
143 "#{@dirname}/#{n}"
144 end
145
146 end # MhMailbox
147
148 MhLoader = MhMailbox
149
150
151 class UNIXMbox
152
153 class << self
154 alias newobj new
155 end
156
157 # Creates a new mailbox object that you can iterate through to collect the
158 # emails from with "each_port".
159 #
160 # You need to pass it a filename of a unix mailbox format file, the format of this
161 # file can be researched at this page at {wikipedia}[link:http://en.wikipedia.org/wiki/Mbox]
162 #
163 # ==== Parameters
164 #
165 # +filename+: The filename of the mailbox you want to open
166 #
167 # +tmpdir+: Can be set to override TMail using the system environment's temp dir. TMail will first
168 # use the temp dir specified by you (if any) or then the temp dir specified in the Environment's TEMP
169 # value then the value in the Environment's TMP value or failing all of the above, '/tmp'
170 #
171 # +readonly+: If set to false, each email you take from the mail box will be removed from the mailbox.
172 # default is *false* - ie, it *WILL* truncate your mailbox file to ZERO once it has read the emails out.
173 #
174 # ==== Options:
175 #
176 # None
177 #
178 # ==== Examples:
179 #
180 # # First show using readonly true:
181 #
182 # require 'ftools'
183 # File.size("../test/fixtures/mailbox")
184 # #=> 20426
185 #
186 # mailbox = TMail::UNIXMbox.new("../test/fixtures/mailbox", nil, true)
187 # #=> #<TMail::UNIXMbox:0x14a2aa8 @readonly=true.....>
188 #
189 # mailbox.each_port do |port|
190 # mail = TMail::Mail.new(port)
191 # puts mail.subject
192 # end
193 # #Testing mailbox 1
194 # #Testing mailbox 2
195 # #Testing mailbox 3
196 # #Testing mailbox 4
197 # require 'ftools'
198 # File.size?("../test/fixtures/mailbox")
199 # #=> 20426
200 #
201 # # Now show with readonly set to the default false
202 #
203 # mailbox = TMail::UNIXMbox.new("../test/fixtures/mailbox")
204 # #=> #<TMail::UNIXMbox:0x14a2aa8 @readonly=false.....>
205 #
206 # mailbox.each_port do |port|
207 # mail = TMail::Mail.new(port)
208 # puts mail.subject
209 # end
210 # #Testing mailbox 1
211 # #Testing mailbox 2
212 # #Testing mailbox 3
213 # #Testing mailbox 4
214 #
215 # File.size?("../test/fixtures/mailbox")
216 # #=> nil
217 def UNIXMbox.new( filename, tmpdir = nil, readonly = false )
218 tmpdir = ENV['TEMP'] || ENV['TMP'] || '/tmp'
219 newobj(filename, "#{tmpdir}/ruby_tmail_#{$$}_#{rand()}", readonly, false)
220 end
221
222 def UNIXMbox.lock( fname )
223 begin
224 f = File.open(fname, 'r+')
225 f.flock File::LOCK_EX
226 yield f
227 ensure
228 f.flock File::LOCK_UN
229 f.close if f and not f.closed?
230 end
231 end
232
233 def UNIXMbox.static_new( fname, dir, readonly = false )
234 newobj(fname, dir, readonly, true)
235 end
236
237 def initialize( fname, mhdir, readonly, static )
238 @filename = fname
239 @readonly = readonly
240 @closed = false
241
242 Dir.mkdir mhdir
243 @real = MhMailbox.new(mhdir)
244 @finalizer = UNIXMbox.mkfinal(@real, @filename, !@readonly, !static)
245 ObjectSpace.define_finalizer self, @finalizer
246 end
247
248 def UNIXMbox.mkfinal( mh, mboxfile, writeback_p, cleanup_p )
249 lambda {
250 if writeback_p
251 lock(mboxfile) {|f|
252 mh.each_port do |port|
253 f.puts create_from_line(port)
254 port.ropen {|r|
255 f.puts r.read
256 }
257 end
258 }
259 end
260 if cleanup_p
261 Dir.foreach(mh.dirname) do |fname|
262 next if /\A\.\.?\z/ === fname
263 File.unlink "#{mh.dirname}/#{fname}"
264 end
265 Dir.rmdir mh.dirname
266 end
267 }
268 end
269
270 # make _From line
271 def UNIXMbox.create_from_line( port )
272 sprintf 'From %s %s',
273 fromaddr(), TextUtils.time2str(File.mtime(port.filename))
274 end
275
276 def UNIXMbox.fromaddr(port)
277 h = HeaderField.new_from_port(port, 'Return-Path') ||
278 HeaderField.new_from_port(port, 'From') ||
279 HeaderField.new_from_port(port, 'EnvelopeSender') or return 'nobody'
280 a = h.addrs[0] or return 'nobody'
281 a.spec
282 end
283
284 def close
285 return if @closed
286
287 ObjectSpace.undefine_finalizer self
288 @finalizer.call
289 @finalizer = nil
290 @real = nil
291 @closed = true
292 @updated = nil
293 end
294
295 def each_port( &block )
296 close_check
297 update
298 @real.each_port(&block)
299 end
300
301 alias each each_port
302
303 def reverse_each_port( &block )
304 close_check
305 update
306 @real.reverse_each_port(&block)
307 end
308
309 alias reverse_each reverse_each_port
310
311 # old #each_mail returns Port
312 #def each_mail( &block )
313 # each_port do |port|
314 # yield Mail.new(port)
315 # end
316 #end
317
318 def each_new_port( mtime = nil )
319 close_check
320 update
321 @real.each_new_port(mtime) {|p| yield p }
322 end
323
324 def new_port
325 close_check
326 @real.new_port
327 end
328
329 private
330
331 def close_check
332 @closed and raise ArgumentError, 'accessing already closed mbox'
333 end
334
335 def update
336 return if FileTest.zero?(@filename)
337 return if @updated and File.mtime(@filename) < @updated
338 w = nil
339 port = nil
340 time = nil
341 UNIXMbox.lock(@filename) {|f|
342 begin
343 f.each do |line|
344 if /\AFrom / === line
345 w.close if w
346 File.utime time, time, port.filename if time
347
348 port = @real.new_port
349 w = port.wopen
350 time = fromline2time(line)
351 else
352 w.print line if w
353 end
354 end
355 ensure
356 if w and not w.closed?
357 w.close
358 File.utime time, time, port.filename if time
359 end
360 end
361 f.truncate(0) unless @readonly
362 @updated = Time.now
363 }
364 end
365
366 def fromline2time( line )
367 m = /\AFrom \S+ \w+ (\w+) (\d+) (\d+):(\d+):(\d+) (\d+)/.match(line) \
368 or return nil
369 Time.local(m[6].to_i, m[1], m[2].to_i, m[3].to_i, m[4].to_i, m[5].to_i)
370 end
371
372 end # UNIXMbox
373
374 MboxLoader = UNIXMbox
375
376
377 class Maildir
378
379 extend Mutex_m
380
381 PORT_CLASS = MaildirPort
382
383 @seq = 0
384 def Maildir.unique_number
385 synchronize {
386 @seq += 1
387 return @seq
388 }
389 end
390
391 def initialize( dir = nil )
392 @dirname = dir || ENV['MAILDIR']
393 raise ArgumentError, "not directory: #{@dirname}"\
394 unless FileTest.directory? @dirname
395 @new = "#{@dirname}/new"
396 @tmp = "#{@dirname}/tmp"
397 @cur = "#{@dirname}/cur"
398 end
399
400 def directory
401 @dirname
402 end
403
404 def inspect
405 "#<#{self.class} #{@dirname}>"
406 end
407
408 def close
409 end
410
411 def each_port
412 mail_files(@cur).each do |path|
413 yield PORT_CLASS.new(path)
414 end
415 end
416
417 alias each each_port
418
419 def reverse_each_port
420 mail_files(@cur).reverse_each do |path|
421 yield PORT_CLASS.new(path)
422 end
423 end
424
425 alias reverse_each reverse_each_port
426
427 def new_port
428 fname = nil
429 tmpfname = nil
430 newfname = nil
431
432 begin
433 fname = "#{Time.now.to_i}.#{$$}_#{Maildir.unique_number}.#{Socket.gethostname}"
434
435 tmpfname = "#{@tmp}/#{fname}"
436 newfname = "#{@new}/#{fname}"
437 end while FileTest.exist? tmpfname
438
439 if block_given?
440 File.open(tmpfname, 'w') {|f| yield f }
441 File.rename tmpfname, newfname
442 PORT_CLASS.new(newfname)
443 else
444 File.open(tmpfname, 'w') {|f| f.write "\n\n" }
445 PORT_CLASS.new(tmpfname)
446 end
447 end
448
449 def each_new_port
450 mail_files(@new).each do |path|
451 dest = @cur + '/' + File.basename(path)
452 File.rename path, dest
453 yield PORT_CLASS.new(dest)
454 end
455
456 check_tmp
457 end
458
459 TOO_OLD = 60 * 60 * 36 # 36 hour
460
461 def check_tmp
462 old = Time.now.to_i - TOO_OLD
463
464 each_filename(@tmp) do |full, fname|
465 if FileTest.file? full and
466 File.stat(full).mtime.to_i < old
467 File.unlink full
468 end
469 end
470 end
471
472 private
473
474 def mail_files( dir )
475 Dir.entries(dir)\
476 .select {|s| s[0] != ?. }\
477 .sort_by {|s| s.slice(/\A\d+/).to_i }\
478 .map {|s| "#{dir}/#{s}" }\
479 .select {|path| FileTest.file? path }
480 end
481
482 def each_filename( dir )
483 Dir.foreach(dir) do |fname|
484 path = "#{dir}/#{fname}"
485 if fname[0] != ?. and FileTest.file? path
486 yield path, fname
487 end
488 end
489 end
490
491 end # Maildir
492
493 MaildirLoader = Maildir
494
495 end # module TMail