rgrove-larch 0.0.1.4 → 1.0.0

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 CHANGED
@@ -26,14 +26,15 @@ EOS
26
26
 
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
- opt :from_folder, "Source folder to copy from.", :short => :none, :default => 'INBOX'
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
- opt :no_create_folder, "Don't create destination folders that don't already exist.", :short => :none
33
- opt :to_folder, "Destination folder to copy to.", :short => :none, :default => 'INBOX'
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
- opt :verbosity, "Output verbosity: debug, info, warn, error, or fatal.", :short => '-V', :default => 'info'
29
+ opt :from_folder, "Source folder to copy from", :short => :none, :default => 'INBOX'
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
+ opt :max_retries, "Maximum number of times to retry after a recoverable error", :short => :none, :default => 3
33
+ opt :no_create_folder, "Don't create destination folders that don't already exist", :short => :none
34
+ opt :to_folder, "Destination folder to copy to", :short => :none, :default => 'INBOX'
35
+ opt :to_pass, "Destination server password (Default: prompt)", :short => :none, :type => :string
36
+ opt :to_user, "Destination server username (Default: prompt)", :short => :none, :type => :string
37
+ opt :verbosity, "Output verbosity: debug, info, warn, error, or fatal", :short => '-V', :default => 'info'
37
38
  end
38
39
 
39
40
  # Validate command-line options.
@@ -74,11 +75,13 @@ EOS
74
75
  init(options[:verbosity])
75
76
 
76
77
  source = IMAP.new(options[:from], options[:from_user], options[:from_pass],
77
- :fast_scan => options[:fast_scan])
78
+ :fast_scan => options[:fast_scan],
79
+ :max_retries => options[:max_retries])
78
80
 
79
81
  dest = IMAP.new(options[:to], options[:to_user], options[:to_pass],
80
82
  :create_mailbox => !options[:no_create_folder],
81
- :fast_scan => options[:fast_scan])
83
+ :fast_scan => options[:fast_scan],
84
+ :max_retries => options[:max_retries])
82
85
 
83
86
  for sig in [:SIGINT, :SIGQUIT, :SIGTERM]
84
87
  trap(sig) { @log.fatal "Interrupted (#{sig})"; summary; exit }
data/lib/larch.rb CHANGED
@@ -34,7 +34,7 @@ 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(8)
38
38
  mutex = Mutex.new
39
39
 
40
40
  @copied = 0
@@ -64,7 +64,9 @@ module Larch
64
64
  begin
65
65
  msgq << source.peek(id)
66
66
  rescue Larch::IMAP::Error => e
67
+ # TODO: Keep failed message envelopes in a buffer for later output?
67
68
  mutex.synchronize { @failed += 1 }
69
+ @log.error e.message
68
70
  next
69
71
  end
70
72
  end
data/lib/larch/imap.rb CHANGED
@@ -4,7 +4,7 @@ module Larch
4
4
  #
5
5
  # This class borrows heavily from Sup, the source code of which should be
6
6
  # required reading if you're doing anything with IMAP in Ruby:
7
- # http://sup.rubyforge.org/
7
+ # http://sup.rubyforge.org
8
8
  class IMAP
9
9
 
10
10
  # Recoverable connection errors.
@@ -36,17 +36,20 @@ class IMAP
36
36
  #
37
37
  # The following options may also be specified:
38
38
  #
39
- # [+create_mailbox+]
39
+ # [:create_mailbox]
40
40
  # If +true+, the specified mailbox will be created if necessary.
41
41
  #
42
- # [+fast_scan+]
42
+ # [:fast_scan]
43
43
  # If +true+, a faster but less accurate method will be used to scan
44
44
  # mailboxes. This will speed up the initial mailbox scan, but will also
45
- # reduce the effectiveness of the message unique id generator.
45
+ # reduce the effectiveness of the message unique id generator. This is
46
+ # probably acceptable when copying a very large mailbox to an empty mailbox,
47
+ # but if the destination already contains messages, using this option is not
48
+ # advised.
46
49
  #
47
- # This is probably acceptable when copying a very large mailbox to an empty
48
- # mailbox, but if the destination already contains messages, using this
49
- # option is not advised.
50
+ # [:max_retries]
51
+ # After a recoverable error occurs, retry the operation up to this many
52
+ # times. Default is 3.
50
53
  #
51
54
  def initialize(uri, username, password, options = {})
52
55
  raise ArgumentError, "not an IMAP URI: #{uri}" unless uri.is_a?(URI) || uri =~ REGEX_URI
@@ -56,7 +59,7 @@ class IMAP
56
59
  @uri = uri.is_a?(URI) ? uri : URI(uri)
