imap_processor 1.1.1 → 1.2

Sign up to get free protection for your applications and to get access to all the features.
data.tar.gz.sig CHANGED
Binary file
data/.autotest CHANGED
@@ -2,22 +2,7 @@
2
2
 
3
3
  require 'autotest/restart'
4
4
 
5
- # Autotest.add_hook :initialize do |at|
6
- # at.extra_files << "../some/external/dependency.rb"
7
- #
8
- # at.libs << ":../some/external"
9
- #
10
- # at.add_exception 'vendor'
11
- #
12
- # at.add_mapping(/dependency.rb/) do |f, _|
13
- # at.files_matching(/test_.*rb$/)
14
- # end
15
- #
16
- # %w(TestA TestB).each do |klass|
17
- # at.extra_class_map[klass] = "test/test_misc.rb"
18
- # end
19
- # end
5
+ Autotest.add_hook :initialize do |at|
6
+ at.testlib = 'minitest/autorun'
7
+ end
20
8
 
21
- # Autotest.add_hook :run_command do |at|
22
- # system "rake build"
23
- # end
data/History.txt CHANGED
@@ -1,3 +1,18 @@
1
+ === 1.2 / 2009-06-02
2
+
3
+ * 2 major enhancements
4
+ * imap_archive which archives old mail to dated mailboxes
5
+ * imap_idle which lists messages that were added or expunged from a mailbox
6
+
7
+ * 4 minor enhancements
8
+ * Added IMAPProcessor#create_mailbox
9
+ * Added IMAPProcessor#delete_messages
10
+ * Added IMAPProcessor#move_messages
11
+ * Disabled verification of SSL certs for 1.9
12
+
13
+ * 1 bug fix
14
+ * Fixed options file names, they should be Symbol keys
15
+
1
16
  === 1.1.1 / 2009-05-19
2
17
 
3
18
  * 1 bug fix
data/Manifest.txt CHANGED
@@ -3,7 +3,14 @@ History.txt
3
3
  Manifest.txt
4
4
  README.txt
5
5
  Rakefile
6
+ bin/imap_archive
7
+ bin/imap_idle
6
8
  bin/imap_keywords
7
9
  lib/imap_processor.rb
10
+ lib/imap_processor/archive.rb
11
+ lib/imap_processor/idle.rb
8
12
  lib/imap_processor/keywords.rb
9
13
  lib/imap_sasl_plain.rb
14
+ lib/net/imap/date.rb
15
+ lib/net/imap/idle.rb
16
+ test/test_imap_processor.rb
data/README.txt CHANGED
@@ -8,8 +8,10 @@ IMAPProcessor is a client for processing messages on an IMAP server. It
8
8
  provides some basic mechanisms for connecting to an IMAP server, determining
9
9
  capabilities and handling messages.
10
10
 
11
- IMAPProcessor ships with the imap_keywords executable which can query an IMAP
12
- server for keywords set on messages in mailboxes.
11
+ IMAPProcessor ships with the executables imap_keywords which can query an IMAP
12
+ server for keywords set on messages in mailboxes, imap_idle which can show new
13
+ messages in a mailbox and imap_archive which will archive old messages to a
14
+ new mailbox.
13
15
 
14
16
  == FEATURES/PROBLEMS:
15
17
 
data/Rakefile CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  require 'rubygems'
4
4
  require 'hoe'
5
- $:.unshift 'lib'
6
- require 'imap_processor'
7
5
 
8
- Hoe.new 'imap_processor', IMAPProcessor::VERSION do |ip|
6
+ Hoe.plugin :seattlerb
7
+
8
+ Hoe.spec 'imap_processor' do |ip|
9
9
  ip.rubyforge_name = 'seattlerb'
10
- ip.developer('Eric Hodel', 'drbrain@segment7.net')
10
+ ip.developer 'Eric Hodel', 'drbrain@segment7.net'
11
11
  end
12
12
 
13
13
  # vim: syntax=Ruby
