rgrove-larch 1.0.2.2 → 1.0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/HISTORY +8 -0
- data/README.rdoc +137 -55
- data/bin/larch +52 -78
- data/lib/larch.rb +10 -8
- data/lib/larch/config.rb +105 -0
- data/lib/larch/errors.rb +4 -0
- data/lib/larch/imap.rb +0 -8
- data/lib/larch/imap/mailbox.rb +1 -1
- data/lib/larch/version.rb +1 -1
- metadata +3 -2
data/HISTORY
CHANGED
|
@@ -6,9 +6,17 @@ Version 1.1.0 (git)
|
|
|
6
6
|
database, which allows Larch to resync and resume interrupted syncs much
|
|
7
7
|
more quickly without having to rescan all messages. As a result, SQLite 3 is
|
|
8
8
|
now a dependency.
|
|
9
|
+
* Larch now loads config options from ~/.larch/config.yaml if it exists, or
|
|
10
|
+
from the file specified by the --config command-line option. This file may
|
|
11
|
+
contain multiple sections. If a section name is specified via the
|
|
12
|
+
command-line, Larch will use the options in that section for the session;
|
|
13
|
+
otherwise it will use the options in the "default" section. See the README
|
|
14
|
+
for more details.
|
|
9
15
|
* Folders are now copied recursively by default.
|
|
10
16
|
* Progress information is now displayed regularly while scanning large
|
|
11
17
|
mailboxes.
|
|
18
|
+
* Added short versions of common command-line options.
|
|
19
|
+
* The --fast-scan option has been removed.
|
|
12
20
|
|
|
13
21
|
Version 1.0.2 (2009-08-05)
|
|
14
22
|
* Fixed a bug that caused Larch to try to set the read-only \Recent flag on
|
data/README.rdoc
CHANGED
|
@@ -2,90 +2,99 @@
|
|
|
2
2
|
|
|
3
3
|
Larch is a tool to copy messages from one IMAP server to another quickly and
|
|
4
4
|
safely. It's smart enough not to copy messages that already exist on the
|
|
5
|
-
destination and robust enough to deal with
|
|
5
|
+
destination and robust enough to deal with interruptions caused by flaky
|
|
6
|
+
connections or misbehaving servers.
|
|
6
7
|
|
|
7
8
|
Larch is particularly well-suited for copying email to, from, or between Gmail
|
|
8
9
|
accounts.
|
|
9
10
|
|
|
10
11
|
*Author*:: Ryan Grove (mailto:ryan@wonko.com)
|
|
11
|
-
*Version*:: 1.0
|
|
12
|
+
*Version*:: 1.1.0 (git)
|
|
12
13
|
*Copyright*:: Copyright (c) 2009 Ryan Grove. All rights reserved.
|
|
13
14
|
*License*:: GPL 2.0 (http://opensource.org/licenses/gpl-2.0.php)
|
|
14
15
|
*Website*:: http://github.com/rgrove/larch
|
|
15
16
|
|
|
16
17
|
== Installation
|
|
17
18
|
|
|
18
|
-
Install Larch via RubyGems:
|
|
19
|
+
Install the latest stable version of Larch via RubyGems:
|
|
19
20
|
|
|
20
21
|
gem install larch
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
larch --from <uri> --to <uri> [options]
|
|
23
|
+
Or you can install the latest development version from GitHub:
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
--to, -t <s>: URI of the destination IMAP server.
|
|
29
|
-
|
|
30
|
-
Copy Options:
|
|
31
|
-
--all: Copy all folders recursively
|
|
32
|
-
--all-subscribed: Copy all subscribed folders recursively
|
|
33
|
-
--exclude <s+>: List of mailbox names/patterns that shouldn't be
|
|
34
|
-
copied
|
|
35
|
-
--exclude-file <s>: Filename containing mailbox names/patterns that
|
|
36
|
-
shouldn't be copied
|
|
37
|
-
--from-folder <s>: Source folder to copy from (default: INBOX)
|
|
38
|
-
--from-pass <s>: Source server password (default: prompt)
|
|
39
|
-
--from-user <s>: Source server username (default: prompt)
|
|
40
|
-
--to-folder <s>: Destination folder to copy to (default: INBOX)
|
|
41
|
-
--to-pass <s>: Destination server password (default: prompt)
|
|
42
|
-
--to-user <s>: Destination server username (default: prompt)
|
|
25
|
+
gem sources -a http://gems.github.com
|
|
26
|
+
gem install rgrove-larch
|
|
43
27
|
|
|
44
|
-
|
|
45
|
-
--dry-run, -n: Don't actually make any changes.
|
|
46
|
-
--fast-scan: Use a faster (but less accurate) method to scan
|
|
47
|
-
mailboxes. This may result in messages being
|
|
48
|
-
re-copied.
|
|
49
|
-
--max-retries <i>: Maximum number of times to retry after a recoverable
|
|
50
|
-
error (default: 3)
|
|
51
|
-
--no-create-folder: Don't create destination folders that don't already
|
|
52
|
-
exist
|
|
53
|
-
--ssl-certs <s>: Path to a trusted certificate bundle to use to
|
|
54
|
-
verify server SSL certificates
|
|
55
|
-
--ssl-verify: Verify server SSL certificates
|
|
56
|
-
--verbosity, -V <s>: Output verbosity: debug, info, warn, error, or fatal
|
|
57
|
-
(default: info)
|
|
58
|
-
--version, -v: Print version and exit
|
|
59
|
-
--help, -h: Show this message
|
|
60
|
-
|
|
61
|
-
== Examples
|
|
62
|
-
|
|
63
|
-
Larch is run from the command line. At a minimum, you must specify a source
|
|
64
|
-
server and a destination server in the form of IMAP URIs. You may also specify
|
|
65
|
-
one or more options:
|
|
28
|
+
== Usage
|
|
66
29
|
|
|
30
|
+
larch [config section] [options]
|
|
67
31
|
larch --from <uri> --to <uri> [options]
|
|
68
32
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
33
|
+
Server Options:
|
|
34
|
+
--from, -f <s>: URI of the source IMAP server.
|
|
35
|
+
--from-folder, -F <s>: Source folder to copy from (default: INBOX)
|
|
36
|
+
--from-pass, -p <s>: Source server password (default: prompt)
|
|
37
|
+
--from-user, -u <s>: Source server username (default: prompt)
|
|
38
|
+
--to, -t <s>: URI of the destination IMAP server.
|
|
39
|
+
--to-folder, -T <s>: Destination folder to copy to (default: INBOX)
|
|
40
|
+
--to-pass, -P <s>: Destination server password (default: prompt)
|
|
41
|
+
--to-user, -U <s>: Destination server username (default: prompt)
|
|
42
|
+
|
|
43
|
+
Sync Options:
|
|
44
|
+
--all, -a: Copy all folders recursively
|
|
45
|
+
--all-subscribed, -s: Copy all subscribed folders recursively
|
|
46
|
+
--exclude <s+>: List of mailbox names/patterns that shouldn't be
|
|
47
|
+
copied
|
|
48
|
+
--exclude-file <s>: Filename containing mailbox names/patterns that
|
|
49
|
+
shouldn't be copied
|
|
72
50
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
51
|
+
General Options:
|
|
52
|
+
--config, -c <s>: Specify a non-default config file to use (default:
|
|
53
|
+
~/.larch/config.yaml)
|
|
54
|
+
--database <s>: Specify a non-default message database to use
|
|
55
|
+
(default: ~/.larch/larch.db)
|
|
56
|
+
--dry-run, -n: Don't actually make any changes
|
|
57
|
+
--max-retries <i>: Maximum number of times to retry after a recoverable
|
|
58
|
+
error (default: 3)
|
|
59
|
+
--no-create-folder: Don't create destination folders that don't already
|
|
60
|
+
exist
|
|
61
|
+
--ssl-certs <s>: Path to a trusted certificate bundle to use to
|
|
62
|
+
verify server SSL certificates
|
|
63
|
+
--ssl-verify: Verify server SSL certificates
|
|
64
|
+
--verbosity, -V <s>: Output verbosity: debug, info, warn, error, or fatal
|
|
65
|
+
(default: info)
|
|
66
|
+
--version, -v: Print version and exit
|
|
67
|
+
--help, -h: Show this message
|
|
68
|
+
|
|
69
|
+
== Usage Examples
|
|
70
|
+
|
|
71
|
+
Larch is run from the command line. The following examples demonstrate how to
|
|
72
|
+
run Larch using only command line arguments, but you may also place these
|
|
73
|
+
options in a config file and run Larch without any arguments if you prefer. See
|
|
74
|
+
the "Configuration" section below for more details.
|
|
75
|
+
|
|
76
|
+
For an overview of all available options, run:
|
|
77
|
+
|
|
78
|
+
larch -h
|
|
79
|
+
|
|
80
|
+
At a minimum, you must <b>specify a source server and a destination server</b>
|
|
81
|
+
in the form of IMAP URIs:
|
|
76
82
|
|
|
77
83
|
larch --from imap://mail.example.com --to imap://imap.gmail.com
|
|
78
84
|
|
|
85
|
+
Larch will prompt you for the necessary usernames and passwords, then sync the
|
|
86
|
+
contents of the source's +INBOX+ folder to the destination's INBOX folder.
|
|
87
|
+
|
|
79
88
|
To <b>connect using SSL</b>, specify a URI beginning with <tt>imaps://</tt>:
|
|
80
89
|
|
|
81
90
|
larch --from imaps://mail.example.com --to imaps://imap.gmail.com
|
|
82
91
|
|
|
83
92
|
If you'd like to <b>sync a specific folder</b> other than +INBOX+, specify the
|
|
84
93
|
source and destination folders using <tt>--from-folder</tt> and
|
|
85
|
-
<tt>--to-folder</tt
|
|
94
|
+
<tt>--to-folder</tt>. Folder names containing spaces must be enclosed in quotes:
|
|
86
95
|
|
|
87
96
|
larch --from imaps://mail.example.com --to imaps://imap.gmail.com \
|
|
88
|
-
--from-folder
|
|
97
|
+
--from-folder 'Sent Mail' --to-folder 'Sent Mail'
|
|
89
98
|
|
|
90
99
|
To <b>sync all folders</b>, use the <tt>--all</tt> option (or
|
|
91
100
|
<tt>--all-subscribed</tt> if you only want to <b>sync subscribed folders</b>):
|
|
@@ -99,7 +108,7 @@ already exist. To prevent this, add the <tt>--no-create-folder</tt> option:
|
|
|
99
108
|
--no-create-folder
|
|
100
109
|
|
|
101
110
|
You can <b>prevent Larch from syncing one or more folders</b> by using the
|
|
102
|
-
<tt>--exclude</tt> option:
|
|
111
|
+
<tt>--exclude</tt> option, which accepts multiple arguments:
|
|
103
112
|
|
|
104
113
|
larch --from imaps://mail.example.com --to imaps://imap.gmail.com --all \
|
|
105
114
|
--exclude Spam Trash Drafts "[Gmail]/*"
|
|
@@ -112,10 +121,83 @@ option:
|
|
|
112
121
|
--exclude-file exclude.txt
|
|
113
122
|
|
|
114
123
|
The wildcard characters <tt>*</tt> and <tt>?</tt> are supported in exclusion
|
|
115
|
-
lists. You
|
|
124
|
+
lists. You may also use a regular expression by enclosing a pattern in
|
|
116
125
|
forward slashes, so the previous example could be achieved with the
|
|
117
126
|
pattern <tt>/(Spam|Trash|Drafts|\[Gmail\]\/.*)/</tt>
|
|
118
127
|
|
|
128
|
+
== Configuration
|
|
129
|
+
|
|
130
|
+
While it's possible to control Larch entirely from the command line, this can be
|
|
131
|
+
inconvenient if you need to specify a lot of options or if you run Larch
|
|
132
|
+
frequently and can't always remember which options to use. Using a configuration
|
|
133
|
+
file can simplify things.
|
|
134
|
+
|
|
135
|
+
By default, Larch looks for a config file at <tt>~/.larch/config.yaml</tt> and
|
|
136
|
+
uses it if found. You may specify a custom config file using the
|
|
137
|
+
<tt>--config</tt> command line option.
|
|
138
|
+
|
|
139
|
+
The Larch configuration file is a simple YAML[http://yaml.org/] file that may
|
|
140
|
+
contain multiple sections, each with a different set of options, as well as a
|
|
141
|
+
special +default+ section. The options in the +default+ section will be used
|
|
142
|
+
unless they're overridden either in another config section or on the command
|
|
143
|
+
line.
|
|
144
|
+
|
|
145
|
+
=== Example
|
|
146
|
+
|
|
147
|
+
Here's a sample Larch config file:
|
|
148
|
+
|
|
149
|
+
default:
|
|
150
|
+
all-subscribed: true # Copy all subscribed folders by default
|
|
151
|
+
|
|
152
|
+
# Copy mail from Gmail to my server, excluding stuff I don't want.
|
|
153
|
+
gmail to my server:
|
|
154
|
+
from: imaps://imap.gmail.com
|
|
155
|
+
from-user: example
|
|
156
|
+
from-pass: secret
|
|
157
|
+
|
|
158
|
+
to: imaps://mail.example.com
|
|
159
|
+
to-user: example
|
|
160
|
+
to-pass: secret
|
|
161
|
+
|
|
162
|
+
exclude:
|
|
163
|
+
- "[Gmail]/Sent Mail"
|
|
164
|
+
- "[Gmail]/Spam"
|
|
165
|
+
- "[Gmail]/Trash"
|
|
166
|
+
|
|
167
|
+
# Copy mail from my INBOX to Gmail's INBOX
|
|
168
|
+
my inbox to gmail inbox:
|
|
169
|
+
all-subscribed: false
|
|
170
|
+
|
|
171
|
+
from: imaps://mail.example.com
|
|
172
|
+
from-folder: INBOX
|
|
173
|
+
from-user: example
|
|
174
|
+
from-pass: secret
|
|
175
|
+
|
|
176
|
+
to: imaps://imap.gmail.com
|
|
177
|
+
to-folder: INBOX
|
|
178
|
+
to-user: example
|
|
179
|
+
to-pass: secret
|
|
180
|
+
|
|
181
|
+
This file contains three sections. The options from +default+ will be used in
|
|
182
|
+
all other sections as well unless they're overridden.
|
|
183
|
+
|
|
184
|
+
To specify which config section you want Larch to use, just pass its name on the
|
|
185
|
+
command line (use quotes if the name contains spaces):
|
|
186
|
+
|
|
187
|
+
larch 'gmail to my server'
|
|
188
|
+
|
|
189
|
+
If you specify additional command line options, they'll override options in the
|
|
190
|
+
config file:
|
|
191
|
+
|
|
192
|
+
larch 'gmail to my server' --from-user anotheruser
|
|
193
|
+
|
|
194
|
+
Running Larch with no command line arguments will cause the +default+ section
|
|
195
|
+
to be used. With the example above, this will result in an error since the
|
|
196
|
+
+default+ section doesn't contain the required +from+ and +to+ options, but if
|
|
197
|
+
you only need to use Larch with a single configuration, you could use the
|
|
198
|
+
+default+ section for everything and save yourself some typing on the command
|
|
199
|
+
line.
|
|
200
|
+
|
|
119
201
|
== Server Compatibility
|
|
120
202
|
|
|
121
203
|
Larch should work well with any server that properly supports
|
data/bin/larch
CHANGED
|
@@ -11,123 +11,97 @@ module Larch
|
|
|
11
11
|
# Parse command-line options.
|
|
12
12
|
options = Trollop.options do
|
|
13
13
|
version "Larch #{APP_VERSION}\n" << APP_COPYRIGHT
|
|
14
|
-
banner
|
|
14
|
+
banner <<-EOS
|
|
15
15
|
Larch syncs messages from one IMAP server to another. Awesomely.
|
|
16
16
|
|
|
17
17
|
Usage:
|
|
18
|
+
larch [config section] [options]
|
|
18
19
|
larch --from <uri> --to <uri> [options]
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
Server Options:
|
|
21
22
|
EOS
|
|
22
|
-
opt :from,
|
|
23
|
-
opt :
|
|
23
|
+
opt :from, "URI of the source IMAP server.", :short => '-f', :type => :string
|
|
24
|
+
opt :from_folder, "Source folder to copy from", :short => '-F', :default => Config::DEFAULT['from-folder']
|
|
25
|
+
opt :from_pass, "Source server password (default: prompt)", :short => '-p', :type => :string
|
|
26
|
+
opt :from_user, "Source server username (default: prompt)", :short => '-u', :type => :string
|
|
27
|
+
opt :to, "URI of the destination IMAP server.", :short => '-t', :type => :string
|
|
28
|
+
opt :to_folder, "Destination folder to copy to", :short => '-T', :default => Config::DEFAULT['to-folder']
|
|
29
|
+
opt :to_pass, "Destination server password (default: prompt)", :short => '-P', :type => :string
|
|
30
|
+
opt :to_user, "Destination server username (default: prompt)", :short => '-U', :type => :string
|
|
24
31
|
|
|
25
32
|
text "\nSync Options:"
|
|
26
|
-
opt :all, "Copy all folders recursively", :short =>
|
|
27
|
-
opt :all_subscribed, "Copy all subscribed folders recursively", :short =>
|
|
33
|
+
opt :all, "Copy all folders recursively", :short => '-a'
|
|
34
|
+
opt :all_subscribed, "Copy all subscribed folders recursively", :short => '-s'
|
|
28
35
|
opt :exclude, "List of mailbox names/patterns that shouldn't be copied", :short => :none, :type => :strings, :multi => true
|
|
29
36
|
opt :exclude_file, "Filename containing mailbox names/patterns that shouldn't be copied", :short => :none, :type => :string
|
|
30
|
-
opt :from_folder, "Source folder to copy from", :short => :none, :default => 'INBOX'
|
|
31
|
-
opt :from_pass, "Source server password (default: prompt)", :short => :none, :type => :string
|
|
32
|
-
opt :from_user, "Source server username (default: prompt)", :short => :none, :type => :string
|
|
33
|
-
opt :to_folder, "Destination folder to copy to", :short => :none, :default => 'INBOX'
|
|
34
|
-
opt :to_pass, "Destination server password (default: prompt)", :short => :none, :type => :string
|
|
35
|
-
opt :to_user, "Destination server username (default: prompt)", :short => :none, :type => :string
|
|
36
37
|
|
|
37
38
|
text "\nGeneral Options:"
|
|
38
|
-
opt :
|
|
39
|
+
opt :config, "Specify a non-default config file to use", :short => '-c', :default => Config::DEFAULT['config']
|
|
40
|
+
opt :database, "Specify a non-default message database to use", :short => :none, :default => Config::DEFAULT['database']
|
|
39
41
|
opt :dry_run, "Don't actually make any changes", :short => '-n'
|
|
40
|
-
opt :
|
|
41
|
-
opt :max_retries, "Maximum number of times to retry after a recoverable error", :short => :none, :default => 3
|
|
42
|
+
opt :max_retries, "Maximum number of times to retry after a recoverable error", :short => :none, :default => Config::DEFAULT['max-retries']
|
|
42
43
|
opt :no_create_folder, "Don't create destination folders that don't already exist", :short => :none
|
|
43
44
|
opt :ssl_certs, "Path to a trusted certificate bundle to use to verify server SSL certificates", :short => :none, :type => :string
|
|
44
45
|
opt :ssl_verify, "Verify server SSL certificates", :short => :none
|
|
45
|
-
opt :verbosity, "Output verbosity: debug, info, warn, error, or fatal", :short => '-V', :default => '
|
|
46
|
+
opt :verbosity, "Output verbosity: debug, info, warn, error, or fatal", :short => '-V', :default => Config::DEFAULT['verbosity']
|
|
46
47
|
end
|
|
47
48
|
|
|
48
|
-
#
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
unless Logger::LEVELS.has_key?(options[:verbosity].to_sym)
|
|
56
|
-
Trollop.die :verbosity, "must be one of: #{Logger::LEVELS.keys.join(', ')}"
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
if options[:exclude_file_given]
|
|
60
|
-
filename = options[:exclude_file]
|
|
61
|
-
|
|
62
|
-
Trollop.die :exclude_file, ": file not found: #{filename}" unless File.file?(filename)
|
|
63
|
-
Trollop.die :exclude_file, ": file cannot be read: #{filename}" unless File.readable?(filename)
|
|
49
|
+
# Load config.
|
|
50
|
+
config = Config.new(ARGV.shift || 'default', options[:config], options)
|
|
51
|
+
|
|
52
|
+
if options[:config_given]
|
|
53
|
+
Trollop.die :config, ": file not found: #{options[:config]}" unless File.exist?(options[:config])
|
|
64
54
|
end
|
|
65
55
|
|
|
66
|
-
#
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
if options[:all_subscribed_given]
|
|
74
|
-
[:all, :from_folder, :to_folder].each do |o|
|
|
75
|
-
Trollop.die :all_subscribed, "may not be used with --#{o.to_s.gsub('_', '-')}" if options["#{o}_given".to_sym]
|
|
76
|
-
end
|
|
56
|
+
# Validate config.
|
|
57
|
+
begin
|
|
58
|
+
config.validate
|
|
59
|
+
rescue Config::Error => e
|
|
60
|
+
abort "Config error: #{e}"
|
|
77
61
|
end
|
|
78
62
|
|
|
79
63
|
# Create URIs.
|
|
80
|
-
uri_from = URI(
|
|
81
|
-
uri_to = URI(
|
|
64
|
+
uri_from = URI(config.from)
|
|
65
|
+
uri_to = URI(config.to)
|
|
82
66
|
|
|
83
|
-
# --
|
|
84
|
-
|
|
67
|
+
# Use --from-folder and --to-folder unless folders were specified in the URIs.
|
|
68
|
+
uri_from.path ||= '/' + CGI.escape(config.from_folder.gsub(/^\//, ''))
|
|
69
|
+
uri_to.path ||= '/' + CGI.escape(config.to_folder.gsub(/^\//, ''))
|
|
70
|
+
|
|
71
|
+
# --all and --all-subscribed options override folders
|
|
72
|
+
if config.all || config.all_subscribed
|
|
85
73
|
uri_from.path = ''
|
|
86
74
|
uri_to.path = ''
|
|
87
75
|
end
|
|
88
76
|
|
|
89
|
-
# --from-folder and --to-folder options override folders specified in URIs
|
|
90
|
-
if options[:from_folder_given] || options[:to_folder_given]
|
|
91
|
-
uri_from.path = '/' + CGI.escape(options[:from_folder].gsub(/^\//, ''))
|
|
92
|
-
uri_to.path = '/' + CGI.escape(options[:to_folder].gsub(/^\//, ''))
|
|
93
|
-
end
|
|
94
|
-
|
|
95
77
|
# Usernames and passwords specified as arguments override those in the URIs
|
|
96
|
-
uri_from.user = CGI.escape(
|
|
97
|
-
uri_from.password = CGI.escape(
|
|
98
|
-
uri_to.user = CGI.escape(
|
|
99
|
-
uri_to.password = CGI.escape(
|
|
78
|
+
uri_from.user = CGI.escape(config.from_user) if config.from_user
|
|
79
|
+
uri_from.password = CGI.escape(config.from_pass) if config.from_pass
|
|
80
|
+
uri_to.user = CGI.escape(config.to_user) if config.to_user
|
|
81
|
+
uri_to.password = CGI.escape(config.to_pass) if config.to_pass
|
|
100
82
|
|
|
101
|
-
# If usernames/passwords aren't specified in either URIs or
|
|
83
|
+
# If usernames/passwords aren't specified in either URIs or config, then prompt.
|
|
102
84
|
uri_from.user ||= CGI.escape(ask("Source username (#{uri_from.host}): "))
|
|
103
85
|
uri_from.password ||= CGI.escape(ask("Source password (#{uri_from.host}): ") {|q| q.echo = false })
|
|
104
86
|
uri_to.user ||= CGI.escape(ask("Destination username (#{uri_to.host}): "))
|
|
105
87
|
uri_to.password ||= CGI.escape(ask("Destination password (#{uri_to.host}): ") {|q| q.echo = false })
|
|
106
88
|
|
|
107
89
|
# Go go go!
|
|
108
|
-
init(
|
|
109
|
-
:exclude => options[:exclude] ? options[:exclude].flatten : [],
|
|
110
|
-
:exclude_file => options[:exclude_file],
|
|
111
|
-
:log_level => options[:verbosity]
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
Net::IMAP.debug = true if @log.level == :insane
|
|
90
|
+
init(config)
|
|
115
91
|
|
|
116
92
|
imap_from = Larch::IMAP.new(uri_from,
|
|
117
|
-
:dry_run =>
|
|
118
|
-
:
|
|
119
|
-
:
|
|
120
|
-
:
|
|
121
|
-
:ssl_verify => options[:ssl_verify]
|
|
93
|
+
:dry_run => config[:dry_run],
|
|
94
|
+
:max_retries => config[:max_retries],
|
|
95
|
+
:ssl_certs => config[:ssl_certs] || nil,
|
|
96
|
+
:ssl_verify => config[:ssl_verify]
|
|
122
97
|
)
|
|
123
98
|
|
|
124
99
|
imap_to = Larch::IMAP.new(uri_to,
|
|
125
|
-
:create_mailbox => !
|
|
126
|
-
:dry_run =>
|
|
127
|
-
:
|
|
128
|
-
:
|
|
129
|
-
:
|
|
130
|
-
:ssl_verify => options[:ssl_verify]
|
|
100
|
+
:create_mailbox => !config[:no_create_folder] && !config[:dry_run],
|
|
101
|
+
:dry_run => config[:dry_run],
|
|
102
|
+
:max_retries => config[:max_retries],
|
|
103
|
+
:ssl_certs => config[:ssl_certs] || nil,
|
|
104
|
+
:ssl_verify => config[:ssl_verify]
|
|
131
105
|
)
|
|
132
106
|
|
|
133
107
|
unless RUBY_PLATFORM =~ /mswin|mingw|bccwin|wince|java/
|
|
@@ -139,9 +113,9 @@ EOS
|
|
|
139
113
|
end
|
|
140
114
|
end
|
|
141
115
|
|
|
142
|
-
if
|
|
116
|
+
if config.all
|
|
143
117
|
copy_all(imap_from, imap_to)
|
|
144
|
-
elsif
|
|
118
|
+
elsif config.all_subscribed
|
|
145
119
|
copy_all(imap_from, imap_to, true)
|
|
146
120
|
else
|
|
147
121
|
copy_folder(imap_from, imap_to)
|
data/lib/larch.rb
CHANGED
|
@@ -8,10 +8,12 @@ require 'fileutils'
|
|
|
8
8
|
require 'net/imap'
|
|
9
9
|
require 'time'
|
|
10
10
|
require 'uri'
|
|
11
|
+
require 'yaml'
|
|
11
12
|
|
|
12
13
|
require 'sequel'
|
|
13
14
|
require 'sequel/extensions/migration'
|
|
14
15
|
|
|
16
|
+
require 'larch/config'
|
|
15
17
|
require 'larch/errors'
|
|
16
18
|
require 'larch/imap'
|
|
17
19
|
require 'larch/imap/mailbox'
|
|
@@ -21,21 +23,19 @@ require 'larch/version'
|
|
|
21
23
|
module Larch
|
|
22
24
|
|
|
23
25
|
class << self
|
|
24
|
-
attr_reader :db, :log, :exclude
|
|
26
|
+
attr_reader :config, :db, :log, :exclude
|
|
25
27
|
|
|
26
28
|
EXCLUDE_COMMENT = /#.*$/
|
|
27
29
|
EXCLUDE_REGEX = /^\s*\/(.*)\/\s*/
|
|
28
30
|
GLOB_PATTERNS = {'*' => '.*', '?' => '.'}
|
|
29
31
|
LIB_DIR = File.join(File.dirname(File.expand_path(__FILE__)), 'larch')
|
|
30
32
|
|
|
31
|
-
def init(
|
|
32
|
-
|
|
33
|
-
:exclude => [],
|
|
34
|
-
:log_level => :info
|
|
35
|
-
}.merge(config)
|
|
33
|
+
def init(config)
|
|
34
|
+
raise ArgumentError, "config must be a Larch::Config instance" unless config.is_a?(Config)
|
|
36
35
|
|
|
37
|
-
@
|
|
38
|
-
@
|
|
36
|
+
@config = config
|
|
37
|
+
@log = Logger.new(@config[:verbosity])
|
|
38
|
+
@db = open_db(@config[:database])
|
|
39
39
|
|
|
40
40
|
@exclude = @config[:exclude].map do |e|
|
|
41
41
|
if e =~ EXCLUDE_REGEX
|
|
@@ -47,6 +47,8 @@ module Larch
|
|
|
47
47
|
|
|
48
48
|
load_exclude_file(@config[:exclude_file]) if @config[:exclude_file]
|
|
49
49
|
|
|
50
|
+
Net::IMAP.debug = true if @log.level == :insane
|
|
51
|
+
|
|
50
52
|
# Stats
|
|
51
53
|
@copied = 0
|
|
52
54
|
@failed = 0
|
data/lib/larch/config.rb
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
module Larch
|
|
2
|
+
|
|
3
|
+
class Config
|
|
4
|
+
attr_reader :filename, :section
|
|
5
|
+
|
|
6
|
+
DEFAULT = {
|
|
7
|
+
'all' => false,
|
|
8
|
+
'all-subscribed' => false,
|
|
9
|
+
'config' => File.join('~', '.larch', 'config.yaml'),
|
|
10
|
+
'database' => File.join('~', '.larch', 'larch.db'),
|
|
11
|
+
'dry-run' => false,
|
|
12
|
+
'exclude' => [],
|
|
13
|
+
'exclude-file' => nil,
|
|
14
|
+
'from' => nil,
|
|
15
|
+
'from-folder' => 'INBOX',
|
|
16
|
+
'from-pass' => nil,
|
|
17
|
+
'from-user' => nil,
|
|
18
|
+
'max-retries' => 3,
|
|
19
|
+
'no-create-folder' => false,
|
|
20
|
+
'ssl-certs' => nil,
|
|
21
|
+
'ssl-verify' => false,
|
|
22
|
+
'to' => nil,
|
|
23
|
+
'to-folder' => 'INBOX',
|
|
24
|
+
'to-pass' => nil,
|
|
25
|
+
'to-user' => nil,
|
|
26
|
+
'verbosity' => 'info'
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
def initialize(section = 'default', filename = DEFAULT['config'], override = {})
|
|
30
|
+
@section = section.to_s
|
|
31
|
+
@override = {}
|
|
32
|
+
|
|
33
|
+
override.each do |k, v|
|
|
34
|
+
k = k.to_s.gsub('_', '-')
|
|
35
|
+
@override[k] = v if DEFAULT.has_key?(k) && v != DEFAULT[k]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
load_file(filename)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def fetch(name)
|
|
42
|
+
(@cached || {})[name.to_s.gsub('_', '-')] || nil
|
|
43
|
+
end
|
|
44
|
+
alias [] fetch
|
|
45
|
+
|
|
46
|
+
def load_file(filename)
|
|
47
|
+
@filename = File.expand_path(filename)
|
|
48
|
+
|
|
49
|
+
config = {}
|
|
50
|
+
|
|
51
|
+
if File.exist?(@filename)
|
|
52
|
+
begin
|
|
53
|
+
config = YAML.load_file(@filename)
|
|
54
|
+
rescue => e
|
|
55
|
+
raise Larch::Config::Error, "config error in #{filename}: #{e}"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
@lookup = [@override, config[@section] || {}, config['default'] || {}, DEFAULT]
|
|
60
|
+
cache_config
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def method_missing(name)
|
|
64
|
+
fetch(name)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def validate
|
|
68
|
+
['from', 'to'].each do |s|
|
|
69
|
+
raise Error, "'#{s}' must be a valid IMAP URI (e.g. imap://example.com)" unless fetch(s) =~ IMAP::REGEX_URI
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
unless Logger::LEVELS.has_key?(verbosity.to_sym)
|
|
73
|
+
raise Error, "'verbosity' must be one of: #{Logger::LEVELS.keys.join(', ')}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
if exclude_file
|
|
77
|
+
raise Error, "exclude file not found: #{exclude_file}" unless File.file?(exclude_file)
|
|
78
|
+
raise Error, "exclude file cannot be read: #{exclude_file}" unless File.readable?(exclude_file)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
# Merges configs such that those earlier in the lookup chain override those
|
|
85
|
+
# later in the chain.
|
|
86
|
+
def cache_config
|
|
87
|
+
@cached = {}
|
|
88
|
+
|
|
89
|
+
@lookup.reverse.each do |c|
|
|
90
|
+
c.each {|k, v| @cached[k] = config_merge(@cached[k] || {}, v) }
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def config_merge(master, value)
|
|
95
|
+
if value.is_a?(Hash)
|
|
96
|
+
value.each {|k, v| master[k] = config_merge(master[k] || {}, v) }
|
|
97
|
+
return master
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
value
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
end
|
data/lib/larch/errors.rb
CHANGED
data/lib/larch/imap.rb
CHANGED
|
@@ -30,14 +30,6 @@ class IMAP
|
|
|
30
30
|
# that it's not actually possible to simulate mailbox creation, so
|
|
31
31
|
# +:dry_run+ mode always behaves as if +:create_mailbox+ is +false+.
|
|
32
32
|
#
|
|
33
|
-
# [:fast_scan]
|
|
34
|
-
# If +true+, a faster but less accurate method will be used to scan
|
|
35
|
-
# mailboxes. This will speed up the initial mailbox scan, but will also
|
|
36
|
-
# reduce the effectiveness of the message unique id generator. This is
|
|
37
|
-
# probably acceptable when copying a very large mailbox to an empty mailbox,
|
|
38
|
-
# but if the destination already contains messages, using this option is not
|
|
39
|
-
# advised.
|
|
40
|
-
#
|
|
41
33
|
# [:max_retries]
|
|
42
34
|
# After a recoverable error occurs, retry the operation up to this many
|
|
43
35
|
# times. Default is 3.
|
data/lib/larch/imap/mailbox.rb
CHANGED
|
@@ -181,7 +181,7 @@ class Mailbox
|
|
|
181
181
|
if @db_mailbox.uidvalidity && @db_mailbox.uidnext &&
|
|
182
182
|
status['UIDVALIDITY'] == @db_mailbox.uidvalidity
|
|
183
183
|
|
|
184
|
-
# The UIDVALIDITY is the same as what we saw last time we scanned
|
|
184
|
+
# The UIDVALIDITY is the same as what we saw last time we scanned this
|
|
185
185
|
# mailbox, which means that all the existing messages in the database are
|
|
186
186
|
# still valid. We only need to request headers for new messages.
|
|
187
187
|
#
|
data/lib/larch/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rgrove-larch
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.2.
|
|
4
|
+
version: 1.0.2.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ryan Grove
|
|
@@ -9,7 +9,7 @@ autorequire:
|
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
11
|
|
|
12
|
-
date: 2009-08-
|
|
12
|
+
date: 2009-08-22 00:00:00 -07:00
|
|
13
13
|
default_executable: larch
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
@@ -65,6 +65,7 @@ files:
|
|
|
65
65
|
- LICENSE
|
|
66
66
|
- README.rdoc
|
|
67
67
|
- bin/larch
|
|
68
|
+
- lib/larch/config.rb
|
|
68
69
|
- lib/larch/db/account.rb
|
|
69
70
|
- lib/larch/db/mailbox.rb
|
|
70
71
|
- lib/larch/db/message.rb
|