Index: Lib/mailbox.py =================================================================== --- Lib/mailbox.py (revision 54661) +++ Lib/mailbox.py (working copy) @@ -492,6 +492,25 @@ continue +class _recordingDict(dict): + def __init__(self, *args, **kwargs): + super(_recordingDict, self).__init__(*args, **kwargs) + self.all_offsets = set(tuple(value) for value in self.itervalues()) + def __setitem__(self, key, value): + self.all_offsets.add(value) + super(_recordingDict, self).__setitem__(key, value) + def update(self, *args, **kwargs): + as_dict = dict(*args, **kwargs) + self.all_offsets.update(tuple(value) for value in as_dict.itervalues()) + super(_recordingDict, self).update(as_dict) + def setdefault(self, key, default=None): + try: + return self[key] + except KeyError: + self[key] = default + return default + + class _singlefileMailbox(Mailbox): """A single-file mailbox.""" @@ -562,6 +581,7 @@ warnings.warn("lock() method called with pending changes; " "should have been locked before making changes", stacklevel=2) + self._update_toc() def unlock(self): """Unlock the mailbox if it is locked.""" @@ -599,7 +619,7 @@ remove_temp_file = True new_file = _create_temporary(self._path) try: - new_toc = {} + new_toc = _recordingDict() self._pre_mailbox_hook(new_file) for key in sorted(self._toc.keys()): start, stop = self._toc[key] @@ -653,6 +673,7 @@ orig_file.close() if self._locked: _lock_file(self._file, dotlock=False) + self._update_toc() def _pre_mailbox_hook(self, f): """Called before writing the mailbox to file f.""" @@ -676,13 +697,42 @@ def _lookup(self, key=None): """Return (start, stop) or raise KeyError.""" if self._toc is None: - self._generate_toc() + self._update_toc() if key is not None: try: return self._toc[key] except KeyError: raise KeyError('No message with key: %s' % key) + def _update_toc(self): + """Reread file, and merge new messages into self._toc.""" + old_toc = self._toc + our_next_key = self._next_key + try: + self._toc = None + self._next_key = 0 + self._generate_toc() + new_toc = self._toc + finally: + self._toc = old_toc + self._next_key = our_next_key + new_offsets = frozenset(tuple(value) for value in new_toc.itervalues()) + if self._toc is None: + self._toc = _recordingDict() + if self._pending: + old_offsets = self._toc.all_offsets + else: + old_offsets = frozenset(tuple(value) for value in + self._toc.itervalues()) + if not old_offsets.issubset(new_offsets): + raise ExternalClashError('Existing messages have been modified, ' + 'moved or deleted.') + for offsets in sorted(new_offsets - old_offsets): + self._toc[self._next_key] = offsets + self._next_key += 1 + self._file.seek(0, 2) + self._file_length = self._file.tell() + def _append_message(self, message): """Append message to mailbox and return (start, stop) offsets.""" self._file.seek(0, 2) @@ -782,7 +832,6 @@ break self._toc = dict(enumerate(zip(starts, stops))) self._next_key = len(self._toc) - self._file_length = self._file.tell() class MMDF(_mboxMMDF): @@ -826,8 +875,6 @@ break self._toc = dict(enumerate(zip(starts, stops))) self._next_key = len(self._toc) - self._file.seek(0, 2) - self._file_length = self._file.tell() class MH(Mailbox): @@ -1238,8 +1285,6 @@ self._toc = dict(enumerate(zip(starts, stops))) self._labels = dict(enumerate(label_lists)) self._next_key = len(self._toc) - self._file.seek(0, 2) - self._file_length = self._file.tell() def _pre_mailbox_hook(self, f): """Called before writing the mailbox to file f."""