data/bin/imap_archive ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'imap_processor/archive'
4
+
5
+ IMAPProcessor::Archive.run ARGV
data/bin/imap_idle ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'imap_processor/idle'
4
+
5
+ IMAPProcessor::IDLE.run ARGV
6
+
@@ -1,6 +1,7 @@
1
1
  require 'rubygems'
2
2
  require 'optparse'
3
3
  require 'net/imap'
4
+ require 'net/imap/date'
4
5
  require 'imap_sasl_plain'
5
6
 
6
7
  ##
@@ -19,7 +20,13 @@ class IMAPProcessor
19
20
  ##
20
21
  # The version of IMAPProcessor you are using
21
22
 
22
- VERSION = '1.1.1'
23
+ VERSION = '1.2'
24
+
25
+ ##
26
+ # Base IMAPProcessor error class
27
+
28
+ class Error < RuntimeError
29
+ end
23
30
 
24
31
  ##
25
32
  # A Connection Struct that has +imap+ and +capability+ accessors
@@ -67,7 +74,7 @@ class IMAPProcessor
67
74
  opts.on( "--move=MAILBOX",
68
75
  "Mailbox to move message to",
69
76
  "Default: #{options[:MoveTo].inspect}",
70
- "Options file name: MoveTo") do |mailbox|
77
+ "Options file name: :MoveTo") do |mailbox|
71
78
  options[:MoveTo] = mailbox
72
79
  end
73
80
  end
@@ -91,15 +98,18 @@ class IMAPProcessor
91
98
  # opts.on( "--move=MAILBOX",
92
99
  # "Mailbox to move message to",
93
100
  # "Default: #{options[:MoveTo].inspect}",
94
- # "Options file name: MoveTo") do |mailbox|
101
+ # "Options file name: :MoveTo") do |mailbox|
95
102
  # options[:MoveTo] = mailbox
96
103
  # end
97
104
  # end
98
105
  # end
106
+ #
107
+ # NOTE: You can add a --move option using ::add_move
99
108
 
100
109
  def self.process_args(processor_file, args,
101
110
  required_options = {}) # :yield: OptionParser
102
111
  opts_file_name = File.basename processor_file, '.rb'
112
+ opts_file_name = "imap_#{opts_file_name}" unless opts_file_name =~ /^imap_/
103
113
  opts_file = File.expand_path "~/.#{opts_file_name}"
104
114
  options = @@options.dup
105
115
 
@@ -131,7 +141,7 @@ class IMAPProcessor
131
141
  options[k] ||= v
132
142
  end
133
143
 
134
- opts = OptionParser.new do |opts|
144
+ op = OptionParser.new do |opts|
135
145
  opts.program_name = File.basename $0
136
146
  opts.banner = "Usage: #{opts.program_name} [options]\n\n"
137
147
 
@@ -141,28 +151,28 @@ class IMAPProcessor
141
151
  opts.on("-H", "--host HOST",
142
152
  "IMAP server host",
143
153
  "Default: #{options[:Host].inspect}",
144
- "Options file name: Host") do |host|
154
+ "Options file name: :Host") do |host|
145
155
  options[:Host] = host
146
156
  end
147
157
 
148
158
  opts.on("-P", "--port PORT",
149
159
  "IMAP server port",
150
160
  "Default: The correct port SSL/non-SSL mode",
151
- "Options file name: Port") do |port|
161
+ "Options file name: :Port") do |port|
152
162
  options[:Port] = port
153
163
  end
154
164
 
155
165
  opts.on("-s", "--[no-]ssl",
156
166
  "Use SSL for IMAP connection",
157
167
  "Default: #{options[:SSL].inspect}",
158
- "Options file name: SSL") do |ssl|
168
+ "Options file name: :SSL") do |ssl|
159
169
  options[:SSL] = ssl
160
170
  end
161
171
 
162
172
  opts.on( "--[no-]debug",
163
173
  "Display Net::IMAP debugging info",
164
174
  "Default: #{options[:Debug].inspect}",
165
- "Options file name: Debug") do |debug|
175
+ "Options file name: :Debug") do |debug|
166
176
  options[:Debug] = debug
167
177
  end
168
178
 
