#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:
parent
8237258e09
commit
f29435f9f0
|
@ -587,16 +587,19 @@ class _singlefileMailbox(Mailbox):
|
||||||
self._file = f
|
self._file = f
|
||||||
self._toc = None
|
self._toc = None
|
||||||
self._next_key = 0
|
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._locked = False
|
||||||
self._file_length = None # Used to record mailbox size
|
self._file_length = None # Used to record mailbox size
|
||||||
|
|
||||||
def add(self, message):
|
def add(self, message):
|
||||||
"""Add message and return assigned key."""
|
"""Add message and return assigned key."""
|
||||||
self._lookup()
|
self._lookup()
|
||||||
self._toc[self._next_key] = self._append_message(message)
|
self._toc[self._next_key] = self._append_message(message)
|
||||||
self._next_key += 1
|
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
|
return self._next_key - 1
|
||||||
|
|
||||||
def remove(self, key):
|
def remove(self, key):
|
||||||
|
@ -642,6 +645,11 @@ class _singlefileMailbox(Mailbox):
|
||||||
def flush(self):
|
def flush(self):
|
||||||
"""Write any pending changes to disk."""
|
"""Write any pending changes to disk."""
|
||||||
if not self._pending:
|
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
|
return
|
||||||
|
|
||||||
# In order to be writing anything out at all, self._toc must
|
# In order to be writing anything out at all, self._toc must
|
||||||
|
@ -695,6 +703,7 @@ class _singlefileMailbox(Mailbox):
|
||||||
self._file = open(self._path, 'rb+')
|
self._file = open(self._path, 'rb+')
|
||||||
self._toc = new_toc
|
self._toc = new_toc
|
||||||
self._pending = False
|
self._pending = False
|
||||||
|
self._pending_sync = False
|
||||||
if self._locked:
|
if self._locked:
|
||||||
_lock_file(self._file, dotlock=False)
|
_lock_file(self._file, dotlock=False)
|
||||||
|
|
||||||
|
@ -731,6 +740,9 @@ class _singlefileMailbox(Mailbox):
|
||||||
"""Append message to mailbox and return (start, stop) offsets."""
|
"""Append message to mailbox and return (start, stop) offsets."""
|
||||||
self._file.seek(0, 2)
|
self._file.seek(0, 2)
|
||||||
before = self._file.tell()
|
before = self._file.tell()
|
||||||
|
if len(self._toc) == 0:
|
||||||
|
# This is the first message
|
||||||
|
self._pre_mailbox_hook(self._file)
|
||||||
try:
|
try:
|
||||||
self._pre_message_hook(self._file)
|
self._pre_message_hook(self._file)
|
||||||
offsets = self._install_message(message)
|
offsets = self._install_message(message)
|
||||||
|
|
|
@ -942,7 +942,32 @@ class TestMaildir(TestMailbox, unittest.TestCase):
|
||||||
self._box._refresh()
|
self._box._refresh()
|
||||||
self.assertTrue(refreshed())
|
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):
|
def tearDown(self):
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
|
@ -1217,7 +1242,7 @@ class TestMH(TestMailbox, unittest.TestCase):
|
||||||
return os.path.join(self._path, '.mh_sequences.lock')
|
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)
|
_factory = lambda self, path, factory=None: mailbox.Babyl(path, factory)
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,10 @@ Core and Builtins
|
||||||
Library
|
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
|
- Issue #14653: email.utils.mktime_tz() no longer relies on system
|
||||||
mktime() when timezone offest is supplied.
|
mktime() when timezone offest is supplied.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue