rgrove-larch 1.0.0 → 1.0.0.1

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/lib/larch/imap.rb +91 -62
  2. data/lib/larch.rb +7 -8
  3. metadata +2 -3
  4. data/lib/larch/util.rb +0 -17
data/lib/larch/imap.rb CHANGED
@@ -6,6 +6,10 @@ module Larch
6
6
  # required reading if you're doing anything with IMAP in Ruby:
7
7
  # http://sup.rubyforge.org
8
8
  class IMAP
9
+ include MonitorMixin
10
+
11
+ # Maximum number of messages to fetch at once.
12
+ MAX_FETCH_COUNT = 1024
9
13
 
10
14
  # Recoverable connection errors.
11
15
  RECOVERABLE_ERRORS = [
@@ -52,6 +56,8 @@ class IMAP
52
56
  # times. Default is 3.
53
57
  #
54
58
  def initialize(uri, username, password, options = {})
59
+ super()
60
+
55
61
  raise ArgumentError, "not an IMAP URI: #{uri}" unless uri.is_a?(URI) || uri =~ REGEX_URI
56
62
  raise ArgumentError, "must provide a username and password" unless username && password
57
63
  raise ArgumentError, "options must be a Hash" unless options.is_a?(Hash)
@@ -66,7 +72,6 @@ class IMAP
66
72
  @last_id = 0
67
73
  @last_scan = nil
68
74
  @message_ids = nil
69
- @mutex = Mutex.new
70
75
 
71
76
  # Create private convenience methods (debug, info, warn, etc.) to make
72
77
  # logging easier.
@@ -89,18 +94,16 @@ class IMAP
89
94
  return false if has_message?(message)
90
95
 
91
96
  safely do
92
- @mutex.synchronize do
93
- begin
94
- @imap.select(mailbox)
95
- rescue Net::IMAP::NoResponseError => e
96
- if @options[:create_mailbox]
97
- info "creating mailbox: #{mailbox}"
98
- @imap.create(mailbox)
99
- retry
100
- end
101
-
102
- raise
97
+ begin
98
+ @imap.select(mailbox)
99
+ rescue Net::IMAP::NoResponseError => e
100
+ if @options[:create_mailbox]
101
+ info "creating mailbox: #{mailbox}"
102
+ @imap.create(mailbox)
103
+ retry
103
104
  end
105
+
106
+ raise
104
107
  end
105
108
 
106
109
  debug "appending message: #{message.id}"
@@ -122,20 +125,19 @@ class IMAP
122
125
  def disconnect
123
126
  return unless @imap
124
127
 
125
- @imap.disconnect
126
- @imap = nil
128
+ synchronize do
129
+ @imap.disconnect
130
+ @imap = nil
131
+ end
127
132
 
128
133
  info "disconnected"
129
134
  end
130
- synchronized :disconnect
131
135
 
132
136
  # Iterates through Larch message ids in this mailbox, yielding each one to the
133
137
  # provided block.
134
138
  def each
135
- ids = @mutex.synchronize do
136
- unsync_scan_mailbox
137
- @ids
138
- end
139
+ scan_mailbox
140
+ ids = @ids
139
141
 
140
142
  ids.each_key {|id| yield id }
141
143
  end
@@ -204,51 +206,52 @@ class IMAP
204
206
 
205
207
  # Fetches message headers from the current mailbox.
206
208
  def scan_mailbox
207
- return if @last_scan && (Time.now - @last_scan) < SCAN_INTERVAL
209
+ synchronize do
210
+ return if @last_scan && (Time.now - @last_scan) < SCAN_INTERVAL
208
211
 
209
- last_id = safely do
210
- begin
211
- @imap.examine(mailbox)
212
- rescue Net::IMAP::NoResponseError => e
213
- return if @options[:create_mailbox]
214
- raise FatalError, "unable to open mailbox: #{e.message}"
212
+ last_id = safely do
213
+ begin
214
+ @imap.examine(mailbox)
215
+ rescue Net::IMAP::NoResponseError => e
216
+ return if @options[:create_mailbox]
217
+ raise FatalError, "unable to open mailbox: #{e.message}"
218
+ end
219
+
220
+ @imap.responses['EXISTS'].last
215
221
  end
216
222
 
217
- @imap.responses['EXISTS'].last
218
- end
223
+ @last_scan = Time.now
224
+ return if last_id == @last_id
219
225
 
220
- @last_scan = Time.now
221
- return if last_id == @last_id
226
+ range = (@last_id + 1)..last_id
227
+ @last_id = last_id
222
228
 
223
- range = (@last_id + 1)..last_id
224
- @last_id = last_id
229
+ info "fetching message headers #{range}" <<
230
+ (@options[:fast_scan] ? ' (fast scan)' : '')
225
231
 
226
- info "fetching message headers #{range}" <<
227
- (@options[:fast_scan] ? ' (fast scan)' : '')
232
+ fields = if @options[:fast_scan]
233
+ ['UID', 'RFC822.SIZE', 'INTERNALDATE']
234
+ else
235
+ "(UID BODY.PEEK[HEADER.FIELDS (MESSAGE-ID)] RFC822.SIZE INTERNALDATE)"
236
+ end
228
237
 
229
- fields = if @options[:fast_scan]
230
- ['UID', 'RFC822.SIZE', 'INTERNALDATE']
231
- else
232
- "(UID BODY.PEEK[HEADER.FIELDS (MESSAGE-ID)] RFC822.SIZE INTERNALDATE)"
233
- end
238
+ imap_fetch(range, fields).each do |data|
239
+ id = create_id(data)
234
240
 
235
- imap_fetch(range, fields).each do |data|
236
- id = create_id(data)
241
+ unless uid = data.attr['UID']
242
+ error "UID not in IMAP response for message: #{id}"
243
+ next
244
+ end
237
245
 
238
- unless uid = data.attr['UID']
239
- error "UID not in IMAP response for message: #{id}"
240
- next
241
- end
246
+ if @ids.has_key?(id) && Larch.log.level == :debug
247
+ envelope = imap_uid_fetch([uid], 'ENVELOPE').first.attr['ENVELOPE']
248
+ debug "duplicate message? #{id} (Subject: #{envelope.subject})"
249
+ end
242
250
 
243
- if @ids.has_key?(id) && Larch.log.level == :debug
244
- envelope = imap_uid_fetch([uid], 'ENVELOPE').first.attr['ENVELOPE']
245
- debug "duplicate message? #{id} (Subject: #{envelope.subject})"
251
+ @ids[id] = uid
246
252
  end
247
-
248
- @ids[id] = uid
249
253
  end
250
254
  end
251
- synchronized :scan_mailbox
252
255
 
253
256
  # Gets the SSL status.
254
257
  def ssl?
@@ -284,7 +287,19 @@ class IMAP
284
287
  # Fetches the specified _fields_ for the specified message sequence id(s) from
285
288
  # the IMAP server.
286
289
  def imap_fetch(ids, fields)
287
- data = safely { @imap.fetch(ids, fields) }
290
+ ids = ids.to_a
291
+
292
+ data = safely do
293
+ pos = 0
294
+ results = []
295
+
296
+ while pos < ids.length
297
+ results += @imap.fetch(ids[pos, MAX_FETCH_COUNT], fields)
298
+ pos += MAX_FETCH_COUNT
299
+ end
300
+
301
+ results
302
+ end
288
303
 
289
304
  # If fields isn't an array, make it one.
290
305
  fields = REGEX_FIELDS.match(fields).captures unless fields.is_a?(Array)
@@ -309,7 +324,19 @@ class IMAP
309
324
  # Fetches the specified _fields_ for the specified UID(s) from the IMAP
310
325
  # server.
311
326
  def imap_uid_fetch(uids, fields)
312
- data = safely { @imap.uid_fetch(uids, fields) }
327
+ uids = uids.to_a
328
+
329
+ data = safely do
330
+ pos = 0
331
+ results = []
332
+
333
+ while pos < uids.length
334
+ results += @imap.uid_fetch(uids[pos, MAX_FETCH_COUNT], fields)
335
+ pos += MAX_FETCH_COUNT
336
+ end
337
+
338
+ results
339
+ end
313
340
 
314
341
  # If fields isn't an array, make it one.
315
342
  fields = REGEX_FIELDS.match(fields).captures unless fields.is_a?(Array)
@@ -337,15 +364,17 @@ class IMAP
337
364
  def safely
338
365
  retries = 0
339
366
 
340
- begin
341
- unsafe_connect unless @imap
342
- rescue *RECOVERABLE_ERRORS => e
343
- info "#{e.class.name}: #{e.message} (will retry)"
344
- raise unless (retries += 1) <= @options[:max_retries]
345
-
346
- @imap = nil
347
- sleep 1 * retries
348
- retry
367
+ synchronize do
368
+ begin
369
+ unsafe_connect unless @imap
370
+ rescue *RECOVERABLE_ERRORS => e
371
+ info "#{e.class.name}: #{e.message} (will retry)"
372
+ raise unless (retries += 1) <= @options[:max_retries]
373
+
374
+ @imap = nil
375
+ sleep 1 * retries
376
+ retry
377
+ end
349
378
  end
350
379
 
351
380
  retries = 0
data/lib/larch.rb CHANGED
@@ -5,11 +5,10 @@ $:.uniq!
5
5
  require 'cgi'
6
6
  require 'digest/md5'
7
7
  require 'net/imap'
8
- require 'thread'
8
+ require 'monitor'
9
9
  require 'time'
10
10
  require 'uri'
11
11
 
12
- require 'larch/util'
13
12
  require 'larch/errors'
14
13
  require 'larch/imap'
15
14
  require 'larch/logger'
@@ -23,9 +22,9 @@ module Larch
23
22
  def init(log_level = :info)
24
23
  @log = Logger.new(log_level)
25
24
 
26
- @copied = 0
27
- @failed = 0
28
- @total = 0
25
+ @copied = 0
26
+ @failed = 0
27
+ @total = 0
29
28
  end
30
29
 
31
30
  # Copies messages from _source_ to _dest_ if they don't already exist in
@@ -37,9 +36,9 @@ module Larch
37
36
  msgq = SizedQueue.new(8)
38
37
  mutex = Mutex.new
39
38
 
40
- @copied = 0
41
- @failed = 0
42
- @total = 0
39
+ @copied = 0
40
+ @failed = 0
41
+ @total = 0
43
42
 
44
43
  @log.info "copying messages from #{source.uri} to #{dest.uri}"
45
44
 
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
4
+ version: 1.0.0.1
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-02-25 00:00:00 -08:00
12
+ date: 2009-03-21 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -47,7 +47,6 @@ files:
47
47
  - lib/larch/errors.rb
48
48
  - lib/larch/imap.rb
49
49
  - lib/larch/logger.rb
50
- - lib/larch/util.rb
51
50
  - lib/larch/version.rb
52
51
  has_rdoc: false
53
52
  homepage: http://github.com/rgrove/larch/
data/lib/larch/util.rb DELETED
@@ -1,17 +0,0 @@
1
- class Module
2
-
3
- # Java-style whole-method synchronization, shamelessly stolen from Sup:
4
- # http://sup.rubyforge.org. Assumes the existence of a <tt>@mutex</tt>
5
- # variable.
6
- def synchronized(*methods)
7
- methods.each do |method|
8
- class_eval <<-EOF
9
- alias unsync_#{method} #{method}
10
- def #{method}(*a, &b)
11
- @mutex.synchronize { unsync_#{method}(*a, &b) }
12
- end
13
- EOF
14
- end
15
- end
16
-
17
- end