@@ -172,14 +182,14 @@ class IMAPProcessor
172
182
  opts.on("-u", "--username USERNAME",
173
183
  "IMAP username",
174
184
  "Default: #{options[:Username].inspect}",
175
- "Options file name: Username") do |username|
185
+ "Options file name: :Username") do |username|
176
186
  options[:Username] = username
177
187
  end
178
188
 
179
189
  opts.on("-p", "--password PASSWORD",
180
190
  "IMAP password",
181
191
  "Default: Read from ~/.#{opts_file_name}",
182
- "Options file name: Password") do |password|
192
+ "Options file name: :Password") do |password|
183
193
  options[:Password] = password
184
194
  end
185
195
 
@@ -190,7 +200,7 @@ class IMAPProcessor
190
200
  "Authentication type will be auto-",
191
201
  "discovered",
192
202
  "Default: #{options[:Auth].inspect}",
193
- "Options file name: Auth") do |auth|
203
+ "Options file name: :Auth") do |auth|
194
204
  options[:Auth] = auth
195
205
  end
196
206
 
@@ -200,7 +210,7 @@ class IMAPProcessor
200
210
  opts.on("-r", "--root ROOT",
201
211
  "Root of mailbox hierarchy",
202
212
  "Default: #{options[:Root].inspect}",
203
- "Options file name: Root") do |root|
213
+ "Options file name: :Root") do |root|
204
214
  options[:Root] = root
205
215
  end
206
216
 
@@ -208,14 +218,14 @@ class IMAPProcessor
208
218
  "Comma-separated list of mailbox names",
209
219
  "to search",
210
220
  "Default: #{options[:Boxes].inspect}",
211
- "Options file name: Boxes") do |boxes|
221
+ "Options file name: :Boxes") do |boxes|
212
222
  options[:Boxes] = boxes
213
223
  end
214
224
 
215
225
  opts.on("-v", "--[no-]verbose",
216
226
  "Be verbose",
217
227
  "Default: #{options[:Verbose].inspect}",
218
- "Options file name: Verbose") do |verbose|
228
+ "Options file name: :Verbose") do |verbose|
219
229
  options[:Verbose] = verbose
220
230
  end
221
231
 
@@ -248,7 +258,7 @@ Example ~/.#{opts_file_name}:
248
258
  EOF
249
259
  end
250
260
 
251
- opts.parse! args
261
+ op.parse! args
252
262
 
253
263
  options[:Port] ||= options[:SSL] ? 993 : 143
254
264
 
@@ -306,8 +316,13 @@ Example ~/.#{opts_file_name}:
306
316
  #
307
317
  # Returns a Connection object.
308
318
 
309
- def connect(host, port, ssl, username, password, auth = nil)
310
- imap = Net::IMAP.new host, port, ssl
319
+ def connect(host = @options[:Host],
320
+ port = @options[:Port],
321
+ ssl = @options[:SSL],
322
+ username = @options[:Username],
323
+ password = @options[:Password],
324
+ auth = @options[:Auth]) # :yields: Connection
325
+ imap = Net::IMAP.new host, port, ssl, nil, false
311
326
  log "Connected to imap://#{host}:#{port}/"
312
327
 
313
328
  capability = imap.capability
@@ -326,7 +341,41 @@ Example ~/.#{opts_file_name}:
326
341
  imap.authenticate auth, username, password
327
342
  log "Logged in as #{username}"
328
343
 
329
- Connection.new imap, capability
344
+ connection = Connection.new imap, capability
345
+
346
+ if block_given? then
347
+ begin
348
+ yield connection
349
+ ensure
350
+ connection.imap.logout
351
+ end
352
+ else
353
+ return connection
354
+ end
355
+ end
356
+
357
+ ##
358
+ # Create the mailbox +name+ if it doesn't exist. Note that this will SELECT
359
+ # the mailbox if it exists.
360
+
361
+ def create_mailbox name
362
+ log "LIST #{name}"
363
+ list = imap.list '', name
364
+ return if list
365
+ log "CREATE #{name}"
366
+ imap.create name
367
+ end
368
+
369
+ ##
370
+ # Delete and +expunge+ the specified +uids+.
371
+
372
+ def delete_messages uids, expunge = true
373
+ log "DELETING [...#{uids.size} uids]"
374
+ imap.store uids, '+FLAGS.SILENT', [:Deleted]
375
+ if expunge then
376
+ log "EXPUNGE"
377
+ imap.expunge
378
+ end
330
379
  end
