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/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.