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/lib/larch.rb
CHANGED
@@ -4,10 +4,16 @@ $:.uniq!
|
|
4
4
|
|
5
5
|
require 'cgi'
|
6
6
|
require 'digest/md5'
|
7
|
+
require 'fileutils'
|
7
8
|
require 'net/imap'
|
8
9
|
require 'time'
|
9
10
|
require 'uri'
|
11
|
+
require 'yaml'
|
10
12
|
|
13
|
+
require 'sequel'
|
14
|
+
require 'sequel/extensions/migration'
|
15
|
+
|
16
|
+
require 'larch/config'
|
11
17
|
require 'larch/errors'
|
12
18
|
require 'larch/imap'
|
13
19
|
require 'larch/imap/mailbox'
|
@@ -17,16 +23,21 @@ require 'larch/version'
|
|
17
23
|
module Larch
|
18
24
|
|
19
25
|
class << self
|
20
|
-
attr_reader :log, :exclude
|
26
|
+
attr_reader :config, :db, :log, :exclude
|
21
27
|
|
22
28
|
EXCLUDE_COMMENT = /#.*$/
|
23
29
|
EXCLUDE_REGEX = /^\s*\/(.*)\/\s*/
|
24
30
|
GLOB_PATTERNS = {'*' => '.*', '?' => '.'}
|
31
|
+
LIB_DIR = File.join(File.dirname(File.expand_path(__FILE__)), 'larch')
|
32
|
+
|
33
|
+
def init(config)
|
34
|
+
raise ArgumentError, "config must be a Larch::Config instance" unless config.is_a?(Config)
|
25
35
|
|
26
|
-
|
27
|
-
@log
|
36
|
+
@config = config
|
37
|
+
@log = Logger.new(@config[:verbosity])
|
38
|
+
@db = open_db(@config[:database])
|
28
39
|
|
29
|
-
@exclude = exclude.map do |e|
|
40
|
+
@exclude = @config[:exclude].map do |e|
|
30
41
|
if e =~ EXCLUDE_REGEX
|
31
42
|
Regexp.new($1, Regexp::IGNORECASE)
|
32
43
|
else
|
@@ -34,7 +45,9 @@ module Larch
|
|
34
45
|
end
|
35
46
|
end
|
36
47
|
|
37
|
-
load_exclude_file(exclude_file) if exclude_file
|
48
|
+
load_exclude_file(@config[:exclude_file]) if @config[:exclude_file]
|
49
|
+
|
50
|
+
Net::IMAP.debug = true if @log.level == :insane
|
38
51
|
|
39
52
|
# Stats
|
40
53
|
@copied = 0
|
@@ -59,7 +72,7 @@ module Larch
|
|
59
72
|
mailbox_to = imap_to.mailbox(mailbox_from.name, mailbox_from.delim)
|
60
73
|
mailbox_to.subscribe if mailbox_from.subscribed?
|
61
74
|
|
62
|
-
copy_messages(
|
75
|
+
copy_messages(mailbox_from, mailbox_to)
|
63
76
|
end
|
64
77
|
|
65
78
|
rescue => e
|
@@ -69,8 +82,8 @@ module Larch
|
|
69
82
|
summary
|
70
83
|
end
|
71
84
|
|
72
|
-
# Copies the messages in a single IMAP folder
|
73
|
-
# source to the destination.
|
85
|
+
# Copies the messages in a single IMAP folder and all its subfolders
|
86
|
+
# (recursively) from the source to the destination.
|
74
87
|
def copy_folder(imap_from, imap_to)
|
75
88
|
raise ArgumentError, "imap_from must be a Larch::IMAP instance" unless imap_from.is_a?(IMAP)
|
76
89
|
raise ArgumentError, "imap_to must be a Larch::IMAP instance" unless imap_to.is_a?(IMAP)
|
@@ -79,13 +92,10 @@ module Larch
|
|
79
92
|
@failed = 0
|
80
93
|
@total = 0
|
81
94
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
return if excluded?(from_name) || excluded?(to_name)
|
95
|
+
mailbox_from = imap_from.mailbox(imap_from.uri_mailbox || 'INBOX')
|
96
|
+
mailbox_to = imap_to.mailbox(imap_to.uri_mailbox || 'INBOX')
|
86
97
|
|
87
|
-
|
88
|
-
imap_to.mailbox(to_name))
|
98
|
+
copy_mailbox(mailbox_from, mailbox_to)
|
89
99
|
|
90
100
|
imap_from.disconnect
|
91
101
|
imap_to.disconnect
|
@@ -97,32 +107,85 @@ module Larch
|
|
97
107
|
summary
|
98
108
|
end
|
99
109
|
|
110
|
+
# Opens a connection to the Larch message database, creating it if
|
111
|
+
# necessary.
|
112
|
+
def open_db(database)
|
113
|
+
filename = File.expand_path(database)
|
114
|
+
directory = File.dirname(filename)
|
115
|
+
|
116
|
+
unless File.exist?(directory)
|
117
|
+
FileUtils.mkdir_p(directory)
|
118
|
+
File.chmod(0700, directory)
|
119
|
+
end
|
120
|
+
|
121
|
+
begin
|
122
|
+
db = Sequel.connect("sqlite://#{filename}")
|
123
|
+
db.test_connection
|
124
|
+
rescue => e
|
125
|
+
@log.fatal "unable to open message database: #{e}"
|
126
|
+
abort
|
127
|
+
end
|
128
|
+
|
129
|
+
# Ensure that the database schema is up to date.
|
130
|
+
migration_dir = File.join(LIB_DIR, 'db', 'migrate')
|
131
|
+
|
132
|
+
unless Sequel::Migrator.get_current_migration_version(db) ==
|
133
|
+
Sequel::Migrator.latest_migration_version(migration_dir)
|
134
|
+
begin
|
135
|
+
Sequel::Migrator.apply(db, migration_dir)
|
136
|
+
rescue => e
|
137
|
+
@log.fatal "unable to migrate message database: #{e}"
|
138
|
+
abort
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
require 'larch/db/message'
|
143
|
+
require 'larch/db/mailbox'
|
144
|
+
require 'larch/db/account'
|
145
|
+
|
146
|
+
db
|
147
|
+
end
|
148
|
+
|
100
149
|
def summary
|
101
150
|
@log.info "#{@copied} message(s) copied, #{@failed} failed, #{@total - @copied - @failed} untouched out of #{@total} total"
|
102
151
|
end
|
103
152
|
|
104
153
|
private
|
105
154
|
|
106
|
-
def
|
107
|
-
raise ArgumentError, "
|
108
|
-
raise ArgumentError, "
|
109
|
-
raise ArgumentError, "imap_to must be a Larch::IMAP instance" unless imap_to.is_a?(IMAP)
|
110
|
-
raise ArgumentError, "mailbox_to must be a Larch::IMAP::Mailbox instance" unless mailbox_to.is_a?(IMAP::Mailbox)
|
155
|
+
def copy_mailbox(mailbox_from, mailbox_to)
|
156
|
+
raise ArgumentError, "mailbox_from must be a Larch::IMAP::Mailbox instance" unless mailbox_from.is_a?(Larch::IMAP::Mailbox)
|
157
|
+
raise ArgumentError, "mailbox_to must be a Larch::IMAP::Mailbox instance" unless mailbox_to.is_a?(Larch::IMAP::Mailbox)
|
111
158
|
|
112
159
|
return if excluded?(mailbox_from.name) || excluded?(mailbox_to.name)
|
113
160
|
|
114
|
-
|
161
|
+
mailbox_to.subscribe if mailbox_from.subscribed?
|
162
|
+
copy_messages(mailbox_from, mailbox_to)
|
163
|
+
|
164
|
+
mailbox_from.each_mailbox do |child_from|
|
165
|
+
next if excluded?(child_from.name)
|
166
|
+
child_to = mailbox_to.imap.mailbox(child_from.name, child_from.delim)
|
167
|
+
copy_mailbox(child_from, child_to)
|
168
|
+
end
|
169
|
+
end
|
115
170
|
|
116
|
-
|
117
|
-
|
171
|
+
def copy_messages(mailbox_from, mailbox_to)
|
172
|
+
raise ArgumentError, "mailbox_from must be a Larch::IMAP::Mailbox instance" unless mailbox_from.is_a?(Larch::IMAP::Mailbox)
|
173
|
+
raise ArgumentError, "mailbox_to must be a Larch::IMAP::Mailbox instance" unless mailbox_to.is_a?(Larch::IMAP::Mailbox)
|
174
|
+
|
175
|
+
return if excluded?(mailbox_from.name) || excluded?(mailbox_to.name)
|
176
|
+
|
177
|
+
imap_from = mailbox_from.imap
|
178
|
+
imap_to = mailbox_to.imap
|
179
|
+
|
180
|
+
@log.info "copying messages from #{imap_from.host}/#{mailbox_from.name} to #{imap_to.host}/#{mailbox_to.name}"
|
118
181
|
|
119
182
|
@total += mailbox_from.length
|
120
183
|
|
121
|
-
mailbox_from.
|
122
|
-
next if mailbox_to.
|
184
|
+
mailbox_from.each_guid do |guid|
|
185
|
+
next if mailbox_to.has_guid?(guid)
|
123
186
|
|
124
187
|
begin
|
125
|
-
msg = mailbox_from.peek(
|
188
|
+
next unless msg = mailbox_from.peek(guid)
|
126
189
|
|
127
190
|
if msg.envelope.from
|
128
191
|
env_from = msg.envelope.from.first
|
@@ -137,7 +200,6 @@ module Larch
|
|
137
200
|
@copied += 1
|
138
201
|
|
139
202
|
rescue Larch::IMAP::Error => e
|
140
|
-
# TODO: Keep failed message envelopes in a buffer for later output?
|
141
203
|
@failed += 1
|
142
204
|
@log.error e.message
|
143
205
|
next
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: larch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0dev20091006
|
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-
|
12
|
+
date: 2009-10-06 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -22,6 +22,26 @@ dependencies:
|
|
22
22
|
- !ruby/object:Gem::Version
|
23
23
|
version: 1.5.0
|
24
24
|
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: sequel
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.3.0
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: sqlite3-ruby
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.2.5
|
44
|
+
version:
|
25
45
|
- !ruby/object:Gem::Dependency
|
26
46
|
name: trollop
|
27
47
|
type: :runtime
|
@@ -45,13 +65,18 @@ files:
|
|
45
65
|
- LICENSE
|
46
66
|
- README.rdoc
|
47
67
|
- bin/larch
|
48
|
-
- lib/larch.rb
|
68
|
+
- lib/larch/config.rb
|
69
|
+
- lib/larch/db/account.rb
|
70
|
+
- lib/larch/db/mailbox.rb
|
71
|
+
- lib/larch/db/message.rb
|
72
|
+
- lib/larch/db/migrate/001_create_schema.rb
|
49
73
|
- lib/larch/errors.rb
|
50
|
-
- lib/larch/imap.rb
|
51
74
|
- lib/larch/imap/mailbox.rb
|
75
|
+
- lib/larch/imap.rb
|
52
76
|
- lib/larch/logger.rb
|
53
77
|
- lib/larch/version.rb
|
54
|
-
|
78
|
+
- lib/larch.rb
|
79
|
+
has_rdoc: true
|
55
80
|
homepage: http://github.com/rgrove/larch/
|
56
81
|
licenses: []
|
57
82
|
|
@@ -68,14 +93,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
68
93
|
version:
|
69
94
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
95
|
requirements:
|
71
|
-
- - "
|
96
|
+
- - ">"
|
72
97
|
- !ruby/object:Gem::Version
|
73
|
-
version:
|
98
|
+
version: 1.3.1
|
74
99
|
version:
|
75
100
|
requirements: []
|
76
101
|
|
77
102
|
rubyforge_project:
|
78
|
-
rubygems_version: 1.3.
|
103
|
+
rubygems_version: 1.3.5
|
79
104
|
signing_key:
|
80
105
|
specification_version: 3
|
81
106
|
summary: Larch syncs messages from one IMAP server to another. Awesomely.
|