1 # Copyright (c) 2006 Tilman Sauerbeck (tilman at code-monkey de)
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of version 2 of the GNU General Public License as
5 # published by the Free Software Foundation.
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
12 # You should have received a copy of the GNU General Public License
13 # along with this program; if not, write to the Free Software Foundation,
14 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
22 class IMAPError < StandardError; end
23 class LoginError < IMAPError; end
25 class MailboxStatusEvent < Ecore::Event
26 attr_reader :name, :count
28 def initialize(name, count)
31 @name, @count = name, count
35 class FinishedEvent < Ecore::Event; end
38 attr_reader :connection
40 def initialize(config)
41 @login = config[:server][:login]
42 @password = config[:server][:password]
45 flags |= 16 if config[:server][:use_ssl]
47 @connection = Ecore::Con::Server.new(flags,
48 config[:server][:host],
49 config[:server][:port])
55 @mboxes = config[:mailboxes].dup
56 @state = :disconnected
59 Ecore::EventHandler.new(Ecore::Con::ServerAddEvent,
61 Ecore::EventHandler.new(Ecore::Con::ServerDataEvent,
63 Ecore::EventHandler.new(Ecore::Con::ServerDelEvent,
69 @tag_id = 0 if @tag_id == 0xffff
71 "0x%x" % [@tag_id += 1]
75 def login(login, password)
76 @requests << LoginRequest.new(self, login, password).send
80 @requests << LogoutRequest.new(self).send
83 def query_status(mailbox)
84 @requests << StatusRequest.new(self, mailbox).send
88 return true unless ev.server == @connection
96 return true unless ev.server == @connection
98 lines = (@buffer + ev.data).split(/\r\n/, -1)
101 lines.each { |line| handle_line(line.strip) }
107 return true unless ev.server == @connection
109 @handlers.each { |h| h.delete }
111 @state = :disconnected
119 def handle_line(line)
123 handle_response(Response.deserialize(line))
125 if @state == :connected
126 login(@login, @password)
130 def handle_response(resp)
132 handle_untagged_response(resp)
134 req = @requests.find { |r| r.tag == resp.tag }
135 handle_tagged_response(resp, req)
136 @requests.delete(req)
140 def handle_tagged_response(resp, req)
141 return unless req.is_a?(LoginRequest)
147 @mboxes.each { |mb| query_status(mb) }
149 raise(LoginError, "cannot login - #{resp.data}")
153 def handle_untagged_response(resp)
154 return if resp.key != "STATUS"
156 md = resp.data.match(/\"(.+)\" \(UNSEEN (\d+)\)/)
158 raise(IMAPError, "invalid status response - #{resp.data}")
161 name, count = md.captures.first, md.captures.last.to_i
163 MailboxStatusEvent.raise(name, count)
176 def initialize(session)
178 @tag = session.next_tag
183 puts("-> #{@tag} #{serialize}")
185 @session.connection << "#{@tag} #{serialize}\r\n"
191 class LoginRequest < Request
192 def initialize(session, login, password)
200 "LOGIN #{@login} #{@password}"
204 class LogoutRequest < Request
210 class StatusRequest < Request
211 def initialize(session, mailbox)
218 "STATUS #{@mailbox} (UNSEEN)"
223 class ResponseError < IMAPError; end
227 def Response.deserialize(line)
229 when /^\* (\w+) (.*)$/
230 UntaggedResponse.new($1, $2)
231 when /^(0x[[:xdigit:]]+) (OK|NO|BAD) /
233 rest = line[(tag.length + 1)..-1]
234 status = $2.downcase.to_sym
236 TaggedResponse.new(tag, status, rest)
238 raise(ResponseError, "cannot parse - #{line}")
243 @data = data.to_str.freeze
251 class TaggedResponse < Response
252 attr_reader :tag, :status
254 def initialize(tag, status, data)
266 class UntaggedResponse < Response
269 def initialize(key, data)