Skip to content
This repository has been archived by the owner on Nov 9, 2017. It is now read-only.

Commit

Permalink
Add --delete option to delete messages from the source after copying …
Browse files Browse the repository at this point in the history
…them

to the destination, or if they already exist at the destination.

Add --expunge option to expunge deleted messages from the source.

Closes #22
  • Loading branch information
rgrove committed Feb 7, 2010
1 parent e36ac2f commit 0d47dc8
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 37 deletions.
3 changes: 3 additions & 0 deletions HISTORY
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ Version 1.1.0 (git)
* Folders are now copied recursively by default.
* Progress information is now displayed regularly while scanning large
mailboxes.
* Added --delete option to delete messages from the source after copying them
to the destination, or if they already exist at the destination.
* Added --expunge option to expunge deleted messages from the source.
* Added --sync-flags option to synchronize message flags (like Seen, Flagged,
etc.) from the source server to the destination server for messages that
already exist on the destination.
Expand Down
11 changes: 7 additions & 4 deletions README.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ accounts.

*Author*:: Ryan Grove (mailto:ryan@wonko.com)
*Version*:: 1.1.0 (git)
*Copyright*:: Copyright (c) 2009 Ryan Grove. All rights reserved.
*Copyright*:: Copyright (c) 2010 Ryan Grove. All rights reserved.
*License*:: GPL 2.0 (http://opensource.org/licenses/gpl-2.0.php)
*Website*:: http://github.com/rgrove/larch

Expand All @@ -28,7 +28,7 @@ Latest development version:

larch [config section] [options]
larch --from <uri> --to <uri> [options]

Server Options:
--from, -f <s>: URI of the source IMAP server
--from-folder, -F <s>: Source folder to copy from (default: INBOX)
Expand All @@ -39,13 +39,16 @@ Latest development version:
--to-pass, -P <s>: Destination server password (default: prompt)
--to-user, -U <s>: Destination server username (default: prompt)

Sync Options:
Copy Options:
--all, -a: Copy all folders recursively
--all-subscribed, -s: Copy all subscribed folders recursively
--delete, -d: Delete messages from the source after copying
them, or if they already exist at the destination
--exclude <s+>: List of mailbox names/patterns that shouldn't be
copied
--exclude-file <s>: Filename containing mailbox names/patterns that
shouldn't be copied
--expunge, -x: Expunge deleted messages from the source
--sync-flags, -S: Sync message flags from the source to the
destination for messages that already exist at the
destination
Expand Down Expand Up @@ -334,7 +337,7 @@ Gray II).

== License

Copyright (c) 2009 Ryan Grove <ryan@wonko.com>
Copyright (c) 2010 Ryan Grove <ryan@wonko.com>

Licensed under the GNU General Public License version 2.0.

Expand Down
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ require 'larch/version'

gemspec = Gem::Specification.new do |s|
s.name = 'larch'
s.summary = 'Larch syncs messages from one IMAP server to another. Awesomely.'
s.summary = 'Larch copies messages from one IMAP server to another. Awesomely.'
s.version = "#{Larch::APP_VERSION}"
s.author = "#{Larch::APP_AUTHOR}"
s.email = "#{Larch::APP_EMAIL}"
Expand Down
6 changes: 4 additions & 2 deletions bin/larch
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ module Larch
options = Trollop.options do
version "Larch #{APP_VERSION}\n" << APP_COPYRIGHT
banner <<-EOS
Larch syncs messages from one IMAP server to another. Awesomely.
Larch copies messages from one IMAP server to another. Awesomely.
Usage:
larch [config section] [options]
Expand All @@ -29,11 +29,13 @@ EOS
opt :to_pass, "Destination server password (default: prompt)", :short => '-P', :type => :string
opt :to_user, "Destination server username (default: prompt)", :short => '-U', :type => :string

text "\nSync Options:"
text "\nCopy Options:"
opt :all, "Copy all folders recursively", :short => '-a'
opt :all_subscribed, "Copy all subscribed folders recursively", :short => '-s'
opt :delete, "Delete messages from the source after copying them, or if they already exist at the destination", :short => '-d'
opt :exclude, "List of mailbox names/patterns that shouldn't be copied", :short => :none, :type => :strings, :multi => true
opt :exclude_file, "Filename containing mailbox names/patterns that shouldn't be copied", :short => :none, :type => :string
opt :expunge, "Expunge deleted messages from the source", :short => '-x'
opt :sync_flags, "Sync message flags from the source to the destination for messages that already exist at the destination", :short => '-S'

text "\nGeneral Options:"
Expand Down
6 changes: 3 additions & 3 deletions larch.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

Gem::Specification.new do |s|
s.name = %q{larch}
s.version = "1.1.0.dev.20100120"
s.version = "1.1.0.dev.20100206"

s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
s.authors = ["Ryan Grove"]
s.date = %q{2010-01-20}
s.date = %q{2010-02-06}
s.default_executable = %q{larch}
s.email = %q{ryan@wonko.com}
s.executables = ["larch"]
Expand All @@ -15,7 +15,7 @@ Gem::Specification.new do |s|
s.require_paths = ["lib"]
s.required_ruby_version = Gem::Requirement.new(">= 1.8.6")
s.rubygems_version = %q{1.3.5}
s.summary = %q{Larch syncs messages from one IMAP server to another. Awesomely.}
s.summary = %q{Larch copies messages from one IMAP server to another. Awesomely.}

