larch 1.0.2 → 1.1.0dev20091006
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY +18 -0
- data/README.rdoc +141 -54
- data/bin/larch +55 -80
- data/lib/larch/config.rb +105 -0
- data/lib/larch/db/account.rb +12 -0
- data/lib/larch/db/mailbox.rb +12 -0
- data/lib/larch/db/message.rb +6 -0
- data/lib/larch/db/migrate/001_create_schema.rb +42 -0
- data/lib/larch/errors.rb +4 -0
- data/lib/larch/imap/mailbox.rb +301 -81
- data/lib/larch/imap.rb +23 -18
- data/lib/larch/version.rb +1 -1
- data/lib/larch.rb +88 -26
- metadata +33 -8
data/HISTORY
CHANGED
@@ -1,6 +1,24 @@
|
|
1
1
|
Larch History
|
2
2
|
================================================================================
|
3
3
|
|
4
|
+
Version 1.1.0 (git)
|
5
|
+
* Mailbox and message state information is now stored in a local SQLite
|
6
|
+
database, which allows Larch to resync and resume interrupted syncs much
|
7
|
+
more quickly without having to rescan all messages. As a result, SQLite 3 is
|
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.
|
15
|
+
* Folders are now copied recursively by default.
|
16
|
+
* Progress information is now displayed regularly while scanning large
|
17
|
+
mailboxes.
|
18
|
+
* Added short versions of common command-line options.
|
19
|
+
* The --fast-scan option has been removed.
|
20
|
+
* Fixed encoding issues when creating mailboxes and getting mailbox lists.
|
21
|
+
|
4
22
|
Version 1.0.2 (2009-08-05)
|
5
23
|
* Fixed a bug that caused Larch to try to set the read-only \Recent flag on
|
6
24
|
the destination server.
|
data/README.rdoc
CHANGED
@@ -2,90 +2,104 @@
|
|
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
|
+
Or you can install the latest development snapshot. To do so, first install
|
24
|
+
Gemcutter (if you haven't already):
|
23
25
|
|
24
|
-
|
26
|
+
gem install gemcutter
|
27
|
+
gem tumble
|
25
28
|
|
26
|
-
|
27
|
-
--from, -f <s>: URI of the source IMAP server.
|
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)
|
29
|
+
Then install the latest Larch prerelease gem:
|
43
30
|
|
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:
|
31
|
+
gem install larch --prerelease
|
66
32
|
|
67
|
-
|
33
|
+
== Usage
|
68
34
|
|
69
|
-
|
35
|
+
larch [config section] [options]
|
36
|
+
larch --from <uri> --to <uri> [options]
|
70
37
|
|
71
|
-
|
38
|
+
Server Options:
|
39
|
+
--from, -f <s>: URI of the source IMAP server.
|
40
|
+
--from-folder, -F <s>: Source folder to copy from (default: INBOX)
|
41
|
+
--from-pass, -p <s>: Source server password (default: prompt)
|
42
|
+
--from-user, -u <s>: Source server username (default: prompt)
|
43
|
+
--to, -t <s>: URI of the destination IMAP server.
|
44
|
+
--to-folder, -T <s>: Destination folder to copy to (default: INBOX)
|
45
|
+
--to-pass, -P <s>: Destination server password (default: prompt)
|
46
|
+
--to-user, -U <s>: Destination server username (default: prompt)
|
47
|
+
|
48
|
+
Sync Options:
|
49
|
+
--all, -a: Copy all folders recursively
|
50
|
+
--all-subscribed, -s: Copy all subscribed folders recursively
|
51
|
+
--exclude <s+>: List of mailbox names/patterns that shouldn't be
|
52
|
+
copied
|
53
|
+
--exclude-file <s>: Filename containing mailbox names/patterns that
|
54
|
+
shouldn't be copied
|
72
55
|
|
73
|
-
|
74
|
-
|
75
|
-
|
56
|
+
General Options:
|
57
|
+
--config, -c <s>: Specify a non-default config file to use (default:
|
58
|
+
~/.larch/config.yaml)
|
59
|
+
--database <s>: Specify a non-default message database to use
|
60
|
+
(default: ~/.larch/larch.db)
|
61
|
+
--dry-run, -n: Don't actually make any changes
|
62
|
+
--max-retries <i>: Maximum number of times to retry after a recoverable
|
63
|
+
error (default: 3)
|
64
|
+
--no-create-folder: Don't create destination folders that don't already
|
65
|
+
exist
|
66
|
+
--ssl-certs <s>: Path to a trusted certificate bundle to use to
|
67
|
+
verify server SSL certificates
|
68
|
+
--ssl-verify: Verify server SSL certificates
|
69
|
+
--verbosity, -V <s>: Output verbosity: debug, info, warn, error, or fatal
|
70
|
+
(default: info)
|
71
|
+
--version, -v: Print version and exit
|
72
|
+
--help, -h: Show this message
|
73
|
+
|
74
|
+
== Usage Examples
|
75
|
+
|
76
|
+
Larch is run from the command line. The following examples demonstrate how to
|
77
|
+
run Larch using only command line arguments, but you may also place these
|
78
|
+
options in a config file and run Larch without any arguments if you prefer. See
|
79
|
+
the "Configuration" section below for more details.
|
80
|
+
|
81
|
+
For an overview of all available options, run:
|
82
|
+
|
83
|
+
larch -h
|
84
|
+
|
85
|
+
At a minimum, you must <b>specify a source server and a destination server</b>
|
86
|
+
in the form of IMAP URIs:
|
76
87
|
|
77
88
|
larch --from imap://mail.example.com --to imap://imap.gmail.com
|
78
89
|
|
90
|
+
Larch will prompt you for the necessary usernames and passwords, then sync the
|
91
|
+
contents of the source's +INBOX+ folder to the destination's INBOX folder.
|
92
|
+
|
79
93
|
To <b>connect using SSL</b>, specify a URI beginning with <tt>imaps://</tt>:
|
80
94
|
|
81
95
|
larch --from imaps://mail.example.com --to imaps://imap.gmail.com
|
82
96
|
|
83
97
|
If you'd like to <b>sync a specific folder</b> other than +INBOX+, specify the
|
84
98
|
source and destination folders using <tt>--from-folder</tt> and
|
85
|
-
<tt>--to-folder</tt
|
99
|
+
<tt>--to-folder</tt>. Folder names containing spaces must be enclosed in quotes:
|
86
100
|
|
87
101
|
larch --from imaps://mail.example.com --to imaps://imap.gmail.com \
|
88
|
-
--from-folder
|
102
|
+
--from-folder 'Sent Mail' --to-folder 'Sent Mail'
|
89
103
|
|
90
104
|
To <b>sync all folders</b>, use the <tt>--all</tt> option (or
|
91
105
|
<tt>--all-subscribed</tt> if you only want to <b>sync subscribed folders</b>):
|
@@ -99,7 +113,7 @@ already exist. To prevent this, add the <tt>--no-create-folder</tt> option:
|
|
99
113
|
--no-create-folder
|
100
114
|
|
101
115
|
You can <b>prevent Larch from syncing one or more folders</b> by using the
|
102
|
-
<tt>--exclude</tt> option:
|
116
|
+
<tt>--exclude</tt> option, which accepts multiple arguments:
|
103
117
|
|
104
118
|
larch --from imaps://mail.example.com --to imaps://imap.gmail.com --all \
|
105
119
|
--exclude Spam Trash Drafts "[Gmail]/*"
|
@@ -112,10 +126,83 @@ option:
|
|
112
126
|
--exclude-file exclude.txt
|
113
127
|
|
114
128
|
The wildcard characters <tt>*</tt> and <tt>?</tt> are supported in exclusion
|
115
|
-
lists. You
|
129
|
+
lists. You may also use a regular expression by enclosing a pattern in
|
116
130
|
forward slashes, so the previous example could be achieved with the
|
117
131
|
pattern <tt>/(Spam|Trash|Drafts|\[Gmail\]\/.*)/</tt>
|
118
132
|
|
133
|
+
== Configuration
|
134
|
+
|
135
|
+
While it's possible to control Larch entirely from the command line, this can be
|
136
|
+
inconvenient if you need to specify a lot of options or if you run Larch
|
137
|
+
frequently and can't always remember which options to use. Using a configuration
|
138
|
+
file can simplify things.
|
139
|
+
|
140
|
+
By default, Larch looks for a config file at <tt>~/.larch/config.yaml</tt> and
|
141
|
+
uses it if found. You may specify a custom config file using the
|
142
|
+
<tt>--config</tt> command line option.
|
143
|
+
|
144
|
+
The Larch configuration file is a simple YAML[http://yaml.org/] file that may
|
145
|
+
contain multiple sections, each with a different set of options, as well as a
|
146
|
+
special +default+ section. The options in the +default+ section will be used
|
147
|
+
unless they're overridden either in another config section or on the command
|
148
|
+
line.
|
149
|
+
|
150
|
+
=== Example
|
151
|
+
|
152
|
+
Here's a sample Larch config file:
|
153
|
+
|
154
|
+
default:
|
155
|
+
all-subscribed: true # Copy all subscribed folders by default
|
156
|
+
|
157
|
+
# Copy mail from Gmail to my server, excluding stuff I don't want.
|
158
|
+
gmail to my server:
|
159
|
+
from: imaps://imap.gmail.com
|
160
|
+
from-user: example
|
161
|
+
from-pass: secret
|
162
|
+
|
163
|
+
to: imaps://mail.example.com
|
164
|
+
to-user: example
|
165
|
+
to-pass: secret
|
166
|
+
|
167
|
+
exclude:
|
168
|
+
- "[Gmail]/Sent Mail"
|
169
|
+
- "[Gmail]/Spam"
|
170
|
+
- "[Gmail]/Trash"
|
171
|
+
|
172
|
+
# Copy mail from my INBOX to Gmail's INBOX
|
173
|
+
my inbox to gmail inbox:
|
174
|
+
all-subscribed: false
|
175
|
+
|
176
|
+
from: imaps://mail.example.com
|
177
|
+
from-folder: INBOX
|
178
|
+
from-user: example
|
179
|
+
from-pass: secret
|
180
|
+
|
181
|
+
to: imaps://imap.gmail.com
|
182
|
+
to-folder: INBOX
|
183
|
+
to-user: example
|
184
|
+
to-pass: secret
|
185
|
+
|
186
|
+
This file contains three sections. The options from +default+ will be used in
|
187
|
+
all other sections as well unless they're overridden.
|
188
|
+
|
189
|
+
To specify which config section you want Larch to use, just pass its name on the
|
190
|
+
command line (use quotes if the name contains spaces):
|
191
|
+
|
192
|
+
larch 'gmail to my server'
|
193
|
+
|
194
|
+
If you specify additional command line options, they'll override options in the
|
195
|
+
config file:
|
196
|
+
|
197
|
+
larch 'gmail to my server' --from-user anotheruser
|
198
|
+
|
199
|
+
Running Larch with no command line arguments will cause the +default+ section
|
200
|
+
to be used. With the example above, this will result in an error since the
|
201
|
+
+default+ section doesn't contain the required +from+ and +to+ options, but if
|
202
|
+
you only need to use Larch with a single configuration, you could use the
|
203
|
+
+default+ section for everything and save yourself some typing on the command
|
204
|
+
line.
|
205
|
+
|
119
206
|
== Server Compatibility
|
120
207
|
|
121
208
|
Larch should work well with any server that properly supports
|
data/bin/larch
CHANGED
@@ -11,122 +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 :
|
24
|
-
|
25
|
-
|
26
|
-
opt :
|
27
|
-
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
|
31
|
+
|
32
|
+
text "\nSync Options:"
|
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 :
|
40
|
-
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']
|
41
|
+
opt :dry_run, "Don't actually make any changes", :short => '-n'
|
42
|
+
opt :max_retries, "Maximum number of times to retry after a recoverable error", :short => :none, :default => Config::DEFAULT['max-retries']
|
41
43
|
opt :no_create_folder, "Don't create destination folders that don't already exist", :short => :none
|
42
44
|
opt :ssl_certs, "Path to a trusted certificate bundle to use to verify server SSL certificates", :short => :none, :type => :string
|
43
45
|
opt :ssl_verify, "Verify server SSL certificates", :short => :none
|
44
|
-
opt :verbosity, "Output verbosity: debug, info, warn, error, or fatal", :short => '-V', :default => '
|
45
|
-
end
|
46
|
-
|
47
|
-
# Validate command-line options.
|
48
|
-
[:from, :to].each do |sym|
|
49
|
-
unless options[sym] =~ IMAP::REGEX_URI
|
50
|
-
Trollop.die sym, "must be a valid IMAP URI (e.g. imap://example.com)"
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
unless Logger::LEVELS.has_key?(options[:verbosity].to_sym)
|
55
|
-
Trollop.die :verbosity, "must be one of: #{Logger::LEVELS.keys.join(', ')}"
|
56
|
-
end
|
57
|
-
|
58
|
-
if options[:exclude_file_given]
|
59
|
-
filename = options[:exclude_file]
|
60
|
-
|
61
|
-
Trollop.die :exclude_file, ": file not found: #{filename}" unless File.file?(filename)
|
62
|
-
Trollop.die :exclude_file, ": file cannot be read: #{filename}" unless File.readable?(filename)
|
46
|
+
opt :verbosity, "Output verbosity: debug, info, warn, error, or fatal", :short => '-V', :default => Config::DEFAULT['verbosity']
|
63
47
|
end
|
64
48
|
|
65
|
-
#
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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])
|
70
54
|
end
|
71
55
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
56
|
+
# Validate config.
|
57
|
+
begin
|
58
|
+
config.validate
|
59
|
+
rescue Config::Error => e
|
60
|
+
abort "Config error: #{e}"
|
76
61
|
end
|
77
62
|
|
78
63
|
# Create URIs.
|
79
|
-
uri_from = URI(
|
80
|
-
uri_to = URI(
|
64
|
+
uri_from = URI(config.from)
|
65
|
+
uri_to = URI(config.to)
|
66
|
+
|
67
|
+
# Use --from-folder and --to-folder unless folders were specified in the URIs.
|
68
|
+
uri_from.path = uri_from.path.empty? ? '/' + CGI.escape(config.from_folder.gsub(/^\//, '')) : uri_from.path
|
69
|
+
uri_to.path = uri_to.path.empty? ? '/' + CGI.escape(config.to_folder.gsub(/^\//, '')) : uri_to.path
|
81
70
|
|
82
|
-
# --all and --all-subscribed options override folders
|
83
|
-
if
|
71
|
+
# --all and --all-subscribed options override folders
|
72
|
+
if config.all || config.all_subscribed
|
84
73
|
uri_from.path = ''
|
85
74
|
uri_to.path = ''
|
86
75
|
end
|
87
76
|
|
88
|
-
# --from-folder and --to-folder options override folders specified in URIs
|
89
|
-
if options[:from_folder_given] || options[:to_folder_given]
|
90
|
-
uri_from.path = '/' + CGI.escape(options[:from_folder].gsub(/^\//, ''))
|
91
|
-
uri_to.path = '/' + CGI.escape(options[:to_folder].gsub(/^\//, ''))
|
92
|
-
end
|
93
|
-
|
94
77
|
# Usernames and passwords specified as arguments override those in the URIs
|
95
|
-
uri_from.user = CGI.escape(
|
96
|
-
uri_from.password = CGI.escape(
|
97
|
-
uri_to.user = CGI.escape(
|
98
|
-
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
|
99
82
|
|
100
|
-
# If usernames/passwords aren't specified in either URIs or
|
83
|
+
# If usernames/passwords aren't specified in either URIs or config, then prompt.
|
101
84
|
uri_from.user ||= CGI.escape(ask("Source username (#{uri_from.host}): "))
|
102
85
|
uri_from.password ||= CGI.escape(ask("Source password (#{uri_from.host}): ") {|q| q.echo = false })
|
103
86
|
uri_to.user ||= CGI.escape(ask("Destination username (#{uri_to.host}): "))
|
104
87
|
uri_to.password ||= CGI.escape(ask("Destination password (#{uri_to.host}): ") {|q| q.echo = false })
|
105
88
|
|
106
89
|
# Go go go!
|
107
|
-
init(
|
108
|
-
options[:verbosity],
|
109
|
-
options[:exclude] ? options[:exclude].flatten : [],
|
110
|
-
options[:exclude_file]
|
111
|
-
)
|
112
|
-
|
113
|
-
Net::IMAP.debug = true if @log.level == :insane
|
90
|
+
init(config)
|
114
91
|
|
115
92
|
imap_from = Larch::IMAP.new(uri_from,
|
116
|
-
:dry_run =>
|
117
|
-
:
|
118
|
-
:
|
119
|
-
:
|
120
|
-
: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]
|
121
97
|
)
|
122
98
|
|
123
99
|
imap_to = Larch::IMAP.new(uri_to,
|
124
|
-
:create_mailbox => !
|
125
|
-
:dry_run =>
|
126
|
-
:
|
127
|
-
:
|
128
|
-
:
|
129
|
-
: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]
|
130
105
|
)
|
131
106
|
|
132
107
|
unless RUBY_PLATFORM =~ /mswin|mingw|bccwin|wince|java/
|
@@ -138,9 +113,9 @@ EOS
|
|
138
113
|
end
|
139
114
|
end
|
140
115
|
|
141
|
-
if
|
116
|
+
if config.all
|
142
117
|
copy_all(imap_from, imap_to)
|
143
|
-
elsif
|
118
|
+
elsif config.all_subscribed
|
144
119
|
copy_all(imap_from, imap_to, true)
|
145
120
|
else
|
146
121
|
copy_folder(imap_from, imap_to)
|
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
|
+
opt = k.to_s.gsub('_', '-')
|
35
|
+
@override[opt] = v if DEFAULT.has_key?(opt) && override["#{k}_given".to_sym] && v != DEFAULT[opt]
|
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
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Larch; module Database
|
2
|
+
|
3
|
+
class Mailbox < Sequel::Model
|
4
|
+
plugin :hook_class_methods
|
5
|
+
one_to_many :messages, :class => Larch::Database::Message
|
6
|
+
|
7
|
+
before_destroy do
|
8
|
+
Larch::Database::Message.filter(:mailbox_id => id).destroy
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
end; end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class CreateSchema < Sequel::Migration
|
2
|
+
def down
|
3
|
+
drop_table :accounts, :mailboxes, :messages
|
4
|
+
end
|
5
|
+
|
6
|
+
def up
|
7
|
+
create_table :accounts do
|
8
|
+
primary_key :id
|
9
|
+
text :hostname, :null => false
|
10
|
+
text :username, :null => false
|
11
|
+
|
12
|
+
unique [:hostname, :username]
|
13
|
+
end
|
14
|
+
|
15
|
+
create_table :mailboxes do
|
16
|
+
primary_key :id
|
17
|
+
foreign_key :account_id, :table => :accounts
|
18
|
+
text :name, :null => false
|
19
|
+
text :delim, :null => false
|
20
|
+
text :attr, :null => false, :default => ''
|
21
|
+
integer :subscribed, :null => false, :default => 0
|
22
|
+
integer :uidvalidity
|
23
|
+
integer :uidnext
|
24
|
+
|
25
|
+
unique [:account_id, :name, :uidvalidity]
|
26
|
+
end
|
27
|
+
|
28
|
+
create_table :messages do
|
29
|
+
primary_key :id
|
30
|
+
foreign_key :mailbox_id, :table => :mailboxes
|
31
|
+
integer :uid, :null => false
|
32
|
+
text :guid, :null => false
|
33
|
+
text :message_id
|
34
|
+
integer :rfc822_size, :null => false
|
35
|
+
integer :internaldate, :null => false
|
36
|
+
text :flags, :null => false, :default => ''
|
37
|
+
|
38
|
+
index :guid
|
39
|
+
unique [:mailbox_id, :uid]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|