57
60
  @username = username
58
61
  @password = password
59
- @options = options
62
+ @options = {:max_retries => 3}.merge(options)
60
63
 
61
64
  @ids = {}
62
65
  @imap = nil
@@ -148,7 +151,7 @@ class IMAP
148
151
  imap_uid_fetch([uid], 'ENVELOPE').first.attr['ENVELOPE']
149
152
  end
150
153
 
151
- # Fetches a Larch::Message instance representing the message with the
154
+ # Fetches a Larch::IMAP::Message struct representing the message with the
152
155
  # specified Larch message id.
153
156
  def fetch(message_id, peek = false)
154
157
  scan_mailbox
@@ -171,6 +174,7 @@ class IMAP
171
174
  @ids.has_key?(message_id)
172
175
  end
173
176
 
177
+ # Gets the IMAP hostname.
174
178
  def host
175
179
  @uri.host
176
180
  end
@@ -182,20 +186,22 @@ class IMAP
182
186
  end
183
187
  alias size length
184
188
 
189
+ # Gets the IMAP mailbox.
190
+ def mailbox
191
+ mb = @uri.path[1..-1]
192
+ mb.nil? || mb.empty? ? 'INBOX' : CGI.unescape(mb)
193
+ end
194
+
185
195
  # Same as fetch, but doesn't mark the message as seen.
186
196
  def peek(message_id)
187
197
  fetch(message_id, true)
188
198
  end
189
199
 
200
+ # Gets the IMAP port number.
190
201
  def port
191
202
  @uri.port || (ssl? ? 993 : 143)
192
203
  end
193
204
 
194
- def mailbox
195
- mb = @uri.path[1..-1]
196
- mb.nil? || mb.empty? ? 'INBOX' : CGI.unescape(mb)
197
- end
198
-
199
205
  # Fetches message headers from the current mailbox.
200
206
  def scan_mailbox
201
207
  return if @last_scan && (Time.now - @last_scan) < SCAN_INTERVAL
@@ -244,10 +250,12 @@ class IMAP
244
250
  end
245
251
  synchronized :scan_mailbox
246
252
 
253
+ # Gets the SSL status.
247
254
  def ssl?
248
255
  @uri.scheme == 'imaps'
249
256
  end
250
257
 
258
+ # Gets the IMAP URI.
251
259
  def uri
252
260
  @uri.to_s
253
261
  end
@@ -333,10 +341,10 @@ class IMAP
333
341
  unsafe_connect unless @imap
334
342
  rescue *RECOVERABLE_ERRORS => e
335
343
  info "#{e.class.name}: #{e.message} (will retry)"
336
- raise unless (retries += 1) <= 3
344
+ raise unless (retries += 1) <= @options[:max_retries]
337
345
 
338
346
  @imap = nil
339
- sleep 2 * retries
347
+ sleep 1 * retries
340
348
  retry
341
349
  end
342
350
 
@@ -346,9 +354,9 @@ class IMAP
346
354
  yield
347
355
  rescue *RECOVERABLE_ERRORS => e
348
356
  info "#{e.class.name}: #{e.message} (will retry)"
349
- raise unless (retries += 1) <= 3
357
+ raise unless (retries += 1) <= @options[:max_retries]
350
358
 
351
- sleep 2 * retries
359
+ sleep 1 * retries
352
360
  retry
353
361
  end
354
362
 
data/lib/larch/util.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  class Module
2
2
 
3
3
  # Java-style whole-method synchronization, shamelessly stolen from Sup:
4
- # http://sup.rubyforge.org/. Assumes the existence of a @mutex variable.
4
+ # http://sup.rubyforge.org. Assumes the existence of a <tt>@mutex</tt>
5
+ # variable.
5
6
  def synchronized(*methods)
6
7
  methods.each do |method|
7
8
  class_eval <<-EOF
data/lib/larch/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Larch
2
2
  APP_NAME = 'Larch'
3
- APP_VERSION = '0.0.1'
3
+ APP_VERSION = '1.0.0'
4
4
  APP_AUTHOR = 'Ryan Grove'
5
5
  APP_EMAIL = 'ryan@wonko.com'
6
6
  APP_URL = 'http://github.com/rgrove/larch/'
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.4
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Grove
@@ -30,7 +30,7 @@ dependencies:
30
30
  requirements:
31
31
  - - ~>
32
32
  - !ruby/object:Gem::Version
33
- version: "1.12"
33
+ version: "1.13"
34
34
  version:
35
35
  description:
36
36
  email: ryan@wonko.com