rgrove-larch 0.0.1.3 → 0.0.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/bin/larch +31 -12
  2. data/lib/larch/imap.rb +8 -3
  3. data/lib/larch.rb +33 -14
  4. metadata +1 -1
data/bin/larch CHANGED
@@ -27,21 +27,25 @@ EOS
27
27
  # opt :dry_run, "Don't actually do anything.", :short => '-n'
28
28
  opt :fast_scan, "Use a faster (but less accurate) method to scan mailboxes. This may result in messages being re-copied.", :short => :none
29
29
  opt :from_folder, "Source folder to copy from.", :short => :none, :default => 'INBOX'
30
- opt :from_pass, "Source server password (will prompt if not specified).", :short => :none, :type => :string
31
- opt :from_user, "Source server username (will prompt if not specified).", :short => :none, :type => :string
30
+ opt :from_pass, "Source server password (Default: prompt).", :short => :none, :type => :string
31
+ opt :from_user, "Source server username (Default: prompt).", :short => :none, :type => :string
32
32
  opt :no_create_folder, "Don't create destination folders that don't already exist.", :short => :none
33
33
  opt :to_folder, "Destination folder to copy to.", :short => :none, :default => 'INBOX'
34
- opt :to_pass, "Destination server password (will prompt if not specified).", :short => :none, :type => :string
35
- opt :to_user, "Destination server username (will prompt if not specified).", :short => :none, :type => :string
34
+ opt :to_pass, "Destination server password (Default: prompt).", :short => :none, :type => :string
35
+ opt :to_user, "Destination server username (Default: prompt).", :short => :none, :type => :string
36
36
  opt :verbosity, "Output verbosity: debug, info, warn, error, or fatal.", :short => '-V', :default => 'info'
37
37
  end
38
38
 
39
39
  # Validate command-line options.
40
40
  [:from, :to].each do |sym|
41
- Trollop.die sym, "must be a valid IMAP URI (e.g. imap://example.com)" unless options[sym] =~ IMAP::REGEX_URI
41
+ unless options[sym] =~ IMAP::REGEX_URI
42
+ Trollop.die sym, "must be a valid IMAP URI (e.g. imap://example.com)"
43
+ end
42
44
  end
43
45
 
44
- Trollop.die :verbosity, "must be one of: #{Logger::LEVELS.keys.join(', ')}" unless Logger::LEVELS.has_key?(options[:verbosity].to_sym)
46
+ unless Logger::LEVELS.has_key?(options[:verbosity].to_sym)
47
+ Trollop.die :verbosity, "must be one of: #{Logger::LEVELS.keys.join(', ')}"
48
+ end
45
49
 
46
50
  # Create URIs.
47
51
  options[:from] = URI(options[:from])
