From 2d32e4dd6dd578a2880a9d35d96d19a1635d7f4a Mon Sep 17 00:00:00 2001 From: shugo Date: Mon, 26 Mar 2001 12:29:50 +0000 Subject: * lib/net/imap.rb: supports response handlers and multiple commands. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@1283 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 4 + lib/net/imap.rb | 242 ++++++++++++++++++++++++++++++++++++++------------------ 2 files changed, 167 insertions(+), 79 deletions(-) diff --git a/ChangeLog b/ChangeLog index f7cb410a32..f8c1d13d97 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +Mon Mar 26 21:16:56 2001 Shugo Maeda + + * lib/net/imap.rb: supports response handlers and multiple commands. + Mon Mar 26 17:21:07 2001 Yukihiro Matsumoto * eval.c: remove TMP_PROTECT_END to prevent C_ALLOCA crash. diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 23b22c0282..a5cccdfbea 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -11,6 +11,20 @@ You can freely distribute/modify this library. Net::IMAP implements Internet Message Access Protocol (IMAP) clients. +Net::IMAP supports multiple commands. For example, + + imap = Net::IMAP.new("imap.foo.net", "imap2") + imap.authenticate("cram-md5", "bar", "password") + imap.select("inbox") + t = Thread.start { + p imap.fetch(1..-1, "UID") + } + p imap.search(["BODY", "hello"]) + t.join + imap.disconnect + +This script invokes the FETCH command and the SEARCH command concurrently. + === Super Class Object @@ -210,14 +224,26 @@ Object p imap.sort(["DATE"], ["SUBJECT", "hello"], "US-ASCII") #=> [6, 7, 8, 1] +: add_response_handler(handler = Proc.new) + Adds a response handler. + +: remove_response_handler(handler) + Removes the response handler. + +: response_handlers + Returns all response handlers. + =end require "socket" +require "monitor" require "md5" module Net class IMAP - attr_reader :greeting, :responses + include MonitorMixin + + attr_reader :greeting, :responses, :response_handlers def self.debug return @@debug @@ -232,12 +258,16 @@ module Net end def disconnect + @sock.shutdown + @receiver_thread.join @sock.close end def capability - send_command("CAPABILITY") - return @responses.delete("CAPABILITY")[-1] + synchronize do + send_command("CAPABILITY") + return @responses.delete("CAPABILITY")[-1] + end end def noop @@ -268,13 +298,17 @@ module Net end def select(mailbox) - @responses.clear - send_command("SELECT", mailbox) + synchronize do + @responses.clear + send_command("SELECT", mailbox) + end end def examine(mailbox) - @responses.clear - send_command("EXAMINE", mailbox) + synchronize do + @responses.clear + send_command("EXAMINE", mailbox) + end end def create(mailbox) @@ -298,18 +332,24 @@ module Net end def list(refname, mailbox) - send_command("LIST", refname, mailbox) - return @responses.delete("LIST") + synchronize do + send_command("LIST", refname, mailbox) + return @responses.delete("LIST") + end end def lsub(refname, mailbox) - send_command("LSUB", refname, mailbox) - return @responses.delete("LSUB") + synchronize do + send_command("LSUB", refname, mailbox) + return @responses.delete("LSUB") + end end def status(mailbox, attr) - send_command("STATUS", mailbox, attr) - return @responses.delete("STATUS")[-1][1] + synchronize do + send_command("STATUS", mailbox, attr) + return @responses.delete("STATUS")[-1][1] + end end def append(mailbox, message, flags = nil, date_time = nil) @@ -331,8 +371,10 @@ module Net end def expunge - send_command("EXPUNGE") - return @responses.delete("EXPUNGE") + synchronize do + send_command("EXPUNGE") + return @responses.delete("EXPUNGE") + end end def search(keys, charset = nil) @@ -375,6 +417,14 @@ module Net return sort_internal("UID SORT", sort_keys, search_keys, charset) end + def add_response_handler(handler = Proc.new) + @response_handlers.push(handler) + end + + def remove_response_handler(handler) + @response_handlers.delete(handler) + end + private CRLF = "\r\n" @@ -384,6 +434,7 @@ module Net @@authenticators = {} def initialize(host, port = PORT) + super() @host = host @port = port @tag_prefix = "RUBY" @@ -391,69 +442,54 @@ module Net @parser = ResponseParser.new @sock = TCPSocket.open(host, port) @responses = Hash.new([].freeze) + @tagged_responses = {} + @response_handlers = [] + @tag_arrival = new_cond + @greeting = get_response if /\ABYE\z/ni =~ @greeting.name @sock.close raise ByeResponseError, resp[0] end - end - - def send_command(cmd, *args, &block) - tag = generate_tag - data = args.collect {|i| format_data(i)}.join(" ") - if data.length > 0 - put_line(tag + " " + cmd + " " + data) - else - put_line(tag + " " + cmd) - end - return get_all_responses(tag, cmd, &block) - end - def generate_tag - @tagno += 1 - return format("%s%04d", @tag_prefix, @tagno) - end - - def send_data(*args) - data = args.collect {|i| format_data(i)}.join(" ") - put_line(data) - end - - def put_line(line) - line = line + CRLF - @sock.print(line) - if @@debug - $stderr.print(line.gsub(/^/n, "C: ")) - end + @receiver_thread = Thread.start { + receive_responses + } end - def get_all_responses(tag, cmd, &block) + def receive_responses while resp = get_response - if @@debug - $stderr.printf("R: %s\n", resp.inspect) - end - case resp - when TaggedResponse - case resp.name - when /\A(?:NO)\z/ni - raise NoResponseError, resp.data.text - when /\A(?:BAD)\z/ni - raise BadResponseError, resp.data.text - else - return resp - end - when UntaggedResponse - if /\ABYE\z/ni =~ resp.name && - cmd != "LOGOUT" - raise ByeResponseError, resp.data.text + synchronize do + case resp + when TaggedResponse + @tagged_responses[resp.tag] = resp + @tag_arrival.broadcast + when UntaggedResponse + record_response(resp.name, resp.data) + if resp.data.instance_of?(ResponseText) && + (code = resp.data.code) + record_response(code.name, code.data) + end end - record_response(resp.name, resp.data) - if resp.data.instance_of?(ResponseText) && - (code = resp.data.code) - record_response(code.name, code.data) + @response_handlers.each do |handler| + handler.call(resp) end end - block.call(resp) if block + end + end + + def get_tagged_response(tag, cmd) + until @tagged_responses.key?(tag) + @tag_arrival.wait + end + resp = @tagged_responses.delete(tag) + case resp.name + when /\A(?:NO)\z/ni + raise NoResponseError, resp.data.text + when /\A(?:BAD)\z/ni + raise BadResponseError, resp.data.text + else + return resp end end @@ -484,6 +520,46 @@ module Net @responses[name].push(data) end + def send_command(cmd, *args, &block) + synchronize do + tag = generate_tag + data = args.collect {|i| format_data(i)}.join(" ") + if data.length > 0 + put_line(tag + " " + cmd + " " + data) + else + put_line(tag + " " + cmd) + end + if block + add_response_handler(block) + end + begin + return get_tagged_response(tag, cmd) + ensure + if block + remove_response_handler(block) + end + end + end + end + + def generate_tag + @tagno += 1 + return format("%s%04d", @tag_prefix, @tagno) + end + + def send_data(*args) + data = args.collect {|i| format_data(i)}.join(" ") + put_line(data) + end + + def put_line(line) + line = line + CRLF + @sock.print(line) + if @@debug + $stderr.print(line.gsub(/^/n, "C: ")) + end + end + def format_data(data) case data when nil @@ -550,30 +626,36 @@ module Net else normalize_searching_criteria(keys) end - if charset - send_command(cmd, "CHARSET", charset, *keys) - else - send_command(cmd, *keys) + synchronize do + if charset + send_command(cmd, "CHARSET", charset, *keys) + else + send_command(cmd, *keys) + end + return @responses.delete("SEARCH")[-1] end - return @responses.delete("SEARCH")[-1] end def fetch_internal(cmd, set, attr) if attr.instance_of?(String) attr = RawData.new(attr) end - @responses.delete("FETCH") - send_command(cmd, MessageSet.new(set), attr) - return @responses.delete("FETCH") + synchronize do + @responses.delete("FETCH") + send_command(cmd, MessageSet.new(set), attr) + return @responses.delete("FETCH") + end end def store_internal(cmd, set, attr, flags) if attr.instance_of?(String) attr = RawData.new(attr) end - @responses.delete("FETCH") - send_command(cmd, MessageSet.new(set), attr, flags) - return @responses.delete("FETCH") + synchronize do + @responses.delete("FETCH") + send_command(cmd, MessageSet.new(set), attr, flags) + return @responses.delete("FETCH") + end end def copy_internal(cmd, set, mailbox) @@ -587,8 +669,10 @@ module Net normalize_searching_criteria(search_keys) end normalize_searching_criteria(search_keys) - send_command(cmd, sort_keys, charset, *search_keys) - return @responses.delete("SORT")[-1] + synchronize do + send_command(cmd, sort_keys, charset, *search_keys) + return @responses.delete("SORT")[-1] + end end def normalize_searching_criteria(keys) -- cgit v1.2.3