""" Written by Ben Leslie (c) 2003. """ #!/usr/bin/python2.3 import os, email.Message, email.Parser, rfc822, stat, new import mx.DateTime import jwzthreading import sys debug = 0 def uniq(seq): """Return unique elements in a list""" return dict(zip(seq, [None,]*len(seq))).keys() class MaildirMessage(email.Message.Message): """MaildirMessage extends the basic message funcionality of of the Mesasge class to provide useful Maildir specific methods and also support the interface to the jwzthreading module""" def _set_status(self, status): """Set the status field. Called by the MaildirMailbox class""" self._status = status def _set_filename(self, filename): """Set the filename of the message. Should only be called by the MaildirMailbox class""" self.filename = filename def is_flagged(self): """Return true if this message is flagged""" return 'F' in self._status def is_seen(self): """Return true if this message has been seen""" return 'S' in self._status def get_date(self): """Return the date of this message""" return mx.DateTime.Parser.DateTimeFromString(self["Date"]) def getheader(self, header, default): """Return a given header, or if it doesn't exist return default""" if self.has_key(header): return self[header] else: return default def references(self): """Return the IDs of emails that this email references""" refs = [] if self.has_key["References"]: refs += self["References"].split() if self.has_key["In-Reply-To"]: refs.append(self["In-Reply-To"]) return refs def __repr__(self): return "<%s %s %s>" % (self.is_seen(), int((mx.DateTime.localtime() - self.get_date()).days), self["Subject"]) def is_maildir(directory): """Return true if directory is a valid Maildir folder. This is determined by the fact that it has three sub directories called cur, new and tmp.""" return reduce(lambda last, dir: dir in os.listdir(directory) and last, ["cur", "new", "tmp"], True) def is_dir(directory): try: s = os.stat(directory) return stat.S_ISDIR(s[stat.ST_MODE]) except OSError, x: return False return False class Maildir: """A Maildir mailbox. This is based on the original mailbox.Maildir code, unfrotunately it isn't really sufficient for my needs here""" def __init__(self, basedir, name="", level=1, factory=email.Parser.Parser(MaildirMessage).parse, create=False): self.basedir = basedir self.name = name if (level > 1): self.dirname = os.path.join(self.basedir, name) else: self.dirname = self.basedir self.factory = factory self.level = level if create and not os.access(self.dirname, os.F_OK): os.mkdir(self.dirname) os.mkdir(os.path.join(self.dirname, "cur")) os.mkdir(os.path.join(self.dirname, "new")) os.mkdir(os.path.join(self.dirname, "tmp")) boxes = [] if is_dir(os.path.join(self.dirname, 'cur')): # Now check for current mail in this maildir curdir = os.path.join(self.dirname, 'cur') boxes += [os.path.join(curdir, f) for f in os.listdir(curdir) if f[0] != '.'] self.boxes = boxes else: self.boxes = [] def folders(self): """Returns a list of subfolders""" folds = [] possible = os.listdir(self.basedir) possible = filter(lambda x: x[0] == '.', possible) for folder in possible: if (is_dir(os.path.join(self.basedir, folder)) and is_maildir(os.path.join(self.basedir, folder))): if (len(folder.split(".")) > self.level and folder.startswith(self.name)): name = folder.split(".")[self.level] if name != "old": folds.append(name) return map(lambda x: self.__class__(self.basedir, self.name + "." + x, self.level+1), uniq(folds)) def messages(self): return iter(self.next, None) def __repr__(self): return "" % (self.name, self.basedir, id(self)) def next(self): if not self.boxes: return None fn = self.boxes[0] del self.boxes[0] fp = open(fn) msg = self.factory(fp) msg._set_status(fn.split(":")[1].split(",")[1]) msg._set_filename(os.path.split(fn)[-1]) return msg def get_archive(self): """Return this folder .old subfolder""" return self.__class__(self.basedir, self.name + ".old", self.level+1, create=1) def archive(self, message): os.rename(os.path.join(os.path.join(self.dirname, "cur"), message.filename), os.path.join(os.path.join(self.get_archive().dirname, "cur"), message.filename)) def keep(msg): """Return true if this message should be kept""" if not msg.is_seen(): return 1 if msg.is_flagged(): return 1 if (mx.DateTime.localtime() - msg.get_date()).days < 3: return 1 return 0 def keep_thread(thread): for msg in thread: if keep(msg): return 1 return 0 def flatten_md(md): mds = [md] for folder in md.folders(): mds += flatten_md(folder) return mds def flatten_container(container): results = [] if container.message: results.append(container.message.message) for child in container.children: results += flatten_container(child) return results def get_threads(md): threads = [] all = [] count = 0 for message in md.messages(): count += 1 if message["Message-Id"]: all.append(jwzthreading.make_message(message)) else: threads.append([message]) subject_table = jwzthreading.thread(all, strict=1) for container in subject_table: threads.append(flatten_container(container)) return threads def main(args): global debug if len(args) > 2: debug = 1 for md in flatten_md(Maildir(args[1])): if md.name in (".sent", ".spambox"): continue if debug: print "Handling box: %s" % md.name for thread in get_threads(md): if not keep_thread(thread): if debug: print "Moving thread: %r" % thread[0] for message in thread: md.archive(message) else: if debug: print "Keeping thread: %r" % thread[0] if __name__ == "__main__": main(sys.argv)