if s.respond_to? :specification_version then
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
Expand Down
63 changes: 44 additions & 19 deletions lib/larch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@ def init(config)
Net::IMAP.debug = true if @log.level == :insane

# Stats
@copied = 0
@failed = 0
@total = 0
@copied = 0
@deleted = 0
@failed = 0
@total = 0
end

# Recursively copies all messages in all folders from the source to the
Expand All @@ -49,9 +50,10 @@ 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
@failed = 0
@total = 0
@copied = 0
@deleted = 0
@failed = 0
@total = 0

imap_from.each_mailbox do |mailbox_from|
next if excluded?(mailbox_from.name)
Expand Down Expand Up @@ -82,9 +84,10 @@ 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
@failed = 0
@total = 0
@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')
Expand Down Expand Up @@ -144,7 +147,7 @@ def open_db(database)
end

def summary
@log.info "#{@copied} message(s) copied, #{@failed} failed, #{@total - @copied - @failed} untouched out of #{@total} total"
@log.info "#{@copied} message(s) copied, #{@failed} failed, #{@deleted} deleted out of #{@total} total"
end


Expand Down Expand Up @@ -182,19 +185,26 @@ def copy_messages(mailbox_from, mailbox_to)

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)
next unless @config['sync_flags']

begin
to_db_message = mailbox_to.fetch_db_message(guid)
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?

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

@log.info "syncing flags: UID #{to_db_message.uid}: #{new_flags}"
mailbox_to.set_flags(guid, from_db_message.flags)
if @config['delete'] && !from_db_message.flags.include?(:Deleted)
@log.info "[<] deleting uid #{uid} (already exists at destination)"
mailbox_from.set_flags(guid, [:Deleted], true)
@deleted += 1
end
rescue Larch::IMAP::Error => e
@log.error e.message
Expand All @@ -213,17 +223,32 @@ def copy_messages(mailbox_from, mailbox_to)
from = '?'
end

@log.info "copying: #{from} - #{msg.envelope.subject}"
@log.info "[>] copying uid #{uid}: #{from} - #{msg.envelope.subject}"

mailbox_to << msg
@copied += 1

if @config['delete']
@log.info "[<] deleting uid #{uid}"
mailbox_from.set_flags(guid, [:Deleted], true)
@deleted += 1
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
end

def db_maintenance
Expand Down
2 changes: 2 additions & 0 deletions lib/larch/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ class Config
'all-subscribed' => false,
'config' => File.join('~', '.larch', 'config.yaml'),
'database' => File.join('~', '.larch', 'larch.db'),
'delete' => false,
'dry-run' => false,
'exclude' => [],
'exclude-file' => nil,
'expunge' => false,
'from' => nil,
'from-folder' => nil, # actually INBOX; see validate()
'from-pass' => nil,
Expand Down
33 changes: 27 additions & 6 deletions lib/larch/imap/mailbox.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,19 @@ def each_mailbox # :yields: mailbox
mailboxes.each {|mb| yield mb }
end

# Expunges this mailbox, permanently removing all messages with the \Deleted
# flag.
def expunge
return false unless imap_select

@imap.safely do
debug "expunging deleted messages"

@last_scan = nil
@imap.conn.expunge unless @imap.options[:dry_run]
end
end

# Returns a Larch::IMAP::Message struct representing the message with the
# specified Larch _guid_, or +nil+ if the specified guid was not found in this
# mailbox.
Expand Down Expand Up @@ -229,16 +242,24 @@ def scan
return
end

# Sets the IMAP flags for the message specified by _guid_, replacing any
# existing flags (except <code>:Recent</code>). _flags_ should be an array of
# symbols for standard flags, strings for custom flags. Returns +true+ on
# success, +false+ on failure.
def set_flags(guid, flags)
# Sets the IMAP flags for the message specified by _guid_. _flags_ should be
# an array of symbols for standard flags, strings for custom flags.
#
# If _merge_ is +true+, the specified flags will be merged with the message's
# existing flags. Otherwise, all existing flags will be cleared and replaced
# with the specified flags.
#
# Note that the :Recent flag cannot be manually set or removed.
#
# Returns +true+ on success, +false+ on failure.
def set_flags(guid, flags, merge = false)
raise ArgumentError, "flags must be an Array" unless flags.is_a?(Array)

return false unless db_message = fetch_db_message(guid)

supported_flags = get_supported_flags(flags)
merged_flags = merge ? (db_message.flags + flags).uniq : flags
supported_flags = get_supported_flags(merged_flags)

return true if db_message.flags == supported_flags

return false if !imap_select
Expand Down
4 changes: 2 additions & 2 deletions lib/larch/version.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
module Larch
APP_NAME = 'Larch'
APP_VERSION = '1.1.0.dev.20100120'
APP_VERSION = '1.1.0.dev.20100206'
APP_AUTHOR = 'Ryan Grove'
APP_EMAIL = 'ryan@wonko.com'
APP_URL = 'http://github.com/rgrove/larch/'
APP_COPYRIGHT = 'Copyright (c) 2009 Ryan Grove <ryan@wonko.com>. All ' <<
APP_COPYRIGHT = 'Copyright (c) 2010 Ryan Grove <ryan@wonko.com>. All ' <<
'rights reserved.'
end

0 comments on commit 0d47dc8

Please sign in to comment.