rgrove-larch 1.0.0.1 → 1.0.0.2
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/bin/larch +7 -2
- data/lib/larch/imap.rb +86 -55
- data/lib/larch.rb +9 -7
- metadata +1 -1
data/bin/larch
CHANGED
@@ -83,8 +83,13 @@ EOS
|
|
83
83
|
:fast_scan => options[:fast_scan],
|
84
84
|
:max_retries => options[:max_retries])
|
85
85
|
|
86
|
-
|
87
|
-
|
86
|
+
unless RUBY_PLATFORM =~ /mswin|mingw|bccwin|wince|java/
|
87
|
+
begin
|
88
|
+
for sig in [:SIGINT, :SIGQUIT, :SIGTERM]
|
89
|
+
trap(sig) { @log.fatal "Interrupted (#{sig})"; exit }
|
90
|
+
end
|
91
|
+
rescue => e
|
92
|
+
end
|
88
93
|
end
|
89
94
|
|
90
95
|
copy(source, dest)
|
data/lib/larch/imap.rb
CHANGED
@@ -11,14 +11,6 @@ class IMAP
|
|
11
11
|
# Maximum number of messages to fetch at once.
|
12
12
|
MAX_FETCH_COUNT = 1024
|
13
13
|
|
14
|
-
# Recoverable connection errors.
|
15
|
-
RECOVERABLE_ERRORS = [
|
16
|
-
Errno::EPIPE,
|
17
|
-
Errno::ETIMEDOUT,
|
18
|
-
Net::IMAP::NoResponseError,
|
19
|
-
OpenSSL::SSL::SSLError
|
20
|
-
]
|
21
|
-
|
22
14
|
# Regex to capture the individual fields in an IMAP fetch command.
|
23
15
|
REGEX_FIELDS = /([0-9A-Z\.]+\[[^\]]+\](?:<[0-9\.]+>)?|[0-9A-Z\.]+)/
|
24
16
|
|
@@ -126,7 +118,12 @@ class IMAP
|
|
126
118
|
return unless @imap
|
127
119
|
|
128
120
|
synchronize do
|
129
|
-
|
121
|
+
begin
|
122
|
+
@imap.disconnect
|
123
|
+
rescue Errno::ENOTCONN => e
|
124
|
+
debug "#{e.class.name}: #{e.message}"
|
125
|
+
end
|
126
|
+
|
130
127
|
@imap = nil
|
131
128
|
end
|
132
129
|
|
@@ -359,17 +356,23 @@ class IMAP
|
|
359
356
|
good_results
|
360
357
|
end
|
361
358
|
|
362
|
-
|
363
|
-
# recoverable error occurs, die if an unrecoverable error occurs.
|
364
|
-
def safely
|
365
|
-
retries = 0
|
366
|
-
|
359
|
+
def safe_connect
|
367
360
|
synchronize do
|
361
|
+
return if @imap
|
362
|
+
|
363
|
+
retries = 0
|
364
|
+
|
368
365
|
begin
|
369
|
-
unsafe_connect
|
370
|
-
|
371
|
-
|
366
|
+
unsafe_connect
|
367
|
+
|
368
|
+
rescue Errno::EPIPE,
|
369
|
+
Errno::ETIMEDOUT,
|
370
|
+
IOError,
|
371
|
+
Net::IMAP::NoResponseError,
|
372
|
+
OpenSSL::SSL::SSLError => e
|
373
|
+
|
372
374
|
raise unless (retries += 1) <= @options[:max_retries]
|
375
|
+
info "#{e.class.name}: #{e.message} (will retry)"
|
373
376
|
|
374
377
|
@imap = nil
|
375
378
|
sleep 1 * retries
|
@@ -377,70 +380,98 @@ class IMAP
|
|
377
380
|
end
|
378
381
|
end
|
379
382
|
|
383
|
+
rescue => e
|
384
|
+
raise FatalError, "#{e.class.name}: #{e.message} (cannot recover)"
|
385
|
+
end
|
386
|
+
|
387
|
+
# Connect if necessary, execute the given block, retry up to 3 times if a
|
388
|
+
# recoverable error occurs, die if an unrecoverable error occurs.
|
389
|
+
def safely
|
390
|
+
safe_connect
|
391
|
+
|
392
|
+
synchronize do
|
393
|
+
# Explicitly set Net::IMAP's client thread to the current thread to ensure
|
394
|
+
# that exceptions aren't raised in a dead thread.
|
395
|
+
@imap.client_thread = Thread.current
|
396
|
+
end
|
397
|
+
|
380
398
|
retries = 0
|
381
399
|
|
382
400
|
begin
|
383
401
|
yield
|
384
|
-
|
385
|
-
|
402
|
+
|
403
|
+
rescue EOFError,
|
404
|
+
Errno::ENOTCONN,
|
405
|
+
Errno::EPIPE,
|
406
|
+
Errno::ETIMEDOUT,
|
407
|
+
IOError,
|
408
|
+
Net::IMAP::ByeResponseError,
|
409
|
+
OpenSSL::SSL::SSLError => e
|
410
|
+
|
386
411
|
raise unless (retries += 1) <= @options[:max_retries]
|
387
412
|
|
413
|
+
info "#{e.class.name}: #{e.message} (reconnecting)"
|
414
|
+
|
415
|
+
synchronize { @imap = nil }
|
416
|
+
sleep 1 * retries
|
417
|
+
safe_connect
|
418
|
+
retry
|
419
|
+
|
420
|
+
rescue Net::IMAP::BadResponseError,
|
421
|
+
Net::IMAP::NoResponseError,
|
422
|
+
Net::IMAP::ResponseParseError => e
|
423
|
+
|
424
|
+
raise unless (retries += 1) <= @options[:max_retries]
|
425
|
+
|
426
|
+
info "#{e.class.name}: #{e.message} (will retry)"
|
427
|
+
|
388
428
|
sleep 1 * retries
|
389
429
|
retry
|
390
430
|
end
|
391
431
|
|
392
|
-
rescue Net::IMAP::
|
432
|
+
rescue Net::IMAP::Error => e
|
393
433
|
raise Error, "#{e.class.name}: #{e.message} (giving up)"
|
394
434
|
|
395
|
-
rescue
|
396
|
-
raise
|
435
|
+
rescue Larch::Error => e
|
436
|
+
raise
|
437
|
+
|
438
|
+
rescue => e
|
439
|
+
raise FatalError, "#{e.class.name}: #{e.message} (cannot recover)"
|
397
440
|
end
|
398
441
|
|
399
442
|
def unsafe_connect
|
400
443
|
info "connecting..."
|
401
444
|
|
402
|
-
|
445
|
+
@imap = Net::IMAP.new(host, port, ssl?)
|
403
446
|
|
404
|
-
|
405
|
-
begin
|
406
|
-
@imap = Net::IMAP.new(host, port, ssl?)
|
447
|
+
info "connected on port #{port}" << (ssl? ? ' using SSL' : '')
|
407
448
|
|
408
|
-
|
449
|
+
auth_methods = ['PLAIN']
|
450
|
+
tried = []
|
409
451
|
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
['LOGIN', 'CRAM-MD5'].each do |method|
|
414
|
-
auth_methods << method if @imap.capability.include?("AUTH=#{method}")
|
415
|
-
end
|
416
|
-
|
417
|
-
begin
|
418
|
-
tried << method = auth_methods.pop
|
419
|
-
|
420
|
-
debug "authenticating using #{method}"
|
452
|
+
['LOGIN', 'CRAM-MD5'].each do |method|
|
453
|
+
auth_methods << method if @imap.capability.include?("AUTH=#{method}")
|
454
|
+
end
|
421
455
|
|
422
|
-
|
423
|
-
|
424
|
-
else
|
425
|
-
@imap.authenticate(method, @username, @password)
|
426
|
-
end
|
456
|
+
begin
|
457
|
+
tried << method = auth_methods.pop
|
427
458
|
|
428
|
-
|
459
|
+
debug "authenticating using #{method}"
|
429
460
|
|
430
|
-
|
431
|
-
|
432
|
-
|
461
|
+
if method == 'PLAIN'
|
462
|
+
@imap.login(@username, @password)
|
463
|
+
else
|
464
|
+
@imap.authenticate(method, @username, @password)
|
465
|
+
end
|
433
466
|
|
434
|
-
|
435
|
-
end
|
467
|
+
info "authenticated using #{method}"
|
436
468
|
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
end
|
441
|
-
end.join
|
469
|
+
rescue Net::IMAP::BadResponseError, Net::IMAP::NoResponseError => e
|
470
|
+
debug "#{method} auth failed: #{e.message}"
|
471
|
+
retry unless auth_methods.empty?
|
442
472
|
|
443
|
-
|
473
|
+
raise e, "#{e.message} (tried #{tried.join(', ')})"
|
474
|
+
end
|
444
475
|
end
|
445
476
|
end
|
446
477
|
|
data/lib/larch.rb
CHANGED
@@ -42,13 +42,11 @@ module Larch
|
|
42
42
|
|
43
43
|
@log.info "copying messages from #{source.uri} to #{dest.uri}"
|
44
44
|
|
45
|
-
|
46
|
-
|
47
|
-
end
|
45
|
+
source.connect
|
46
|
+
dest.connect
|
48
47
|
|
49
|
-
|
50
|
-
|
51
|
-
end
|
48
|
+
source_scan = Thread.new { source.scan_mailbox }
|
49
|
+
dest_scan = Thread.new { dest.scan_mailbox }
|
52
50
|
|
53
51
|
source_scan.join
|
54
52
|
dest_scan.join
|
@@ -96,7 +94,7 @@ module Larch
|
|
96
94
|
mutex.synchronize { @copied += 1 }
|
97
95
|
end
|
98
96
|
|
99
|
-
rescue IMAP::FatalError => e
|
97
|
+
rescue Larch::IMAP::FatalError => e
|
100
98
|
@log.fatal e.message
|
101
99
|
|
102
100
|
rescue => e
|
@@ -111,6 +109,10 @@ module Larch
|
|
111
109
|
source.disconnect
|
112
110
|
dest.disconnect
|
113
111
|
|
112
|
+
rescue => e
|
113
|
+
@log.fatal e.message
|
114
|
+
|
115
|
+
ensure
|
114
116
|
summary
|
115
117
|
end
|
116
118
|
|