diff --git a/autotests/mboxbenchmark.cpp b/autotests/mboxbenchmark.cpp index 515f74c..138aab3 100644 --- a/autotests/mboxbenchmark.cpp +++ b/autotests/mboxbenchmark.cpp @@ -1,133 +1,131 @@ /* Copyright (c) 2009 Bertjan Broeksema This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mboxbenchmark.h" #include #include #include QTEST_MAIN(MBoxBenchmark) #include "test-entries.h" using namespace KMBox; static const char *testDir = "libmbox-unit-test"; static const char *testFile = "test-mbox-file"; QString MBoxBenchmark::fileName() { return mTempDir->path() + QLatin1Char('/') + QLatin1String(testFile); } void MBoxBenchmark::initTestCase() { mTempDir = new QTemporaryDir(QDir::tempPath() + QLatin1Char('/') + QLatin1String(testDir)); mMail1 = KMime::Message::Ptr(new KMime::Message); mMail1->setContent(KMime::CRLFtoLF(sEntry1)); mMail1->parse(); } void MBoxBenchmark::cleanupTestCase() { mTempDir->remove(); } void MBoxBenchmark::testNoLockPerformance() { MBox mbox; mbox.setLockType(MBox::None); mbox.load(fileName()); for (int i = 0; i < 1000; ++i) { mbox.appendMessage(mMail1); } mbox.save(fileName()); QBENCHMARK { MBox mbox2; mbox2.setLockType(MBox::None); mbox2.setUnlockTimeout(5000); mbox2.load(fileName()); - foreach (const MBoxEntry &entry, mbox2.entries()) - { + foreach (const MBoxEntry &entry, mbox2.entries()) { mbox2.readMessage(entry); } } } void MBoxBenchmark::testProcfileLockPerformance() { mMail1 = KMime::Message::Ptr(new KMime::Message); mMail1->setContent(KMime::CRLFtoLF(sEntry1)); mMail1->parse(); MBox mbox; mbox.setLockType(MBox::ProcmailLockfile); mbox.load(fileName()); for (int i = 0; i < 1000; ++i) { mbox.appendMessage(mMail1); } mbox.save(fileName()); QBENCHMARK { MBox mbox2; mbox2.setLockType(MBox::ProcmailLockfile); mbox2.load(fileName()); mbox2.setUnlockTimeout(5000); // Keep the mbox locked for five seconds. - foreach (const MBoxEntry &entry, mbox2.entries()) - { + foreach (const MBoxEntry &entry, mbox2.entries()) { mbox2.readMessage(entry); } } } void MBoxBenchmark::voidTestMD5Performance() { MBox mbox; mbox.setLockType(MBox::None); mbox.load(fileName()); for (int i = 0; i < 1000; ++i) { mbox.appendMessage(mMail1); } mbox.save(fileName()); QBENCHMARK { QFile file(fileName()); QVERIFY(file.exists()); QVERIFY(file.open(QIODevice::ReadOnly)); QCryptographicHash hash(QCryptographicHash::Md5); qint64 blockSize = 512 * 1024; // Read blocks of 512K while (!file.atEnd()) { hash.addData(file.read(blockSize)); } file.close(); } } diff --git a/autotests/mboxtest.cpp b/autotests/mboxtest.cpp index 7d25808..28caf44 100644 --- a/autotests/mboxtest.cpp +++ b/autotests/mboxtest.cpp @@ -1,534 +1,534 @@ /* Copyright (C) 2009 Bertjan Broeksema This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mboxtest.h" #include #include #include #include #include QTEST_MAIN(MboxTest) #include "test-entries.h" using namespace KMBox; static const char *testDir = "libmbox-unit-test"; static const char *testFile = "test-mbox-file"; static const char *testLockFile = "test-mbox-lock-file"; QString MboxTest::fileName() { return mTempDir->path() + QLatin1Char('/') + QLatin1String(testFile); } QString MboxTest::lockFileName() { return mTempDir->path() + QLatin1Char('/') + QLatin1String(testLockFile); } void MboxTest::removeTestFile() { QFile file(fileName()); file.remove(); QVERIFY(!file.exists()); } void MboxTest::initTestCase() { mTempDir = new QTemporaryDir(QDir::tempPath() + QLatin1Char('/') + QLatin1String(testDir)); QDir temp(mTempDir->path()); QVERIFY(temp.exists()); QFile mboxfile(fileName()); mboxfile.open(QIODevice::WriteOnly); mboxfile.close(); QVERIFY(mboxfile.exists()); mMail1 = KMime::Message::Ptr(new KMime::Message); mMail1->setContent(KMime::CRLFtoLF(sEntry1)); mMail1->parse(); mMail2 = KMime::Message::Ptr(new KMime::Message); mMail2->setContent(KMime::CRLFtoLF(sEntry2)); mMail2->parse(); } void MboxTest::testSetLockMethod() { MBox mbox1; if (!QStandardPaths::findExecutable(QStringLiteral("lockfile")).isEmpty()) { QVERIFY(mbox1.setLockType(MBox::ProcmailLockfile)); } else { QVERIFY(!mbox1.setLockType(MBox::ProcmailLockfile)); } if (!QStandardPaths::findExecutable(QStringLiteral("mutt_dotlock")).isEmpty()) { QVERIFY(mbox1.setLockType(MBox::MuttDotlock)); QVERIFY(mbox1.setLockType(MBox::MuttDotlockPrivileged)); } else { QVERIFY(!mbox1.setLockType(MBox::MuttDotlock)); QVERIFY(!mbox1.setLockType(MBox::MuttDotlockPrivileged)); } QVERIFY(mbox1.setLockType(MBox::None)); } void MboxTest::testLockBeforeLoad() { // Should fail because it's not known which file to lock. MBox mbox; if (!QStandardPaths::findExecutable(QStringLiteral("lockfile")).isEmpty()) { QVERIFY(mbox.setLockType(MBox::ProcmailLockfile)); QVERIFY(!mbox.lock()); } if (!QStandardPaths::findExecutable(QStringLiteral("mutt_dotlock")).isEmpty()) { QVERIFY(mbox.setLockType(MBox::MuttDotlock)); QVERIFY(!mbox.lock()); QVERIFY(mbox.setLockType(MBox::MuttDotlockPrivileged)); QVERIFY(!mbox.lock()); } QVERIFY(mbox.setLockType(MBox::None)); QVERIFY(!mbox.lock()); } void MboxTest::testProcMailLock() { // It really only makes sense to test this if the lockfile executable can be // found. MBox mbox; if (!mbox.setLockType(MBox::ProcmailLockfile)) { QEXPECT_FAIL("", "This test only works when procmail is installed.", Abort); QVERIFY(false); } QVERIFY(mbox.load(fileName())); // By default the filename is used as part of the lockfile filename. QVERIFY(!QFile(fileName() + QLatin1String(".lock")).exists()); QVERIFY(mbox.lock()); QVERIFY(QFile(fileName() + QLatin1String(".lock")).exists()); QVERIFY(mbox.unlock()); QVERIFY(!QFile(fileName() + QLatin1String(".lock")).exists()); mbox.setLockFile(lockFileName()); QVERIFY(!QFile(lockFileName()).exists()); QVERIFY(mbox.lock()); QVERIFY(QFile(lockFileName()).exists()); QVERIFY(mbox.unlock()); QVERIFY(!QFile(lockFileName()).exists()); } void MboxTest::testConcurrentAccess() { // tests if mbox works correctly when another program locks the file // and appends new messages MBox mbox; if (!mbox.setLockType(MBox::ProcmailLockfile)) { QEXPECT_FAIL("", "This test only works when procmail is installed.", Abort); QVERIFY(false); } ThreadFillsMBox thread(fileName()); // locks the mbox file and adds a new message thread.start(); QVERIFY(mbox.load(fileName())); MBoxEntry entry = mbox.appendMessage(mMail1); // as the thread appended sEntry1, the offset for the now appended message // must be greater QVERIFY(static_cast(entry.messageOffset()) > sEntry1.length()); thread.wait(); } void MboxTest::testAppend() { removeTestFile(); QFileInfo info(fileName()); QCOMPARE(info.size(), static_cast(0)); MBox mbox; mbox.setLockType(MBox::None); QVERIFY(mbox.load(fileName())); // First message added to an emtpy file should be at offset 0 QCOMPARE(mbox.entries().size(), 0); QCOMPARE(mbox.appendMessage(mMail1).messageOffset(), static_cast(0)); QCOMPARE(mbox.entries().size(), 1); QVERIFY(mbox.entries().first().separatorSize() > 0); QCOMPARE(mbox.entries().first().messageSize(), static_cast(sEntry1.size())); const MBoxEntry offsetMail2 = mbox.appendMessage(mMail2); QVERIFY(offsetMail2.messageOffset() > static_cast(sEntry1.size())); QCOMPARE(mbox.entries().size(), 2); QVERIFY(mbox.entries().last().separatorSize() > 0); QCOMPARE(mbox.entries().last().messageSize(), static_cast(sEntry2.size())); // check if appended entries can be read MBoxEntry::List list = mbox.entries(); foreach (const MBoxEntry &msgInfo, list) { const QByteArray header = mbox.readMessageHeaders(msgInfo); QVERIFY(!header.isEmpty()); KMime::Message *message = mbox.readMessage(msgInfo); QVERIFY(message != nullptr); KMime::Message *headers = new KMime::Message(); headers->setHead(KMime::CRLFtoLF(header)); headers->parse(); QCOMPARE(message->messageID()->identifier(), headers->messageID()->identifier()); QCOMPARE(message->subject()->as7BitString(), headers->subject()->as7BitString()); QCOMPARE(message->to()->as7BitString(), headers->to()->as7BitString()); QCOMPARE(message->from()->as7BitString(), headers->from()->as7BitString()); if (msgInfo.messageOffset() == 0) { QCOMPARE(message->messageID()->identifier(), mMail1->messageID()->identifier()); QCOMPARE(message->subject()->as7BitString(), mMail1->subject()->as7BitString()); QCOMPARE(message->to()->as7BitString(), mMail1->to()->as7BitString()); QCOMPARE(message->from()->as7BitString(), mMail1->from()->as7BitString()); } else if (msgInfo.messageOffset() == offsetMail2.messageOffset()) { QCOMPARE(message->messageID()->identifier(), mMail2->messageID()->identifier()); QCOMPARE(message->subject()->as7BitString(), mMail2->subject()->as7BitString()); QCOMPARE(message->to()->as7BitString(), mMail2->to()->as7BitString()); QCOMPARE(message->from()->as7BitString(), mMail2->from()->as7BitString()); } delete message; delete headers; } } void MboxTest::testSaveAndLoad() { removeTestFile(); MBox mbox; QVERIFY(mbox.setLockType(MBox::None)); QVERIFY(mbox.load(fileName())); QVERIFY(mbox.entries().isEmpty()); mbox.appendMessage(mMail1); mbox.appendMessage(mMail2); MBoxEntry::List infos1 = mbox.entries(); QCOMPARE(infos1.size(), 2); QVERIFY(mbox.save()); QVERIFY(QFileInfo(fileName()).exists()); MBoxEntry::List infos2 = mbox.entries(); QCOMPARE(infos2.size(), 2); for (int i = 0; i < 2; ++i) { QCOMPARE(infos1.at(i).messageOffset(), infos2.at(i).messageOffset()); QCOMPARE(infos1.at(i).separatorSize(), infos2.at(i).separatorSize()); QCOMPARE(infos1.at(i).messageSize(), infos2.at(i).messageSize()); } MBox mbox2; QVERIFY(mbox2.setLockType(MBox::None)); QVERIFY(mbox2.load(fileName())); MBoxEntry::List infos3 = mbox2.entries(); QCOMPARE(infos3.size(), 2); for (int i = 0; i < 2; ++i) { QCOMPARE(infos3.at(i), infos2.at(i)); QCOMPARE(infos3.at(i).messageOffset(), infos1.at(i).messageOffset()); QCOMPARE(infos3.at(i).separatorSize(), infos1.at(i).separatorSize()); QCOMPARE(infos3.at(i).messageSize(), infos1.at(i).messageSize()); quint64 minSize = infos2.at(i).messageSize(); quint64 maxSize = infos2.at(i).messageSize() + 1; QVERIFY(infos3.at(i).messageSize() >= minSize); QVERIFY(infos3.at(i).messageSize() <= maxSize); } } void MboxTest::testBlankLines() { for (int i = 0; i < 5; ++i) { removeTestFile(); KMime::Message::Ptr mail = KMime::Message::Ptr(new KMime::Message); mail->setContent(KMime::CRLFtoLF(sEntry1 + QByteArray(i, '\n'))); mail->parse(); MBox mbox1; QVERIFY(mbox1.setLockType(MBox::None)); QVERIFY(mbox1.load(fileName())); mbox1.appendMessage(mail); mbox1.appendMessage(mail); mbox1.appendMessage(mail); mbox1.save(); MBox mbox2; QVERIFY(mbox1.setLockType(MBox::None)); QVERIFY(mbox1.load(fileName())); QCOMPARE(mbox1.entries().size(), 3); quint64 minSize = sEntry1.size() + i - 1; // Possibly on '\n' falls off. quint64 maxSize = sEntry1.size() + i; for (int i = 0; i < 3; ++i) { QVERIFY(mbox1.entries().at(i).messageSize() >= minSize); QVERIFY(mbox1.entries().at(i).messageSize() <= maxSize); } } } void MboxTest::testEntries() { removeTestFile(); MBox mbox1; QVERIFY(mbox1.setLockType(MBox::None)); QVERIFY(mbox1.load(fileName())); mbox1.appendMessage(mMail1); mbox1.appendMessage(mMail2); mbox1.appendMessage(mMail1); MBoxEntry::List infos = mbox1.entries(); - QCOMPARE(infos.size() , 3); + QCOMPARE(infos.size(), 3); MBoxEntry::List deletedEntries; deletedEntries << infos.at(0); MBoxEntry::List infos2 = mbox1.entries(deletedEntries); - QCOMPARE(infos2.size() , 2); + QCOMPARE(infos2.size(), 2); QVERIFY(infos2.first() != infos.first()); QVERIFY(infos2.last() != infos.first()); deletedEntries << infos.at(1); infos2 = mbox1.entries(deletedEntries); - QCOMPARE(infos2.size() , 1); + QCOMPARE(infos2.size(), 1); QVERIFY(infos2.first() != infos.at(0)); QVERIFY(infos2.first() != infos.at(1)); deletedEntries << infos.at(2); infos2 = mbox1.entries(deletedEntries); - QCOMPARE(infos2.size() , 0); + QCOMPARE(infos2.size(), 0); QVERIFY(!deletedEntries.contains(MBoxEntry(10))); // some random offset infos2 = mbox1.entries(MBoxEntry::List() << MBoxEntry(10)); - QCOMPARE(infos2.size() , 3); + QCOMPARE(infos2.size(), 3); QCOMPARE(infos2.at(0), infos.at(0)); QCOMPARE(infos2.at(1), infos.at(1)); QCOMPARE(infos2.at(2), infos.at(2)); } void MboxTest::testPurge() { MBox mbox1; QVERIFY(mbox1.setLockType(MBox::None)); QVERIFY(mbox1.load(fileName())); mbox1.appendMessage(mMail1); mbox1.appendMessage(mMail1); mbox1.appendMessage(mMail1); QVERIFY(mbox1.save()); MBoxEntry::List list = mbox1.entries(); // First test: Delete only the first (all messages afterwards have to be moved). mbox1.purge(MBoxEntry::List() << list.first()); MBox mbox2; QVERIFY(mbox2.load(fileName())); MBoxEntry::List list2 = mbox2.entries(); QCOMPARE(list2.size(), 2); // Is a message actually gone? quint64 newOffsetSecondMessage = list.last().messageOffset() - list.at(1).messageOffset(); QCOMPARE(list2.first().messageOffset(), static_cast(0)); QCOMPARE(list2.last().messageOffset(), newOffsetSecondMessage); // Second test: Delete the first two (the last message have to be moved). removeTestFile(); QVERIFY(mbox1.load(fileName())); mbox1.appendMessage(mMail1); mbox1.appendMessage(mMail1); mbox1.appendMessage(mMail1); QVERIFY(mbox1.save()); list = mbox1.entries(); mbox1.purge(MBoxEntry::List() << list.at(0) << list.at(1)); QVERIFY(mbox2.load(fileName())); list2 = mbox2.entries(); QCOMPARE(list2.size(), 1); // Are the messages actually gone? QCOMPARE(list2.first().messageOffset(), static_cast(0)); // Third test: Delete all messages. removeTestFile(); QVERIFY(mbox1.load(fileName())); mbox1.appendMessage(mMail1); mbox1.appendMessage(mMail1); mbox1.appendMessage(mMail1); QVERIFY(mbox1.save()); list = mbox1.entries(); mbox1.purge(MBoxEntry::List() << list.at(0) << list.at(1) << list.at(2)); QVERIFY(mbox2.load(fileName())); list2 = mbox2.entries(); QCOMPARE(list2.size(), 0); // Are the messages actually gone? } void MboxTest::testLockTimeout() { MBox mbox; mbox.load(fileName()); mbox.setLockType(MBox::None); mbox.setUnlockTimeout(1000); QVERIFY(!mbox.locked()); mbox.lock(); QVERIFY(mbox.locked()); QTest::qWait(1500); QVERIFY(!mbox.locked()); } void MboxTest::testHeaders() { MBox mbox; QVERIFY(mbox.setLockType(MBox::None)); QVERIFY(mbox.load(fileName())); mbox.appendMessage(mMail1); mbox.appendMessage(mMail2); QVERIFY(mbox.save()); const MBoxEntry::List list = mbox.entries(); foreach (const MBoxEntry &msgInfo, list) { const QByteArray header = mbox.readMessageHeaders(msgInfo); QVERIFY(!header.isEmpty()); KMime::Message *message = mbox.readMessage(msgInfo); QVERIFY(message != nullptr); KMime::Message *headers = new KMime::Message(); headers->setHead(KMime::CRLFtoLF(header)); headers->parse(); QCOMPARE(message->messageID()->identifier(), headers->messageID()->identifier()); QCOMPARE(message->subject()->as7BitString(), headers->subject()->as7BitString()); QCOMPARE(message->to()->as7BitString(), headers->to()->as7BitString()); QCOMPARE(message->from()->as7BitString(), headers->from()->as7BitString()); delete message; delete headers; } } void MboxTest::testReadOnlyMbox() { { // create a non-empty mbox file MBox mbox; QVERIFY(mbox.load(fileName())); mbox.appendMessage(mMail1); mbox.appendMessage(mMail2); QVERIFY(mbox.save()); } QFile::Permissions perm = QFile::permissions(fileName()); QFile::setPermissions(fileName(), QFile::ReadOwner); MBox mbox; QVERIFY(mbox.load(fileName())); QVERIFY(mbox.isReadOnly()); // this still works since we could save it to a different file MBoxEntry entry = mbox.appendMessage(mMail1); QVERIFY(!mbox.save()); // original mbox is read-only // reading back the appended message (from memory) QByteArray msg = mbox.readRawMessage(entry); QVERIFY(!msg.isEmpty()); // read first message from disk MBoxEntry::List list = mbox.entries(); msg = mbox.readRawMessage(list.at(0)); QVERIFY(!msg.isEmpty()); QVERIFY(!mbox.purge(list)); // original mbox is read-only QString tmpSaved = mTempDir->path() + QLatin1String("tempSaved.mbox"); QVERIFY(mbox.save(tmpSaved)); // other mbox file can be written MBox savedMbox; savedMbox.setReadOnly(); QVERIFY(savedMbox.isReadOnly()); QVERIFY(savedMbox.load(tmpSaved)); QVERIFY(!savedMbox.save()); // set back to initial permissions QFile::setPermissions(fileName(), perm); } void MboxTest::cleanupTestCase() { mTempDir->remove(); } //--------------------------------------------------------------------- ThreadFillsMBox::ThreadFillsMBox(const QString &fileName) { mbox = new MBox; QVERIFY(mbox->load(fileName)); mbox->setLockType(MBox::ProcmailLockfile); mbox->lock(); } void ThreadFillsMBox::run() { QTest::qSleep(2000); QFile file(mbox->fileName()); file.open(QIODevice::WriteOnly | QIODevice::Append); QByteArray message = KMime::CRLFtoLF(sEntry1); - file.write(QByteArray("From test@local.local ") + - QDateTime::currentDateTime().toString(Qt::ISODate).toUtf8() + "\n"); + file.write(QByteArray("From test@local.local ") + +QDateTime::currentDateTime().toString(Qt::ISODate).toUtf8() + "\n"); file.write(message); file.write("\n\n"); file.close(); mbox->unlock(); delete mbox; } diff --git a/src/mbox.cpp b/src/mbox.cpp index a4a3233..1a68161 100644 --- a/src/mbox.cpp +++ b/src/mbox.cpp @@ -1,711 +1,711 @@ /* Copyright (c) 1996-1998 Stefan Taferner Copyright (c) 2009 Bertjan Broeksema This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. NOTE: Most of the code inside here is an slightly adjusted version of kdepim/kmail/kmfoldermbox.cpp. This is why I added a line for Stefan Taferner. Bertjan Broeksema, april 2009 */ #include "mbox.h" #include "mbox_p.h" #include "mboxentry_p.h" #include "kmbox_debug.h" #include #include #include #include using namespace KMBox; /// public methods. MBox::MBox() : d(new MBoxPrivate(this)) { // Set some sane defaults d->mFileLocked = false; d->mLockType = None; d->mUnlockTimer.setInterval(0); d->mUnlockTimer.setSingleShot(true); } MBox::~MBox() { if (d->mFileLocked) { unlock(); } d->close(); delete d; } // Appended entries works as follows: When an mbox file is loaded from disk, // d->mInitialMboxFileSize is set to the file size at that moment. New entries // are stored in memory (d->mAppendedEntries). The initial file size and the size // of the buffer determine the offset for the next message to append. MBoxEntry MBox::appendMessage(const KMime::Message::Ptr &entry) { // It doesn't make sense to add entries when we don't have an reference file. Q_ASSERT(!d->mMboxFile.fileName().isEmpty()); const QByteArray rawEntry = MBoxPrivate::escapeFrom(entry->encodedContent()); if (rawEntry.size() <= 0) { qCDebug(KMBOX_LOG) << "Message added to folder `" << d->mMboxFile.fileName() << "' contains no data. Ignoring it."; return MBoxEntry(); } int nextOffset = d->mAppendedEntries.size(); // Offset of the appended message // Make sure the byte array is large enough to check for an end character. // Then check if the required newlines are there. if (nextOffset < 1 && d->mMboxFile.size() > 0) { // Empty, add one empty line d->mAppendedEntries.append("\n"); ++nextOffset; } else if (nextOffset == 1 && d->mAppendedEntries.at(0) != '\n') { // This should actually not happen, but catch it anyway. if (d->mMboxFile.size() < 0) { d->mAppendedEntries.append("\n"); ++nextOffset; } } else if (nextOffset >= 2) { if (d->mAppendedEntries.at(nextOffset - 1) != '\n') { if (d->mAppendedEntries.at(nextOffset) != '\n') { d->mAppendedEntries.append("\n\n"); nextOffset += 2; } else { d->mAppendedEntries.append("\n"); ++nextOffset; } } } const QByteArray separator = MBoxPrivate::mboxMessageSeparator(rawEntry); d->mAppendedEntries.append(separator); d->mAppendedEntries.append(rawEntry); if (rawEntry[rawEntry.size() - 1] != '\n') { d->mAppendedEntries.append("\n\n"); } else { d->mAppendedEntries.append("\n"); } MBoxEntry resultEntry; resultEntry.d->mOffset = d->mInitialMboxFileSize + nextOffset; resultEntry.d->mMessageSize = rawEntry.size(); resultEntry.d->mSeparatorSize = separator.size(); d->mEntries << resultEntry; return resultEntry; } MBoxEntry::List MBox::entries(const MBoxEntry::List &deletedEntries) const { if (deletedEntries.isEmpty()) { // fast path return d->mEntries; } MBoxEntry::List result; result.reserve(d->mEntries.size()); for (const MBoxEntry &entry : qAsConst(d->mEntries)) { if (!deletedEntries.contains(entry)) { result << entry; } } return result; } QString MBox::fileName() const { return d->mMboxFile.fileName(); } bool MBox::load(const QString &fileName) { if (d->mFileLocked) { return false; } d->initLoad(fileName); if (!lock()) { qCDebug(KMBOX_LOG) << "Failed to lock"; return false; } d->mInitialMboxFileSize = d->mMboxFile.size(); // AFTER the file has been locked QByteArray line; QByteArray prevSeparator; quint64 offs = 0; // The offset of the next message to read. while (!d->mMboxFile.atEnd()) { quint64 pos = d->mMboxFile.pos(); line = d->mMboxFile.readLine(); // if atEnd, use mail only if there was a separator line at all, // otherwise it's not a valid mbox - if (d->isMBoxSeparator(line) || - (d->mMboxFile.atEnd() && (prevSeparator.size() != 0))) { - + if (d->isMBoxSeparator(line) + || (d->mMboxFile.atEnd() && (prevSeparator.size() != 0))) { // if we are the at the file end, update pos to not forget the last line if (d->mMboxFile.atEnd()) { pos = d->mMboxFile.pos(); } // Found the separator or at end of file, the message starts at offs quint64 msgSize = pos - offs; if (pos > 0) { // This is not the separator of the first mail in the file. If pos == 0 // than we matched the separator of the first mail in the file. MBoxEntry entry; entry.d->mOffset = offs; entry.d->mSeparatorSize = prevSeparator.size(); entry.d->mMessageSize = msgSize - 1; // Don't add the separator size and the newline up to the message size. entry.d->mMessageSize -= prevSeparator.size() + 1; d->mEntries << entry; } if (d->isMBoxSeparator(line)) { prevSeparator = line; } offs += msgSize; // Mark the beginning of the next message. } } // FIXME: What if unlock fails? // if no separator was found, the file is still valid if it is empty return unlock() && ((prevSeparator.size() != 0) || (d->mMboxFile.size() == 0)); } bool MBox::lock() { if (d->mMboxFile.fileName().isEmpty()) { return false; // We cannot lock if there is no file loaded. } // We can't load another file when the mbox currently is locked so if d->mFileLocked // is true atm just return true. if (locked()) { return true; } if (d->mLockType == None) { d->mFileLocked = true; if (d->open()) { d->startTimerIfNeeded(); return true; } d->mFileLocked = false; return false; } QStringList args; int rc = 0; switch (d->mLockType) { case ProcmailLockfile: args << QStringLiteral("-l20") << QStringLiteral("-r5"); if (!d->mLockFileName.isEmpty()) { args << QString::fromLocal8Bit(QFile::encodeName(d->mLockFileName)); } else { - args << QString::fromLocal8Bit(QFile::encodeName(d->mMboxFile.fileName() + - QLatin1String(".lock"))); + args << QString::fromLocal8Bit(QFile::encodeName(d->mMboxFile.fileName() + +QLatin1String(".lock"))); } rc = QProcess::execute(QStringLiteral("lockfile"), args); if (rc != 0) { qCDebug(KMBOX_LOG) << "lockfile -l20 -r5 " << d->mMboxFile.fileName() << ": Failed (" << rc << ") switching to read only mode"; d->mReadOnly = true; // In case the MBox object was created read/write we // set it to read only when locking failed. } else { d->mFileLocked = true; } break; case MuttDotlock: args << QString::fromLocal8Bit(QFile::encodeName(d->mMboxFile.fileName())); rc = QProcess::execute(QStringLiteral("mutt_dotlock"), args); if (rc != 0) { qCDebug(KMBOX_LOG) << "mutt_dotlock " << d->mMboxFile.fileName() << ": Failed (" << rc << ") switching to read only mode"; d->mReadOnly = true; // In case the MBox object was created read/write we // set it to read only when locking failed. } else { d->mFileLocked = true; } break; case MuttDotlockPrivileged: args << QStringLiteral("-p") << QString::fromLocal8Bit(QFile::encodeName(d->mMboxFile.fileName())); rc = QProcess::execute(QStringLiteral("mutt_dotlock"), args); if (rc != 0) { qCDebug(KMBOX_LOG) << "mutt_dotlock -p " << d->mMboxFile.fileName() << ":" << ": Failed (" << rc << ") switching to read only mode"; d->mReadOnly = true; } else { d->mFileLocked = true; } break; case None: d->mFileLocked = true; break; default: break; } if (d->mFileLocked) { if (!d->open()) { const bool unlocked = unlock(); Q_ASSERT(unlocked); // If this fails we're in trouble. Q_UNUSED(unlocked); } } d->startTimerIfNeeded(); return d->mFileLocked; } bool MBox::locked() const { return d->mFileLocked; } static bool lessThanByOffset(const MBoxEntry &left, const MBoxEntry &right) { return left.messageOffset() < right.messageOffset(); } bool MBox::purge(const MBoxEntry::List &deletedEntries, QList *movedEntries) { - if ( d->mMboxFile.fileName().isEmpty() || d->mReadOnly ) { + if (d->mMboxFile.fileName().isEmpty() || d->mReadOnly) { return false; // No file loaded yet or it's readOnly } if (deletedEntries.isEmpty()) { return true; // Nothing to do. } if (!lock()) { return false; } for (const MBoxEntry &entry : qAsConst(deletedEntries)) { d->mMboxFile.seek(entry.messageOffset()); const QByteArray line = d->mMboxFile.readLine(); if (!d->isMBoxSeparator(line)) { qCDebug(KMBOX_LOG) << "Found invalid separator at:" << entry.messageOffset(); unlock(); return false; // The file is messed up or the index is incorrect. } } // All entries are deleted, so just resize the file to a size of 0. if (deletedEntries.size() == d->mEntries.size()) { d->mEntries.clear(); d->mMboxFile.resize(0); qCDebug(KMBOX_LOG) << "Purge comleted successfully, unlocking the file."; return unlock(); } std::sort(d->mEntries.begin(), d->mEntries.end(), lessThanByOffset); quint64 writeOffset = 0; bool writeOffSetInitialized = false; MBoxEntry::List resultingEntryList; QList tmpMovedEntries; quint64 origFileSize = d->mMboxFile.size(); QVectorIterator i(d->mEntries); while (i.hasNext()) { MBoxEntry entry = i.next(); if (deletedEntries.contains(entry) && !writeOffSetInitialized) { writeOffset = entry.messageOffset(); writeOffSetInitialized = true; - } else if (writeOffSetInitialized && - writeOffset < entry.messageOffset() && - !deletedEntries.contains(entry)) { + } else if (writeOffSetInitialized + && writeOffset < entry.messageOffset() + && !deletedEntries.contains(entry)) { // The current message doesn't have to be deleted, but must be moved. // First determine the size of the entry that must be moved. quint64 entrySize = 0; if (i.hasNext()) { entrySize = i.next().messageOffset() - entry.messageOffset(); i.previous(); // Go back to make sure that we also handle the next entry. } else { entrySize = origFileSize - entry.messageOffset(); } Q_ASSERT(entrySize > 0); // MBox entries really cannot have a size <= 0; // we map the whole area of the file starting at the writeOffset up to the // message that have to be moved into memory. This includes eventually the // messages that are the deleted between the first deleted message // encountered and the message that has to be moved. quint64 mapSize = entry.messageOffset() + entrySize - writeOffset; // Now map writeOffSet + mapSize into mem. uchar *memArea = d->mMboxFile.map(writeOffset, mapSize); // Now read the entry that must be moved to writeOffset. quint64 startOffset = entry.messageOffset() - writeOffset; memmove(memArea, memArea + startOffset, entrySize); d->mMboxFile.unmap(memArea); MBoxEntry resultEntry; resultEntry.d->mOffset = writeOffset; resultEntry.d->mSeparatorSize = entry.separatorSize(); resultEntry.d->mMessageSize = entry.messageSize(); resultingEntryList << resultEntry; tmpMovedEntries << MBoxEntry::Pair(MBoxEntry(entry.messageOffset()), MBoxEntry(resultEntry.messageOffset())); writeOffset += entrySize; } else if (!deletedEntries.contains(entry)) { // Unmoved and not deleted entry, can only ocure before the first deleted // entry. Q_ASSERT(!writeOffSetInitialized); resultingEntryList << entry; } } // Chop off remaining entry bits. d->mMboxFile.resize(writeOffset); d->mEntries = resultingEntryList; qCDebug(KMBOX_LOG) << "Purge comleted successfully, unlocking the file."; if (movedEntries) { *movedEntries = tmpMovedEntries; } return unlock(); // FIXME: What if this fails? It will return false but the // file has changed. } QByteArray MBox::readRawMessage(const MBoxEntry &entry) { const bool wasLocked = locked(); if (!wasLocked) { if (!lock()) { return QByteArray(); } } // TODO: Add error handling in case locking failed. quint64 offset = entry.messageOffset(); Q_ASSERT(d->mFileLocked); Q_ASSERT(d->mMboxFile.isOpen()); Q_ASSERT((d->mInitialMboxFileSize + d->mAppendedEntries.size()) > offset); QByteArray message; if (offset < d->mInitialMboxFileSize) { d->mMboxFile.seek(offset); QByteArray line = d->mMboxFile.readLine(); if (!d->isMBoxSeparator(line)) { qCDebug(KMBOX_LOG) << "[MBox::readEntry] Invalid entry at:" << offset; if (!wasLocked) { unlock(); } return QByteArray(); // The file is messed up or the index is incorrect. } line = d->mMboxFile.readLine(); while (!d->isMBoxSeparator(line)) { message += line; if (d->mMboxFile.atEnd()) { break; } line = d->mMboxFile.readLine(); } } else { offset -= d->mInitialMboxFileSize; if (offset > static_cast(d->mAppendedEntries.size())) { if (!wasLocked) { unlock(); } return QByteArray(); } QBuffer buffer(&(d->mAppendedEntries)); buffer.open(QIODevice::ReadOnly); buffer.seek(offset); QByteArray line = buffer.readLine(); if (!d->isMBoxSeparator(line)) { qCDebug(KMBOX_LOG) << "[MBox::readEntry] Invalid appended entry at:" << offset; if (!wasLocked) { unlock(); } return QByteArray(); // The file is messed up or the index is incorrect. } line = buffer.readLine(); while (!d->isMBoxSeparator(line) && !buffer.atEnd()) { message += line; line = buffer.readLine(); } } // Remove te last '\n' added by writeEntry. if (message.endsWith('\n')) { message.chop(1); } MBoxPrivate::unescapeFrom(message.data(), message.size()); if (!wasLocked) { if (!d->startTimerIfNeeded()) { const bool unlocked = unlock(); Q_ASSERT(unlocked); Q_UNUSED(unlocked); } } return message; } KMime::Message *MBox::readMessage(const MBoxEntry &entry) { const QByteArray message = readRawMessage(entry); if (message.isEmpty()) { return nullptr; } KMime::Message *mail = new KMime::Message(); mail->setContent(KMime::CRLFtoLF(message)); mail->parse(); return mail; } QByteArray MBox::readMessageHeaders(const MBoxEntry &entry) { const bool wasLocked = d->mFileLocked; if (!wasLocked) { if (!lock()) { qCDebug(KMBOX_LOG) << "Failed to lock"; return QByteArray(); } } const quint64 offset = entry.messageOffset(); Q_ASSERT(d->mFileLocked); Q_ASSERT(d->mMboxFile.isOpen()); Q_ASSERT((d->mInitialMboxFileSize + d->mAppendedEntries.size()) > offset); QByteArray headers; if (offset < d->mInitialMboxFileSize) { d->mMboxFile.seek(offset); QByteArray line = d->mMboxFile.readLine(); while (line[0] != '\n' && !d->mMboxFile.atEnd()) { headers += line; line = d->mMboxFile.readLine(); } } else { QBuffer buffer(&(d->mAppendedEntries)); buffer.open(QIODevice::ReadOnly); buffer.seek(offset - d->mInitialMboxFileSize); QByteArray line = buffer.readLine(); while (line[0] != '\n' && !buffer.atEnd()) { headers += line; line = buffer.readLine(); } } if (!wasLocked) { unlock(); } return headers; } bool MBox::save(const QString &fileName) { if (!fileName.isEmpty() && QUrl(fileName).toLocalFile() != d->mMboxFile.fileName()) { if (!d->mMboxFile.copy(fileName)) { return false; } else { // if the original file was read-only, also the copied file is read-only // Let's make it writable now QFile::setPermissions(fileName, d->mMboxFile.permissions() | QFile::WriteOwner); } if (d->mAppendedEntries.isEmpty()) { return true; // Nothing to do } QFile otherFile(fileName); Q_ASSERT(otherFile.exists()); if (!otherFile.open(QIODevice::ReadWrite)) { return false; } otherFile.seek(d->mMboxFile.size()); otherFile.write(d->mAppendedEntries); // Don't clear mAppendedEntries and don't update mInitialFileSize. These // are still valid for the original file. return true; } - if ( d->mReadOnly ) + if (d->mReadOnly) { return false; + } if (d->mAppendedEntries.isEmpty()) { return true; // Nothing to do. } if (!lock()) { return false; } Q_ASSERT(d->mMboxFile.isOpen()); d->mMboxFile.seek(d->mMboxFile.size()); d->mMboxFile.write(d->mAppendedEntries); d->mAppendedEntries.clear(); d->mInitialMboxFileSize = d->mMboxFile.size(); return unlock(); } bool MBox::setLockType(LockType ltype) { if (d->mFileLocked) { qCDebug(KMBOX_LOG) << "File is currently locked."; return false; // Don't change the method if the file is currently locked. } switch (ltype) { case ProcmailLockfile: if (QStandardPaths::findExecutable(QStringLiteral("lockfile")).isEmpty()) { qCDebug(KMBOX_LOG) << "Could not find the lockfile executable"; return false; } break; case MuttDotlock: // fall through case MuttDotlockPrivileged: if (QStandardPaths::findExecutable(QStringLiteral("mutt_dotlock")).isEmpty()) { qCDebug(KMBOX_LOG) << "Could not find the mutt_dotlock executable"; return false; } break; default: break; // We assume fcntl available and lock_none doesn't need a check. } d->mLockType = ltype; return true; } void MBox::setLockFile(const QString &lockFile) { d->mLockFileName = lockFile; } void MBox::setUnlockTimeout(int msec) { d->mUnlockTimer.setInterval(msec); } bool MBox::unlock() { if (d->mLockType == None && !d->mFileLocked) { d->mFileLocked = false; d->mMboxFile.close(); return true; } int rc = 0; QStringList args; switch (d->mLockType) { case ProcmailLockfile: // QFile::remove returns true on succes so negate the result. if (!d->mLockFileName.isEmpty()) { rc = !QFile(d->mLockFileName).remove(); } else { rc = !QFile(d->mMboxFile.fileName() + QLatin1String(".lock")).remove(); } break; case MuttDotlock: args << QStringLiteral("-u") << QString::fromLocal8Bit(QFile::encodeName(d->mMboxFile.fileName())); rc = QProcess::execute(QStringLiteral("mutt_dotlock"), args); break; case MuttDotlockPrivileged: args << QStringLiteral("-u") << QStringLiteral("-p") << QString::fromLocal8Bit(QFile::encodeName(d->mMboxFile.fileName())); rc = QProcess::execute(QStringLiteral("mutt_dotlock"), args); break; case None: // Fall through. default: break; } if (rc == 0) { // Unlocking succeeded d->mFileLocked = false; } d->mMboxFile.close(); return !d->mFileLocked; } void MBox::setReadOnly(bool ro) { d->mReadOnly = ro; } bool MBox::isReadOnly() const { return d->mReadOnly; } diff --git a/src/mbox.h b/src/mbox.h index 2132138..fbc5093 100644 --- a/src/mbox.h +++ b/src/mbox.h @@ -1,271 +1,267 @@ /* Copyright (c) 2009 Bertjan Broeksema This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KMBOX_MBOX_H #define KMBOX_MBOX_H #include "kmbox_export.h" #include "mboxentry.h" #include -namespace KMBox -{ - +namespace KMBox { class MBoxPrivate; /** * @short A class to access mail storages in MBox format. * * @author Bertjan Broeksema * @since 4.6 */ class KMBOX_EXPORT MBox { public: /** * Describes the type of locking that will be used. */ enum LockType { ProcmailLockfile, MuttDotlock, MuttDotlockPrivileged, None }; /** * Creates a new mbox object. */ MBox(); /** * Destroys the mbox object. * * The file will be unlocked if it is still open. */ ~MBox(); /** * Appends @p message to the MBox and returns the corresponding mbox entry for it. * You must load a mbox file by making a call to load( const QString& ) before * appending entries. * The returned mbox entry is only valid for that particular file. * * @param message The message to append to the mbox. * @return the corresponding mbox entry for the message in the file or an invalid mbox entry * if the message was not added. */ MBoxEntry appendMessage(const KMime::Message::Ptr &message); /** * Retrieve the mbox entry objects for all emails from the file except the * @p deleteEntries. * The @p deletedEntries should be a list of mbox entries with offsets of deleted messages. * @param deletedEntries list of mbox entries that have been deleted and need not be retrieved * Note: One must call load() before calling this method. */ MBoxEntry::List entries(const MBoxEntry::List &deletedEntries = MBoxEntry::List()) const; /** * Returns the file name that was passed to the last call to load(). */ QString fileName() const; /** * Loads the raw mbox data from disk into the current MBox object. Messages * already present are not preserved. This method does not load the * full messages into memory but only the offsets of the messages and their * sizes. If the file currently is locked this method will do nothing and * return false. Appended messages that are not written yet will get lost. * * @param fileName the name of the mbox on disk. * @return true, if successful, false on error. * * @see save( const QString & ) */ bool load(const QString &fileName); /** * Locks the mbox file using the configured lock method. This can be used * for consecutive calls to readMessage and readMessageHeaders. Calling lock() * before these calls prevents the mbox file being locked for every call. * * NOTE: Even when the lock method is None the mbox is internally marked as * locked. This means that it must be unlocked before calling load(). * * @return true if locked successful, false on error. * * @see setLockType( LockType ), unlock() */ bool lock(); /** * Returns whether or not the mbox currently is locked. */ bool locked() const; /** * Removes all messages for the given mbox entries from the current reference file * (the file that is loaded with load( const QString & ). * This method will first check if all lines at the offsets are actually * separator lines if this is not then no message will be deleted to prevent * corruption. * * @param deletedEntries The mbox entries of the messages that should be removed from * the file. * @param movedEntries Optional list for storing pairs of mbox entries that got moved * within the file due to the deletions. * The @c first member of the pair is the entry with the original offsets * the @c second member is the entry with the new (current) offset * * @return true if all offsets refer to a mbox separator line and a file was * loaded, false otherwise. If the latter, the physical file has * not changed. */ bool purge(const MBoxEntry::List &deletedEntries, QList *movedEntries = nullptr); /** * Reads the entire message from the file for the given mbox @p entry. If the * mbox file is not locked this method will lock the file before reading and * unlock it after reading. If the file already is locked, it will not * unlock the file after reading the entry. * * @param entry The entry in the mbox file. * @return Message for the given entry or 0 if the file could not be locked * or the entry offset > fileSize. * * @see lock(), unlock() */ KMime::Message *readMessage(const MBoxEntry &entry); /** * Reads the headers of the message for the given mbox @p entry. If the * mbox file is not locked this method will lock the file before reading and * unlock it after reading. If the file already is locked, it will not * unlock the file after reading the entry. * * @param entry The entry in the mbox file. * @return QByteArray containing the raw message header data. * * @see lock(), unlock() */ QByteArray readMessageHeaders(const MBoxEntry &entry); /** * Reads the entire message from the file for the given mbox @p entry. If the * mbox file is not locked this method will lock the file before reading and * unlock it after reading. If the file already is locked, it will not * unlock the file after reading the entry. * * @param entry The entry in the mbox file. * @return QByteArray containing the raw message data. * * @see lock(), unlock() */ QByteArray readRawMessage(const MBoxEntry &entry); /** * Writes the mbox to disk. If the fileName is empty only appended messages * will be written to the file that was passed to load( const QString & ). * Otherwise the contents of the file that was loaded with load is copied to * @p fileName first. * * @param fileName the name of the file * @return true if the save was successful; false otherwise. * * @see load( const QString & ) */ bool save(const QString &fileName = QString()); /** * Sets the locktype that should be used for locking the mbox file. If the * new LockType cannot be used (e.g. the lockfile executable could not be * found) the LockType will not be changed. * @param ltype the locktype to set * This method will not do anything if the mbox object is currently locked * to make sure that it doesn't leave a locked file for one of the lockfile * / mutt_dotlock methods. */ bool setLockType(LockType ltype); /** * Sets the lockfile that should be used by the procmail or the KDE lock * file method. If this method is not called and one of the before mentioned * lock methods is used the name of the lock file will be equal to * MBOXFILENAME.lock. * @param lockFile the lockfile to set */ void setLockFile(const QString &lockFile); /** * By default the unlock method will directly unlock the file. However this * is expensive in case of many consecutive calls to readEntry. Setting the * time out to a non zero value will keep the lock open until the timeout has * passed. On each read the timer will be reset. * @param msec the time out to set for file lock */ void setUnlockTimeout(int msec); /** * Unlock the mbox file. * * @return true if the unlock was successful, false otherwise. * * @see lock() */ bool unlock(); /** * Set the access mode of the mbox file to read only. * * If this is set to true, the mbox file can only be read from disk. * When the mbox file given in load() can not be opened in readWrite mode, * but can be opened in readOnly mode, this flag is automatically set to true. * You can still append messages, which are stored in memory * until save() is called, but the mbox can not be saved/purged to itself. * However it is possible to save it to a different file. * @param ro the readOnly flag to use * * @see save( const QString & ) * * @since 4.14.5 */ void setReadOnly(bool ro = true); /** * Returns if the current access mode is set to readOnly. * * The access mode can either be set explicitely with setReadOnly() or * implicitely by calling load() on a readOnly file. * * @since 4.14.5 */ bool isReadOnly() const; - private: //@cond PRIVATE Q_DISABLE_COPY(MBox) friend class MBoxPrivate; MBoxPrivate *const d; //@endcond }; - } #endif // KMBOX_MBOX_H diff --git a/src/mbox_p.cpp b/src/mbox_p.cpp index 4f8bd59..b47002a 100644 --- a/src/mbox_p.cpp +++ b/src/mbox_p.cpp @@ -1,228 +1,232 @@ /* Copyright (c) 2009 Bertjan Broeksema This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mbox_p.h" #include "kmbox_debug.h" #include #include using namespace KMBox; MBoxPrivate::MBoxPrivate(MBox *mbox) - : mInitialMboxFileSize(0), mMBox(mbox), - mSeparatorMatcher(QStringLiteral("^From .*[0-9][0-9]:[0-9][0-9]")), mLockType(MBox::None), mFileLocked(false), mReadOnly(false) + : mInitialMboxFileSize(0) + , mMBox(mbox) + , mSeparatorMatcher(QStringLiteral("^From .*[0-9][0-9]:[0-9][0-9]")) + , mLockType(MBox::None) + , mFileLocked(false) + , mReadOnly(false) { connect(&mUnlockTimer, &QTimer::timeout, this, &MBoxPrivate::unlockMBox); } MBoxPrivate::~MBoxPrivate() { if (mMboxFile.isOpen()) { mMboxFile.close(); } } bool MBoxPrivate::open() { if (mMboxFile.isOpen()) { return true; // already open } QIODevice::OpenMode mode = mReadOnly ? QIODevice::ReadOnly : QIODevice::ReadWrite; - if ( !mMboxFile.open( mode ) ) { // messages file + if (!mMboxFile.open(mode)) { // messages file // failed to open readWrite -> try to open readOnly - if ( !mMboxFile.open( QIODevice::ReadOnly ) ) { + if (!mMboxFile.open(QIODevice::ReadOnly)) { qCDebug(KMBOX_LOG) << "Cannot open mbox file `" << mMboxFile.fileName() << "' FileError:" - << mMboxFile.errorString(); + << mMboxFile.errorString(); return false; } else { mReadOnly = true; } } return true; } void MBoxPrivate::close() { if (mMboxFile.isOpen()) { mMboxFile.close(); } mFileLocked = false; } void MBoxPrivate::initLoad(const QString &fileName) { QUrl url = QUrl::fromLocalFile(fileName); mMboxFile.setFileName(url.toLocalFile()); mAppendedEntries.clear(); mEntries.clear(); } bool MBoxPrivate::startTimerIfNeeded() { if (mUnlockTimer.interval() > 0) { mUnlockTimer.start(); return true; } return false; } void MBoxPrivate::unlockMBox() { mMBox->unlock(); } QByteArray MBoxPrivate::mboxMessageSeparator(const QByteArray &msg) { KMime::Message mail; QByteArray body, header; KMime::HeaderParsing::extractHeaderAndBody(KMime::CRLFtoLF(msg), header, body); body.clear(); mail.setHead(header); mail.parse(); QByteArray separator = "From "; KMime::Headers::From *from = mail.from(false); if (!from || from->addresses().isEmpty()) { separator += "unknown@unknown.invalid"; } else { separator += from->addresses().at(0) + ' '; } // format dateTime according to the mbox "standard" RFC4155 KMime::Headers::Date *date = mail.date(false); QDateTime dateTime; if (!date || date->isEmpty()) { dateTime = QDateTime::currentDateTimeUtc(); } else { dateTime = date->dateTime().toUTC(); } separator += QLocale::c().toString(dateTime, QStringLiteral("ddd MMM dd HH:mm:ss yyyy")).toUtf8() + '\n'; return separator; } #define STRDIM(x) (sizeof(x)/sizeof(*x)-1) QByteArray MBoxPrivate::escapeFrom(const QByteArray &str) { const unsigned int strLen = str.length(); if (strLen <= STRDIM("From ")) { return str; } // worst case: \nFrom_\nFrom_\nFrom_... => grows to 7/6 QByteArray result(int(strLen + 5) / 6 * 7 + 1, '\0'); const char *s = str.data(); const char *const e = s + strLen - STRDIM("From "); char *d = result.data(); bool onlyAnglesAfterLF = false; // dont' match ^From_ while (s < e) { switch (*s) { case '\n': onlyAnglesAfterLF = true; break; case '>': break; case 'F': if (onlyAnglesAfterLF && qstrncmp(s + 1, "rom ", STRDIM("rom ")) == 0) { *d++ = '>'; } // fall through Q_FALLTHROUGH(); default: onlyAnglesAfterLF = false; break; } *d++ = *s++; } while (s < str.data() + strLen) { *d++ = *s++; } result.truncate(d - result.data()); return result; } // performs (\n|^)>{n}From_ -> \1>{n-1}From_ conversion void MBoxPrivate::unescapeFrom(char *str, size_t strLen) { if (!str) { return; } if (strLen <= STRDIM(">From ")) { return; } // yes, *d++ = *s++ is a no-op as long as d == s (until after the // first >From_), but writes are cheap compared to reads and the // data is already in the cache from the read, so special-casing // might even be slower... const char *s = str; char *d = str; const char *const e = str + strLen - STRDIM(">From "); while (s < e) { if (*s == '\n' && *(s + 1) == '>') { // we can do the lookahead, // since e is 6 chars from the end! *d++ = *s++; // == '\n' *d++ = *s++; // == '>' while (s < e && *s == '>') { *d++ = *s++; } if (qstrncmp(s, "From ", STRDIM("From ")) == 0) { --d; } } *d++ = *s++; // yes, s might be e here, but e is not the end :-) } // copy the rest: while (s < str + strLen) { *d++ = *s++; } if (d < s) { // only NUL-terminate if it's shorter *d = 0; } } bool MBoxPrivate::isMBoxSeparator(const QByteArray &line) const { if (!line.startsWith("From ")) { //krazy:exclude=strings return false; } return mSeparatorMatcher.indexIn(QString::fromLatin1(line)) >= 0; } #undef STRDIM diff --git a/src/mbox_p.h b/src/mbox_p.h index 434ee17..e2a5d85 100644 --- a/src/mbox_p.h +++ b/src/mbox_p.h @@ -1,84 +1,80 @@ /* Copyright (c) 2009 Bertjan Broeksema This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KMBOX_MBOX_P_H #define KMBOX_MBOX_P_H #include "mbox.h" #include #include #include -namespace KMBox -{ - +namespace KMBox { class MBoxPrivate : public QObject { Q_OBJECT public: MBoxPrivate(MBox *mbox); virtual ~MBoxPrivate(); void close(); void initLoad(const QString &fileName); bool open(); bool startTimerIfNeeded(); bool isMBoxSeparator(const QByteArray &line) const; public Q_SLOTS: void unlockMBox(); public: - QByteArray mAppendedEntries; + QByteArray mAppendedEntries; MBoxEntry::List mEntries; - quint64 mInitialMboxFileSize = 0; - QString mLockFileName; - MBox *mMBox = nullptr; - QFile mMboxFile; - QTimer mUnlockTimer; - QRegExp mSeparatorMatcher; - MBox::LockType mLockType; - bool mFileLocked = false; - bool mReadOnly = false; - + quint64 mInitialMboxFileSize = 0; + QString mLockFileName; + MBox *mMBox = nullptr; + QFile mMboxFile; + QTimer mUnlockTimer; + QRegExp mSeparatorMatcher; + MBox::LockType mLockType; + bool mFileLocked = false; + bool mReadOnly = false; public: /// Static helper methods static QByteArray escapeFrom(const QByteArray &msg); /** * Generates a mbox message sperator line for given message. */ static QByteArray mboxMessageSeparator(const QByteArray &msg); /** * Unescapes the raw message read from the file. */ static void unescapeFrom(char *msg, size_t size); }; - } #endif // KMBOX_MBOX_P_H diff --git a/src/mboxentry.cpp b/src/mboxentry.cpp index b1bbd66..6c6381e 100644 --- a/src/mboxentry.cpp +++ b/src/mboxentry.cpp @@ -1,83 +1,83 @@ /* Copyright (c) 2010 Tobias Koenig This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mboxentry.h" #include "mboxentry_p.h" using namespace KMBox; MBoxEntry::MBoxEntry() : d(new MBoxEntryPrivate) { } MBoxEntry::MBoxEntry(quint64 offset) : d(new MBoxEntryPrivate) { d->mOffset = offset; } MBoxEntry::MBoxEntry(const MBoxEntry &other) : d(other.d) { } MBoxEntry::~MBoxEntry() { } MBoxEntry &MBoxEntry::operator=(const MBoxEntry &other) { if (this != &other) { d = other.d; } return *this; } bool MBoxEntry::operator==(const MBoxEntry &other) const { - return (d->mOffset == other.d->mOffset); + return d->mOffset == other.d->mOffset; } bool MBoxEntry::operator!=(const MBoxEntry &other) const { return !(other == *this); } bool MBoxEntry::isValid() const { - return ((d->mOffset != 0) && (d->mMessageSize != 0)); + return (d->mOffset != 0) && (d->mMessageSize != 0); } quint64 MBoxEntry::messageOffset() const { return d->mOffset; } quint64 MBoxEntry::messageSize() const { return d->mMessageSize; } quint64 MBoxEntry::separatorSize() const { return d->mSeparatorSize; } diff --git a/src/mboxentry.h b/src/mboxentry.h index 1b8c7d4..d516191 100644 --- a/src/mboxentry.h +++ b/src/mboxentry.h @@ -1,124 +1,122 @@ /* Copyright (c) 2010 Tobias Koenig This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KMBOX_MBOXENTRY_H #define KMBOX_MBOXENTRY_H #include "kmbox_export.h" #include #include #include #include -namespace KMBox -{ +namespace KMBox { class MBoxEntryPrivate; /** * @short A class that encapsulates an entry of a MBox. * * @author Tobias Koenig * @since 4.6 */ class KMBOX_EXPORT MBoxEntry { public: /** * Describes a list of mbox entry objects. */ typedef QVector List; /** * Describes a pair of mbox entry objects. */ typedef QPair Pair; /** * Creates an invalid mbox entry object. */ MBoxEntry(); /** * Creates an mbox entry object. * * @param offset The offset of the message the object references. */ explicit MBoxEntry(quint64 offset); /** * Creates an mbox entry object from an @p other object. */ MBoxEntry(const MBoxEntry &other); /** * Destroys the mbox entry object. */ ~MBoxEntry(); /** * Replaces this mbox entry object with an @p other object. */ MBoxEntry &operator=(const MBoxEntry &other); /** * Returns whether this mbox entry object is equal to an @p other. */ bool operator==(const MBoxEntry &other) const; /** * Returns whether this mbox entry object is not equal to an @p other. */ bool operator!=(const MBoxEntry &other) const; /** * Returns whether this is a valid mbox entry object. */ bool isValid() const; /** * Returns the offset of the message that is referenced by this * mbox entry object. */ quint64 messageOffset() const; /** * Returns the size of the message that is referenced by this * mbox entry object. */ quint64 messageSize() const; /** * Returns the separator size of the message that is referenced by this * mbox entry object. */ quint64 separatorSize() const; private: //@cond PRIVATE friend class MBox; QSharedDataPointer d; //@endcond }; - } Q_DECLARE_TYPEINFO(KMBox::MBoxEntry, Q_MOVABLE_TYPE); #endif // KMBOX_MBOXENTRY_H diff --git a/src/mboxentry_p.h b/src/mboxentry_p.h index 17eca68..a064bfb 100644 --- a/src/mboxentry_p.h +++ b/src/mboxentry_p.h @@ -1,50 +1,47 @@ /* Copyright (c) 2010 Tobias Koenig This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KMBOX_MBOXENTRY_P_H #define KMBOX_MBOXENTRY_P_H #include -namespace KMBox -{ - +namespace KMBox { class MBoxEntryPrivate : public QSharedData { public: MBoxEntryPrivate() { } MBoxEntryPrivate(const MBoxEntryPrivate &other) : QSharedData(other) { mOffset = other.mOffset; mMessageSize = other.mMessageSize; mSeparatorSize = other.mSeparatorSize; } quint64 mOffset = 0; quint64 mMessageSize = 0; quint64 mSeparatorSize = 0; }; - } #endif // KMBOX_MBOXENTRY_P_H