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)
147 @mboxes.each { |mb| query_status(mb) }
149 raise(LoginError, "cannot login - #{resp.data}")
152 @mboxes.delete(req.mailbox)
161 def handle_untagged_response(resp)
162 return if resp.key != "STATUS"
164 md = resp.data.match(/\"(.+)\" \(UNSEEN (\d+)\)/)
166 raise(IMAPError, "invalid status response - #{resp.data}")
169 name, count = md.captures.first, md.captures.last.to_i
171 MailboxStatusEvent.raise(name, count)
178 def initialize(session)
180 @tag = session.next_tag
185 puts("-> #{@tag} #{serialize}")
187 @session.connection << "#{@tag} #{serialize}\r\n"
193 class LoginRequest < Request
194 def initialize(session, login, password)
202 "LOGIN #{@login} #{@password}"
206 class LogoutRequest < Request
212 class StatusRequest < Request
215 def initialize(session, mailbox)
222 "STATUS #{@mailbox} (UNSEEN)"
227 class ResponseError < IMAPError; end
231 def Response.deserialize(line)
233 when /^\* (\w+) (.*)$/
234 UntaggedResponse.new($1, $2)
235 when /^(0x[[:xdigit:]]+) (OK|NO|BAD) /
237 rest = line[(tag.length + 1)..-1]
238 status = $2.downcase.to_sym
240 TaggedResponse.new(tag, status, rest)
242 raise(ResponseError, "cannot parse - #{line}")
247 @data = data.to_str.freeze
255 class TaggedResponse < Response
256 attr_reader :tag, :status
258 def initialize(tag, status, data)
270 class UntaggedResponse < Response
273 def initialize(key, data)