@@ -50,16 +54,31 @@ EOS
50
54
  options[:to].path = '/' + CGI.escape(options[:to_folder].gsub(/^\//, ''))
51
55
 
52
56
  # Prompt for usernames and passwords if necessary.
53
- options[:from_user] = ask("Source username (#{options[:from].host}): ") unless options[:from_user]
54
- options[:from_pass] = ask("Source password (#{options[:from].host}): ") {|q| q.echo = false } unless options[:from_pass]
55
- options[:to_user] = ask("Destination username (#{options[:to].host}): ") unless options[:to_user]
56
- options[:to_pass] = ask("Destination password (#{options[:to].host}): ") {|q| q.echo = false } unless options[:to_pass]
57
+ unless options[:from_user]
58
+ options[:from_user] = ask("Source username (#{options[:from].host}): ")
59
+ end
60
+
61
+ unless options[:from_pass]
62
+ options[:from_pass] = ask("Source password (#{options[:from].host}): ") {|q| q.echo = false }
63
+ end
64
+
65
+ unless options[:to_user]
66
+ options[:to_user] = ask("Destination username (#{options[:to].host}): ")
67
+ end
68
+
69
+ unless options[:to_pass]
70
+ options[:to_pass] = ask("Destination password (#{options[:to].host}): ") {|q| q.echo = false }
71
+ end
57
72
 
58
73
  # Go go go!
59
74
  init(options[:verbosity])
60
75
 
61
- source = IMAP.new(options[:from], options[:from_user], options[:from_pass], :fast_scan => options[:fast_scan])
62
- dest = IMAP.new(options[:to], options[:to_user], options[:to_pass], :create_mailbox => !options[:no_create_folder], :fast_scan => options[:fast_scan])
76
+ source = IMAP.new(options[:from], options[:from_user], options[:from_pass],
77
+ :fast_scan => options[:fast_scan])
78
+
79
+ dest = IMAP.new(options[:to], options[:to_user], options[:to_pass],
80
+ :create_mailbox => !options[:no_create_folder],
81
+ :fast_scan => options[:fast_scan])
63
82
 
64
83
  for sig in [:SIGINT, :SIGQUIT, :SIGTERM]
65
84
  trap(sig) { @log.fatal "Interrupted (#{sig})"; summary; exit }
data/lib/larch/imap.rb CHANGED
@@ -145,7 +145,7 @@ class IMAP
145
145
  raise NotFoundError, "message not found: #{message_id}" if uid.nil?
146
146
 
147
147
  debug "fetching envelope: #{message_id}"
148
- safely { imap_uid_fetch([uid], 'ENVELOPE').first.attr['ENVELOPE'] }
148
+ imap_uid_fetch([uid], 'ENVELOPE').first.attr['ENVELOPE']
149
149
  end
150
150
 
151
151
  # Fetches a Larch::Message instance representing the message with the
@@ -157,7 +157,7 @@ class IMAP
157
157
  raise NotFoundError, "message not found: #{message_id}" if uid.nil?
158
158
 
159
159
  debug "#{peek ? 'peeking at' : 'fetching'} message: #{message_id}"
160
- data = safely { imap_uid_fetch([uid], [(peek ? 'BODY.PEEK[]' : 'BODY[]'), 'FLAGS', 'INTERNALDATE', 'ENVELOPE']) }.first
160
+ data = imap_uid_fetch([uid], [(peek ? 'BODY.PEEK[]' : 'BODY[]'), 'FLAGS', 'INTERNALDATE', 'ENVELOPE']).first
161
161
 
162
162
  Message.new(message_id, data.attr['ENVELOPE'], data.attr['BODY[]'],
163
163
  data.attr['FLAGS'], Time.parse(data.attr['INTERNALDATE']))
@@ -332,6 +332,7 @@ class IMAP
332
332
  begin
333
333
  unsafe_connect unless @imap
334
334
  rescue *RECOVERABLE_ERRORS => e
335
+ info "#{e.class.name}: #{e.message} (will retry)"
335
336
  raise unless (retries += 1) <= 3
336
337
 
337
338
  @imap = nil
@@ -344,14 +345,18 @@ class IMAP
344
345
  begin
345
346
  yield
346
347
  rescue *RECOVERABLE_ERRORS => e
348
+ info "#{e.class.name}: #{e.message} (will retry)"
347
349
  raise unless (retries += 1) <= 3
348
350
 
349
351
  sleep 2 * retries
350
352
  retry
351
353
  end
352
354
 
355
+ rescue Net::IMAP::NoResponseError => e
356
+ raise Error, "#{e.class.name}: #{e.message} (giving up)"
357
+
353
358
  rescue IOError, Net::IMAP::Error, OpenSSL::SSL::SSLError, SocketError, SystemCallError => e
354
- raise FatalError, "while communicating with IMAP server (#{e.class.name}): #{e.message}"
359
+ raise FatalError, "#{e.class.name}: #{e.message} (giving up)"
355
360
  end
356
361
 
357
362
  def unsafe_connect
data/lib/larch.rb CHANGED
@@ -34,7 +34,8 @@ module Larch
34
34
  raise ArgumentError, "source must be a Larch::IMAP instance" unless source.is_a?(IMAP)
35
35
  raise ArgumentError, "dest must be a Larch::IMAP instance" unless dest.is_a?(IMAP)
36
36
 
37
- msgq = SizedQueue.new(32)
37
+ msgq = SizedQueue.new(32)
38
+ mutex = Mutex.new
38
39
 
39
40
  @copied = 0
40
41
  @failed = 0
@@ -42,18 +43,30 @@ module Larch
42
43
 
43
44
  @log.info "copying messages from #{source.uri} to #{dest.uri}"
44
45
 
45
- # Note that the stats variables are being accessed without synchronization
46
- # in the following threads. This is currently safe because the threads
47
- # never access the same variables. If we end up adding additional threads,
48
- # these accesses need to be synchronized.
46
+ source_scan = Thread.new do
47
+ source.scan_mailbox
48
+ end
49
+
50
+ dest_scan = Thread.new do
51
+ dest.scan_mailbox
52
+ end
49
53
 
50
- source_thread = Thread.new do
54
+ source_scan.join
55
+ dest_scan.join
56
+
57
+ source_copy = Thread.new do
51
58
  begin
52
- @total = source.length
59
+ mutex.synchronize { @total = source.length }
53
60
 
54
61
  source.each do |id|
55
62
  next if dest.has_message?(id)
56
- msgq << source.peek(id)
63
+
64
+ begin
65
+ msgq << source.peek(id)
66
+ rescue Larch::IMAP::Error => e
67
+ mutex.synchronize { @failed += 1 }
68
+ next
69
+ end
57
70
  end
58
71
 
59
72
  rescue => e
@@ -64,29 +77,35 @@ module Larch
64
77
  end
65
78
  end
66
79
 
67
- dest_thread = Thread.new do
80
+ dest_copy = Thread.new do
68
81
  begin
69
82
  while msg = msgq.pop do
70
83
  break if msg == :finished
71
84
 
72
- from = msg.envelope.from.first
73
- @log.info "copying message: #{from.mailbox}@#{from.host} - #{msg.envelope.subject}"
85
+ if msg.envelope.from
86
+ env_from = msg.envelope.from.first
87
+ from = "#{env_from.mailbox}@#{env_from.host}"
88
+ else
89
+ from = '?'
90
+ end
91
+
92
+ @log.info "copying message: #{from} - #{msg.envelope.subject}"
74
93
  dest << msg
75
94
 
76
- @copied += 1
95
+ mutex.synchronize { @copied += 1 }
77
96
  end
78
97
 
79
98
  rescue IMAP::FatalError => e
80
99
  @log.fatal e.message
81
100
 
82
101
  rescue => e
83
- @failed += 1
102
+ mutex.synchronize { @failed += 1 }
84
103
  @log.error e.message
85
104
  retry
86
105
  end
87
106
  end
88
107
 
89
- dest_thread.join
108
+ dest_copy.join
90
109
 
91
110
  source.disconnect
92
111
  dest.disconnect
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: 0.0.1.3
4
+ version: 0.0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Grove