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.
Files changed (4) hide show
  1. data/bin/larch +7 -2
  2. data/lib/larch/imap.rb +86 -55
  3. data/lib/larch.rb +9 -7
  4. 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
- for sig in [:SIGINT, :SIGQUIT, :SIGTERM]
87
- trap(sig) { @log.fatal "Interrupted (#{sig})"; summary; exit }
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
- @imap.disconnect
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
- # Connect if necessary, execute the given block, retry up to 3 times if a
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 unless @imap
370
- rescue *RECOVERABLE_ERRORS => e
371
- info "#{e.class.name}: #{e.message} (will retry)"
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
- rescue *RECOVERABLE_ERRORS => e
385
- info "#{e.class.name}: #{e.message} (will retry)"
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::NoResponseError => e
432
+ rescue Net::IMAP::Error => e
393
433
  raise Error, "#{e.class.name}: #{e.message} (giving up)"
394
434
 
395
- rescue IOError, Net::IMAP::Error, OpenSSL::SSL::SSLError, SocketError, SystemCallError => e
396
- raise FatalError, "#{e.class.name}: #{e.message} (giving up)"
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
- exception = nil
445
+ @imap = Net::IMAP.new(host, port, ssl?)
403
446
 
404
- Thread.new do
405
- begin
406
- @imap = Net::IMAP.new(host, port, ssl?)
447
+ info "connected on port #{port}" << (ssl? ? ' using SSL' : '')
407
448
 
408
- info "connected on port #{port}" << (ssl? ? ' using SSL' : '')
449
+ auth_methods = ['PLAIN']
450
+ tried = []
409
451
 
410
- auth_methods = ['PLAIN']
411
- tried = []
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
- if method == 'PLAIN'
423
- @imap.login(@username, @password)
424
- else
425
- @imap.authenticate(method, @username, @password)
426
- end
456
+ begin
457
+ tried << method = auth_methods.pop
427
458
 
428
- info "authenticated using #{method}"
459
+ debug "authenticating using #{method}"
429
460
 
430
- rescue Net::IMAP::BadResponseError, Net::IMAP::NoResponseError => e
431
- debug "#{method} auth failed: #{e.message}"
432
- retry unless auth_methods.empty?
461
+ if method == 'PLAIN'
462
+ @imap.login(@username, @password)
463
+ else
464
+ @imap.authenticate(method, @username, @password)
465
+ end
433
466
 
434
- raise e, "#{e.message} (tried #{tried.join(', ')})"
435
- end
467
+ info "authenticated using #{method}"
436
468
 
437
- rescue => e
438
- exception = e
439
- error e.message
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
- raise exception if exception
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
- source_scan = Thread.new do
46
- source.scan_mailbox
47
- end
45
+ source.connect
46
+ dest.connect
48
47
 
49
- dest_scan = Thread.new do
50
- dest.scan_mailbox
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
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rgrove-larch
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.1
4
+ version: 1.0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Grove