KMBox Library
mbox.cpp
00001 /* 00002 Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org> 00003 Copyright (c) 2009 Bertjan Broeksema <broeksema@kde.org> 00004 00005 This library is free software; you can redistribute it and/or modify it 00006 under the terms of the GNU Library General Public License as published by 00007 the Free Software Foundation; either version 2 of the License, or (at your 00008 option) any later version. 00009 00010 This library is distributed in the hope that it will be useful, but WITHOUT 00011 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 00012 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public 00013 License for more details. 00014 00015 You should have received a copy of the GNU Library General Public License 00016 along with this library; see the file COPYING.LIB. If not, write to the 00017 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 00018 02110-1301, USA. 00019 00020 NOTE: Most of the code inside here is an slightly adjusted version of 00021 kdepim/kmail/kmfoldermbox.cpp. This is why I added a line for Stefan 00022 Taferner. 00023 00024 Bertjan Broeksema, april 2009 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 // Set some sane defaults 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 // Appended entries works as follows: When an mbox file is loaded from disk, 00065 // d->mInitialMboxFileSize is set to the file size at that moment. New entries 00066 // are stored in memory (d->mAppendedEntries). The initial file size and the size 00067 // of the buffer determine the offset for the next message to append. 00068 MBoxEntry MBox::appendMessage( const KMime::Message::Ptr &entry ) 00069 { 00070 // It doesn't make sense to add entries when we don't have an reference file. 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(); // Offset of the appended message 00082 00083 // Make sure the byte array is large enough to check for an end character. 00084 // Then check if the required newlines are there. 00085 if ( nextOffset < 1 && d->mMboxFile.size() > 0 ) { // Empty, add one empty line 00086 d->mAppendedEntries.append( "\n" ); 00087 ++nextOffset; 00088 } else if ( nextOffset == 1 && d->mAppendedEntries.at( 0 ) != '\n' ) { 00089 // This should actually not happen, but catch it anyway. 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; // The offset of the next message to read. 00158 00159 while ( !d->mMboxFile.atEnd() ) { 00160 quint64 pos = d->mMboxFile.pos(); 00161 00162 line = d->mMboxFile.readLine(); 00163 00164 // if atEnd, use mail only if there was a separator line at all, 00165 // otherwise it's not a valid mbox 00166 if ( d->isMBoxSeparator( line ) || 00167 ( d->mMboxFile.atEnd() && ( prevSeparator.size() != 0 ) ) ) { 00168 00169 // if we are the at the file end, update pos to not forget the last line 00170 if ( d->mMboxFile.atEnd() ) 00171 pos = d->mMboxFile.pos(); 00172 00173 // Found the separator or at end of file, the message starts at offs 00174 quint64 msgSize = pos - offs; 00175 00176 if( pos > 0 ) { 00177 // This is not the separator of the first mail in the file. If pos == 0 00178 // than we matched the separator of the first mail in the file. 00179 MBoxEntry entry; 00180 entry.d->mOffset = offs; 00181 entry.d->mSeparatorSize = prevSeparator.size(); 00182 entry.d->mMessageSize = msgSize - 1; 00183 00184 // Don't add the separator size and the newline up to the message size. 00185 entry.d->mMessageSize -= prevSeparator.size() + 1; 00186 00187 d->mEntries << entry; 00188 } 00189 00190 if ( d->isMBoxSeparator( line ) ) { 00191 prevSeparator = line; 00192 } 00193 00194 offs += msgSize; // Mark the beginning of the next message. 00195 } 00196 } 00197 00198 // FIXME: What if unlock fails? 00199 // if no separator was found, the file is still valid if it is empty 00200 return unlock() && ( ( prevSeparator.size() != 0 ) || ( d->mMboxFile.size() == 0 ) ); 00201 } 00202 00203 bool MBox::lock() 00204 { 00205 if ( d->mMboxFile.fileName().isEmpty() ) { 00206 return false; // We cannot lock if there is no file loaded. 00207 } 00208 00209 // We can't load another file when the mbox currently is locked so if d->mFileLocked 00210 // is true atm just return true. 00211 if ( locked() ) { 00212 return true; 00213 } 00214 00215 if ( d->mLockType == None ) { 00216 d->mFileLocked = true; 00217 if ( d->open() ) { 00218 d->startTimerIfNeeded(); 00219 return true; 00220 } 00221 00222 d->mFileLocked = false; 00223 return false; 00224 } 00225 00226 QStringList args; 00227 int rc = 0; 00228 00229 switch( d->mLockType ) { 00230 case ProcmailLockfile: 00231 args << QLatin1String( "-l20" ) << QLatin1String( "-r5" ); 00232 if ( !d->mLockFileName.isEmpty() ) { 00233 args << QString::fromLocal8Bit( QFile::encodeName( d->mLockFileName ) ); 00234 } else { 00235 args << QString::fromLocal8Bit( QFile::encodeName( d->mMboxFile.fileName() + 00236 QLatin1String( ".lock" ) ) ); 00237 } 00238 00239 rc = QProcess::execute( QLatin1String( "lockfile" ), args ); 00240 if ( rc != 0 ) { 00241 kDebug() << "lockfile -l20 -r5 " << d->mMboxFile.fileName() 00242 << ": Failed ("<< rc << ") switching to read only mode"; 00243 d->mReadOnly = true; // In case the MBox object was created read/write we 00244 // set it to read only when locking failed. 00245 } else { 00246 d->mFileLocked = true; 00247 } 00248 break; 00249 00250 case MuttDotlock: 00251 args << QString::fromLocal8Bit( QFile::encodeName( d->mMboxFile.fileName() ) ); 00252 rc = QProcess::execute( QLatin1String( "mutt_dotlock" ), args ); 00253 00254 if ( rc != 0 ) { 00255 kDebug() << "mutt_dotlock " << d->mMboxFile.fileName() 00256 << ": Failed (" << rc << ") switching to read only mode"; 00257 d->mReadOnly = true; // In case the MBox object was created read/write we 00258 // set it to read only when locking failed. 00259 } else { 00260 d->mFileLocked = true; 00261 } 00262 break; 00263 00264 case MuttDotlockPrivileged: 00265 args << QLatin1String( "-p" ) 00266 << QString::fromLocal8Bit( QFile::encodeName( d->mMboxFile.fileName() ) ); 00267 rc = QProcess::execute( QLatin1String( "mutt_dotlock" ), args ); 00268 00269 if ( rc != 0 ) { 00270 kDebug() << "mutt_dotlock -p " << d->mMboxFile.fileName() << ":" 00271 << ": Failed (" << rc << ") switching to read only mode"; 00272 d->mReadOnly = true; 00273 } else { 00274 d->mFileLocked = true; 00275 } 00276 break; 00277 00278 case None: 00279 d->mFileLocked = true; 00280 break; 00281 default: 00282 break; 00283 } 00284 00285 if ( d->mFileLocked ) { 00286 if ( !d->open() ) { 00287 const bool unlocked = unlock(); 00288 Q_ASSERT( unlocked ); // If this fails we're in trouble. 00289 Q_UNUSED( unlocked ); 00290 } 00291 } 00292 00293 d->startTimerIfNeeded(); 00294 return d->mFileLocked; 00295 } 00296 00297 bool MBox::locked() const 00298 { 00299 return d->mFileLocked; 00300 } 00301 00302 static bool lessThanByOffset( const MBoxEntry &left, const MBoxEntry &right ) 00303 { 00304 return left.messageOffset() < right.messageOffset(); 00305 } 00306 00307 bool MBox::purge( const MBoxEntry::List &deletedEntries, QList<MBoxEntry::Pair> *movedEntries ) 00308 { 00309 if ( d->mMboxFile.fileName().isEmpty() ) { 00310 return false; // No file loaded yet. 00311 } 00312 00313 if ( deletedEntries.isEmpty() ) { 00314 return true; // Nothing to do. 00315 } 00316 00317 if ( !lock() ) { 00318 return false; 00319 } 00320 00321 foreach ( const MBoxEntry &entry, deletedEntries ) { 00322 d->mMboxFile.seek( entry.messageOffset() ); 00323 const QByteArray line = d->mMboxFile.readLine(); 00324 00325 if ( !d->isMBoxSeparator( line ) ) { 00326 qDebug() << "Found invalid separator at:" << entry.messageOffset(); 00327 unlock(); 00328 return false; // The file is messed up or the index is incorrect. 00329 } 00330 } 00331 00332 // All entries are deleted, so just resize the file to a size of 0. 00333 if ( deletedEntries.size() == d->mEntries.size() ) { 00334 d->mEntries.clear(); 00335 d->mMboxFile.resize( 0 ); 00336 kDebug() << "Purge comleted successfully, unlocking the file."; 00337 return unlock(); 00338 } 00339 00340 qSort( d->mEntries.begin(), d->mEntries.end(), lessThanByOffset ); 00341 quint64 writeOffset = 0; 00342 bool writeOffSetInitialized = false; 00343 MBoxEntry::List resultingEntryList; 00344 QList<MBoxEntry::Pair> tmpMovedEntries; 00345 00346 quint64 origFileSize = d->mMboxFile.size(); 00347 00348 QListIterator<MBoxEntry> i( d->mEntries ); 00349 while ( i.hasNext() ) { 00350 MBoxEntry entry = i.next(); 00351 00352 if ( deletedEntries.contains( entry ) && !writeOffSetInitialized ) { 00353 writeOffset = entry.messageOffset(); 00354 writeOffSetInitialized = true; 00355 } else if ( writeOffSetInitialized && 00356 writeOffset < entry.messageOffset() && 00357 !deletedEntries.contains( entry ) ) { 00358 // The current message doesn't have to be deleted, but must be moved. 00359 // First determine the size of the entry that must be moved. 00360 quint64 entrySize = 0; 00361 if ( i.hasNext() ) { 00362 entrySize = i.next().messageOffset() - entry.messageOffset(); 00363 i.previous(); // Go back to make sure that we also handle the next entry. 00364 } else { 00365 entrySize = origFileSize - entry.messageOffset(); 00366 } 00367 00368 Q_ASSERT( entrySize > 0 ); // MBox entries really cannot have a size <= 0; 00369 00370 // we map the whole area of the file starting at the writeOffset up to the 00371 // message that have to be moved into memory. This includes eventually the 00372 // messages that are the deleted between the first deleted message 00373 // encountered and the message that has to be moved. 00374 quint64 mapSize = entry.messageOffset() + entrySize - writeOffset; 00375 00376 // Now map writeOffSet + mapSize into mem. 00377 uchar *memArea = d->mMboxFile.map( writeOffset, mapSize ); 00378 00379 // Now read the entry that must be moved to writeOffset. 00380 quint64 startOffset = entry.messageOffset() - writeOffset; 00381 memmove( memArea, memArea + startOffset, entrySize ); 00382 00383 d->mMboxFile.unmap( memArea ); 00384 00385 MBoxEntry resultEntry; 00386 resultEntry.d->mOffset = writeOffset; 00387 resultEntry.d->mSeparatorSize = entry.separatorSize(); 00388 resultEntry.d->mMessageSize = entry.messageSize(); 00389 00390 resultingEntryList << resultEntry; 00391 tmpMovedEntries << MBoxEntry::Pair( MBoxEntry( entry.messageOffset() ), 00392 MBoxEntry( resultEntry.messageOffset() ) ); 00393 writeOffset += entrySize; 00394 } else if ( !deletedEntries.contains( entry ) ) { 00395 // Unmoved and not deleted entry, can only ocure before the first deleted 00396 // entry. 00397 Q_ASSERT( !writeOffSetInitialized ); 00398 resultingEntryList << entry; 00399 } 00400 } 00401 00402 // Chop off remaining entry bits. 00403 d->mMboxFile.resize( writeOffset ); 00404 d->mEntries = resultingEntryList; 00405 00406 kDebug() << "Purge comleted successfully, unlocking the file."; 00407 if ( movedEntries ) { 00408 *movedEntries = tmpMovedEntries; 00409 } 00410 return unlock(); // FIXME: What if this fails? It will return false but the 00411 // file has changed. 00412 } 00413 00414 QByteArray MBox::readRawMessage( const MBoxEntry &entry ) 00415 { 00416 const bool wasLocked = locked(); 00417 if ( !wasLocked ) { 00418 if ( !lock() ) { 00419 return QByteArray(); 00420 } 00421 } 00422 00423 // TODO: Add error handling in case locking failed. 00424 00425 quint64 offset = entry.messageOffset(); 00426 00427 Q_ASSERT( d->mFileLocked ); 00428 Q_ASSERT( d->mMboxFile.isOpen() ); 00429 Q_ASSERT( ( d->mInitialMboxFileSize + d->mAppendedEntries.size() ) > offset ); 00430 00431 QByteArray message; 00432 00433 if ( offset < d->mInitialMboxFileSize ) { 00434 d->mMboxFile.seek( offset ); 00435 00436 QByteArray line = d->mMboxFile.readLine(); 00437 00438 if ( !d->isMBoxSeparator( line ) ) { 00439 kDebug() << "[MBox::readEntry] Invalid entry at:" << offset; 00440 if ( !wasLocked ) { 00441 unlock(); 00442 } 00443 return QByteArray(); // The file is messed up or the index is incorrect. 00444 } 00445 00446 line = d->mMboxFile.readLine(); 00447 while ( !d->isMBoxSeparator( line ) ) { 00448 message += line; 00449 if ( d->mMboxFile.atEnd() ) 00450 break; 00451 line = d->mMboxFile.readLine(); 00452 } 00453 } else { 00454 offset -= d->mInitialMboxFileSize; 00455 if ( offset > static_cast<quint64>( d->mAppendedEntries.size() ) ) { 00456 if ( !wasLocked ) { 00457 unlock(); 00458 } 00459 return QByteArray(); 00460 } 00461 00462 QBuffer buffer( &(d->mAppendedEntries) ); 00463 buffer.open( QIODevice::ReadOnly ); 00464 buffer.seek( offset ); 00465 00466 QByteArray line = buffer.readLine(); 00467 00468 if ( !d->isMBoxSeparator( line ) ) { 00469 kDebug() << "[MBox::readEntry] Invalid appended entry at:" << offset; 00470 if ( !wasLocked ) { 00471 unlock(); 00472 } 00473 return QByteArray(); // The file is messed up or the index is incorrect. 00474 } 00475 00476 line = buffer.readLine(); 00477 while ( !d->isMBoxSeparator( line ) && !buffer.atEnd() ) { 00478 message += line; 00479 line = buffer.readLine(); 00480 } 00481 } 00482 00483 // Remove te last '\n' added by writeEntry. 00484 if ( message.endsWith( '\n' ) ) { 00485 message.chop( 1 ); 00486 } 00487 00488 MBoxPrivate::unescapeFrom( message.data(), message.size() ); 00489 00490 if ( !wasLocked ) { 00491 if ( !d->startTimerIfNeeded() ) { 00492 const bool unlocked = unlock(); 00493 Q_ASSERT( unlocked ); 00494 Q_UNUSED( unlocked ); 00495 } 00496 } 00497 00498 return message; 00499 } 00500 00501 KMime::Message *MBox::readMessage( const MBoxEntry &entry ) 00502 { 00503 const QByteArray message = readRawMessage( entry ); 00504 if ( message.isEmpty() ) { 00505 return 0; 00506 } 00507 00508 KMime::Message *mail = new KMime::Message(); 00509 mail->setContent( KMime::CRLFtoLF( message ) ); 00510 mail->parse(); 00511 00512 return mail; 00513 } 00514 00515 QByteArray MBox::readMessageHeaders( const MBoxEntry &entry ) 00516 { 00517 const bool wasLocked = d->mFileLocked; 00518 if ( !wasLocked ) { 00519 lock(); 00520 } 00521 00522 const quint64 offset = entry.messageOffset(); 00523 00524 Q_ASSERT( d->mFileLocked ); 00525 Q_ASSERT( d->mMboxFile.isOpen() ); 00526 Q_ASSERT( ( d->mInitialMboxFileSize + d->mAppendedEntries.size() ) > offset ); 00527 00528 QByteArray headers; 00529 if ( offset < d->mInitialMboxFileSize ) { 00530 d->mMboxFile.seek( offset ); 00531 QByteArray line = d->mMboxFile.readLine(); 00532 00533 while ( line[0] != '\n' && !d->mMboxFile.atEnd() ) { 00534 headers += line; 00535 line = d->mMboxFile.readLine(); 00536 } 00537 } else { 00538 QBuffer buffer( &(d->mAppendedEntries) ); 00539 buffer.open( QIODevice::ReadOnly ); 00540 buffer.seek( offset - d->mInitialMboxFileSize ); 00541 QByteArray line = buffer.readLine(); 00542 00543 while ( line[0] != '\n' && !buffer.atEnd() ) { 00544 headers += line; 00545 line = buffer.readLine(); 00546 } 00547 } 00548 00549 if ( !wasLocked ) { 00550 unlock(); 00551 } 00552 00553 return headers; 00554 } 00555 00556 bool MBox::save( const QString &fileName ) 00557 { 00558 if ( !fileName.isEmpty() && KUrl( fileName ).toLocalFile() != d->mMboxFile.fileName() ) { 00559 if ( !d->mMboxFile.copy( fileName ) ) { 00560 return false; 00561 } 00562 00563 if ( d->mAppendedEntries.size() == 0 ) { 00564 return true; // Nothing to do 00565 } 00566 00567 QFile otherFile( fileName ); 00568 Q_ASSERT( otherFile.exists() ); 00569 if ( !otherFile.open( QIODevice::ReadWrite ) ) { 00570 return false; 00571 } 00572 00573 otherFile.seek( d->mMboxFile.size() ); 00574 otherFile.write( d->mAppendedEntries ); 00575 00576 // Don't clear mAppendedEntries and don't update mInitialFileSize. These 00577 // are still valid for the original file. 00578 return true; 00579 } 00580 00581 if ( d->mAppendedEntries.size() == 0 ) { 00582 return true; // Nothing to do. 00583 } 00584 00585 if ( !lock() ) { 00586 return false; 00587 } 00588 00589 Q_ASSERT( d->mMboxFile.isOpen() ); 00590 00591 d->mMboxFile.seek( d->mMboxFile.size() ); 00592 d->mMboxFile.write( d->mAppendedEntries ); 00593 d->mAppendedEntries.clear(); 00594 d->mInitialMboxFileSize = d->mMboxFile.size(); 00595 00596 return unlock(); 00597 } 00598 00599 bool MBox::setLockType( LockType ltype ) 00600 { 00601 if ( d->mFileLocked ) { 00602 kDebug() << "File is currently locked."; 00603 return false; // Don't change the method if the file is currently locked. 00604 } 00605 00606 switch ( ltype ) { 00607 case ProcmailLockfile: 00608 if ( KStandardDirs::findExe( QLatin1String( "lockfile" ) ).isEmpty() ) { 00609 kDebug() << "Could not find the lockfile executable"; 00610 return false; 00611 } 00612 break; 00613 case MuttDotlock: // fall through 00614 case MuttDotlockPrivileged: 00615 if ( KStandardDirs::findExe( QLatin1String( "mutt_dotlock" ) ).isEmpty() ) { 00616 kDebug() << "Could not find the mutt_dotlock executable"; 00617 return false; 00618 } 00619 break; 00620 default: 00621 break; // We assume fcntl available and lock_none doesn't need a check. 00622 } 00623 00624 d->mLockType = ltype; 00625 return true; 00626 } 00627 00628 void MBox::setLockFile( const QString &lockFile ) 00629 { 00630 d->mLockFileName = lockFile; 00631 } 00632 00633 void MBox::setUnlockTimeout( int msec ) 00634 { 00635 d->mUnlockTimer.setInterval( msec ); 00636 } 00637 00638 bool MBox::unlock() 00639 { 00640 if ( d->mLockType == None && !d->mFileLocked ) { 00641 d->mFileLocked = false; 00642 d->mMboxFile.close(); 00643 return true; 00644 } 00645 00646 int rc = 0; 00647 QStringList args; 00648 00649 switch( d->mLockType ) { 00650 case ProcmailLockfile: 00651 // QFile::remove returns true on succes so negate the result. 00652 if ( !d->mLockFileName.isEmpty() ) { 00653 rc = !QFile( d->mLockFileName ).remove(); 00654 } else { 00655 rc = !QFile( d->mMboxFile.fileName() + QLatin1String( ".lock" ) ).remove(); 00656 } 00657 break; 00658 00659 case MuttDotlock: 00660 args << QLatin1String( "-u" ) 00661 << QString::fromLocal8Bit( QFile::encodeName( d->mMboxFile.fileName() ) ); 00662 rc = QProcess::execute( QLatin1String( "mutt_dotlock" ), args ); 00663 break; 00664 00665 case MuttDotlockPrivileged: 00666 args << QLatin1String( "-u" ) << QLatin1String( "-p" ) 00667 << QString::fromLocal8Bit( QFile::encodeName( d->mMboxFile.fileName() ) ); 00668 rc = QProcess::execute( QLatin1String( "mutt_dotlock" ), args ); 00669 break; 00670 00671 case None: // Fall through. 00672 default: 00673 break; 00674 } 00675 00676 if ( rc == 0 ) { // Unlocking succeeded 00677 d->mFileLocked = false; 00678 } 00679 00680 d->mMboxFile.close(); 00681 00682 return !d->mFileLocked; 00683 }