Skip to content
This repository has been archived by the owner on Dec 5, 2023. It is now read-only.

Commit

Permalink
Refactor into a better structure
Browse files Browse the repository at this point in the history
  • Loading branch information
ConradIrwin committed Apr 14, 2011
1 parent 2cf31f4 commit a2cbccf
Show file tree
Hide file tree
Showing 7 changed files with 262 additions and 250 deletions.
118 changes: 16 additions & 102 deletions lib/imap.rb
Original file line number Diff line number Diff line change
@@ -1,108 +1,22 @@
module EventMachine
class Imap
def self.connect(host, port, ssl=false)
conn = EventMachine.connect(host, port, EventMachine::ImapConnection)
conn.start_tls if ssl
new(conn)
end

def initialize(connection)
@connection = connection
end
require 'net/imap'

def disconnect
@connection.close_connection
end

## 6.1 Client commands - any state.

def capability
one_data_response("CAPABILITY")
end

def noop
tagged_response("NOOP")
end
require 'rubygems'
require 'eventmachine'
require 'deferrable_gratification'

# Logout and close the connection.
def logout
tagged_response("LOGOUT").errback do |e|
if e.is_a? Net::IMAP::ByeResponseError
# RFC 3501 says the server MUST send a BYE response and then close the connection.
disconnect
succeed
end
end.callback do |response|
fail Net::IMAP::ResponseParseError.new("Received the wrong response to LOGOUT: #{response}")
end
end

## 6.2 Client commands - "Not authenticated" state

def starttls
raise NotImplementedError
end

def authenticate(auth_type, *args)
auth_type = auth_type.upcase
raise "bleargh"
end


def create(mailbox)
tagged_response("CREATE")
end

def delete(mailbox)
tagged_response("DELETE")
end
$:.unshift File.dirname( __FILE__ )
require 'imap/command_sender'
require 'imap/response_parser'
require 'imap/connection'
require 'imap/client'
$:.shift

def examine(mailbox)
tagged_response("EXAMINE")
end

def login(username, password)
tagged_response("LOGIN", username, password)
end

def logout
tagged_response("LOGOUT")
end

def select(mailbox)
tagged_response("SELECT", mailbox)
end

def subscribe(mailbox)
send_command("SUBSCRIBE", mailbox)
end

def rename(mailbox, newname)
tagged_response("RENAME", mailbox, newname)
end

private

# The callback of a Command returns both a tagged response,
# and optionally a list of untagged responses that were
# generated at the same time.
def tagged_response(*command)
send_command(*command).transform{ |response, data| response }
end

def one_data_response(*command)
send_command(*command).transform{ |response, data| data.last }
end

def multi_data_response(*command)
send_command(*command).transform{ |response, data| data }
end

def send_command(cmd, *args)
connection.send_command(cmd, *args)
module EventMachine
module Imap
def self.connect(host, port, ssl=false)
conn = EventMachine.connect(host, port, EventMachine::Imap::Connection)
conn.start_tls if ssl
Client.new(conn)
end

attr_reader :connection
private :connection
end
end
Empty file added lib/imap/authenticators.rb
Empty file.
101 changes: 101 additions & 0 deletions lib/imap/client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
module EventMachine
module Imap
class Client
def initialize(connection)
@connection = connection
end

def disconnect
@connection.close_connection
end

## 6.1 Client commands - any state.

def capability
one_data_response("CAPABILITY")
end

def noop
tagged_response("NOOP")
end

# Logout and close the connection.
def logout
tagged_response("LOGOUT").errback do |e|
if e.is_a? Net::IMAP::ByeResponseError
# RFC 3501 says the server MUST send a BYE response and then close the connection.
disconnect
succeed
end
end.callback do |response|
fail Net::IMAP::ResponseParseError.new("Received the wrong response to LOGOUT: #{response}")
end
end

## 6.2 Client commands - "Not authenticated" state

def starttls
raise NotImplementedError
end

def authenticate(auth_type, *args)
auth_type = auth_type.upcase
raise "bleargh"
end


def create(mailbox)
tagged_response("CREATE")
end

def delete(mailbox)
tagged_response("DELETE")
end

def examine(mailbox)
tagged_response("EXAMINE")
end

def login(username, password)
tagged_response("LOGIN", username, password)
end

def logout
tagged_response("LOGOUT")
end

def select(mailbox)
tagged_response("SELECT", mailbox)
end

