rgrove-larch 1.0.0 → 1.0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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