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(server_info, mboxes)
41 @server_info = server_info
44 flags |= 16 if @server_info.use_ssl
46 @connection = Ecore::Con::Server.new(flags,
55 @state = :disconnected
58 Ecore::EventHandler.new(Ecore::Con::ServerAddEvent,
60 Ecore::EventHandler.new(Ecore::Con::ServerDataEvent,
62 Ecore::EventHandler.new(Ecore::Con::ServerDelEvent,
68 @tag_id = 0 if @tag_id == 0xffff
70 "0x%x" % [@tag_id += 1]
74 def login(login, password)
75 @requests << LoginRequest.new(self, login, password).send
79 @requests << LogoutRequest.new(self).send
82 def query_status(mailbox)
83 @requests << StatusRequest.new(self, mailbox).send
87 return true unless ev.server == @connection
95 return true unless ev.server == @connection
97 lines = (@buffer + ev.data).split(/\r\n/, -1)
100 lines.each { |line| handle_line(line.strip) }
106 return true unless ev.server == @connection
108 @handlers.each { |h| h.delete }
110 @state = :disconnected
118 def handle_line(line)
122 handle_response(Response.deserialize(line))
124 if @state == :connected
125 login(@server_info[:login], @server_info[:password])
129 def handle_response(resp)
131 handle_untagged_response(resp)
133 req = @requests.find { |r| r.tag == resp.tag }
134 handle_tagged_response(resp, req)
135 @requests.delete(req)
139 def handle_tagged_response(resp, req)
140 return unless req.is_a?(LoginRequest)
146 @mboxes.each { |mb| query_status(mb) }
148 raise(LoginError, "cannot login - #{resp.data}")
152 def handle_untagged_response(resp)
153 return if resp.key != "STATUS"
155 md = resp.data.match(/\"(.+)\" \(UNSEEN (\d+)\)/)
157 raise(IMAPError, "invalid status response - #{resp.data}")
160 name, count = md.captures.first, md.captures.last.to_i
162 MailboxStatusEvent.raise(name, count)
175 def initialize(session)
177 @tag = session.next_tag
182 puts("-> #{@tag} #{serialize}")
184 @session.connection << "#{@tag} #{serialize}\r\n"
190 class LoginRequest < Request
191 def initialize(session, login, password)
199 "LOGIN #{@login} #{@password}"
203 class LogoutRequest < Request
204 def initialize(session)
213 class StatusRequest < Request
214 def initialize(session, mailbox)
221 "STATUS #{@mailbox} (UNSEEN)"
226 class ResponseError < IMAPError; end
230 def Response.deserialize(line)
232 when /^\* (\w+) (.*)$/
233 UntaggedResponse.new($1, $2)
234 when /^(0x[[:xdigit:]]+) (OK|NO|BAD) /
236 rest = line[(tag.length + 1)..-1]
237 status = $2.downcase.to_sym
239 TaggedResponse.new(tag, status, rest)
241 raise(ResponseError, "cannot parse - #{line}")
246 @data = data.to_str.freeze
254 class TaggedResponse < Response
255 attr_reader :tag, :status
257 def initialize(tag, status, data)
269 class UntaggedResponse < Response
272 def initialize(key, data)