IMAPCleanse 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/.DS_Store ADDED
Binary file
data/CVS/Entries ADDED
@@ -0,0 +1,6 @@
1
+ D/bin////
2
+ D/lib////
3
+ /Manifest.txt/1.1/Wed Mar 29 02:21:53 2006//
4
+ /README/1.1/Wed Mar 29 01:49:02 2006//
5
+ /Rakefile/1.1/Wed Mar 29 02:23:42 2006//
6
+ /LICENSE/0/dummy timestamp//
data/CVS/Repository ADDED
@@ -0,0 +1 @@
1
+ ruby/imap_cleanse
data/CVS/Root ADDED
@@ -0,0 +1 @@
1
+ :ext:drbrain@ziz:/home/cvs/
data/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ Copyright 2005 Eric Hodel. All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions
5
+ are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright
8
+ notice, this list of conditions and the following disclaimer.
9
+ 2. Redistributions in binary form must reproduce the above copyright
10
+ notice, this list of conditions and the following disclaimer in the
11
+ documentation and/or other materials provided with the distribution.
12
+ 3. Neither the names of the authors nor the names of their contributors
13
+ may be used to endorse or promote products derived from this software
14
+ without specific prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
17
+ OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
20
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
21
+ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
22
+ OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
23
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
24
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
25
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
26
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+
data/Manifest.txt ADDED
@@ -0,0 +1,16 @@
1
+ .DS_Store
2
+ CVS/Entries
3
+ CVS/Repository
4
+ CVS/Root
5
+ LICENSE
6
+ Manifest.txt
7
+ README
8
+ Rakefile
9
+ bin/CVS/Entries
10
+ bin/CVS/Repository
11
+ bin/CVS/Root
12
+ bin/imap_cleanse
13
+ lib/CVS/Entries
14
+ lib/CVS/Repository
15
+ lib/CVS/Root
16
+ lib/imap_cleanse.rb
data/README ADDED
@@ -0,0 +1,80 @@
1
+ = IMAPCleanse
2
+
3
+ Rubyforge Project:
4
+
5
+ http://rubyforge.org/projects/seattlerb/
6
+
7
+ Documentation:
8
+
9
+ http://seattlerb.rubyforge.org/IMAPCleanse/
10
+
11
+ == About
12
+
13
+ IMAPCleanse removes old, read, unflagged messages from your IMAP mailboxes.
14
+
15
+ == Why?
16
+
17
+ I'm lazy. I don't delete read messages from my mailboxes because I like to
18
+ have context when reading threads. Since I'm lazy my more-trafficed mailboxes
19
+ can end up with tens of thousands of read messages. Deleting this many
20
+ messages with Mail.app is time consuming and boring.
21
+
22
+ So I wrote IMAPCleanse to clean out my old mailboxes for me. If I want to keep
23
+ a message around for forever I'll just flag it and IMAPCleanse won't touch it.
24
+
25
+ == Installing IMAPCleanse
26
+
27
+ Just install the gem:
28
+
29
+ $ sudo gem install IMAPCleanse
30
+
31
+ == Using IMAPCleanse
32
+
33
+ In short:
34
+
35
+ imap_cleanse -H mail.example.com -p mypassword -b Lists/FreeBSD/current,Lists/Ruby -a 30
36
+
37
+ The help for IMAPCleanse should be sufficiently verbose, but here's a couple of
38
+ tips:
39
+
40
+ === --noop and --verbose
41
+
42
+ The --noop flag tells IMAPCleanse not to delete anything. When combined with
43
+ the --verbose flag you can see how many messages IMAPCleanse would have deleted
44
+ from which mailboxes.
45
+
46
+ $ ruby -Ilib bin/imap_cleanse -nv
47
+ # Connected to mail.example.com:993
48
+ # Logged in as drbrain
49
+ # Cleansing read, unflagged messages older than 26-Feb-2006 17:04 PST
50
+ # Found 23 mailboxes to cleanse:
51
+ # mail/Lists/FreeBSD/current
52
+ [...]
53
+ # mail/Lists/Ruby/ZineBoard
54
+ # Selected mail/Lists/FreeBSD/current
55
+ # Scanning for messages
56
+ # Found 0 messages
57
+ [...]
58
+ # Selected mail/Lists/Ruby/ZineBoard
59
+ # Scanning for messages
60
+ # Found 0 messages
61
+ # Done. Found 0 messages in 23 mailboxes
62
+
63
+ (Since I just ran IMAPCleanse it didn't have anything to do.)
64
+
65
+ === ~/.imap_cleanse
66
+
67
+ The ~/.imap_cleanse file can hold your password and other options so you don't
68
+ have to type them in on the command line every time. The format is simple,
69
+ just the options file name followed by '=' followed by the argument.
70
+
71
+ No whitespace is stripped from options, so be sure to do that yourself. Mine
72
+ looks something like this:
73
+
74
+ Host=mail.example.com
75
+ SSL=true
76
+ Username=drbrain
77
+ Boxes=Lists/FreeBSD/current,Lists/FreeBSD/performance,Lists/FreeBSD/Soekris,Lists/FreeBSD/stable,Lists/Ruby
78
+ Age=30
79
+ Password=my password
80
+
data/Rakefile ADDED
@@ -0,0 +1,58 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/gempackagetask'
6
+
7
+ $VERBOSE = nil
8
+
9
+ spec = Gem::Specification.new do |s|
10
+ s.name = 'IMAPCleanse'
11
+ s.version = '1.0.0'
12
+ s.summary = 'Cleanses your IMAP mailboxes of oldness'
13
+ s.description = 'IMAPCleanse removes old, read, unflagged messages from your IMAP mailboxes so you don\'t have to!'
14
+ s.author = 'Eric Hodel'
15
+ s.email = 'drbrain@segment7.net'
16
+
17
+ s.has_rdoc = true
18
+ s.files = File.read('Manifest.txt').split($/)
19
+ s.require_path = 'lib'
20
+ s.executables = 'imap_cleanse'
21
+ end
22
+
23
+ desc 'Run tests'
24
+ task :default => [ :test ]
25
+
26
+ Rake::TestTask.new('test') do |t|
27
+ t.libs << 'test'
28
+ t.pattern = 'test/test_*.rb'
29
+ t.verbose = true
30
+ end
31
+
32
+ desc 'Update Manifest.txt'
33
+ task :update_manifest do
34
+ sh "find . -type f | sed -e 's%./%%' | egrep -v 'svn|swp|~' | egrep -v '^(doc|pkg)/' | sort > Manifest.txt"
35
+ end
36
+
37
+ desc 'Generate RDoc'
38
+ Rake::RDocTask.new :rdoc do |rd|
39
+ rd.rdoc_dir = 'doc'
40
+ rd.rdoc_files.add 'lib', 'README', 'LICENSE'
41
+ rd.main = 'README'
42
+ rd.options << '-d' if `which dot` =~ /\/dot/
43
+ rd.options << '-t IMAPCreate'
44
+ end
45
+
46
+ desc 'Build Gem'
47
+ Rake::GemPackageTask.new spec do |pkg|
48
+ pkg.need_tar = true
49
+ end
50
+
51
+ desc 'Clean up'
52
+ task :clean => [ :clobber_rdoc, :clobber_package ]
53
+
54
+ desc 'Clean up'
55
+ task :clobber => [ :clean ]
56
+
57
+ # vim: syntax=Ruby
58
+
data/bin/CVS/Entries ADDED
@@ -0,0 +1,2 @@
1
+ /imap_cleanse/1.1/Wed Mar 29 02:25:22 2006//
2
+ D
@@ -0,0 +1 @@
1
+ ruby/imap_cleanse/bin
data/bin/CVS/Root ADDED
@@ -0,0 +1 @@
1
+ :ext:drbrain@ziz:/home/cvs/
data/bin/imap_cleanse ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ require 'imap_cleanse'
4
+
5
+ IMAPCleanse.run
6
+
data/lib/CVS/Entries ADDED
@@ -0,0 +1,2 @@
1
+ /imap_cleanse.rb/1.1/Wed Mar 29 00:45:15 2006//
2
+ D
@@ -0,0 +1 @@
1
+ ruby/imap_cleanse/lib
data/lib/CVS/Root ADDED
@@ -0,0 +1 @@
1
+ :ext:drbrain@ziz:/home/cvs/
@@ -0,0 +1,347 @@
1
+ require 'net/imap'
2
+ require 'optparse'
3
+ require 'pp'
4
+ require 'time'
5
+
6
+ class Time
7
+
8
+ ##
9
+ # Formats this Time as an IMAP-style date.
10
+ #
11
+ # RFC 2060 doesn't specify the format of its times. Unfortunately it is
12
+ # almost but not quite RFC 822 dates.
13
+
14
+ def imapdate
15
+ strftime '%d-%b-%Y %H:%M %Z'
16
+ end
17
+ end
18
+
19
+ ##
20
+ # RFC 2595 PLAIN Authenticator for Net::IMAP. Only for use with SSL (but not
21
+ # enforced).
22
+
23
+ class Net::IMAP::PlainAuthenticator
24
+
25
+ ##
26
+ # From RFC 2595 Section 6. PLAIN SASL Authentication
27
+ #
28
+ # The mechanism consists of a single message from the client to the
29
+ # server. The client sends the authorization identity (identity to
30
+ # login as), followed by a US-ASCII NUL character, followed by the
31
+ # authentication identity (identity whose password will be used),
32
+ # followed by a US-ASCII NUL character, followed by the clear-text
33
+ # password. The client may leave the authorization identity empty to
34
+ # indicate that it is the same as the authentication identity.
35
+
36
+ def process(data)
37
+ return [@user, @user, @password].join("\0")
38
+ end
39
+
40
+ private
41
+
42
+ ##
43
+ # Creates a new PlainAuthenticator that will authenticate with +user+ and
44
+ # +password+.
45
+
46
+ def initialize(user, password)
47
+ @user = user
48
+ @password = password
49
+ end
50
+
51
+ end
52
+
53
+ if defined? OpenSSL then
54
+ Net::IMAP.add_authenticator 'PLAIN', Net::IMAP::PlainAuthenticator
55
+ end
56
+
57
+ ##
58
+ # IMAPCleanse removes old messages from your IMAP mailboxes so you don't have
59
+ # to!
60
+
61
+ class IMAPCleanse
62
+
63
+ ##
64
+ # Handles processing of +args+.
65
+
66
+ def self.process_args(args)
67
+ opts_file = File.expand_path '~/.imap_cleanse'
68
+
69
+ unless File.stat(opts_file).mode & 077 == 0 then
70
+ STDERR.puts "WARNING! #{opts_file} is group/other readable or writable!"
71
+ STDERR.puts "WARNING! I'm not doing a thing until you fix it!"
72
+ exit 1
73
+ end
74
+
75
+ options = {}
76
+
77
+ File.readlines(opts_file).map { |l| l.chomp.split '=', 2 }.each do |k,v|
78
+ v = true if v == 'true'
79
+ v = false if v == 'false'
80
+ v = Integer(v) rescue v
81
+ options[k.intern] = v
82
+ end
83
+
84
+ options[:SSL] ||= true
85
+ options[:Username] ||= ENV['USER']
86
+ options[:Root] ||= 'mail'
87
+ options[:Noop] ||= false
88
+ options[:Verbose] ||= false
89
+
90
+ opts = OptionParser.new do |opts|
91
+ opts.banner = 'Usage: imap_cleanse [options]'
92
+ opts.separator ''
93
+ opts.separator 'Options may also be set in the options file ~/.imap_cleanse.'
94
+ opts.separator ''
95
+ opts.separator 'Example ~/.imap_cleanse:'
96
+ opts.separator "\tHost=mail.example.com"
97
+ opts.separator "\tPassword=my password"
98
+
99
+ opts.separator ''
100
+ opts.separator 'Connection options:'
101
+
102
+ opts.on("-H", "--host HOST",
103
+ "IMAP server host",
104
+ "Default: #{options[:Host].inspect}",
105
+ "Options file name: Host") do |host|
106
+ options[:Host] = host
107
+ end
108
+
109
+ opts.on("-P", "--port PORT",
110
+ "IMAP server port",
111
+ "Default: The correct port SSL/non-SSL mode",
112
+ "Options file name: Port") do |port|
113
+ options[:Port] = port
114
+ end
115
+
116
+ opts.on("-s", "--[no-]ssl",
117
+ "Use SSL for IMAP connection",
118
+ "Default: #{options[:SSL].inspect}",
119
+ "Options file name: SSL") do |ssl|
120
+ options[:SSL] = ssl
121
+ end
122
+
123
+ opts.separator ''
124
+ opts.separator 'Login options:'
125
+
126
+ opts.on("-u", "--username USERNAME",
127
+ "IMAP username",
128
+ "Default: #{options[:Username].inspect}",
129
+ "Options file name: Username") do |username|
130
+ options[:Username] = username
131
+ end
132
+
133
+ opts.on("-p", "--password PASSWORD",
134
+ "IMAP password",
135
+ "Default: Read from ~/.imap_cleanse",
136
+ "Options file name: Password") do |password|
137
+ options[:Password] = password
138
+ end
139
+
140
+ opts.separator ''
141
+ opts.separator 'Cleansing options:'
142
+
143
+ opts.on("-r", "--root ROOT",
144
+ "Root of mailbox hierarchy",
145
+ "Default: #{options[:Root].inspect}",
146
+ "Options file name: Root") do |root|
147
+ options[:Root] = root
148
+ end
149
+
150
+ opts.on("-b", "--boxes BOXES",
151
+ "Comma-separated list of mailbox name",
152
+ "prefixes to cleanse",
153
+ "Default: #{options[:Boxes].inspect}",
154
+ "Options file name: Boxes") do |boxes|
155
+ options[:Boxes] = boxes
156
+ end
157
+
158
+ opts.on("-a", "--age AGE",
159
+ "Delete messages more than AGE days old",
160
+ "Default: #{options[:Age].inspect}",
161
+ "Options file name: Age", Integer) do |age|
162
+ options[:Age] = age
163
+ end
164
+
165
+ opts.on("-n", "--noop",
166
+ "Perform no destructive operations",
167
+ "Best used with the verbose option",
168
+ "Default: #{options[:Noop].inspect}",
169
+ "Options file name: Noop") do |noop|
170
+ options[:Noop] = noop
171
+ end
172
+
173
+ opts.on("-v", "--[no-]verbose",
174
+ "Be verbose",
175
+ "Default: #{options[:Verbose].inspect}",
176
+ "Options file name: Verbose") do |verbose|
177
+ options[:Verbose] = verbose
178
+ end
179
+
180
+ opts.separator ''
181
+
182
+ opts.on("-h", "--help",
183
+ "You're looking at it") do
184
+ STDERR.puts opts
185
+ exit 1
186
+ end
187
+
188
+ opts.separator ''
189
+ end
190
+
191
+ opts.parse! args
192
+
193
+ options[:Port] ||= options[:SSL] ? 993 : 143
194
+
195
+ if options[:Host].nil? or
196
+ options[:Password].nil? or
197
+ options[:Boxes].nil? or
198
+ options[:Age].nil? then
199
+ STDERR.puts opts
200
+ STDERR.puts
201
+ STDERR.puts "Host name not set" if options[:Host].nil?
202
+ STDERR.puts "Password not set" if options[:Password].nil?
203
+ STDERR.puts "Boxes option not set" if options[:Boxes].nil?
204
+ STDERR.puts "Age option not set" if options[:Age].nil?
205
+ exit 1
206
+ end
207
+
208
+ return options
209
+ end
210
+
211
+ ##
212
+ # Sets up IMAPCleanse options then cleanses mailboxes.
213
+
214
+ def self.run(args = ARGV)
215
+ options = process_args args
216
+ cleanser = new options
217
+ cleanser.cleanse
218
+ end
219
+
220
+ ##
221
+ # Creates a new IMAPCleanse from +options+.
222
+ #
223
+ # Options include:
224
+ # +:Verbose+:: Verbose flag
225
+ # +:Noop+:: Don't delete anything flag
226
+ # +:Root+:: IMAP root path
227
+ # +:Boxes+:: Comma-separated list of mailbox prefixes to cleanse
228
+ # +:Age+:: Delete messages older than this many days ago
229
+ # +:Host+:: IMAP server
230
+ # +:Port+:: IMAP server port
231
+ # +:SSL+:: SSL flag
232
+ # +:Username+:: IMAP username
233
+ # +:Password+:: IMAP password
234
+
235
+ def initialize(options)
236
+ @verbose = options[:Verbose]
237
+ @noop = options[:Noop]
238
+ @root = options[:Root]
239
+
240
+ boxes = options[:Boxes].split(',').map { |b| Regexp.escape b }
241
+ @box_re = /^#{Regexp.escape @root}\/#{Regexp.union(*boxes)}/
242
+
243
+ @before_date = (Time.now - 86400 * options[:Age]).imapdate
244
+
245
+ connect options[:Host], options[:Port], options[:SSL],
246
+ options[:Username], options[:Password]
247
+ end
248
+
249
+ ##
250
+ # Removes read, unflagged messages from all selected mailboxes...
251
+ #
252
+ # Unless :Noop was set, then it just prints out what it would do.
253
+
254
+ def cleanse
255
+ log "Cleansing read, unflagged messages older than #{@before_date}"
256
+
257
+ message_count = 0
258
+ mailboxes = find_mailboxes
259
+
260
+ mailboxes.each do |mailbox|
261
+ @imap.select mailbox
262
+ log "Selected #{mailbox}"
263
+
264
+ messages = old_messages_in_curr
265
+ next if messages.empty?
266
+
267
+ message_count += messages.length
268
+
269
+ if @noop then
270
+ log "Noop - nothing deleted"
271
+ next
272
+ end
273
+
274
+ purge messages
275
+ end
276
+
277
+ log "Done. Found #{message_count} messages in #{mailboxes.length} mailboxes"
278
+ end
279
+
280
+ private
281
+
282
+ ##
283
+ # Connects to IMAP server +host+ at +port+ using ssl if +ssl+ is true then
284
+ # logs in as +username+ with +password+. IMAPCleanse will really only work
285
+ # with PLAIN auth on SSL sockets, sorry.
286
+
287
+ def connect(host, port, ssl, username, password)
288
+ @imap = Net::IMAP.new host, port, ssl
289
+ log "Connected to #{host}:#{port}"
290
+ @imap.authenticate 'PLAIN', username, password
291
+ log "Logged in as #{username}"
292
+ end
293
+
294
+ ##
295
+ # Finds mailboxes with messages that were selected by the :Boxes option.
296
+
297
+ def find_mailboxes
298
+ mailboxes = @imap.list(@root, "*")
299
+ mailboxes.reject! { |mailbox| mailbox.attr.include? :Noselect }
300
+ mailboxes.map! { |mailbox| mailbox.name }
301
+ mailboxes.reject! { |mailbox| mailbox !~ @box_re }
302
+ mailboxes = mailboxes.sort_by { |m| m.downcase }
303
+ log "Found #{mailboxes.length} mailboxes to cleanse:"
304
+ mailboxes.each { |mailbox| log "\t#{mailbox}" } if @verbose
305
+ return mailboxes
306
+ end
307
+
308
+ ##
309
+ # Logs +message+ to STDERR if :Verbose was selected.
310
+
311
+ def log(message)
312
+ return unless @verbose
313
+ STDERR.puts "# #{message}"
314
+ end
315
+
316
+ ##
317
+ # Searches for read, unflagged messages older than :Age in the currently
318
+ # selected mailbox (see Net::IMAP#select).
319
+
320
+ def old_messages_in_curr
321
+ log "Scanning for messages"
322
+ messages = @imap.search [
323
+ 'NOT', 'NEW',
324
+ 'NOT', 'FLAGGED',
325
+ 'BEFORE', @before_date
326
+ ]
327
+ log "Found #{messages.length} messages"
328
+ return messages
329
+ end
330
+
331
+ ##
332
+ # Marks +messages+ in the currently selected mailbox for deletion then
333
+ # expunges the mailbox (see Net::IMAP#store and Net::IMAP#expunge).
334
+
335
+ def purge(messages)
336
+ until messages.empty? do
337
+ chunk = messages.slice! 0, 500
338
+ @imap.store chunk, '+FLAGS', [:Deleted]
339
+ end
340
+ log "Marked messages for deletion"
341
+
342
+ @imap.expunge
343
+ log "Expunged deleted messages"
344
+ end
345
+
346
+ end
347
+
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11.6
3
+ specification_version: 1
4
+ name: IMAPCleanse
5
+ version: !ruby/object:Gem::Version
6
+ version: 1.0.0
7
+ date: 2006-03-28 00:00:00 -08:00
8
+ summary: Cleanses your IMAP mailboxes of oldness
9
+ require_paths:
10
+ - lib
11
+ email: drbrain@segment7.net
12
+ homepage:
13
+ rubyforge_project:
14
+ description: IMAPCleanse removes old, read, unflagged messages from your IMAP mailboxes so you don't have to!
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Eric Hodel
31
+ files:
32
+ - .DS_Store
33
+ - CVS/Entries
34
+ - CVS/Repository
35
+ - CVS/Root
36
+ - LICENSE
37
+ - Manifest.txt
38
+ - README
39
+ - Rakefile
40
+ - bin/CVS/Entries
41
+ - bin/CVS/Repository
42
+ - bin/CVS/Root
43
+ - bin/imap_cleanse
44
+ - lib/CVS/Entries
45
+ - lib/CVS/Repository
46
+ - lib/CVS/Root
47
+ - lib/imap_cleanse.rb
48
+ test_files: []
49
+
50
+ rdoc_options: []
51
+
52
+ extra_rdoc_files: []
53
+
54
+ executables:
55
+ - imap_cleanse
56
+ extensions: []
57
+
58
+ requirements: []
59
+
60
+ dependencies: []
61
+