331
380
 
332
381
  ##
@@ -379,7 +428,7 @@ Example ~/.#{opts_file_name}:
379
428
  sequence.unshift "BODY[#{section}.MIME]" unless section == 'TEXT'
380
429
  sequence.unshift 'BODY[HEADER]' if header
381
430
 
382
- body = @imap.fetch(uid, sequence).first
431
+ body = imap.fetch(uid, sequence).first
383
432
 
384
433
  sequence = sequence.map { |item| body.attr[item] }
385
434
 
@@ -411,7 +460,7 @@ Example ~/.#{opts_file_name}:
411
460
  def mime_parts(uids, mime_type)
412
461
  media_type, subtype = mime_type.upcase.split('/', 2)
413
462
 
414
- structures = @imap.fetch uids, 'BODYSTRUCTURE'
463
+ structures = imap.fetch uids, 'BODYSTRUCTURE'
415
464
 
416
465
  structures.zip(uids).map do |body, uid|
417
466
  section = nil
@@ -436,6 +485,41 @@ Example ~/.#{opts_file_name}:
436
485
  end.compact
437
486
  end
438
487
 
488
+ ##
489
+ # Move the specified +uids+ to a new +destination+ then delete and +expunge+
490
+ # them. Creates the destination mailbox if it doesn't exist.
491
+
492
+ def move_messages uids, destination, expunge = true
493
+ return if uids.empty?
494
+ log "COPY [...#{uids.size} uids]"
495
+
496
+ begin
497
+ imap.copy uids, destination
498
+ rescue Net::IMAP::NoResponseError => e
499
+ # ruby-lang bug #1713
500
+ #raise unless e.response.data.code.name == 'TRYCREATE'
501
+ create_mailbox destination
502
+ imap.copy uids, destination
503
+ end
504
+
505
+ delete_messages uids, expunge
506
+ end
507
+
508
+ ##
509
+ # Displays Date, Subject and Message-Id from messages in +uids+
510
+
511
+ def show_messages(uids)
512
+ return if uids.nil? or (Array === uids and uids.empty?)
513
+
514
+ fetch_data = 'BODY.PEEK[HEADER.FIELDS (DATE SUBJECT MESSAGE-ID)]'
515
+ messages = imap.fetch uids, fetch_data
516
+ fetch_data.sub! '.PEEK', '' # stripped by server
517
+
518
+ messages.each do |res|
519
+ puts res.attr[fetch_data].delete("\r")
520
+ end
521
+ end
522
+
439
523
  ##
440
524
  # Did the user set --verbose?
441
525
 
