rgrove-larch 1.0.2.2 → 1.0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|