00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027 #include "mbox.h"
00028 #include "mbox_p.h"
00029 #include "mboxentry_p.h"
00030
00031 #include <KDebug>
00032 #include <KStandardDirs>
00033 #include <KUrl>
00034
00035 #include <QtCore/QBuffer>
00036 #include <QtCore/QProcess>
00037
00038 using namespace KMBox;
00039
00041
00042 MBox::MBox()
00043 : d( new MBoxPrivate( this ) )
00044 {
00045
00046 d->mFileLocked = false;
00047 d->mLockType = None;
00048
00049 d->mUnlockTimer.setInterval( 0 );
00050 d->mUnlockTimer.setSingleShot( true );
00051 }
00052
00053 MBox::~MBox()
00054 {
00055 if ( d->mFileLocked ) {
00056 unlock();
00057 }
00058
00059 d->close();
00060
00061 delete d;
00062 }
00063
00064
00065
00066
00067
00068 MBoxEntry MBox::appendMessage( const KMime::Message::Ptr &entry )
00069 {
00070
00071 Q_ASSERT( !d->mMboxFile.fileName().isEmpty() );
00072
00073 const QByteArray rawEntry = MBoxPrivate::escapeFrom( entry->encodedContent() );
00074
00075 if ( rawEntry.size() <= 0 ) {
00076 kDebug() << "Message added to folder `" << d->mMboxFile.fileName()
00077 << "' contains no data. Ignoring it.";
00078 return MBoxEntry();
00079 }
00080
00081 int nextOffset = d->mAppendedEntries.size();
00082
00083
00084
00085 if ( nextOffset < 1 && d->mMboxFile.size() > 0 ) {
00086 d->mAppendedEntries.append( "\n" );
00087 ++nextOffset;
00088 } else if ( nextOffset == 1 && d->mAppendedEntries.at( 0 ) != '\n' ) {
00089
00090 if ( d->mMboxFile.size() < 0 ) {
00091 d->mAppendedEntries.append( "\n" );
00092 ++nextOffset;
00093 }
00094 } else if ( nextOffset >= 2 ) {
00095 if ( d->mAppendedEntries.at( nextOffset - 1 ) != '\n' ) {
00096 if ( d->mAppendedEntries.at( nextOffset ) != '\n' ) {
00097 d->mAppendedEntries.append( "\n\n" );
00098 nextOffset += 2;
00099 } else {
00100 d->mAppendedEntries.append( "\n" );
00101 ++nextOffset;
00102 }
00103 }
00104 }
00105
00106 const QByteArray separator = MBoxPrivate::mboxMessageSeparator( rawEntry );
00107 d->mAppendedEntries.append( separator );
00108 d->mAppendedEntries.append( rawEntry );
00109 if ( rawEntry[rawEntry.size() - 1] != '\n' ) {
00110 d->mAppendedEntries.append( "\n\n" );
00111 } else {
00112 d->mAppendedEntries.append( "\n" );
00113 }
00114
00115 MBoxEntry resultEntry;
00116 resultEntry.d->mOffset = d->mInitialMboxFileSize + nextOffset;
00117 resultEntry.d->mMessageSize = rawEntry.size();
00118 resultEntry.d->mSeparatorSize = separator.size();
00119 d->mEntries << resultEntry;
00120
00121 return resultEntry;
00122 }
00123
00124 MBoxEntry::List MBox::entries( const MBoxEntry::List &deletedEntries ) const
00125 {
00126 MBoxEntry::List result;
00127
00128 foreach ( const MBoxEntry &entry, d->mEntries ) {
00129 if ( !deletedEntries.contains( entry ) ) {
00130 result << entry;
00131 }
00132 }
00133
00134 return result;
00135 }
00136
00137 QString MBox::fileName() const
00138 {
00139 return d->mMboxFile.fileName();
00140 }
00141
00142 bool MBox::load( const QString &fileName )
00143 {
00144 if ( d->mFileLocked ) {
00145 return false;
00146 }
00147
00148 d->initLoad( fileName );
00149
00150 if ( !lock() ) {
00151 kDebug() << "Failed to lock";
00152 return false;
00153 }
00154
00155 QByteArray line;
00156 QByteArray prevSeparator;
00157 quint64 offs = 0;
00158
00159 while ( !d->mMboxFile.atEnd() ) {
00160 quint64 pos = d->mMboxFile.pos();
00161
00162 line = d->mMboxFile.readLine();
00163
00164
00165
00166 if ( d->isMBoxSeparator( line ) ||
00167 ( d->mMboxFile.atEnd() && ( prevSeparator.size() != 0 ) ) ) {
00168
00169
00170 quint64 msgSize = pos - offs;
00171
00172 if( pos > 0 ) {
00173
00174
00175 MBoxEntry entry;
00176 entry.d->mOffset = offs;
00177 entry.d->mSeparatorSize = prevSeparator.size();
00178
00179
00180
00181
00182
00183
00184 if ( d->mMboxFile.atEnd() ) {
00185 entry.d->mMessageSize = msgSize;
00186 } else {
00187 entry.d->mMessageSize = msgSize - 1;
00188 }
00189
00190
00191 entry.d->mMessageSize -= prevSeparator.size() + 1;
00192
00193 d->mEntries << entry;
00194 }
00195
00196 if ( d->isMBoxSeparator( line ) ) {
00197 prevSeparator = line;
00198 }
00199
00200 offs += msgSize;
00201 }
00202 }
00203
00204
00205
00206 return unlock() && ( ( prevSeparator.size() != 0 ) || ( d->mMboxFile.size() == 0 ) );
00207 }
00208
00209 bool MBox::lock()
00210 {
00211 if ( d->mMboxFile.fileName().isEmpty() ) {
00212 return false;
00213 }
00214
00215
00216
00217 if ( locked() ) {
00218 return true;
00219 }
00220
00221 if ( d->mLockType == None ) {
00222 d->mFileLocked = true;
00223 if ( d->open() ) {
00224 d->startTimerIfNeeded();
00225 return true;
00226 }
00227
00228 d->mFileLocked = false;
00229 return false;
00230 }
00231
00232 QStringList args;
00233 int rc = 0;
00234
00235 switch( d->mLockType ) {
00236 case ProcmailLockfile:
00237 args << QLatin1String( "-l20" ) << QLatin1String( "-r5" );
00238 if ( !d->mLockFileName.isEmpty() ) {
00239 args << QString::fromLocal8Bit( QFile::encodeName( d->mLockFileName ) );
00240 } else {
00241 args << QString::fromLocal8Bit( QFile::encodeName( d->mMboxFile.fileName() +
00242 QLatin1String( ".lock" ) ) );
00243 }
00244
00245 rc = QProcess::execute( QLatin1String( "lockfile" ), args );
00246 if ( rc != 0 ) {
00247 kDebug() << "lockfile -l20 -r5 " << d->mMboxFile.fileName()
00248 << ": Failed ("<< rc << ") switching to read only mode";
00249 d->mReadOnly = true;
00250
00251 } else {
00252 d->mFileLocked = true;
00253 }
00254 break;
00255
00256 case MuttDotlock:
00257 args << QString::fromLocal8Bit( QFile::encodeName( d->mMboxFile.fileName() ) );
00258 rc = QProcess::execute( QLatin1String( "mutt_dotlock" ), args );
00259
00260 if ( rc != 0 ) {
00261 kDebug() << "mutt_dotlock " << d->mMboxFile.fileName()
00262 << ": Failed (" << rc << ") switching to read only mode";
00263 d->mReadOnly = true;
00264
00265 } else {
00266 d->mFileLocked = true;
00267 }
00268 break;
00269
00270 case MuttDotlockPrivileged:
00271 args << QLatin1String( "-p" )
00272 << QString::fromLocal8Bit( QFile::encodeName( d->mMboxFile.fileName() ) );
00273 rc = QProcess::execute( QLatin1String( "mutt_dotlock" ), args );
00274
00275 if ( rc != 0 ) {
00276 kDebug() << "mutt_dotlock -p " << d->mMboxFile.fileName() << ":"
00277 << ": Failed (" << rc << ") switching to read only mode";
00278 d->mReadOnly = true;
00279 } else {
00280 d->mFileLocked = true;
00281 }
00282 break;
00283
00284 case None:
00285 d->mFileLocked = true;
00286 break;
00287 default:
00288 break;
00289 }
00290
00291 if ( d->mFileLocked ) {
00292 if ( !d->open() ) {
00293 const bool unlocked = unlock();
00294 Q_ASSERT( unlocked );
00295 Q_UNUSED( unlocked );
00296 }
00297 }
00298
00299 d->startTimerIfNeeded();
00300 return d->mFileLocked;
00301 }
00302
00303 bool MBox::locked() const
00304 {
00305 return d->mFileLocked;
00306 }
00307
00308 static bool lessThanByOffset( const MBoxEntry &left, const MBoxEntry &right )
00309 {
00310 return left.messageOffset() < right.messageOffset();
00311 }
00312
00313 bool MBox::purge( const MBoxEntry::List &deletedEntries, QList<MBoxEntry::Pair> *movedEntries )
00314 {
00315 if ( d->mMboxFile.fileName().isEmpty() ) {
00316 return false;
00317 }
00318
00319 if ( deletedEntries.isEmpty() ) {
00320 return true;
00321 }
00322
00323 if ( !lock() ) {
00324 return false;
00325 }
00326
00327 foreach ( const MBoxEntry &entry, deletedEntries ) {
00328 d->mMboxFile.seek( entry.messageOffset() );
00329 const QByteArray line = d->mMboxFile.readLine();
00330
00331 if ( !d->isMBoxSeparator( line ) ) {
00332 qDebug() << "Found invalid separator at:" << entry.messageOffset();
00333 unlock();
00334 return false;
00335 }
00336 }
00337
00338
00339 if ( deletedEntries.size() == d->mEntries.size() ) {
00340 d->mEntries.clear();
00341 d->mMboxFile.resize( 0 );
00342 kDebug() << "Purge comleted successfully, unlocking the file.";
00343 return unlock();
00344 }
00345
00346 qSort( d->mEntries.begin(), d->mEntries.end(), lessThanByOffset );
00347 quint64 writeOffset = 0;
00348 bool writeOffSetInitialized = false;
00349 MBoxEntry::List resultingEntryList;
00350 QList<MBoxEntry::Pair> tmpMovedEntries;
00351
00352 quint64 origFileSize = d->mMboxFile.size();
00353
00354 QListIterator<MBoxEntry> i( d->mEntries );
00355 while ( i.hasNext() ) {
00356 MBoxEntry entry = i.next();
00357
00358 if ( deletedEntries.contains( entry ) && !writeOffSetInitialized ) {
00359 writeOffset = entry.messageOffset();
00360 writeOffSetInitialized = true;
00361 } else if ( writeOffSetInitialized &&
00362 writeOffset < entry.messageOffset() &&
00363 !deletedEntries.contains( entry ) ) {
00364
00365
00366 quint64 entrySize = 0;
00367 if ( i.hasNext() ) {
00368 entrySize = i.next().messageOffset() - entry.messageOffset();
00369 i.previous();
00370 } else {
00371 entrySize = origFileSize - entry.messageOffset();
00372 }
00373
00374 Q_ASSERT( entrySize > 0 );
00375
00376
00377
00378
00379
00380 quint64 mapSize = entry.messageOffset() + entrySize - writeOffset;
00381
00382
00383 uchar *memArea = d->mMboxFile.map( writeOffset, mapSize );
00384
00385
00386 quint64 startOffset = entry.messageOffset() - writeOffset;
00387 memmove( memArea, memArea + startOffset, entrySize );
00388
00389 d->mMboxFile.unmap( memArea );
00390
00391 MBoxEntry resultEntry;
00392 resultEntry.d->mOffset = writeOffset;
00393 resultEntry.d->mSeparatorSize = entry.separatorSize();
00394 resultEntry.d->mMessageSize = entry.messageSize();
00395
00396 resultingEntryList << resultEntry;
00397 tmpMovedEntries << MBoxEntry::Pair( MBoxEntry( entry.messageOffset() ),
00398 MBoxEntry( resultEntry.messageOffset() ) );
00399 writeOffset += entrySize;
00400 } else if ( !deletedEntries.contains( entry ) ) {
00401
00402
00403 Q_ASSERT( !writeOffSetInitialized );
00404 resultingEntryList << entry;
00405 }
00406 }
00407
00408
00409 d->mMboxFile.resize( writeOffset );
00410 d->mEntries = resultingEntryList;
00411
00412 kDebug() << "Purge comleted successfully, unlocking the file.";
00413 if ( movedEntries ) {
00414 *movedEntries = tmpMovedEntries;
00415 }
00416 return unlock();
00417
00418 }
00419
00420 QByteArray MBox::readRawMessage( const MBoxEntry &entry )
00421 {
00422 const bool wasLocked = locked();
00423 if ( !wasLocked ) {
00424 if ( !lock() ) {
00425 return QByteArray();
00426 }
00427 }
00428
00429
00430
00431 quint64 offset = entry.messageOffset();
00432
00433 Q_ASSERT( d->mFileLocked );
00434 Q_ASSERT( d->mMboxFile.isOpen() );
00435 Q_ASSERT( ( d->mInitialMboxFileSize + d->mAppendedEntries.size() ) > offset );
00436
00437 QByteArray message;
00438
00439 if ( offset < d->mInitialMboxFileSize ) {
00440 d->mMboxFile.seek( offset );
00441
00442 QByteArray line = d->mMboxFile.readLine();
00443
00444 if ( !d->isMBoxSeparator( line ) ) {
00445 kDebug() << "[MBox::readEntry] Invalid entry at:" << offset;
00446 if ( !wasLocked ) {
00447 unlock();
00448 }
00449 return QByteArray();
00450 }
00451
00452 line = d->mMboxFile.readLine();
00453 while ( !d->isMBoxSeparator( line ) && !d->mMboxFile.atEnd() ) {
00454 message += line;
00455 line = d->mMboxFile.readLine();
00456 }
00457 } else {
00458 offset -= d->mInitialMboxFileSize;
00459 if ( offset > static_cast<quint64>( d->mAppendedEntries.size() ) ) {
00460 if ( !wasLocked ) {
00461 unlock();
00462 }
00463 return QByteArray();
00464 }
00465
00466 QBuffer buffer( &(d->mAppendedEntries) );
00467 buffer.open( QIODevice::ReadOnly );
00468 buffer.seek( offset );
00469
00470 QByteArray line = buffer.readLine();
00471
00472 if ( !d->isMBoxSeparator( line ) ) {
00473 kDebug() << "[MBox::readEntry] Invalid appended entry at:" << offset;
00474 if ( !wasLocked ) {
00475 unlock();
00476 }
00477 return QByteArray();
00478 }
00479
00480 line = buffer.readLine();
00481 while ( !d->isMBoxSeparator( line ) && !buffer.atEnd() ) {
00482 message += line;
00483 line = buffer.readLine();
00484 }
00485 }
00486
00487
00488 if ( message.endsWith( '\n' ) ) {
00489 message.chop( 1 );
00490 }
00491
00492 MBoxPrivate::unescapeFrom( message.data(), message.size() );
00493
00494 if ( !wasLocked ) {
00495 if ( !d->startTimerIfNeeded() ) {
00496 const bool unlocked = unlock();
00497 Q_ASSERT( unlocked );
00498 Q_UNUSED( unlocked );
00499 }
00500 }
00501
00502 return message;
00503 }
00504
00505 KMime::Message *MBox::readMessage( const MBoxEntry &entry )
00506 {
00507 const QByteArray message = readRawMessage( entry );
00508 if ( message.isEmpty() ) {
00509 return 0;
00510 }
00511
00512 KMime::Message *mail = new KMime::Message();
00513 mail->setContent( KMime::CRLFtoLF( message ) );
00514 mail->parse();
00515
00516 return mail;
00517 }
00518
00519 QByteArray MBox::readMessageHeaders( const MBoxEntry &entry )
00520 {
00521 const bool wasLocked = d->mFileLocked;
00522 if ( !wasLocked ) {
00523 lock();
00524 }
00525
00526 const quint64 offset = entry.messageOffset();
00527
00528 Q_ASSERT( d->mFileLocked );
00529 Q_ASSERT( d->mMboxFile.isOpen() );
00530 Q_ASSERT( ( d->mInitialMboxFileSize + d->mAppendedEntries.size() ) > offset );
00531
00532 QByteArray headers;
00533 if ( offset < d->mInitialMboxFileSize ) {
00534 d->mMboxFile.seek( offset );
00535 QByteArray line = d->mMboxFile.readLine();
00536
00537 while ( line[0] != '\n' && !d->mMboxFile.atEnd() ) {
00538 headers += line;
00539 line = d->mMboxFile.readLine();
00540 }
00541 } else {
00542 QBuffer buffer( &(d->mAppendedEntries) );
00543 buffer.open( QIODevice::ReadOnly );
00544 buffer.seek( offset - d->mInitialMboxFileSize );
00545 QByteArray line = buffer.readLine();
00546
00547 while ( line[0] != '\n' && !buffer.atEnd() ) {
00548 headers += line;
00549 line = buffer.readLine();
00550 }
00551 }
00552
00553 if ( !wasLocked ) {
00554 unlock();
00555 }
00556
00557 return headers;
00558 }
00559
00560 bool MBox::save( const QString &fileName )
00561 {
00562 if ( !fileName.isEmpty() && KUrl( fileName ).toLocalFile() != d->mMboxFile.fileName() ) {
00563 if ( !d->mMboxFile.copy( fileName ) ) {
00564 return false;
00565 }
00566
00567 if ( d->mAppendedEntries.size() == 0 ) {
00568 return true;
00569 }
00570
00571 QFile otherFile( fileName );
00572 Q_ASSERT( otherFile.exists() );
00573 if ( !otherFile.open( QIODevice::ReadWrite ) ) {
00574 return false;
00575 }
00576
00577 otherFile.seek( d->mMboxFile.size() );
00578 otherFile.write( d->mAppendedEntries );
00579
00580
00581
00582 return true;
00583 }
00584
00585 if ( d->mAppendedEntries.size() == 0 ) {
00586 return true;
00587 }
00588
00589 if ( !lock() ) {
00590 return false;
00591 }
00592
00593 Q_ASSERT( d->mMboxFile.isOpen() );
00594
00595 d->mMboxFile.seek( d->mMboxFile.size() );
00596 d->mMboxFile.write( d->mAppendedEntries );
00597 d->mAppendedEntries.clear();
00598 d->mInitialMboxFileSize = d->mMboxFile.size();
00599
00600 return unlock();
00601 }
00602
00603 bool MBox::setLockType( LockType ltype )
00604 {
00605 if ( d->mFileLocked ) {
00606 kDebug() << "File is currently locked.";
00607 return false;
00608 }
00609
00610 switch ( ltype ) {
00611 case ProcmailLockfile:
00612 if ( KStandardDirs::findExe( QLatin1String( "lockfile" ) ).isEmpty() ) {
00613 kDebug() << "Could not find the lockfile executable";
00614 return false;
00615 }
00616 break;
00617 case MuttDotlock:
00618 case MuttDotlockPrivileged:
00619 if ( KStandardDirs::findExe( QLatin1String( "mutt_dotlock" ) ).isEmpty() ) {
00620 kDebug() << "Could not find the mutt_dotlock executable";
00621 return false;
00622 }
00623 break;
00624 default:
00625 break;
00626 }
00627
00628 d->mLockType = ltype;
00629 return true;
00630 }
00631
00632 void MBox::setLockFile( const QString &lockFile )
00633 {
00634 d->mLockFileName = lockFile;
00635 }
00636
00637 void MBox::setUnlockTimeout( int msec )
00638 {
00639 d->mUnlockTimer.setInterval( msec );
00640 }
00641
00642 bool MBox::unlock()
00643 {
00644 if ( d->mLockType == None && !d->mFileLocked ) {
00645 d->mFileLocked = false;
00646 d->mMboxFile.close();
00647 return true;
00648 }
00649
00650 int rc = 0;
00651 QStringList args;
00652
00653 switch( d->mLockType ) {
00654 case ProcmailLockfile:
00655
00656 if ( !d->mLockFileName.isEmpty() ) {
00657 rc = !QFile( d->mLockFileName ).remove();
00658 } else {
00659 rc = !QFile( d->mMboxFile.fileName() + QLatin1String( ".lock" ) ).remove();
00660 }
00661 break;
00662
00663 case MuttDotlock:
00664 args << QLatin1String( "-u" )
00665 << QString::fromLocal8Bit( QFile::encodeName( d->mMboxFile.fileName() ) );
00666 rc = QProcess::execute( QLatin1String( "mutt_dotlock" ), args );
00667 break;
00668
00669 case MuttDotlockPrivileged:
00670 args << QLatin1String( "-u" ) << QLatin1String( "-p" )
00671 << QString::fromLocal8Bit( QFile::encodeName( d->mMboxFile.fileName() ) );
00672 rc = QProcess::execute( QLatin1String( "mutt_dotlock" ), args );
00673 break;
00674
00675 case None:
00676 default:
00677 break;
00678 }
00679
00680 if ( rc == 0 ) {
00681 d->mFileLocked = false;
00682 }
00683
00684 d->mMboxFile.close();
00685
00686 return !d->mFileLocked;
00687 }