@@ -0,0 +1,81 @@
1
+ require 'imap_processor'
2
+
3
+ ##
4
+ # Archives old mail on IMAP server by moving it to dated mailboxen.
5
+
6
+ class IMAPProcessor::Archive < IMAPProcessor
7
+ attr_reader :list, :move
8
+
9
+ def self.process_args(args)
10
+ required_options = {
11
+ :List => true,
12
+ :Move => false,
13
+ }
14
+
15
+ super __FILE__, args, required_options do |opts, options|
16
+ opts.banner << <<-EOF
17
+ imap_archive archives old mail on IMAP server by moving it to dated mailboxen.
18
+ EOF
19
+
20
+ opts.on("--[no-]list", "Display messages (on by default)") do |list|
21
+ options[:List] = list
22
+ end
23
+
24
+ opts.on("--[no-]move", "Move the messages (off by default)") do |move|
25
+ options[:Move] = move
26
+ end
27
+ end
28
+ end
29
+
30
+ def initialize(options)
31
+ super
32
+
33
+ @list = options[:List]
34
+ @move = options[:Move]
35
+
36
+ connection = connect
37
+
38
+ @imap = connection.imap
39
+ end
40
+
41
+ def the_first
42
+ t = Time.now
43
+ the_first = Time.local(t.year, t.month, 1)
44
+ end
45
+
46
+ def last_month
47
+ t = the_first - 1
48
+ Time.local(t.year, t.month, 1).strftime("%Y-%m")
49
+ end
50
+
51
+ ##
52
+ # Makes a SEARCH argument set from +keywords+
53
+
54
+ def make_search
55
+ %W[SENTBEFORE #{the_first.imapdate}]
56
+ end
57
+
58
+ def run
59
+ @boxes.each do |mailbox|
60
+ destination = "#{mailbox}.#{last_month}"
61
+
62
+ log "SELECT #{mailbox}"
63
+ response = imap.select mailbox
64
+
65
+ search = make_search
66
+
67
+ log "SEARCH #{search.join ' '}"
68
+ uids = imap.search search
69
+
70
+ next if uids.empty?
71
+
72
+ puts "#{uids.length} messages in #{mailbox}#{list ? ':' : ''}"
73
+
74
+ show_messages uids
75
+
76
+ move_messages uids, destination if move
77
+ end
78
+ end
79
+
80
+ end
81
+
@@ -0,0 +1,76 @@
1
+ require 'imap_processor'
2
+ require 'net/imap/idle'
3
+
4
+ ##
5
+ # Example class that supports IDLE on a mailbox and lists messages added or
6
+ # expunged.
7
+
8
+ class IMAPProcessor::IDLE < IMAPProcessor
9
+
10
+ def self.process_args(args)
11
+ super __FILE__, args do |opts, options|
12
+ opts.banner << <<-EOF
13
+ imap_idle lists messages added or expunged from a mailbox
14
+ EOF
15
+ end
16
+ end
17
+
18
+ def initialize(options)
19
+ super
20
+
21
+ raise IMAPProcessor::Error, 'only one mailbox is supported' if
22
+ @boxes.length > 1
23
+ end
24
+
25
+ def run
26
+ mailbox = @boxes.first
27
+
28
+ connect do |connection|
29
+ raise IMAPProcessor::Error, 'IDLE not supported on this server' unless
30
+ connection.idle?
31
+
32
+ imap = connection.imap
33
+
34
+ imap.select mailbox
35
+ exists = imap.responses['EXISTS'].first
36
+
37
+ log "Starting IDLE"
38
+
39
+ imap.idle do |response|
40
+ next unless Net::IMAP::UntaggedResponse === response
41
+
42
+ case response.name
43
+ when 'EXPUNGE' then
44
+ puts "Expunged message #{response.data}"
45
+ when 'EXISTS' then
46
+ latest_uid = response.data
47
+ new = latest_uid - exists
48
+ puts "#{new} messages added"
49
+
50
+ show_messages_in mailbox, ((exists + 1)..latest_uid)
51
+
52
+ exists = response.data
53
+ when 'RECENT' then
54
+ puts "#{response.data} recent messages"
55
+ when 'OK' then # ending IDLE
56
+ else
57
+ log "Unhandled untagged response: #{response.name}"
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ def show_messages_in(mailbox, uids)
64
+ connect do |connection|
65
+ @imap = connection.imap
66
+
67
+ @imap.select mailbox
68
+
69
+ show_messages uids
70
+ end
71
+ ensure
72
+ @imap = nil
73
+ end
74
+
75
+ end
76
+
@@ -33,7 +33,7 @@ previously set keywords.
33
33
  end
34
34
 
35
35
  opts.on( "--[no-]list",
36
- "Don't display messages") do |list|
36
+ "Display messages") do |list|
37
37
  options[:List] = list
38
38
  end
39
39
 
@@ -47,11 +47,11 @@ previously set keywords.
47
47
  def initialize(options)
48
48
  super
49
49
 
50
- @add = options[:Add]
51
- @delete = options[:Delete]
50
+ @add = options[:Add]
51
+ @delete = options[:Delete]
52
52
  @keywords = options[:Keywords]
53
- @not = options[:Not] ? 'NOT' : nil
54
- @list = options[:List]
53
+ @not = options[:Not] ? 'NOT' : nil
54
+ @list = options[:List]
55
55
 
