diff --git a/kmbox/mbox.cpp b/kmbox/mbox.cpp index 70da1fa08..0b477caa8 100644 --- a/kmbox/mbox.cpp +++ b/kmbox/mbox.cpp @@ -1,663 +1,671 @@ /* 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_p.h" #include #include #include #include #include #include #include #include static QString sMBoxSeperatorRegExp( QLatin1String( "^From .*[0-9][0-9]:[0-9][0-9]" ) ); /// private static methods. static bool isMBoxSeparator( QRegExp &matcher, const QByteArray &line ) { if ( !line.startsWith( "From " ) ) { return false; } return matcher.indexIn( QString::fromLatin1( line ) ) >= 0; } /// 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. qint64 MBox::appendEntry( const MessagePtr &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 ) { kDebug() << "Message added to folder `" << d->mMboxFile.fileName() << "' contains no data. Ignoring it."; return -1; } 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; } } } - d->mAppendedEntries.append( MBoxPrivate::mboxMessageSeparator( rawEntry ) ); + 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" ); } - MsgInfo info; - info.first = d->mInitialMboxFileSize + nextOffset; - info.second = rawEntry.size(); + MsgEntryInfo info; + info.offset = d->mInitialMboxFileSize + nextOffset; + info.separatorSize = separator.size(); + info.entrySize = rawEntry.size(); d->mEntries << info; return d->mInitialMboxFileSize + nextOffset; } -QList MBox::entryList(const QSet &deletedItems) const +QList MBox::entryList(const QSet &deletedItems) const { - QList result; + QList result; - foreach ( const MsgInfo &info, d->mEntries ) { - if ( !deletedItems.contains( info.first ) ) + foreach ( const MsgEntryInfo &info, d->mEntries ) { + if ( !deletedItems.contains( info.offset ) ) result << info; } 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() ) { kDebug() << "Failed to lock"; return false; } QRegExp regexp( sMBoxSeperatorRegExp ); 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 ( isMBoxSeparator( regexp, line ) || ( d->mMboxFile.atEnd() && ( prevSeparator.size() != 0 ) ) ) { // 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. - MsgInfo info; - info.first = offs; + MsgEntryInfo info; + info.offset = offs; + info.separatorSize = line.size(); // There is always a blank line and a separator line between two emails. // Sometimes there are two '\n' characters added to the email (i.e. when // the mail self did not end with a '\n' char) and sometimes only one to // achieve this. When reading the file it is not possible to see which // was the case. if ( d->mMboxFile.atEnd() ) - info.second = msgSize; // We use readLine so there's no additional '\n' + info.entrySize = msgSize; // We use readLine so there's no additional '\n' else - info.second = msgSize - 1; + info.entrySize = msgSize - 1; // Don't add the separator size and the newline up to the message size. - info.second -= prevSeparator.size() + 1; + info.entrySize -= prevSeparator.size() + 1; d->mEntries << info; } if ( isMBoxSeparator( regexp, 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 << QLatin1String( "-l20" ) << QLatin1String( "-r5" ); if ( !d->mLockFileName.isEmpty() ) args << QString::fromLocal8Bit( QFile::encodeName( d->mLockFileName ) ); else args << QString::fromLocal8Bit( QFile::encodeName( d->mMboxFile.fileName() + QLatin1String( ".lock" ) ) ); rc = QProcess::execute( QLatin1String( "lockfile" ), args); if( rc != 0 ) { kDebug() << "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( QLatin1String( "mutt_dotlock" ), args ); if( rc != 0 ) { kDebug() << "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 << QLatin1String( "-p" ) << QString::fromLocal8Bit( QFile::encodeName( d->mMboxFile.fileName() ) ); rc = QProcess::execute( QLatin1String( "mutt_dotlock" ), args ); if( rc != 0 ) { kDebug() << "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 MsgInfo &left, const MsgInfo &right ) +static bool lessThanByOffset( const MsgEntryInfo &left, const MsgEntryInfo &right ) { - return left.first < right.first; + return left.offset < right.offset; } bool MBox::purge( const QSet &deletedItems, QList *movedItems ) { if ( d->mMboxFile.fileName().isEmpty() ) return false; // No file loaded yet. if ( deletedItems.isEmpty() ) return true; // Nothing to do. if ( !lock() ) return false; foreach ( quint64 offset, deletedItems ) { d->mMboxFile.seek( offset ); QByteArray line = d->mMboxFile.readLine(); QRegExp regexp( sMBoxSeperatorRegExp ); if ( !isMBoxSeparator( regexp, line ) ) { qDebug() << "Found invalid separator at:" << offset; 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 ( deletedItems.size() == d->mEntries.size() ) { d->mEntries.clear(); d->mMboxFile.resize( 0 ); kDebug() << "Purge comleted successfully, unlocking the file."; return unlock(); } qSort( d->mEntries.begin(), d->mEntries.end(), lessThanByOffset ); quint64 writeOffset = 0; bool writeOffSetInitialized = false; - QList resultingEntryList; + QList resultingEntryList; QList movedEntries; quint64 origFileSize = d->mMboxFile.size(); - QListIterator i( d->mEntries ); + QListIterator i( d->mEntries ); while ( i.hasNext() ) { - MsgInfo entry = i.next(); + MsgEntryInfo entry = i.next(); - if ( deletedItems.contains( entry.first ) && !writeOffSetInitialized ) { - writeOffset = entry.first; + if ( deletedItems.contains( entry.offset ) && !writeOffSetInitialized ) { + writeOffset = entry.offset; writeOffSetInitialized = true; } else if ( writeOffSetInitialized - && writeOffset < entry.first - && !deletedItems.contains( entry.first ) ) { + && writeOffset < entry.offset + && !deletedItems.contains( entry.offset ) ) { // 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().first - entry.first; + entrySize = i.next().offset - entry.offset; i.previous(); // Go back to make sure that we also handle the next entry. } else { - entrySize = origFileSize - entry.first; + entrySize = origFileSize - entry.offset; } 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.first + entrySize - writeOffset; + quint64 mapSize = entry.offset + 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.first - writeOffset; + quint64 startOffset = entry.offset - writeOffset; memmove( memArea, memArea + startOffset, entrySize ); d->mMboxFile.unmap( memArea ); - resultingEntryList << MsgInfo( writeOffset, entry.second ); - movedEntries << MsgInfo( entry.first, writeOffset ); + MsgEntryInfo resultEntry; + resultEntry.offset = writeOffset; + resultEntry.separatorSize = entry.separatorSize; + resultEntry.entrySize = entry.entrySize; + + resultingEntryList << resultEntry; + movedEntries << MsgInfo( entry.offset, resultEntry.offset ); writeOffset += entrySize; - } else if ( !deletedItems.contains( entry.first ) ) { + } else if ( !deletedItems.contains( entry.offset ) ) { // 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; kDebug() << "Purge comleted successfully, unlocking the file."; if ( movedItems ) { *movedItems = movedEntries; } return unlock(); // FIXME: What if this fails? It will return false but the // file has changed. } QByteArray MBox::readRawEntry(quint64 offset) { bool wasLocked = locked(); if ( ! wasLocked ) { if ( ! lock() ) return QByteArray(); } // TODO: Add error handling in case locking failed. 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(); QRegExp regexp( sMBoxSeperatorRegExp ); if ( !isMBoxSeparator( regexp, line ) ) { kDebug() << "[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 ( !isMBoxSeparator( regexp, line ) && !d->mMboxFile.atEnd() ) { message += line; 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(); QRegExp regexp( sMBoxSeperatorRegExp ); if ( !isMBoxSeparator( regexp, line ) ) { kDebug() << "[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 ( !isMBoxSeparator( regexp, 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::readEntry(quint64 offset) { const QByteArray message = readRawEntry( offset ); if ( message.isEmpty() ) return 0; KMime::Message *mail = new KMime::Message(); mail->setContent( KMime::CRLFtoLF( message ) ); mail->parse(); return mail; } QByteArray MBox::readEntryHeaders( quint64 offset ) { bool wasLocked = d->mFileLocked; if ( ! wasLocked ) lock(); 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' ) { 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' ) { headers += line; line = buffer.readLine(); } } if ( ! wasLocked ) unlock(); return headers; } bool MBox::save( const QString &fileName ) { if ( !fileName.isEmpty() && KUrl( fileName ).toLocalFile() != d->mMboxFile.fileName() ) { if ( !d->mMboxFile.copy( fileName ) ) return false; if ( d->mAppendedEntries.size() == 0 ) 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->mAppendedEntries.size() == 0 ) 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) { kDebug() << "File is currently locked."; return false; // Don't change the method if the file is currently locked. } switch ( ltype ) { case ProcmailLockfile: if ( KStandardDirs::findExe( QLatin1String( "lockfile" ) ).isEmpty() ) { kDebug() << "Could not find the lockfile executable"; return false; } break; case MuttDotlock: // fall through case MuttDotlockPrivileged: if ( KStandardDirs::findExe( QLatin1String( "mutt_dotlock" ) ).isEmpty() ) { kDebug() << "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 << QLatin1String( "-u" ) << QString::fromLocal8Bit( QFile::encodeName( d->mMboxFile.fileName() ) ); rc = QProcess::execute( QLatin1String( "mutt_dotlock" ), args ); break; case MuttDotlockPrivileged: args << QLatin1String( "-u" ) << QLatin1String( "-p" ) << QString::fromLocal8Bit( QFile::encodeName( d->mMboxFile.fileName() ) ); rc = QProcess::execute( QLatin1String( "mutt_dotlock" ), args ); break; case None: // Fall through. default: break; } if ( rc == 0 ) // Unlocking succeeded d->mFileLocked = false; d->mMboxFile.close(); return !d->mFileLocked; } diff --git a/kmbox/mbox.h b/kmbox/mbox.h index 695d30421..872ed5be4 100644 --- a/kmbox/mbox.h +++ b/kmbox/mbox.h @@ -1,235 +1,242 @@ /* 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 MBOX_H #define MBOX_H #include #include #include #include #include "mbox_export.h" +struct MsgEntryInfo +{ + quint64 offset; + quint64 separatorSize; + quint64 entrySize; +}; + typedef QPair MsgInfo; // QPair typedef boost::shared_ptr MessagePtr; class MBoxPrivate; class MBOX_EXPORT MBox { public: enum LockType { ProcmailLockfile, MuttDotlock, MuttDotlockPrivileged, None }; public: MBox(); /** * Unlocks the file if it is still open. */ ~MBox(); /** * Appends @param entry to the MBox. Returns the offset in the file * where the added message starts or -1 if the entry was not added (e.g. * when it doesn't contain data). You must load a mbox file by makeing a call * to load( const QString& ) before appending entries. The returned offset * is only valid for * that particular file. * * @param entry The message to append to the mbox. * @return the offset of the entry in the file or -1 if the entry was not * added. */ qint64 appendEntry( const MessagePtr &entry ); /** * Retrieve MsgInfo objects for all emails from the file except the * @param deleteItems. The @param deletedItems should be a list of file * offsets of messages which are deleted. * - * Each MsgInfo object contains the offset and the size of the messages in - * the file which are not marked as deleted. + * Each MsgEntryInfo object contains the entry offset, the offset of the actual mail + * content and the size of the entry in the file which are not marked as deleted. * - * Note: One must call open() before calling this method. + * Note: One must call load() before calling this method. */ - QList entryList( const QSet &deletedItems = QSet() ) const; + QList entryList( const QSet &deletedItems = QSet() ) 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 readEntry and readEntryHeaders. 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 at given offsets from the current reference file * (i.e. the file that is loaded with load( const QString & ) or the file * from the last save( const QString & ) call if that was not the same file). * This method will first check if all lines at the offsets are actually * separator lines if this is not the no message will be deleted to prevent * corruption. * * @param deletedItems Offsets of the messages that should be removed from * the file. * @param movedItems Optional list for storing offset pairs into which describe * entries that got moved within the file due to the deletions. * The @c first member of the pair is the original offsets the * @c second member is the new (current) offset * * @return true if all offsets refer to a mbox separator line and a file was * loaded, false otherewhise. In the latter the physical file has * not changed. */ bool purge( const QSet &deletedItems, QList *movedItems = 0 ); /** * Reads the entire message from the file at given @param offset. 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 offset The start position of the entry in the mbox file. * @return Message at given offset or 0 if the the file could not be locked * or the offset > fileSize. * * @see lock(), unlock() */ KMime::Message *readEntry( quint64 offset ); /** * Reads the headers of the message at given @param offset. 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 offset The start position of the entry in the mbox file. * @return QByteArray containing the raw Entry data. * * @see lock(), unlock() */ QByteArray readEntryHeaders( quint64 offset ); /** * Reads the entire message from the file at given @param offset. 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 offset The start position of the entry in the mbox file. * @return Message at given offset or QByteArray() if the the file could not be locked * or the offset > fileSize. * * @see lock(), unlock() */ QByteArray readRawEntry( quint64 offset ); /** * 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. * * This method will not do anything if the mbox obeject 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. */ 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. */ void setUnlockTimeout( int msec ); /** * Unlock the mbox file. * * @return true if the unlock was successful, false otherwise. * * @see lock() */ bool unlock(); private: friend class MBoxPrivate; MBoxPrivate * const d; }; #endif // MBOX_H diff --git a/kmbox/mbox_p.h b/kmbox/mbox_p.h index bcb6d9567..c1d36cd0b 100644 --- a/kmbox/mbox_p.h +++ b/kmbox/mbox_p.h @@ -1,76 +1,76 @@ /* 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 MBOX_P_H #define MBOX_P_H #include "mbox.h" #include #include #include #include class MBoxPrivate : public QObject { Q_OBJECT public: MBoxPrivate(MBox *mbox); virtual ~MBoxPrivate(); void close(); void initLoad( QString const &fileName ); bool open(); bool startTimerIfNeeded(); public Q_SLOTS: void unlockMBox(); public: QByteArray mAppendedEntries; - QList mEntries; + QList mEntries; bool mFileLocked; quint64 mInitialMboxFileSize; QString mLockFileName; MBox::LockType mLockType; MBox *mMBox; QFile mMboxFile; bool mReadOnly; QTimer mUnlockTimer; 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 // MBOX_P_H diff --git a/kmbox/tests/mboxbenchmark.cpp b/kmbox/tests/mboxbenchmark.cpp index 0cf9c82e0..931b07953 100644 --- a/kmbox/tests/mboxbenchmark.cpp +++ b/kmbox/tests/mboxbenchmark.cpp @@ -1,126 +1,126 @@ /* 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 "mboxbenchmark.moc" #include #include #include #include QTEST_KDEMAIN( MBoxBenchmark, NoGUI ) #include "test-entries.h" static const char * testDir = "libmbox-unit-test"; static const char * testFile = "test-mbox-file"; QString MBoxBenchmark::fileName() { return mTempDir->name() + QLatin1String( testFile ); } void MBoxBenchmark::initTestCase() { mTempDir = new KTempDir( KStandardDirs::locateLocal( "tmp", QLatin1String( testDir ) ) ); mMail1 = MessagePtr( new KMime::Message ); mMail1->setContent( KMime::CRLFtoLF( sEntry1 ) ); mMail1->parse(); } void MBoxBenchmark::cleanupTestCase() { mTempDir->unlink(); } void MBoxBenchmark::testNoLockPerformance() { MBox mbox; mbox.setLockType(MBox::None); mbox.load(fileName()); for (int i = 0; i < 1000; ++i) mbox.appendEntry(mMail1); mbox.save(fileName()); QBENCHMARK { MBox mbox2; mbox2.setLockType(MBox::None); mbox2.setUnlockTimeout(5000); mbox2.load(fileName()); - foreach (MsgInfo const &info, mbox2.entryList()) { - mbox2.readEntry(info.first); + foreach (MsgEntryInfo const &info, mbox2.entryList()) { + mbox2.readEntry(info.offset); } } } void MBoxBenchmark::testProcfileLockPerformance() { mMail1 = MessagePtr( 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.appendEntry(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 (MsgInfo const &info, mbox2.entryList()) - mbox2.readEntry(info.first); + foreach (MsgEntryInfo const &info, mbox2.entryList()) + mbox2.readEntry(info.offset); } } void MBoxBenchmark::voidTestMD5Performance() { MBox mbox; mbox.setLockType(MBox::None); mbox.load(fileName()); for (int i = 0; i < 1000; ++i) mbox.appendEntry(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/kmbox/tests/mboxtest.cpp b/kmbox/tests/mboxtest.cpp index b248baea1..27cc88252 100644 --- a/kmbox/tests/mboxtest.cpp +++ b/kmbox/tests/mboxtest.cpp @@ -1,426 +1,429 @@ /* 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 "mboxtest.moc" #include #include #include #include #include QTEST_KDEMAIN( MboxTest, NoGUI ) #include "test-entries.h" 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->name() + QLatin1String( testFile ); } QString MboxTest::lockFileName() { return mTempDir->name() + QLatin1String( testLockFile ); } void MboxTest::removeTestFile() { QFile file( fileName() ); file.remove(); QVERIFY( !file.exists() ); } void MboxTest::initTestCase() { mTempDir = new KTempDir( KStandardDirs::locateLocal( "tmp" , QLatin1String( testDir ) ) ); QDir temp(mTempDir->name()); QVERIFY(temp.exists()); QFile mboxfile( fileName() ); mboxfile.open( QIODevice::WriteOnly ); mboxfile.close(); QVERIFY(mboxfile.exists()); mMail1 = MessagePtr( new KMime::Message ); mMail1->setContent( KMime::CRLFtoLF( sEntry1 ) ); mMail1->parse(); mMail2 = MessagePtr( new KMime::Message ); mMail2->setContent( KMime::CRLFtoLF( sEntry2 ) ); mMail2->parse(); } void MboxTest::testSetLockMethod() { MBox mbox1; if ( !KStandardDirs::findExe( QLatin1String( "lockfile" ) ).isEmpty() ) { QVERIFY( mbox1.setLockType(MBox::ProcmailLockfile) ); } else { QVERIFY( !mbox1.setLockType( MBox::ProcmailLockfile ) ); } if ( !KStandardDirs::findExe( QLatin1String( "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 ( !KStandardDirs::findExe( QLatin1String( "lockfile" ) ).isEmpty() ) { QVERIFY( mbox.setLockType( MBox::ProcmailLockfile ) ); QVERIFY( !mbox.lock() ); } if ( !KStandardDirs::findExe( QLatin1String( "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::testAppend() { 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.entryList().size(), 0 ); QCOMPARE( mbox.appendEntry( mMail1 ), static_cast( 0 ) ); QCOMPARE( mbox.entryList().size(), 1 ); - QCOMPARE( mbox.entryList().first().second, static_cast( sEntry1.size() ) ); + QVERIFY( mbox.entryList().first().separatorSize > 0 ); + QCOMPARE( mbox.entryList().first().entrySize, static_cast( sEntry1.size() ) ); const qint64 offsetMail2 = mbox.appendEntry( mMail2 ); QVERIFY( offsetMail2 > sEntry1.size() ); QCOMPARE( mbox.entryList().size(), 2 ); - QCOMPARE( mbox.entryList().last().second, static_cast( sEntry2.size() ) ); + QVERIFY( mbox.entryList().last().separatorSize > 0 ); + QCOMPARE( mbox.entryList().last().entrySize, static_cast( sEntry2.size() ) ); // check if appended entries can be read - QList list = mbox.entryList(); - foreach ( const MsgInfo &msgInfo, list ) { - const QByteArray header = mbox.readEntryHeaders( msgInfo.first ); + QList list = mbox.entryList(); + foreach ( const MsgEntryInfo &msgInfo, list ) { + const QByteArray header = mbox.readEntryHeaders( msgInfo.offset ); QVERIFY( !header.isEmpty() ); - KMime::Message *message = mbox.readEntry( msgInfo.first ); + KMime::Message *message = mbox.readEntry( msgInfo.offset ); QVERIFY( message != 0 ); 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.first == 0 ){ + if ( msgInfo.offset == 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.first == static_cast( offsetMail2 ) ) { + } else if ( msgInfo.offset == static_cast( offsetMail2 ) ) { 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.entryList().isEmpty() ); mbox.appendEntry( mMail1 ); mbox.appendEntry( mMail2 ); - QList infos1 = mbox.entryList(); + QList infos1 = mbox.entryList(); QCOMPARE( infos1.size(), 2 ); QVERIFY( mbox.save() ); QVERIFY( QFileInfo( fileName() ).exists() ); - QList infos2 = mbox.entryList(); + QList infos2 = mbox.entryList(); QCOMPARE( infos2.size(), 2 ); for ( int i = 0; i < 2; ++i ) { - QCOMPARE( infos1.at(i).first, infos2.at(i).first ); - QCOMPARE( infos1.at(i).second, infos2.at(i).second ); + QCOMPARE( infos1.at(i).offset, infos2.at(i).offset ); + QCOMPARE( infos1.at(i).separatorSize, infos2.at(i).separatorSize ); + QCOMPARE( infos1.at(i).entrySize, infos2.at(i).entrySize ); } MBox mbox2; QVERIFY( mbox2.setLockType( MBox::None ) ); QVERIFY( mbox2.load( fileName() ) ); - QList infos3 = mbox2.entryList(); + QList infos3 = mbox2.entryList(); QCOMPARE( infos3.size(), 2 ); for ( int i = 0; i < 2; ++i ) { - QCOMPARE( infos3.at(i).first, infos2.at(i).first ); + QCOMPARE( infos3.at(i).offset, infos2.at(i).offset ); - quint64 minSize = infos2.at(i).second; - quint64 maxSize = infos2.at(i).second + 1; - QVERIFY( infos3.at(i).second >= minSize ); - QVERIFY( infos3.at(i).second <= maxSize ); + quint64 minSize = infos2.at(i).entrySize; + quint64 maxSize = infos2.at(i).entrySize + 1; + QVERIFY( infos3.at(i).entrySize >= minSize ); + QVERIFY( infos3.at(i).entrySize <= maxSize ); } } void MboxTest::testBlankLines() { for ( int i = 0; i < 5; ++i ) { removeTestFile(); MessagePtr mail = MessagePtr( 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.appendEntry( mail ); mbox1.appendEntry( mail ); mbox1.appendEntry( mail ); mbox1.save(); MBox mbox2; QVERIFY( mbox1.setLockType( MBox::None ) ); QVERIFY( mbox1.load( fileName() ) ); QCOMPARE( mbox1.entryList().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.entryList().at( i ).second >= minSize ); - QVERIFY( mbox1.entryList().at( i ).second <= maxSize ); + QVERIFY( mbox1.entryList().at( i ).entrySize >= minSize ); + QVERIFY( mbox1.entryList().at( i ).entrySize <= maxSize ); } } } void MboxTest::testEntries() { removeTestFile(); MBox mbox1; QVERIFY( mbox1.setLockType( MBox::None ) ); QVERIFY( mbox1.load( fileName() ) ); mbox1.appendEntry( mMail1 ); mbox1.appendEntry( mMail2 ); mbox1.appendEntry( mMail1 ); - QList infos = mbox1.entryList(); + QList infos = mbox1.entryList(); QCOMPARE( infos.size() , 3 ); QSet deletedEntries; - deletedEntries << infos.at( 0 ).first; + deletedEntries << infos.at( 0 ).offset; - QList infos2 = mbox1.entryList( deletedEntries ); + QList infos2 = mbox1.entryList( deletedEntries ); QCOMPARE( infos2.size() , 2 ); - QVERIFY( infos2.first().first != infos.first().first ); - QVERIFY( infos2.last().first != infos.first().first ); + QVERIFY( infos2.first().offset != infos.first().offset ); + QVERIFY( infos2.last().offset != infos.first().offset ); - deletedEntries << infos.at( 1 ).first; + deletedEntries << infos.at( 1 ).offset; infos2 = mbox1.entryList( deletedEntries ); QCOMPARE( infos2.size() , 1 ); - QVERIFY( infos2.first().first != infos.at( 0 ).first ); - QVERIFY( infos2.first().first != infos.at( 1 ).first ); + QVERIFY( infos2.first().offset != infos.at( 0 ).offset ); + QVERIFY( infos2.first().offset != infos.at( 1 ).offset ); - deletedEntries << infos.at( 2 ).first; + deletedEntries << infos.at( 2 ).offset; infos2 = mbox1.entryList( deletedEntries ); QCOMPARE( infos2.size() , 0 ); QVERIFY( !deletedEntries.contains( 10 ) ); // some random offset infos2 = mbox1.entryList( QSet() << 10 ); QCOMPARE( infos2.size() , 3 ); - QCOMPARE( infos2.at( 0 ).first, infos.at( 0 ).first ); - QCOMPARE( infos2.at( 1 ).first, infos.at( 1 ).first ); - QCOMPARE( infos2.at( 2 ).first, infos.at( 2 ).first ); + QCOMPARE( infos2.at( 0 ).offset, infos.at( 0 ).offset ); + QCOMPARE( infos2.at( 1 ).offset, infos.at( 1 ).offset ); + QCOMPARE( infos2.at( 2 ).offset, infos.at( 2 ).offset ); } void MboxTest::testPurge() { MBox mbox1; QVERIFY( mbox1.setLockType( MBox::None ) ); QVERIFY( mbox1.load( fileName() ) ); mbox1.appendEntry( mMail1 ); mbox1.appendEntry( mMail1 ); mbox1.appendEntry( mMail1 ); QVERIFY( mbox1.save() ); - QList list = mbox1.entryList(); + QList list = mbox1.entryList(); // First test: Delete only the first (all messages afterwards have to be moved). - mbox1.purge( QSet() << list.first().first ); + mbox1.purge( QSet() << list.first().offset ); MBox mbox2; QVERIFY( mbox2.load( fileName() ) ); - QList list2 = mbox2.entryList(); + QList list2 = mbox2.entryList(); QCOMPARE( list2.size(), 2 ); // Is a message actually gone? - quint64 newOffsetSecondMessage = list.last().first - list.at( 1 ).first; + quint64 newOffsetSecondMessage = list.last().offset - list.at( 1 ).offset; - QCOMPARE( list2.first().first, static_cast( 0 ) ); - QCOMPARE( list2.last().first, newOffsetSecondMessage ); + QCOMPARE( list2.first().offset, static_cast( 0 ) ); + QCOMPARE( list2.last().offset, newOffsetSecondMessage ); // Second test: Delete the first two (the last message have to be moved). removeTestFile(); QVERIFY( mbox1.load( fileName() ) ); mbox1.appendEntry( mMail1 ); mbox1.appendEntry( mMail1 ); mbox1.appendEntry( mMail1 ); QVERIFY( mbox1.save() ); list = mbox1.entryList(); - mbox1.purge( QSet() << list.at( 0 ).first << list.at( 1 ).first ); + mbox1.purge( QSet() << list.at( 0 ).offset << list.at( 1 ).offset ); QVERIFY( mbox2.load( fileName() ) ); list2 = mbox2.entryList(); QCOMPARE( list2.size(), 1 ); // Are the messages actually gone? - QCOMPARE( list2.first().first, static_cast( 0 ) ); + QCOMPARE( list2.first().offset, static_cast( 0 ) ); // Third test: Delete all messages. removeTestFile(); QVERIFY( mbox1.load( fileName() ) ); mbox1.appendEntry( mMail1 ); mbox1.appendEntry( mMail1 ); mbox1.appendEntry( mMail1 ); QVERIFY( mbox1.save() ); list = mbox1.entryList(); - mbox1.purge( QSet() << list.at( 0 ).first << list.at( 1 ).first << list.at( 2 ).first ); + mbox1.purge( QSet() << list.at( 0 ).offset << list.at( 1 ).offset << list.at( 2 ).offset ); QVERIFY( mbox2.load( fileName() ) ); list2 = mbox2.entryList(); 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(1010); QVERIFY(!mbox.locked()); } void MboxTest::testHeaders() { MBox mbox; QVERIFY( mbox.setLockType( MBox::None ) ); QVERIFY( mbox.load( fileName() ) ); mbox.appendEntry( mMail1 ); mbox.appendEntry( mMail2 ); QVERIFY( mbox.save() ); - const QList list = mbox.entryList(); + const QList list = mbox.entryList(); - foreach ( const MsgInfo &msgInfo, list ) { - const QByteArray header = mbox.readEntryHeaders( msgInfo.first ); + foreach ( const MsgEntryInfo &msgInfo, list ) { + const QByteArray header = mbox.readEntryHeaders( msgInfo.offset ); QVERIFY( !header.isEmpty() ); - KMime::Message *message = mbox.readEntry( msgInfo.first ); + KMime::Message *message = mbox.readEntry( msgInfo.offset ); QVERIFY( message != 0 ); 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::cleanupTestCase() { mTempDir->unlink(); }