summaryrefslogtreecommitdiff
path: root/lib/feed2imap/maildir.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/feed2imap/maildir.rb')
-rw-r--r--lib/feed2imap/maildir.rb173
1 files changed, 173 insertions, 0 deletions
diff --git a/lib/feed2imap/maildir.rb b/lib/feed2imap/maildir.rb
new file mode 100644
index 0000000..b91fa64
--- /dev/null
+++ b/lib/feed2imap/maildir.rb
@@ -0,0 +1,173 @@
+=begin
+Feed2Imap - RSS/Atom Aggregator uploading to an IMAP Server, or local Maildir
+Copyright (c) 2009 Andreas Rottmann
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+=end
+
+require 'uri'
+require 'fileutils'
+require 'fcntl'
+
+class MaildirAccount
+ MYHOSTNAME = Socket.gethostname
+
+ attr_reader :uri
+
+ def putmail(folder, mail, date = Time::now)
+ store_message(folder_dir(folder), date, nil) do |f|
+ f.puts(mail)
+ end
+ end
+
+ def updatemail(folder, mail, idx, date = Time::now)
+ dir = folder_dir(folder)
+ guarantee_maildir(dir)
+ mail_files = find_mails(dir, idx)
+ flags = nil
+ if mail_files.length > 0
+ puts "UPDATE: #{mail_files}"
+ # get the info from the first result and delete everything
+ info = maildir_file_info(mail_files[0])
+ mail_files.each { |f| File.delete(File.join(dir, f)) }
+ end
+ store_message(dir, date, info) { |f| f.puts(mail) }
+ end
+
+ def to_s
+ uri.to_s
+ end
+
+ def cleanup(folder, dryrun = false)
+ dir = folder_dir(folder)
+ puts "-- Considering #{dir}:"
+ guarantee_maildir(dir)
+
+ del_count = 0
+ recent_time = Time.now() -- (3 * 24 * 60 * 60) # 3 days
+ Dir[File.join(dir, 'cur', '*')].each do |fn|
+ flags = maildir_file_info_flags(fn)
+ # don't consider not-seen, flagged, or recent messages
+ mtime = File.mtime(fn)
+ next if (not flags.index('S') or
+ flags.index('F') or
+ mtime > recent_time)
+ File.open(fn) do |f|
+ mail = RMail::Parser.read(f)
+ end
+ if dryrun
+ puts "To remove: #{subject} #{mtime}"
+ else
+ puts "Removing: #{subject} #{mtime}"
+ File.delete(fn)
+ end
+ del_count += 1
+ end
+ puts "-- Deleted #{del_count} messages"
+ return del_count
+ end
+
+ private
+
+ def folder_dir(folder)
+ return File.join('/', folder)
+ end
+
+ def store_message(dir, date, info, &block)
+ # TODO: handle `date'
+
+ guarantee_maildir(dir)
+
+ stored = false
+ Dir.chdir(dir) do |d|
+ timer = 30
+ fd = nil
+ while timer >= 0
+ new_fn = new_maildir_basefn
+ tmp_path = File.join(dir, 'tmp', new_fn)
+ new_path = File.join(dir, 'new', new_fn)
+ begin
+ fd = IO::sysopen(tmp_path,
+ Fcntl::O_WRONLY | Fcntl::O_EXCL | Fcntl::O_CREAT)
+ break
+ rescue Errno::EEXIST
+ sleep 2
+ timer -= 2
+ next
+ end
+ end
+
+ if fd
+ begin
+ f = IO.open(fd)
+ # provide a writable interface for the caller
+ yield f
+ f.fsync
+ File.link tmp_path, new_path
+ stored = true
+ ensure
+ File.unlink tmp_path if File.exists? tmp_path
+ end
+ end
+
+ if stored and info
+ cur_path = File.join(dir, 'cur', new_fn + ':' + info)
+ File.rename(new_path, cur_path)
+ end
+ end # Dir.chdir
+
+ return stored
+ end
+
+ def find_mails(dir, idx)
+ dir_paths = []
+ ['cur', 'new'].each do |d|
+ subdir = File.join(dir, d)
+ raise "#{subdir} not a directory" unless File.directory? subdir
+ Dir[File.join(subdir, '*')].each do |fn|
+ File.open(fn) do |f|
+ mail = RMail::Parser.read(f)
+ cache_index = mail.header['X-CacheIndex']
+ next if not (cache_index and
+ cache_index =~ /^-[0-9]+-/ and
+ cache_index[1..-2].to_i == idx)
+ dir_paths.push(File.join(d, File.basename(fn)))
+ end
+ end
+ end
+ return dir_paths
+ end
+
+ def guarantee_maildir(dir)
+ # Ensure maildir-folderness
+ ['new', 'cur', 'tmp'].each do |d|
+ FileUtils.mkdir_p(File.join(dir, d))
+ end
+ end
+
+ def maildir_file_info(file)
+ basename = File.basename(file)
+ colon = basename.rindex(':')
+
+ return (colon and basename.slice(colon + 1, -1))
+ end
+
+ # Shamelessly taken from
+ # http://gitorious.org/sup/mainline/blobs/master/lib/sup/maildir.rb
+ def new_maildir_basefn
+ Kernel::srand()
+ "#{Time.now.to_i.to_s}.#{$$}#{Kernel.rand(1000000)}.#{MYHOSTNAME}"
+ end
+end
+