From 45f0d9835ce9546429b5c94b7bdea9c20ec91b02 Mon Sep 17 00:00:00 2001 From: Petri Lehtinen Date: Thu, 28 Jun 2012 13:48:17 +0300 Subject: [PATCH] #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. --- Lib/mailbox.py | 18 +++++++++++++++--- Lib/test/test_mailbox.py | 29 +++++++++++++++++++++++++++-- Misc/NEWS | 4 ++++ 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/Lib/mailbox.py b/Lib/mailbox.py index 6a69819c968..ecd9f61d76a 100644 --- a/Lib/mailbox.py +++ b/Lib/mailbox.py @@ -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) diff --git a/Lib/test/test_mailbox.py b/Lib/test/test_mailbox.py index 6ebd1a29bd9..a8c692b9817 100644 --- a/Lib/test/test_mailbox.py +++ b/Lib/test/test_mailbox.py @@ -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) diff --git a/Misc/NEWS b/Misc/NEWS index 62968d2f382..aa41678e73a 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -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.