=begin
Feed2Imap - RSS/Atom Aggregator uploading to an IMAP Server
Copyright (c) 2005 Lucas Nussbaum <lucas@lucas-nussbaum.net>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
=end

# Imap connection handling
require 'net/imap'
begin
require 'openssl'
rescue LoadError
end
require 'cgi'
require 'uri'

# This class is a container of IMAP accounts.
# Thanks to it, accounts are re-used : several feeds
# using the same IMAP account will create only one
# IMAP connection.
class ImapAccounts < Hash

  def add_account(uri)
    u = URI::Generic::build({ :scheme => uri.scheme,
                              :userinfo => uri.userinfo,
                              :host => uri.host,
                              :port => uri.port })
    if not include?(u)
      ac = ImapAccount::new(u)
      self[u] = ac
    end
    return self[u]
  end
end

# This class is an IMAP account, with the given fd
# once the connection has been established
class ImapAccount
  attr_reader :uri

  @@no_ssl_verify = false
  def ImapAccount::no_ssl_verify=(v)
    @@no_ssl_verify = v
  end

  def initialize(uri)
    @uri = uri
    @existing_folders = []
    self
  end

  # connects to the IMAP server
  # raises an exception if it fails
  def connect
    port = 143
    usessl = false
    if uri.scheme == 'imap'
      port = 143
      usessl = false
    elsif uri.scheme == 'imaps'
      port = 993
      usessl = true
    else
      raise "Unknown scheme: #{uri.scheme}"
    end
    # use given port if port given
    port = uri.port if uri.port 
    @connection = Net::IMAP::new(uri.host, port, usessl, nil, !@@no_ssl_verify)
    user, password = CGI::unescape(uri.userinfo).split(':',2)
    @connection.login(user, password)
    self
  end

  # disconnect from the IMAP server
  def disconnect
    if @connection
      @connection.logout
      @connection.disconnect
    end
  end

  # tests if the folder exists and create it if not
  def create_folder_if_not_exists(folder)
    return if @existing_folders.include?(folder)
    if @connection.list('', folder).nil?
      @connection.create(folder)
      @connection.subscribe(folder)
    end
    @existing_folders << folder
  end

  # Put the mail in the given folder
  # You should check whether the folder exist first.
  def putmail(folder, mail, date = Time::now)
    create_folder_if_not_exists(folder)
    @connection.append(folder, mail.gsub(/\n/, "\r\n"), nil, date)
  end

  # update a mail
  def updatemail(folder, mail, id, date = Time::now, reupload_if_updated = true)
    create_folder_if_not_exists(folder)
    @connection.select(folder)
    searchres = @connection.search(['HEADER', 'Message-Id', id])
    flags = nil
    if searchres.length > 0
      # we get the flags from the first result and delete everything
      flags = @connection.fetch(searchres[0], 'FLAGS')[0].attr['FLAGS']
      searchres.each { |m| @connection.store(m, "+FLAGS", [:Deleted]) }
      @connection.expunge
      flags -= [ :Recent ] # avoids errors with dovecot
    elsif not reupload_if_updated
      # mail not present, and we don't want to re-upload it
      return
    end
    @connection.append(folder, mail.gsub(/\n/, "\r\n"), flags, date)
  end

  # convert to string
  def to_s
    u2 = uri.clone
    u2.password = 'PASSWORD'
    u2.to_s
  end

  # remove mails in a folder according to a criteria
  def cleanup(folder, dryrun = false)
    puts "-- Considering #{folder}:"
    @connection.select(folder)
    a = ['SEEN', 'NOT', 'FLAGGED', 'BEFORE', (Date::today - 3).strftime('%d-%b-%Y')]
    todel = @connection.search(a)
    todel.each do |m|
      f = @connection.fetch(m, "FULL")
      d = f[0].attr['INTERNALDATE']
      s = f[0].attr['ENVELOPE'].subject
      if s =~ /^=\?utf-8\?b\?/
        s = Base64::decode64(s.gsub(/^=\?utf-8\?b\?(.*)\?=$/, '\1')).force_encoding('utf-8')
      elsif s =~ /^=\?iso-8859-1\?b\?/
        s = Base64::decode64(s.gsub(/^=\?iso-8859-1\?b\?(.*)\?=$/, '\1')).force_encoding('iso-8859-1').encode('utf-8')
      end
      if dryrun
        puts "To remove: #{s} (#{d})"
      else
        puts "Removing: #{s} (#{d})"
        @connection.store(m, "+FLAGS", [:Deleted])
      end
    end
    puts "-- Deleted #{todel.length} messages."
    if not dryrun
      @connection.expunge
    end
    return todel.length
  end
end

