IMAPCleanse 1.0.0

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