module Larch
Constants
- APP_AUTHOR
- APP_COPYRIGHT
- APP_EMAIL
- APP_NAME
- APP_URL
- APP_VERSION
- EXCLUDE_COMMENT
- EXCLUDE_REGEX
- GLOB_PATTERNS
- LIB_DIR
Attributes
config[R]
db[R]
exclude[R]
log[R]
Public Class Methods
copy_all(imap_from, imap_to, subscribed_only = false)
click to toggle source
Recursively copies all messages in all folders from the source to the destination.
# File lib/larch.rb, line 51 def copy_all(imap_from, imap_to, subscribed_only = false) raise ArgumentError, "imap_from must be a Larch::IMAP instance" unless imap_from.is_a?(IMAP) raise ArgumentError, "imap_to must be a Larch::IMAP instance" unless imap_to.is_a?(IMAP) @copied = 0 @deleted = 0 @failed = 0 @total = 0 imap_from.each_mailbox do |mailbox_from| next if excluded?(mailbox_from.name) next if subscribed_only && !mailbox_from.subscribed? if imap_to.uri_mailbox mailbox_to = imap_to.mailbox(imap_to.uri_mailbox) else mailbox_to = imap_to.mailbox(mailbox_from.name, mailbox_from.delim) end mailbox_to.subscribe if mailbox_from.subscribed? copy_messages(mailbox_from, mailbox_to) end rescue => e @log.fatal e.message ensure summary db_maintenance end
copy_folder(imap_from, imap_to)
click to toggle source
Copies the messages in a single IMAP folder and all its subfolders (recursively) from the source to the destination.
# File lib/larch.rb, line 85 def copy_folder(imap_from, imap_to) raise ArgumentError, "imap_from must be a Larch::IMAP instance" unless imap_from.is_a?(IMAP) raise ArgumentError, "imap_to must be a Larch::IMAP instance" unless imap_to.is_a?(IMAP) @copied = 0 @deleted = 0 @failed = 0 @total = 0 mailbox_from = imap_from.mailbox(imap_from.uri_mailbox || 'INBOX') mailbox_to = imap_to.mailbox(imap_to.uri_mailbox || 'INBOX') copy_mailbox(mailbox_from, mailbox_to) imap_from.disconnect imap_to.disconnect rescue => e @log.fatal e.message ensure summary db_maintenance end
init(config)
click to toggle source
# File lib/larch.rb, line 31 def init(config) raise ArgumentError, "config must be a Larch::Config instance" unless config.is_a?(Config) @config = config @log = Logger.new(@config[:verbosity]) @db = open_db(@config[:database]) parse_exclusions Net::IMAP.debug = true if @log.level == :insane # Stats @copied = 0 @deleted = 0 @failed = 0 @total = 0 end
open_db(database)
click to toggle source
Opens a connection to the Larch message database, creating it if necessary.
# File lib/larch.rb, line 112 def open_db(database) unless database == ':memory:' filename = File.expand_path(database) directory = File.dirname(filename) unless File.exist?(directory) FileUtils.mkdir_p(directory) File.chmod(0700, directory) end end begin db = Sequel.sqlite(:database => filename) db.test_connection rescue => e @log.fatal "unable to open message database: #{e}" abort end # Ensure that the database schema is up to date. migration_dir = File.join(LIB_DIR, 'db', 'migrate') begin Sequel::Migrator.apply(db, migration_dir) rescue => e @log.fatal "unable to migrate message database: #{e}" abort end require 'larch/db/message' require 'larch/db/mailbox' require 'larch/db/account' db end
summary()
click to toggle source
# File lib/larch.rb, line 148 def summary @log.info "#{@copied} message(s) copied, #{@failed} failed, #{@deleted} deleted out of #{@total} total" end
Private Class Methods
copy_mailbox(mailbox_from, mailbox_to)
click to toggle source
# File lib/larch.rb, line 156 def copy_mailbox(mailbox_from, mailbox_to) raise ArgumentError, "mailbox_from must be a Larch::IMAP::Mailbox instance" unless mailbox_from.is_a?(Larch::IMAP::Mailbox) raise ArgumentError, "mailbox_to must be a Larch::IMAP::Mailbox instance" unless mailbox_to.is_a?(Larch::IMAP::Mailbox) return if excluded?(mailbox_from.name) || excluded?(mailbox_to.name) mailbox_to.subscribe if mailbox_from.subscribed? copy_messages(mailbox_from, mailbox_to) unless @config['no-recurse'] mailbox_from.each_mailbox do |child_from| next if excluded?(child_from.name) child_to = mailbox_to.imap.mailbox(child_from.name, child_from.delim) copy_mailbox(child_from, child_to) end end end
copy_messages(mailbox_from, mailbox_to)
click to toggle source
# File lib/larch.rb, line 174 def copy_messages(mailbox_from, mailbox_to) raise ArgumentError, "mailbox_from must be a Larch::IMAP::Mailbox instance" unless mailbox_from.is_a?(Larch::IMAP::Mailbox) raise ArgumentError, "mailbox_to must be a Larch::IMAP::Mailbox instance" unless mailbox_to.is_a?(Larch::IMAP::Mailbox) return if excluded?(mailbox_from.name) || excluded?(mailbox_to.name) imap_from = mailbox_from.imap imap_to = mailbox_to.imap @log.info "#{imap_from.host}/#{mailbox_from.name} -> #{imap_to.host}/#{mailbox_to.name}" @total += mailbox_from.length mailbox_from.each_db_message do |from_db_message| guid = from_db_message.guid uid = from_db_message.uid if mailbox_to.has_guid?(guid) begin if @config['sync_flags'] to_db_message = mailbox_to.fetch_db_message(guid) if to_db_message.flags != from_db_message.flags new_flags = from_db_message.flags_str new_flags = '(none)' if new_flags.empty? @log.info "[>] syncing flags: uid #{uid}: #{new_flags}" mailbox_to.set_flags(guid, from_db_message.flags) end end if @config['delete'] && !from_db_message.flags.include?(:Deleted) @log.info "[<] deleting uid #{uid} (already exists at destination)" @deleted += 1 if mailbox_from.delete_message(guid) end rescue Larch::IMAP::Error => e @log.error e.message end next end begin unless msg = mailbox_from.peek(guid) @failed += 1 next end if msg.envelope.from env_from = msg.envelope.from.first from = "#{env_from.mailbox}@#{env_from.host}" else from = '?' end @log.info "[>] copying uid #{uid}: #{from} - #{msg.envelope.subject}" mailbox_to << msg @copied += 1 if @config['delete'] @log.info "[<] deleting uid #{uid}" @deleted += 1 if mailbox_from.delete_message(guid) end rescue Larch::IMAP::Error => e @failed += 1 @log.error e.message next end end if @config['expunge'] begin @log.debug "[<] expunging deleted messages" mailbox_from.expunge rescue Larch::IMAP::Error => e @log.error e.message end end rescue Larch::IMAP::Error => e @log.error e.message end
db_maintenance()
click to toggle source
# File lib/larch.rb, line 261 def db_maintenance @log.debug 'performing database maintenance' # Remove accounts that haven't been used in over 30 days. Database::Account.filter(:updated_at => nil).destroy Database::Account.filter('? - updated_at >= 2592000', Time.now.to_i).destroy # Release unused disk space and defragment the database. @db.run('VACUUM') end
excluded?(name)
click to toggle source
# File lib/larch.rb, line 272 def excluded?(name) name = name.downcase @exclude.each do |e| return true if (e.is_a?(Regexp) ? !!(name =~ e) : File.fnmatch?(e, name)) end return false end
glob_to_regex(str)
click to toggle source
# File lib/larch.rb, line 282 def glob_to_regex(str) str.gsub!(/(.)/) {|c| GLOB_PATTERNS[$1] || Regexp.escape(c) } Regexp.new("^#{str}$", Regexp::IGNORECASE) end
load_exclude_file(filename)
click to toggle source
# File lib/larch.rb, line 287 def load_exclude_file(filename) @exclude ||= [] lineno = 0 File.open(filename, 'rb') do |f| f.each do |line| lineno += 1 # Strip comments. line.sub!(EXCLUDE_COMMENT, '') line.strip! # Skip empty lines. next if line.empty? if line =~ EXCLUDE_REGEX @exclude << Regexp.new($1, Regexp::IGNORECASE) else @exclude << glob_to_regex(line) end end end rescue => e raise Larch::IMAP::FatalError, "error in exclude file at line #{lineno}: #{e}" end
parse_exclusions()
click to toggle source
# File lib/larch.rb, line 314 def parse_exclusions @exclude = @config[:exclude].map do |e| if e =~ EXCLUDE_REGEX Regexp.new($1, Regexp::IGNORECASE) else glob_to_regex(e.strip) end end load_exclude_file(@config[:exclude_file]) if @config[:exclude_file] end