=begin rdoc = Port class =end #-- # Copyright (c) 1998-2003 Minero Aoki # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # # Note: Originally licensed under LGPL v2+. Using MIT license for Rails # with permission of Minero Aoki. #++ require 'tmail/stringio' module TMail class Port def reproducible? false end end ### ### FilePort ### class FilePort < Port def initialize( fname ) @filename = File.expand_path(fname) super() end attr_reader :filename alias ident filename def ==( other ) other.respond_to?(:filename) and @filename == other.filename end alias eql? == def hash @filename.hash end def inspect "#<#{self.class}:#{@filename}>" end def reproducible? true end def size File.size @filename end def ropen( &block ) File.open(@filename, &block) end def wopen( &block ) File.open(@filename, 'w', &block) end def aopen( &block ) File.open(@filename, 'a', &block) end def read_all ropen {|f| return f.read } end def remove File.unlink @filename end def move_to( port ) begin File.link @filename, port.filename rescue Errno::EXDEV copy_to port end File.unlink @filename end alias mv move_to def copy_to( port ) if FilePort === port copy_file @filename, port.filename else File.open(@filename) {|r| port.wopen {|w| while s = r.sysread(4096) w.write << s end } } end end alias cp copy_to private # from fileutils.rb def copy_file( src, dest ) st = r = w = nil File.open(src, 'rb') {|r| File.open(dest, 'wb') {|w| st = r.stat begin while true w.write r.sysread(st.blksize) end rescue EOFError end } } end end module MailFlags def seen=( b ) set_status 'S', b end def seen? get_status 'S' end def replied=( b ) set_status 'R', b end def replied? get_status 'R' end def flagged=( b ) set_status 'F', b end def flagged? get_status 'F' end private def procinfostr( str, tag, true_p ) a = str.upcase.split(//) a.push true_p ? tag : nil a.delete tag unless true_p a.compact.sort.join('').squeeze end end class MhPort < FilePort include MailFlags private def set_status( tag, flag ) begin tmpfile = @filename + '.tmailtmp.' + $$.to_s File.open(tmpfile, 'w') {|f| write_status f, tag, flag } File.unlink @filename File.link tmpfile, @filename ensure File.unlink tmpfile end end def write_status( f, tag, flag ) stat = '' File.open(@filename) {|r| while line = r.gets if line.strip.empty? break elsif m = /\AX-TMail-Status:/i.match(line) stat = m.post_match.strip else f.print line end end s = procinfostr(stat, tag, flag) f.puts 'X-TMail-Status: ' + s unless s.empty? f.puts while s = r.read(2048) f.write s end } end def get_status( tag ) File.foreach(@filename) {|line| return false if line.strip.empty? if m = /\AX-TMail-Status:/i.match(line) return m.post_match.strip.include?(tag[0]) end } false end end class MaildirPort < FilePort def move_to_new new = replace_dir(@filename, 'new') File.rename @filename, new @filename = new end def move_to_cur new = replace_dir(@filename, 'cur') File.rename @filename, new @filename = new end def replace_dir( path, dir ) "#{File.dirname File.dirname(path)}/#{dir}/#{File.basename path}" end private :replace_dir include MailFlags private MAIL_FILE = /\A(\d+\.[\d_]+\.[^:]+)(?:\:(\d),(\w+)?)?\z/ def set_status( tag, flag ) if m = MAIL_FILE.match(File.basename(@filename)) s, uniq, type, info, = m.to_a return if type and type != '2' # do not change anything newname = File.dirname(@filename) + '/' + uniq + ':2,' + procinfostr(info.to_s, tag, flag) else newname = @filename + ':2,' + tag end File.link @filename, newname File.unlink @filename @filename = newname end def get_status( tag ) m = MAIL_FILE.match(File.basename(@filename)) or return false m[2] == '2' and m[3].to_s.include?(tag[0]) end end ### ### StringPort ### class StringPort < Port def initialize( str = '' ) @buffer = str super() end def string @buffer end def to_s @buffer.dup end alias read_all to_s def size @buffer.size end def ==( other ) StringPort === other and @buffer.equal? other.string end alias eql? == def hash @buffer.object_id.hash end def inspect "#<#{self.class}:id=#{sprintf '0x%x', @buffer.object_id}>" end def reproducible? true end def ropen( &block ) @buffer or raise Errno::ENOENT, "#{inspect} is already removed" StringInput.open(@buffer, &block) end def wopen( &block ) @buffer = '' StringOutput.new(@buffer, &block) end def aopen( &block ) @buffer ||= '' StringOutput.new(@buffer, &block) end def remove @buffer = nil end alias rm remove def copy_to( port ) port.wopen {|f| f.write @buffer } end alias cp copy_to def move_to( port ) if StringPort === port str = @buffer port.instance_eval { @buffer = str } else copy_to port end remove end end end # module TMail