larch 1.0.2 → 1.1.0dev20091006
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 +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
|