]> git.cworth.org Git - sup/blob - lib/sup/imap.rb
a0a7267a68f885a2608930cd09887d32ad6a463f
[sup] / lib / sup / imap.rb
1 require 'uri'
2 require 'net/imap'
3 require 'stringio'
4
5 module Redwood
6
7 class IMAP < Source
8   attr_reader :labels
9   
10   def initialize uri, username, password, uid_validity=nil, last_uid=nil, usual=true, archived=false, id=nil
11     raise ArgumentError, "username and password must be specified" unless username && password
12     raise ArgumentError, "not an imap uri" unless uri =~ %r!imaps?://!
13
14     super uri, last_uid, usual, archived, id
15
16     @parsed_uri = URI(uri)
17     @username = username
18     @password = password
19     @uid_validity = uid_validity
20     @imap = nil
21     @labels = [:unread]
22     @labels << :inbox unless archived?
23     @labels << mailbox.intern unless mailbox =~ /inbox/i || mailbox.nil?
24   end
25
26   def connect
27     return false if broken?
28     return true if @imap
29     Redwood::log "connecting to #{@parsed_uri.host} port #{ssl? ? 993 : 143}, ssl=#{ssl?} ..."
30
31     ## ok, this is FUCKING ANNOYING.
32     ##
33     ## what imap.rb likes to do is, if an exception occurs, catch it
34     ## and re-raise it on the calling thread. seems reasonable. but
35     ## what that REALLY means is that the only way to reasonably
36     ## initialize imap is in its own thread, because otherwise, you
37     ## will never be able to catch the exception it raises on the
38     ## calling thread, and the backtrace will not make any sense at
39     ## all, and you will waste HOURS of your life on this fucking
40     ## problem.
41     ##
42     ## FUCK!!!!!!!!!
43
44     BufferManager.say "Connecting to IMAP server #{host}..." do 
45       ::Thread.new do
46         begin
47           #raise Net::IMAP::ByeResponseError, "simulated imap failure"
48           @imap = Net::IMAP.new host, ssl? ? 993 : 143, ssl?
49           @imap.authenticate 'LOGIN', @username, @password
50           @imap.examine mailbox
51           Redwood::log "successfully connected to #{@parsed_uri}, mailbox #{mailbox}"
52           @uid_validity ||= @imap.responses["UIDVALIDITY"][-1]
53           raise SourceError, "Your shitty IMAP server has kindly invalidated all 'unique' ids for the folder '#{mailbox}'. You will have to rescan this folder manually." if @imap.responses["UIDVALIDITY"][-1] != @uid_validity
54         rescue Exception => e
55           self.broken_msg = e.message.chomp # fucking chomp! fuck!!!
56           @imap = nil
57           Redwood::log "error connecting to IMAP server: #{self.broken_msg}"
58         end
59       end.join
60     end
61
62     !!@imap
63   end
64   private :connect
65
66   def host; @parsed_uri.host; end
67   def mailbox; @parsed_uri.path[1..-1] end ##XXXX TODO handle nil
68   def ssl?; @parsed_uri.scheme == 'imaps' end
69
70   def load_header uid=nil
71     MBox::read_header StringIO.new(raw_header(uid))
72   end
73
74   def load_message uid
75     RMail::Parser.read raw_full_message(uid)
76   end
77
78   ## load the full header text
79   def raw_header uid
80     connect or raise SourceError, broken_msg
81     get_imap_field(uid, 'RFC822.HEADER').gsub(/\r\n/, "\n")
82   end
83
84   def raw_full_message uid
85     connect or raise SourceError, broken_msg
86     get_imap_field(uid, 'RFC822').gsub(/\r\n/, "\n")
87   end
88
89   def get_imap_field uid, field
90     f = @imap.uid_fetch uid, field
91     raise SourceError, "null IMAP field '#{field}' for message with uid #{uid}" if f.nil?
92     f[0].attr[field]
93   end
94   private :get_imap_field
95   
96   def each
97     connect or raise SourceError, broken_msg
98     uids = @imap.uid_search ['UID', "#{cur_offset}:#{end_offset}"]
99     uids.each do |uid|
100       @last_uid = uid
101       @dirty = true
102       self.cur_offset = uid
103       yield uid, labels
104     end
105   end
106
107   def start_offset; 1; end
108   def end_offset
109     connect or return start_offset
110     @imap.uid_search(['ALL']).last
111   end
112 end
113
114 Redwood::register_yaml(IMAP, %w(uri username password uid_validity cur_offset usual archived id))
115
116 end