require 'time'
require 'rmail'
require 'cgi'
+require 'set'
## TODO: remove synchronized method protector calls; use a Monitor instead
## (ruby's reentrant mutex)
module Redwood
class IMAP < Source
+ include SerializeLabelsNicely
SCAN_INTERVAL = 60 # seconds
## upon these errors we'll try to rereconnect a few times
@imap_state = {}
@ids = []
@last_scan = nil
- @labels = ((labels || []) - LabelManager::RESERVED_LABELS).uniq.freeze
+ @labels = Set.new((labels || []) - LabelManager::RESERVED_LABELS)
@say_id = nil
@mutex = Mutex.new
end
def == o; o.is_a?(IMAP) && o.uri == self.uri && o.username == self.username; end
def load_header id
- MBox::read_header StringIO.new(raw_header(id))
+ parse_raw_email_header StringIO.new(raw_header(id))
end
def load_message id
end
synchronized :raw_header
+ def store_message date, from_email, &block
+ message = StringIO.new
+ yield message
+ message.string.gsub! /\n/, "\r\n"
+
+ safely { @imap.append mailbox, message.string, [:Seen], Time.now }
+ end
+
def raw_message id
unsynchronized_scan_mailbox
get_imap_fields(id, 'RFC822').first.gsub(/\r\n/, "\n")
end
synchronized :raw_message
+ def mark_as_deleted ids
+ ids = [ids].flatten # accept single arguments
+ unsynchronized_scan_mailbox
+ imap_ids = ids.map { |i| @imap_state[i] && @imap_state[i][:id] }.compact
+ return if imap_ids.empty?
+ @imap.store imap_ids, "+FLAGS", [:Deleted]
+ end
+ synchronized :mark_as_deleted
+
+ def expunge
+ @imap.expunge
+ unsynchronized_scan_mailbox true
+ true
+ end
+ synchronized :expunge
+
def connect
return if @imap
safely { } # do nothing!
end
synchronized :connect
- def scan_mailbox
- return if @last_scan && (Time.now - @last_scan) < SCAN_INTERVAL
+ def scan_mailbox force=false
+ return if !force && @last_scan && (Time.now - @last_scan) < SCAN_INTERVAL
last_id = safely do
@imap.examine mailbox
@imap.responses["EXISTS"].last
end
@last_scan = Time.now
+ @ids = [] if force
return if last_id == @ids.length
range = (@ids.length + 1) .. last_id
%w(RFC822.SIZE INTERNALDATE).each do |w|
raise FatalSourceError, "requested data not in IMAP response: #{w}" unless imap_stuff.attr[w]
end
-
+
msize, mdate = imap_stuff.attr['RFC822.SIZE'] % 10000000, Time.parse(imap_stuff.attr["INTERNALDATE"])
sprintf("%d%07d", mdate.to_i, msize).to_i
end