lib/sup/maildir.rb in sup-0.8.1 vs lib/sup/maildir.rb in sup-0.9

- old
+ new

@@ -7,11 +7,13 @@ ## requires to be really useful. So we must maintain, in memory, a ## mapping between Sup "ids" (timestamps, essentially) and the ## pathnames on disk. class Maildir < Source + include SerializeLabelsNicely SCAN_INTERVAL = 30 # seconds + MYHOSTNAME = Socket.gethostname ## remind me never to use inheritance again. yaml_properties :uri, :cur_offset, :usual, :archived, :id, :labels, :mtimes def initialize uri, last_date=nil, usual=true, archived=false, id=nil, labels=[], mtimes={} super uri, last_date, usual, archived, id @@ -20,11 +22,11 @@ raise ArgumentError, "not a maildir URI" unless uri.scheme == "maildir" raise ArgumentError, "maildir URI cannot have a host: #{uri.host}" if uri.host raise ArgumentError, "maildir URI must have a path component" unless uri.path @dir = uri.path - @labels = (labels || []).freeze + @labels = Set.new(labels || []) @ids = [] @ids_to_fns = {} @last_scan = nil @mutex = Mutex.new #the mtime from the subdirs in the maildir with the unix epoch as default. @@ -42,11 +44,39 @@ scan_mailbox return unless start_offset start = @ids.index(cur_offset || start_offset) or raise OutOfSyncSourceError, "Unknown message id #{cur_offset || start_offset}." # couldn't find the most recent email end - + + def store_message date, from_email, &block + stored = false + new_fn = new_maildir_basefn + ':2,S' + Dir.chdir(@dir) do |d| + tmp_path = File.join(@dir, 'tmp', new_fn) + new_path = File.join(@dir, 'new', new_fn) + begin + sleep 2 if File.stat(tmp_path) + + File.stat(tmp_path) + rescue Errno::ENOENT #this is what we want. + begin + File.open(tmp_path, 'w') do |f| + yield f #provide a writable interface for the caller + f.fsync + end + + File.link tmp_path, new_path + stored = true + ensure + File.unlink tmp_path if File.exists? tmp_path + end + end #rescue Errno... + end #Dir.chdir + + stored + end + def each_raw_message_line id scan_mailbox with_file_for(id) do |f| until f.eof? yield f.gets @@ -84,11 +114,11 @@ return unless @ids.empty? || opts[:rescan] return if @last_scan && (Time.now - @last_scan) < SCAN_INTERVAL initial_poll = @ids.empty? - Redwood::log "scanning maildir #@dir..." + debug "scanning maildir #@dir..." begin @mtimes.each_key do |d| subdir = File.join(@dir, d) raise FatalSourceError, "#{subdir} not a directory" unless File.directory? subdir @@ -103,19 +133,19 @@ id = make_id fn @dir_ids[d] << id @ids_to_fns[id] = fn end else - Redwood::log "no poll on #{d}. mtime on indicates no new messages." + debug "no poll on #{d}. mtime on indicates no new messages." end end @ids = @dir_ids.values.flatten.uniq.sort! rescue SystemCallError, IOError => e raise FatalSourceError, "Problem scanning Maildir directories: #{e.message}." end - Redwood::log "done scanning maildir" + debug "done scanning maildir" @last_scan = Time.now end synchronized :scan_mailbox def each @@ -165,10 +195,15 @@ stat = File.stat(fn) # use 7 digits for the size. why 7? seems nice. sprintf("%d%07d", stat.mtime, stat.size % 10000000).to_i end + def new_maildir_basefn + Kernel::srand() + "#{Time.now.to_i.to_s}.#{$$}#{Kernel.rand(1000000)}.#{MYHOSTNAME}" + end + def with_file_for id fn = @ids_to_fns[id] or raise OutOfSyncSourceError, "No such id: #{id.inspect}." begin File.open(fn) { |f| yield f } rescue SystemCallError, IOError => e @@ -176,10 +211,10 @@ end end def maildir_data msg fn = File.basename @ids_to_fns[msg] - fn =~ %r{^([^:,]+):([12]),([DFPRST]*)$} + fn =~ %r{^([^:]+):([12]),([DFPRST]*)$} [($1 || fn), ($2 || "2"), ($3 || "")] end ## not thread-safe on msg def maildir_mark_file msg, flag