larch 1.1.0.dev.20091126 → 1.1.0.dev.20091203

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -22,7 +22,7 @@ Latest stable release:
22
22
 
23
23
  Latest development version:
24
24
 
25
- gem install larch -s http://gemcutter.org --prerelease
25
+ gem install larch --pre
26
26
 
27
27
  == Usage
28
28
 
@@ -213,8 +213,47 @@ servers:
213
213
  The following servers do not work well with Larch:
214
214
 
215
215
  * BlitzMail - Buggy server implementation; fails to properly quote or escape
216
- some IMAP responses, which can cause Larch to hang waiting for a terminating
217
- character that will never arrive.
216
+ some IMAP responses, which can cause Net::IMAP to hang waiting for a
217
+ terminating character that will never arrive.
218
+
219
+ === Gmail Quirks
220
+
221
+ Gmail's IMAP implementation is quirky. Larch does its best to work around these
222
+ quirks whenever possible, but here are a few things to watch out for:
223
+
224
+ ==== "Some messages could not be FETCHed" error
225
+
226
+ This error indicates that a message on Gmail is corrupt, and Gmail itself is
227
+ unable to read it. The message will continue to show up in the mailbox, but all
228
+ attempts to access it via IMAP, POP, or the Gmail web interface will result in
229
+ errors. Larch will try to skip these messages and continue processing others
230
+ if possible.
231
+
232
+ It's not clear how this corruption occurs or exactly what kind of corruption
233
+ causes these errors, although in every case I'm aware of, the corrupt message
234
+ has originated outside of Gmail (Gmail itself does not corrupt the message).
235
+ There is currently no known solution for this problem apart from deleting the
236
+ corrupted messages.
237
+
238
+ ==== Maximum folder name length of 40 characters
239
+
240
+ Folders in Gmail IMAP are really just labels, and a nested folder hierarchy like
241
+ "Foo\Bar\Baz" is actually just a single label with "\" characters in it. Since
242
+ Gmail limits label names to 40 characters, it's impossible to create a nested
243
+ folder hierarchy with a combined name length of more than 40 characters
244
+ including delimiters.
245
+
246
+ If you try to copy a too-long folder hierarchy to Gmail using Larch, Gmail will
247
+ return a "Folder name is not allowed" error. To work around this, reorganize
248
+ your mail into a shallower hierarchy on the source server to avoid hitting the
249
+ folder name length limit.
250
+
251
+ ==== Folder names cannot contain leading or trailing whitespace
252
+
253
+ Most IMAP servers allow folder names to contain leading and trailing whitespace,
254
+ such as " folder ". Gmail does not. When copying folders to Gmail, Larch will
255
+ automatically remove leading and trailing whitespace in folder names to prevent
256
+ errors.
218
257
 
219
258
  == Known Issues
220
259
 
@@ -223,10 +262,14 @@ The following servers do not work well with Larch:
223
262
  cause a deadlock to occur if a connection drops unexpectedly (either due to
224
263
  network issues or because the server closed the connection without warning)
225
264
  when the server has already begun sending a response and Net::IMAP is
226
- waiting to receive more data.
265
+ waiting to receive more data. If this happens, Net::IMAP will continue waiting
266
+ forever without passing control back to Larch, and you will need to manually
267
+ kill and restart Larch.
227
268
 
228
- If this happens, Net::IMAP will continue waiting forever without passing
229
- control back to Larch, and you will need to manually kill and restart Larch.
269
+ Net::IMAP in Ruby 1.8 has also been known to hang when it can't parse a server
270
+ response, either because the response itself is malformed or because of a
271
+ bug in Net::IMAP's parser. This is rare, but it happens. Unfortunately there's
272
+ nothing Larch can do about this.
230
273
 
231
274
  * The Ruby package on Debian, Ubuntu, and some other Debian-based Linux
232
275
  distributions doesn't include the OpenSSL standard library. If you see an
@@ -241,6 +284,9 @@ The Larch mailing list is the best place for questions, comments, and discussion
241
284
  about Larch. You can join the list or view the archives at
242
285
  http://groups.google.com/group/larch
243
286
 
287
+ First-time senders to the list are moderated to prevent spam, so there may be a
288
+ delay before your first message shows up.
289
+
244
290
  == Credit
245
291
 
