#9559: Append data to single-file mailbox files if messages are only added

If messages were only added, a new file is no longer created and
renamed over the old file when flush() is called on an mbox, MMDF or
Babyl mailbox.
This commit is contained in:
Petri Lehtinen 2012-06-28 13:48:17 +03:00
parent 8c368efa55
commit 45f0d9835c
3 changed files with 46 additions and 5 deletions

View File

@ -561,16 +561,19 @@ class _singlefileMailbox(Mailbox):
self._file = f
self._toc = None
self._next_key = 0
self._pending = False # No changes require rewriting the file.
self._pending = False # No changes require rewriting the file.
self._pending_sync = False # No need to sync the file
self._locked = False
self._file_length = None # Used to record mailbox size
self._file_length = None # Used to record mailbox size
def add(self, message):
"""Add message and return assigned key."""
self._lookup()
self._toc[self._next_key] = self._append_message(message)
self._next_key += 1
self._pending = True
# _append_message appends the message to the mailbox file. We
# don't need a full rewrite + rename, sync is enough.
self._pending_sync = True
return self._next_key - 1
def remove(self, key):
@ -616,6 +619,11 @@ class _singlefileMailbox(Mailbox):
def flush(self):
"""Write any pending changes to disk."""
if not self._pending:
if self._pending_sync:
# Messages have only been added, so syncing the file
# is enough.
_sync_flush(self._file)
self._pending_sync = False
return
# In order to be writing anything out at all, self._toc must
@ -669,6 +677,7 @@ class _singlefileMailbox(Mailbox):
self._file = open(self._path, 'rb+')
self._toc = new_toc
self._pending = False
self._pending_sync = False
if self._locked:
_lock_file(self._file, dotlock=False)
@ -705,6 +714,9 @@ class _singlefileMailbox(Mailbox):
"""Append message to mailbox and return (start, stop) offsets."""
self._file.seek(0, 2)
before = self._file.tell()
if len(self._toc) == 0:
# This is the first message
self._pre_mailbox_hook(self._file)
try:
self._pre_message_hook(self._file)
offsets = self._install_message(message)

View File

@ -824,7 +824,32 @@ class TestMaildir(TestMailbox, unittest.TestCase):
self._box._refresh()
self.assertTrue(refreshed())
class _TestMboxMMDF(TestMailbox):
class _TestSingleFile(TestMailbox):
'''Common tests for single-file mailboxes'''
def test_add_doesnt_rewrite(self):
# When only adding messages, flush() should not rewrite the
# mailbox file. See issue #9559.
# Inode number changes if the contents are written to another
# file which is then renamed over the original file. So we
# must check that the inode number doesn't change.
inode_before = os.stat(self._path).st_ino
self._box.add(self._template % 0)
self._box.flush()
inode_after = os.stat(self._path).st_ino
self.assertEqual(inode_before, inode_after)
# Make sure the message was really added
self._box.close()
self._box = self._factory(self._path)
self.assertEqual(len(self._box), 1)
class _TestMboxMMDF(_TestSingleFile):
def tearDown(self):
self._box.close()
@ -1085,7 +1110,7 @@ class TestMH(TestMailbox, unittest.TestCase):
return os.path.join(self._path, '.mh_sequences.lock')
class TestBabyl(TestMailbox, unittest.TestCase):
class TestBabyl(_TestSingleFile, unittest.TestCase):
_factory = lambda self, path, factory=None: mailbox.Babyl(path, factory)

View File

@ -75,6 +75,10 @@ Core and Builtins
Library
-------
- Issue #9559: If messages were only added, a new file is no longer
created and renamed over the old file when flush() is called on an
mbox, MMDF or Babyl mailbox.
- Issue #14653: email.utils.mktime_tz() no longer relies on system
mktime() when timezone offest is supplied.