Untilted*RSS

moments.each do { post }
Jan
17th
Thu
permalink

Import your IMAP mail to GMail, the easy way

found somewhere on the beautiful internet…

#!/usr/bin/env ruby
require 'net/imap'
require 'timeout'

# Source server connection info.
SOURCE_HOST = "current.mailhost.com"
SOURCE_PORT = 143
SOURCE_SSL  = false
SOURCE_USER = "username"
SOURCE_PASS = "pass"

# Destination server connection info.
DEST_HOST = "imap.gmail.com"
DEST_PORT = 993
DEST_SSL  = true
DEST_USER = "username@gmail.com"
DEST_PASS = "pass"

UID_BLOCK_SIZE = 10 # max number of messages to select at once

#messages with known issues.
blacklist = []

# Mapping of @source folders to @destination folders. The key is the name of the
# folder on the @source server, the value is the name on the @destination server.
# Any folder not specified here will be ignored. If a @destination folder does
# not exist, it will be created.
FOLDERS = {
  # some examples:
  # 'INBOX.@archive.2003' => '2003',
  # 'INBOX.@archive.2004' => '2004',
  # 'INBOX.@archive.2006' => '2006',
  # 'INBOX.@archive.2007' => '2007',
  # 'INBOX.@archive.Sent-Archive' => '[Import].Sent Mail'
  'INBOX' => 'INBOX',
  'INBOX.Sent' => 'Sent Mail'
}

# Utility methods.
def dd(message)
  puts "[#{DEST_HOST}] #{message}"
end

def ds(message)
  puts "[#{SOURCE_HOST}] #{message}"
end

def uid_fetch_block(server, uids, *args)
  pos = 0
  while pos  e
      ds "error: select failed: #{e}"
      next
    end

    # Open (or create) @destination folder in read-write mode.
    begin
      dd "selecting folder ‘#{dest_folder}’..."
      @dest.select(dest_folder)
    rescue => e
      begin
        dd "folder not found; creating…"
        @dest.create(dest_folder)
        @dest.select(dest_folder)
      rescue => ee
        dd "error: could not create folder: #{e}"
        next
      end
    end

    # Build a lookup hash of all message ids present in the @destination folder.
    dest_info = {}

    dd "analyzing existing messages"
    uids = @dest.uid_search(['ALL'])
    dd "found #{uids.length} messages"
    if uids.length > 0
      uid_fetch_block(@dest, uids, ['ENVELOPE']) do |data|
        dest_info[data.attr['ENVELOPE'].message_id] = true
      end
    end

    # Loop through all messages in the @source folder.
    uids = @source.uid_search(['ALL'])
    ds "found #{uids.length} messages"
    if uids.length > 0
      uid_fetch_block(@source, uids, ['ENVELOPE']) do |data|
        mid = data.attr['ENVELOPE'].message_id

        # If this message is already in the @destination folder, skip it.
        next if dest_info[mid]
        #if this message has caused a hang before, skip it
        next if blacklist.include?(mid)  

        # Download the full message body from the @source folder.
        print mid # start with mid, then add d when downloaded, s when stored
        msg = @source.uid_fetch(data.attr['UID'], ['RFC822', 'FLAGS','INTERNALDATE']).first
        print "d" #downloaded

        # Append the message to the @destination folder, preserving flags and
        # internal timestamp.
        proceed = false
        begin
          status = Timeout::timeout(15) {
            @dest.append(dest_folder, msg.attr['RFC822'], msg.attr['FLAGS'], msg.attr['INTERNALDATE'])
          }
          proceed = true
        rescue Net::IMAP::BadResponseError => bre
          puts "Message Failed:  #{bre.message}, skipping ";
          blacklist.push(mid)
          proceed = true
        rescue Net::IMAP::NoResponseError => e
          puts "Got exception: #{e.message}. Retrying…"
          sleep 1
        end until proceed
        print "s " #stored
        $stdout.flush
      end # message store begin/rescue
    end #messages in folder loop
  rescue Timeout::Error => idle
    puts "Timeout… adding #{mid} to blacklist"
    blacklist.push(mid)
    puts "loging out.."
    @source.logout
    puts "disconnecting.."
    @source.disconnect
    # doesnt work if the connection has already hung @dest.disconnect
    setup_connections
    redo
  end # timeout begin/rescue
  @source.close
  @dest.close
end #folders loop

unless blacklist.empty?
  puts "#{blacklist.length} Problematic Messages were skipped:"
  blacklist.each { |mid| puts mid }
end

puts "done"