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