def subscribe(mailbox)
send_command("SUBSCRIBE", mailbox)
end

def rename(mailbox, newname)
tagged_response("RENAME", mailbox, newname)
end

private

# The callback of a Command returns both a tagged response,
# and optionally a list of untagged responses that were
# generated at the same time.
def tagged_response(*command)
send_command(*command).transform{ |response, data| response }
end

def one_data_response(*command)
send_command(*command).transform{ |response, data| data.last }
end

def multi_data_response(*command)
send_command(*command).transform{ |response, data| data }
end

def send_command(cmd, *args)
@connection.send_command(cmd, *args)
end
end
end
end
2 changes: 1 addition & 1 deletion lib/command_sender.rb → lib/imap/command_sender.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module EventMachine
module ImapConnection
module Imap
# Provides a send_command_object method that serializes command objects
# and uses send_data on them. This is the ugly sister to ResponseParser.
module CommandSender
Expand Down
143 changes: 143 additions & 0 deletions lib/imap/connection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
require 'net/imap'
require File.dirname( __FILE__ ) + '/response_parser.rb'
require File.dirname( __FILE__ ) + '/command_sender.rb'
module EventMachine
module Imap
CRLF = "\r\n"
module Connection
attr_reader :waiting

def self.connect(host, port, ssl=false)
EventMachine.connect(host, port, self).tap do |conn|
conn.start_tls if ssl
end
end

def post_init
super
@tagged_commands = {}
@named_responses = {}
end

def send_command(cmd, *args)
Command.new(next_tag!, cmd, args).tap do |command|

add_to_listener_pool(command)
command.bothback do
remove_from_listener_pool(command)
end

send_command_object(command)
end
rescue => e
fail_all e
end

# See also Net::IMAP#receive_responses
def receive_response(response)
case response
when Net::IMAP::TaggedResponse

if @tagged_commands[response.tag]
complete_response @tagged_commands[response.tag], response
else
# The server has responded to a request we didn't make, let's bail.
fail_all Net::IMAP::ResponseParseError.new(response.raw_data)
end

when Net::IMAP::UntaggedResponse
if response.name == "BYE" # && @logout_command_tag.nil?
fail_all Net::IMAP::ByeResponseError.new(response.raw_data)
else
record_response(response.name, response.data)
end

when Net::IMAP::ContinuationRequest
# TODO

end
rescue => e
fail_all e
end

# Net::IMAP#pick_up_tagged_response
def complete_response(command, response)
case response.name
when "NO"
command.fail Net::IMAP::NoResponseError.new(response.data.text)
when "BAD"
command.fail Net::IMAP::BadResponseError.new(response.data.text)
else
command.succeed response, @named_responses.delete(command.cmd)
end
end

# NOTE: This is a pretty horrible way to do things.
def record_response(name, response)
@named_responses[name] ||= []
@named_responses[name] << response
end

def add_to_listener_pool(command)
@tagged_commands[command.tag] = command
end

def remove_from_listener_pool(command)
@tagged_commands.delete command.tag
end

def fail_all(error)
@tagged_commands.values.each do |command|
command.fail error
end
raise error
end

def unbind
unless @tagged_commands.empty?
fail_all EOFError.new("end of file reached")
end
end

# Provides a next_tag! method to generate unique tags
# for an Imap session.
module TagSequence
def post_init
super
# Copying Net::IMAP
@tag_prefix = "RUBY"
@tagno = 0
end

def next_tag!
@tagno += 1
"%s%04d" % [@tag_prefix, @tagno]
end
end

# Intercepts send_data and receive_data and logs them to STDOUT,
# this should be the last module included.
module Debug
def send_data(data)
puts "C: #{data.inspect}"
super
end

def receive_data(data)
puts "S: #{data.inspect}"
super
end
end

class Command < Struct.new(:tag, :cmd, :args)
include EventMachine::Deferrable
end
DG.enhance! Command

include Imap::CommandSender
include Imap::ResponseParser
include Imap::Connection::TagSequence
include Imap::Connection::Debug
end
end
end
2 changes: 1 addition & 1 deletion lib/response_parser.rb → lib/imap/response_parser.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module EventMachine
module ImapConnection
module Imap
# Intercepts the receive_data event and generates receive_response events
# with parsed data.
module ResponseParser
Expand Down
Loading

0 comments on commit a2cbccf

Please sign in to comment.