56
56
  if @add and @delete then
57
57
  raise OptionParser::InvalidOption, "--add and --delete are exclusive"
@@ -60,8 +60,7 @@ previously set keywords.
60
60
  "--add and --delete require --keywords"
61
61
  end
62
62
 
63
- connection = connect options[:Host], options[:Port], options[:SSL],
64
- options[:Username], options[:Password], options[:Auth]
63
+ connection = connect
65
64
 
66
65
  @imap = connection.imap
67
66
  end
@@ -134,18 +133,14 @@ previously set keywords.
134
133
  return unless @list
135
134
 
136
135
  responses = @imap.fetch uids, [
137
- 'BODY.PEEK[HEADER]',
136
+ Net::IMAP::RawData.new('BODY.PEEK[HEADER.FIELDS (SUBJECT MESSAGE-ID)]'),
138
137
  'FLAGS'
139
138
  ]
140
139
 
141
140
  responses.each do |res|
142
- header = res.attr['BODY[HEADER]']
141
+ header = res.attr['BODY[HEADER.FIELDS (SUBJECT MESSAGE-ID)]']
143
142
 
144
- header =~ /^Subject: (.*)/i
145
- puts "Subject: #{$1}"
146
-
147
- header =~ /^Message-Id: (.*)/i
148
- puts "Message-Id: #{$1}"
143
+ puts header.chomp
149
144
 
150
145
  flags = res.attr['FLAGS'].map { |flag| flag.inspect }.join ', '
151
146
 
@@ -1,29 +1,5 @@
1
- require 'time'
2
1
  require 'net/imap'
3
2
 
4
- class Time
5
-
6
- ##
7
- # Formats this Time as an IMAP-style date.
8
-
9
- def imapdate
10
- strftime '%d-%b-%Y'
11
- end
12
-
13
- ##
14
- # Formats this Time as an IMAP-style datetime.
15
- #
16
- # RFC 2060 doesn't specify the format of its times. Unfortunately it is
17
- # almost but not quite RFC 822 compliant.
18
- #--
19
- # Go Mr. Leatherpants!
20
-
21
- def imapdatetime
22
- strftime '%d-%b-%Y %H:%M %Z'
23
- end
24
-
25
- end
26
-
27
3
  ##
28
4
  # RFC 2595 PLAIN Authenticator for Net::IMAP. Only for use with SSL (but not
29
5
  # enforced).
@@ -56,10 +32,9 @@ class Net::IMAP::PlainAuthenticator
56
32
  @password = password
57
33
  end
58
34
 
59
- end
35
+ end unless defined? Net::IMAP::PlainAuthenticator
60
36
 
61
37
  if defined? OpenSSL then
62
38
  Net::IMAP.add_authenticator 'PLAIN', Net::IMAP::PlainAuthenticator
63
39
  end
64
40
 
