# # MH Mailbox support. We aren't using the python mhmail module # because it's... really weird... # # Only partial support. we still use shelved headers. XXX # import string import shelve import marshal import os import os.path import time import glob import pynemsg from pyneheaders import * from superbox import superbox class mhbox(superbox): def __init__(self, user, box, prefix=None): # No prefix specified. Use UID if prefix == None: prefix = box.uid self.user = user self.mhbox_path = box.export_format[1] self.body_db = {} self.prefix = prefix if not os.path.exists(self.mhbox_path): os.mkdir(self.mhbox_path) if not os.path.isfile(prefix+".mhh"): # No headers database. Build it. self.header_db = shelve.open(prefix+".mhh") self.rebuild() else: self.header_db = shelve.open(prefix+".mhh") def rebuild(self): """ Rebuild message-id -> file number links. """ _t = time.time() total_files = glob.glob(self.mhbox_path+"/*") for filename in total_files: # Not an mh body file try: int(os.path.basename(filename)) except ValueError: continue f = open(filename) body = f.read() f.close() # create new message object msg = pynemsg.pynemsg() # convert body from strange mhbox format end_head = string.find(body, "\n--------\n") msg.body = body[:end_head] + "\n\n" + body[end_head+10:] msg.parseheaders(self.user) if not msg.headers.has_key("message-id"): # no message-id exists: create one and make link msg_id = self.get_unique_message_id() self.body_db[msg_id] = os.path.basename(filename) msg.headers["message-id"] = msg_id msg.body = "Message-Id: "+msg_id+"\n"+msg.body else: # link to existing id msg_id = msg.headers["message-id"] self.body_db[msg_id] = os.path.basename(filename) # if we also have headers then we are set if self.header_db.has_key(msg_id): continue # no headers. we must make them msg.date_received = int(time.time()) self.save_article(msg) print "MHBOX: Building headers for ", msg_id # kill old headers head_ids = self.header_db.keys() body_ids = self.body_db.keys() for id in head_ids: if not (id in body_ids): print "MHBOX: Nuking ", id," header" del self.header_db[id] def get_contents(self): return self.header_db.keys() def nuke(self): """ Delete database entirely. We don't delete the contents of the mhbox folder, since this probably isn't the desired behaviour. """ self.header_db.close() os.remove(self.prefix+".mhh") def __del__(self): # shutdown time :-) self.header_db.close() def load_header(self, msg_id): """ See pynebox.py """ try: return marshal.loads(self.header_db[msg_id]) except (TypeError, KeyError): return None def _load_article(self, msg_id): """ Return body for message 'msg_id' """ header = self.load_header(msg_id) try: body_filename = self.body_db[msg_id] f = open(self.mhbox_path+"/"+body_filename, "r") body = f.read() f.close() except IOError: return (header, None) else: # convert body from strange mhbox format end_head = string.find(body, "\n--------\n") body = body[:end_head] + "\n\n" + body[end_head+10:] return (header, body) def _save_article(self, headers, body_text, msg_id): """ Do not use directly. use save_article. """ self.header_db[msg_id] = marshal.dumps(headers) ## end_head = string.find(body_text, "\n\n") body = body_text[:end_head] + "\n--------\n" + body_text[end_head+2:] try: body_filename = self.body_db[msg_id] except KeyError: # No file exists for this. make one files = glob.glob(self.mhbox_path+"/*") sfiles = [] for x in files: sfiles.append(os.path.basename(x)) # find unique number i = 1 while str(i) in sfiles: i = i + 1 # record message-id to file number link self.body_db[msg_id] = str(i) body_filename = str(i) f = open(self.mhbox_path+"/"+body_filename, "w") f.write(body) f.close() def delete_article(self, msg_id): try: body_filename = self.body_db[msg_id] os.remove(self.mhbox_path+"/"+body_filename) except (KeyError, OSError): pass try: del self.header_db[msg_id] except KeyError: pass try: del self.body_db[msg_id] except KeyError: pass def has_article(self, msg_id): """ Is the article cached? """ # Note: we check for presence of headers not bodies if self.header_db.has_key(msg_id): return 1 else: return 0