246
292
  The Larch::IMAP class borrows heavily from Sup[http://sup.rubyforge.org] by
data/lib/larch/config.rb CHANGED
@@ -95,6 +95,8 @@ class Config
95
95
  @cached['from-folder'] ||= 'INBOX'
96
96
  @cached['to-folder'] ||= 'INBOX'
97
97
  end
98
+
99
+ @cached['exclude'].flatten!
98
100
  end
99
101
 
100
102
  private
@@ -16,17 +16,15 @@ class Mailbox
16
16
  def initialize(imap, name, delim, subscribed, *attr)
17
17
  raise ArgumentError, "must provide a Larch::IMAP instance" unless imap.is_a?(Larch::IMAP)
18
18
 
19
+ @attr = attr.flatten
20
+ @delim = delim
21
+ @flags = []
19
22
  @imap = imap
23
+ @last_scan = nil
20
24
  @name = name
21
25
  @name_utf7 = Net::IMAP.encode_utf7(@name)
22
- @delim = delim
23
- @flags = []
24
26
  @perm_flags = []
25
27
  @subscribed = subscribed
26
- @attr = attr.flatten
27
-
28
- @last_scan = nil
29
- @mutex = Mutex.new
30
28
 
31
29
  # Valid mailbox states are :closed (no mailbox open), :examined (mailbox
32
30
  # open and read-only), or :selected (mailbox open and read-write).
@@ -52,6 +50,8 @@ class Mailbox
52
50
  # Create private convenience methods (debug, info, warn, etc.) to make
53
51
  # logging easier.
54
52
  Logger::LEVELS.each_key do |level|
53
+ next if Mailbox.private_method_defined?(level)
54
+
55
55
  Mailbox.class_eval do
56
56
  define_method(level) do |msg|
57
57
  Larch.log.log(level, "#{@imap.username}@#{@imap.host}: #{@name}: #{msg}")
@@ -82,7 +82,7 @@ class Mailbox
82
82
  next true if flag == :Recent
83
83
 
84
84
  unless @flags.include?(flag) || @perm_flags.include?(:*) || @perm_flags.include?(flag)
85
- debug "flag not supported on destination: #{flag}"
85
+ info "flag not supported on destination: #{flag}"
86
86
  true
87
87
  end
88
88
  end
@@ -167,14 +167,15 @@ class Mailbox
167
167
 
168
168
  # Resets the mailbox state.
169
169
  def reset
170
- @mutex.synchronize { @state = :closed }
170
+ @state = :closed
171
171
  end
172
172
 
173
173
  # Fetches message headers from this mailbox.
174
174
  def scan
175
- return if @last_scan && (Time.now - @last_scan) < SCAN_INTERVAL
175
+ now = Time.now.to_i
176
+ return if @last_scan && (now - @last_scan) < SCAN_INTERVAL
176
177
  first_scan = @last_scan.nil?
177
- @mutex.synchronize { @last_scan = Time.now }
178
+ @last_scan = now
178
179
 
179
180
  # Compare the mailbox's current status with its last known status.
180
181
  begin
@@ -314,7 +315,8 @@ class Mailbox
314
315
  rescue => e
315
316
  # Set this mailbox's uidnext value to the last known good UID that was
316
317
  # stored in the database, plus 1. This will allow Larch to resume where
317
- # the error occurred on the next attempt rather than having to start over.
318
+ # the error occurred on the next attempt rather than having to start
319
+ # over.
318
320
  @db_mailbox.update(:uidnext => last_good_uid + 1) if last_good_uid
319
321
  raise
320
322
  end
@@ -329,7 +331,7 @@ class Mailbox
329
331
  return false if subscribed? && !force
330
332
 
331
333
  @imap.safely { @imap.conn.subscribe(@name_utf7) } unless @imap.options[:dry_run]
332
- @mutex.synchronize { @subscribed = true }
334
+ @subscribed = true
333
335
  @db_mailbox.update(:subscribed => 1)
334
336
 
335
337
  true
@@ -345,7 +347,7 @@ class Mailbox
345
347
  return false unless subscribed? || force
346
348
 
347
349
  @imap.safely { @imap.conn.unsubscribe(@name_utf7) } unless @imap.options[:dry_run]
348
- @mutex.synchronize { @subscribed = false }
350
+ @subscribed = false
349
351
  @db_mailbox.update(:subscribed => 0)
350
352
 
351
353
  true
@@ -397,13 +399,13 @@ class Mailbox
397
399
 
398
400
  @imap.safely do
399
401
  begin
400
- @mutex.synchronize { @state = :closed }
402
+ @state = :closed
401
403
 
402
404
  debug "examining mailbox"
403
405
  @imap.conn.examine(@name_utf7)
404
406
  refresh_flags
405
407
 
406
- @mutex.synchronize { @state = :examined }
408
+ @state = :examined
407
409
 
408
410
  rescue Net::IMAP::NoResponseError => e
409
411
  raise Error, "unable to examine mailbox: #{e.message}"
@@ -425,13 +427,13 @@ class Mailbox
425
427
 
426
428
  @imap.safely do
427
429
  begin
428
- @mutex.synchronize { @state = :closed }
430
+ @state = :closed
429
431
 
430
432
  debug "selecting mailbox"
431
433
  @imap.conn.select(@name_utf7)
432
434
  refresh_flags
433
435
 
434
- @mutex.synchronize { @state = :selected }
436
+ @state = :selected
435
437
 
436
438
  rescue Net::IMAP::NoResponseError => e
437
439
  raise Error, "unable to select mailbox: #{e.message}" unless create
data/lib/larch/imap.rb CHANGED
@@ -56,7 +56,6 @@ class IMAP
56
56
 
57
57
  @conn = nil
58
58
  @mailboxes = {}
59
- @mutex = Mutex.new
60
59
  @quirks = {
61
60
  :gmail => false
62
61
  }
@@ -69,6 +68,8 @@ class IMAP
69
68
  # Create private convenience methods (debug, info, warn, etc.) to make
70
69
  # logging easier.
71
70
  Logger::LEVELS.each_key do |level|
71
+ next if IMAP.private_method_defined?(level)
72
+
72
73
  IMAP.class_eval do
73
74
  define_method(level) do |msg|
74
75
  Larch.log.log(level, "#{username}@#{host}: #{msg}")
@@ -185,7 +186,7 @@ class IMAP
185
186
 
186
187
  raise unless (retries += 1) <= @options[:max_retries]
187
188
 
188
- info "#{e.class.name}: #{e.message} (reconnecting)"
189
+ warn "#{e.class.name}: #{e.message} (reconnecting)"
189
190
 
190
191
  reset
191
192
  sleep 1 * retries
@@ -198,7 +199,7 @@ class IMAP
198
199
 
199
200
  raise unless (retries += 1) <= @options[:max_retries]
200
201
 
201
- info "#{e.class.name}: #{e.message} (will retry)"
202
+ warn "#{e.class.name}: #{e.message} (will retry)"
202
203
 
203
204
  sleep 1 * retries
204
205
  retry
@@ -252,10 +253,8 @@ class IMAP
252
253
 
253
254
  # Resets the connection and mailbox state.
254
255
  def reset
255
- @mutex.synchronize do
256
- @conn = nil
257
- @mailboxes.each_value {|mb| mb.reset }
258
- end
256
+ @conn = nil
257
+ @mailboxes.each_value {|mb| mb.reset }
259
258
  end
260
259
 
261
260
  def safe_connect
@@ -277,7 +276,7 @@ class IMAP
277
276
  # verification errors.
278
277
  raise if e.is_a?(OpenSSL::SSL::SSLError) && e.message =~ /certificate verify failed/
279
278
 
280
- info "#{e.class.name}: #{e.message} (will retry)"
279
+ warn "#{e.class.name}: #{e.message} (will retry)"
281
280
 
282
281
  reset
283
282
  sleep 1 * retries
@@ -289,7 +288,7 @@ class IMAP
289
288
  end
290
289
 
291
290
  def unsafe_connect
292
- info "connecting..."
291
+ debug "connecting..."
293
292
 
294
293
  exception = nil
295
294
 
@@ -322,7 +321,7 @@ class IMAP
322
321
  @conn.authenticate(method, username, password)
323
322
  end
324
323
 
325
- info "authenticated using #{method}"
324
+ debug "authenticated using #{method}"
326
325
 
327
326
  rescue Net::IMAP::BadResponseError, Net::IMAP::NoResponseError => e
328
327
  debug "#{method} auth failed: #{e.message}"
@@ -343,18 +342,16 @@ class IMAP
343
342
  all = safely { @conn.list('', '*') } || []
344
343
  subscribed = safely { @conn.lsub('', '*') } || []
345
344
 
346
- @mutex.synchronize do
347
- # Remove cached mailboxes that no longer exist.
348
- @mailboxes.delete_if {|k, v| !all.any?{|mb| Net::IMAP.decode_utf7(mb.name) == k}}
345
+ # Remove cached mailboxes that no longer exist.
346
+ @mailboxes.delete_if {|k, v| !all.any?{|mb| Net::IMAP.decode_utf7(mb.name) == k}}
349
347
 
350
- # Update cached mailboxes.
351
- all.each do |mb|
352
- name = Net::IMAP.decode_utf7(mb.name)
353
- name = 'INBOX' if name.downcase == 'inbox'
348
+ # Update cached mailboxes.
349
+ all.each do |mb|
350
+ name = Net::IMAP.decode_utf7(mb.name)
351
+ name = 'INBOX' if name.downcase == 'inbox'
354
352
 
355
- @mailboxes[name] ||= Mailbox.new(self, name, mb.delim,
356
- subscribed.any?{|s| s.name == mb.name}, mb.attr)
357
- end
353
+ @mailboxes[name] ||= Mailbox.new(self, name, mb.delim,
354
+ subscribed.any?{|s| s.name == mb.name}, mb.attr)
358
355
  end
359
356
 
360
357
  # Remove mailboxes that no longer exist from the database.
data/lib/larch/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Larch
2
2
  APP_NAME = 'Larch'
3
- APP_VERSION = '1.1.0.dev.20091126'
3
+ APP_VERSION = '1.1.0.dev.20091203'
4
4
  APP_AUTHOR = 'Ryan Grove'
5
5
  APP_EMAIL = 'ryan@wonko.com'
6
6
  APP_URL = 'http://github.com/rgrove/larch/'
data/lib/larch.rb CHANGED
@@ -1,7 +1,3 @@
1
- # Prepend this file's directory to the include path if it's not there already.
2
- $:.unshift(File.dirname(File.expand_path(__FILE__)))
3
- $:.uniq!
4
-
5
1
  require 'cgi'
6
2
  require 'digest/md5'
7
3
  require 'fileutils'
@@ -23,7 +19,7 @@ require 'larch/version'
23
19
  module Larch
24
20
 
25
21
  class << self
26
- attr_reader :config, :db, :log, :exclude
22
+ attr_reader :config, :db, :exclude, :log
27
23
 
28
24
  EXCLUDE_COMMENT = /#.*$/
29
25
  EXCLUDE_REGEX = /^\s*\/(.*)\/\s*/
@@ -37,15 +33,7 @@ module Larch
37
33
  @log = Logger.new(@config[:verbosity])
38
34
  @db = open_db(@config[:database])
39
35
 
40
- @exclude = @config[:exclude].map do |e|
41
- if e =~ EXCLUDE_REGEX
42
- Regexp.new($1, Regexp::IGNORECASE)
43
- else
44
- glob_to_regex(e.strip)
45
- end
46
- end
47
-
48
- load_exclude_file(@config[:exclude_file]) if @config[:exclude_file]
36
+ parse_exclusions
49
37
 
50
38
  Net::IMAP.debug = true if @log.level == :insane
51
39
 
@@ -256,6 +244,17 @@ module Larch
256
244
  raise Larch::IMAP::FatalError, "error in exclude file at line #{lineno}: #{e}"
257
245
  end
258
246
 
247
+ def parse_exclusions
248
+ @exclude = @config[:exclude].map do |e|
249
+ if e =~ EXCLUDE_REGEX
250
+ Regexp.new($1, Regexp::IGNORECASE)
251
+ else
252
+ glob_to_regex(e.strip)
253
+ end
254
+ end
255
+
256
+ load_exclude_file(@config[:exclude_file]) if @config[:exclude_file]
257
+ end
259
258
  end
260
259
 
261
260
  end
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.1.0.dev.20091126
4
+ version: 1.1.0.dev.20091203
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-11-26 00:00:00 -08:00
12
+ date: 2009-12-03 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency