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/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
- def init(log_level = :info, exclude = [], exclude_file = nil)
27
- @log = Logger.new(log_level)
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(imap_from, mailbox_from, imap_to, mailbox_to)
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 (non-recursively) from the
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
- from_name = imap_from.uri_mailbox || 'INBOX'
83
- to_name = imap_to.uri_mailbox || 'INBOX'
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
- copy_messages(imap_from, imap_from.mailbox(from_name), imap_to,
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 copy_messages(imap_from, mailbox_from, imap_to, mailbox_to)
107
- raise ArgumentError, "imap_from must be a Larch::IMAP instance" unless imap_from.is_a?(IMAP)
108
- raise ArgumentError, "mailbox_from must be a Larch::IMAP::Mailbox instance" unless mailbox_from.is_a?(IMAP::Mailbox)
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
- @log.info "copying messages from #{imap_from.host}/#{mailbox_from.name} to #{imap_to.host}/#{mailbox_to.name}"
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
- imap_from.connect
117
- imap_to.connect
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.each do |id|
122
- next if mailbox_to.has_message?(id)
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(id)
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.0.2
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-08-05 00:00:00 -07:00
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
- has_rdoc: false
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: "0"
98
+ version: 1.3.1
74
99
  version:
75
100
  requirements: []
76
101
 
77
102
  rubyforge_project:
78
- rubygems_version: 1.3.2
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.