65
-
@@ -0,0 +1,24 @@
1
+
2
+ class Time
3
+
4
+ ##
5
+ # Formats this Time as an IMAP-style date.
6
+
7
+ def imapdate
8
+ strftime '%d-%b-%Y'
9
+ end
10
+
11
+ ##
12
+ # Formats this Time as an IMAP-style datetime.
13
+ #
14
+ # RFC 2060 doesn't specify the format of its times. Unfortunately it is
15
+ # almost but not quite RFC 822 compliant.
16
+ #--
17
+ # Go Mr. Leatherpants!
18
+
19
+ def imapdatetime
20
+ strftime '%d-%b-%Y %H:%M %Z'
21
+ end
22
+
23
+ end
24
+
@@ -0,0 +1,39 @@
1
+ require 'net/imap'
2
+
3
+ class Net::IMAP
4
+
5
+ ##
6
+ # Sends an IDLE command that waits for notifications of new or expunged
7
+ # messages. Yields responses from the server during the IDLE.
8
+ #
9
+ # Use +break+ in the response handler to leave IDLE.
10
+
11
+ def idle(&response_handler)
12
+ raise LocalJumpError, "no block given" unless response_handler
13
+
14
+ response = nil
15
+
16
+ synchronize do
17
+ tag = Thread.current[:net_imap_tag] = generate_tag
18
+ put_string "#{tag} IDLE#{CRLF}"
19
+
20
+ add_response_handler response_handler
21
+
22
+ begin
23
+ response = get_tagged_response tag
24
+ rescue LocalJumpError # can't break cross-threads or something
25
+ ensure
26
+ unless response then
27
+ put_string "DONE#{CRLF}"
28
+ response = get_tagged_response tag
29
+ end
30
+
31
+ remove_response_handler response_handler
32
+ end
33
+ end
34
+
35
+ response
36
+ end
37
+
38
+ end
39
+
@@ -0,0 +1,148 @@
1
+ require 'minitest/autorun'
2
+ require 'imap_processor'
3
+ require 'time'
4
+
5
+ ##
6
+ # These tests expect a local IMAP server with a 'test' login with a 'test'
7
+ # password. It's fairly easy to set up dovecot to do this:
8
+ #
9
+ # In dovecot.conf:
10
+ #
11
+ # mail_location = mbox:~/mail:INBOX=~/Mailbox
12
+ #
13
+ # auth default {
14
+ # mechanisms = plain
15
+ #
16
+ # passdb passwd-file {
17
+ # args = scheme=plain username_format=%n /path/to/etc/dovecot/passwd
18
+ # }
19
+ #
20
+ # userdb passwd-file {
21
+ # args = username_format=%n /path/to/etc/dovecot/passwd
22
+ # }
23
+ # }
24
+ #
25
+ # And in /path/to/etc/dovecot/passwd:
26
+ #
27
+ # test:test:<your uid>:<your gid>::/path/to/your/home/dovecot
28
+
29
+ class TestIMAPProcessor < MiniTest::Unit::TestCase
30
+
31
+ def setup
32
+ host, port, username, password = 'localhost', 143, 'test', 'test'
33
+
34
+ @ip = IMAPProcessor.new :Host => host, :Port => port,
35
+ :Username => username, :Password => password,
36
+ :Verbose => false
37
+
38
+ @connection = @ip.connect
39
+
40
+ @imap = @connection.imap
41
+ @ip.instance_variable_set :@imap, @imap
42
+
43
+ @delim = @imap.list('', 'INBOX').first.delim
44
+ end
45
+
46
+ def teardown
47
+ @imap.select 'INBOX'
48
+ uids = @imap.search 'ALL'
49
+ @imap.store uids, '+FLAGS.SILENT', [:Deleted] unless uids.empty?
50
+ @imap.expunge
51
+ @imap.list('', '*').each do |mailbox|
52
+ next if mailbox.name == 'INBOX'
53
+ @imap.delete mailbox.name
54
+ end
55
+ @imap.disconnect
56
+ end
57
+
58
+ # pre-run cleanup
59
+ test = self.new nil
60
+ test.setup
61
+ test.teardown
62
+
63
+ def test_create_mailbox
64
+ @imap.create "directory#{@delim}"
65
+
66
+ assert_equal nil, @ip.create_mailbox('directory')
67
+
68
+ refute_nil @ip.create_mailbox('destination')
69
+ mailbox = @imap.list('', 'destination').first
70
+ assert_equal 'destination', mailbox.name
71
+ assert_equal [:Noinferiors, :Unmarked], mailbox.attr
72
+ end
73
+
74
+ def test_delete_messages
75
+ util_message
76
+ uids = util_uids
77
+
78
+ @ip.delete_messages uids
79
+
80
+ assert_empty util_uids
81
+ end
82
+
83
+ def test_delete_messages_no_expunge
84
+ util_message
85
+ uids = util_uids
86
+
87
+ @ip.delete_messages uids, false
88
+
89
+ uids = util_uids
90
+
91
+ refute_empty uids
92
+ assert_includes :Deleted, @imap.fetch(uids, 'FLAGS').first.attr['FLAGS']
93
+ end
94
+
95
+ def test_move_messages
96
+ util_message
97
+ uids = util_uids
98
+
99
+ @ip.move_messages uids, 'destination'
100
+
101
+ assert_equal 0, @imap.search('ALL').length
102
+
103
+ assert_equal 1, @imap.list('', 'destination').length
104
+ end
105
+
106
+ def test_show_messages
107
+ now = Time.now
108
+ util_message nil, now
109
+ uids = util_uids
110
+
111
+ out, = capture_io do
112
+ @ip.show_messages uids
113
+ end
114
+
115
+ expected = <<-EXPECTED
116
+ Subject: message 1
117
+ Date: #{now.rfc2822}
118
+ Message-Id: 1
119
+
120
+ EXPECTED
121
+
122
+ assert_equal expected, out
123
+ end
124
+
125
+ def util_message(flags = nil, time = Time.now)
126
+ @count ||= 0
127
+ @count += 1
128
+
129
+ message = <<-MESSAGE
130
+ From: from@example.com
131
+ To: to@example.com
132
+ Subject: message #{@count}
133
+ Date: #{time.rfc2822}
134
+ Message-Id: #{@count}
135
+
136
+ Hi, this is message number #{@count}
137
+ MESSAGE
138
+
139
+ @imap.append 'INBOX', message, flags, time
140
+ end
141
+
142
+ def util_uids
143
+ @imap.select 'INBOX'
144
+ @imap.search 'ALL'
145
+ end
146
+
147
+ end
148
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: imap_processor
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: "1.2"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Hodel
@@ -30,7 +30,7 @@ cert_chain:
30
30
  x52qPcexcYZR7w==
31
31
  -----END CERTIFICATE-----
32
32
 
33
- date: 2009-05-19 00:00:00 -07:00
33
+ date: 2009-07-07 00:00:00 -07:00
34
34
  default_executable:
35
35
  dependencies:
36
36
  - !ruby/object:Gem::Dependency
@@ -41,18 +41,22 @@ dependencies:
41
41
  requirements:
42
42
  - - ">="
43
43
  - !ruby/object:Gem::Version
44
- version: 1.12.1
44
+ version: 2.3.2
45
45
  version:
46
46
  description: |-
47
47
  IMAPProcessor is a client for processing messages on an IMAP server. It
48
48
  provides some basic mechanisms for connecting to an IMAP server, determining
49
49
  capabilities and handling messages.
50
50
 
51
- IMAPProcessor ships with the imap_keywords executable which can query an IMAP
52
- server for keywords set on messages in mailboxes.
51
+ IMAPProcessor ships with the executables imap_keywords which can query an IMAP
52
+ server for keywords set on messages in mailboxes, imap_idle which can show new
53
+ messages in a mailbox and imap_archive which will archive old messages to a
54
+ new mailbox.
53
55
  email:
54
56
  - drbrain@segment7.net
55
57
  executables:
58
+ - imap_archive
59
+ - imap_idle
56
60
  - imap_keywords
57
61
  extensions: []
58
62
 
@@ -66,10 +70,17 @@ files:
66
70
  - Manifest.txt
67
71
  - README.txt
68
72
  - Rakefile
73
+ - bin/imap_archive
74
+ - bin/imap_idle
69
75
  - bin/imap_keywords
70
76
  - lib/imap_processor.rb
77
+ - lib/imap_processor/archive.rb
78
+ - lib/imap_processor/idle.rb
71
79
  - lib/imap_processor/keywords.rb
72
80
  - lib/imap_sasl_plain.rb
81
+ - lib/net/imap/date.rb
82
+ - lib/net/imap/idle.rb
83
+ - test/test_imap_processor.rb
73
84
  has_rdoc: true
74
85
  homepage: http://seattlerb.rubyforge.org/imap_processor
75
86
  licenses: []
@@ -95,9 +106,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
95
106
  requirements: []
96
107
 
97
108
  rubyforge_project: seattlerb
98
- rubygems_version: 1.3.3
109
+ rubygems_version: 1.3.4
99
110
  signing_key:
100
111
  specification_version: 3
101
112
  summary: IMAPProcessor is a client for processing messages on an IMAP server
102
- test_files: []
103
-
113
+ test_files:
114
+ - test/test_imap_processor.rb
metadata.gz.sig CHANGED
Binary file