00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019 #include "backupjob.h"
00020
00021 #include "kmmsgdict.h"
00022 #include "kmfolder.h"
00023 #include "kmfoldercachedimap.h"
00024 #include "kmfolderdir.h"
00025 #include "folderutil.h"
00026
00027 #include "progressmanager.h"
00028
00029 #include "kzip.h"
00030 #include "ktar.h"
00031 #include "tdemessagebox.h"
00032
00033 #include "tqfile.h"
00034 #include "tqfileinfo.h"
00035 #include "tqstringlist.h"
00036
00037 using namespace KMail;
00038
00039 BackupJob::BackupJob( TQWidget *parent )
00040 : TQObject( parent ),
00041 mArchiveType( Zip ),
00042 mRootFolder( 0 ),
00043 mArchive( 0 ),
00044 mParentWidget( parent ),
00045 mCurrentFolderOpen( false ),
00046 mArchivedMessages( 0 ),
00047 mArchivedSize( 0 ),
00048 mProgressItem( 0 ),
00049 mAborted( false ),
00050 mDeleteFoldersAfterCompletion( false ),
00051 mCurrentFolder( 0 ),
00052 mCurrentMessage( 0 ),
00053 mCurrentJob( 0 )
00054 {
00055 }
00056
00057 BackupJob::~BackupJob()
00058 {
00059 mPendingFolders.clear();
00060 if ( mArchive ) {
00061 delete mArchive;
00062 mArchive = 0;
00063 }
00064 }
00065
00066 void BackupJob::setRootFolder( KMFolder *rootFolder )
00067 {
00068 mRootFolder = rootFolder;
00069 }
00070
00071 void BackupJob::setSaveLocation( const KURL &savePath )
00072 {
00073 mMailArchivePath = savePath;
00074 }
00075
00076 void BackupJob::setArchiveType( ArchiveType type )
00077 {
00078 mArchiveType = type;
00079 }
00080
00081 void BackupJob::setDeleteFoldersAfterCompletion( bool deleteThem )
00082 {
00083 mDeleteFoldersAfterCompletion = deleteThem;
00084 }
00085
00086 TQString BackupJob::stripRootPath( const TQString &path ) const
00087 {
00088 TQString ret = path;
00089 ret = ret.remove( mRootFolder->path() );
00090 if ( ret.startsWith( "/" ) )
00091 ret = ret.right( ret.length() - 1 );
00092 return ret;
00093 }
00094
00095 void BackupJob::queueFolders( KMFolder *root )
00096 {
00097 mPendingFolders.append( root );
00098 KMFolderDir *dir = root->child();
00099 if ( dir ) {
00100 for ( KMFolderNode * node = dir->first() ; node ; node = dir->next() ) {
00101 if ( node->isDir() )
00102 continue;
00103 KMFolder *folder = static_cast<KMFolder*>( node );
00104 queueFolders( folder );
00105 }
00106 }
00107 }
00108
00109 bool BackupJob::hasChildren( KMFolder *folder ) const
00110 {
00111 KMFolderDir *dir = folder->child();
00112 if ( dir ) {
00113 for ( KMFolderNode * node = dir->first() ; node ; node = dir->next() ) {
00114 if ( !node->isDir() )
00115 return true;
00116 }
00117 }
00118 return false;
00119 }
00120
00121 void BackupJob::cancelJob()
00122 {
00123 abort( i18n( "The operation was canceled by the user." ) );
00124 }
00125
00126 void BackupJob::abort( const TQString &errorMessage )
00127 {
00128
00129
00130 if ( mAborted )
00131 return;
00132
00133 mAborted = true;
00134 if ( mCurrentFolderOpen && mCurrentFolder ) {
00135 mCurrentFolder->close( "BackupJob" );
00136 mCurrentFolder = 0;
00137 }
00138 if ( mArchive && mArchive->isOpened() ) {
00139 mArchive->close();
00140 }
00141 if ( mCurrentJob ) {
00142 mCurrentJob->kill();
00143 mCurrentJob = 0;
00144 }
00145 if ( mProgressItem ) {
00146 mProgressItem->setComplete();
00147 mProgressItem = 0;
00148
00149 }
00150
00151 TQString text = i18n( "Failed to archive the folder '%1'." ).arg( mRootFolder->name() );
00152 text += "\n" + errorMessage;
00153 KMessageBox::sorry( mParentWidget, text, i18n( "Archiving failed." ) );
00154 deleteLater();
00155
00156 }
00157
00158 void BackupJob::finish()
00159 {
00160 if ( mArchive->isOpened() ) {
00161 mArchive->close();
00162 if ( !mArchive->closeSucceeded() ) {
00163 abort( i18n( "Unable to finalize the archive file." ) );
00164 return;
00165 }
00166 }
00167
00168 mProgressItem->setStatus( i18n( "Archiving finished" ) );
00169 mProgressItem->setComplete();
00170 mProgressItem = 0;
00171
00172 TQFileInfo archiveFileInfo( mMailArchivePath.path() );
00173 TQString text = i18n( "Archiving folder '%1' successfully completed. "
00174 "The archive was written to the file '%2'." )
00175 .arg( mRootFolder->name() ).arg( mMailArchivePath.path() );
00176 text += "\n" + i18n( "1 message of size %1 was archived.",
00177 "%n messages with the total size of %1 were archived.", mArchivedMessages )
00178 .arg( TDEIO::convertSize( mArchivedSize ) );
00179 text += "\n" + i18n( "The archive file has a size of %1." )
00180 .arg( TDEIO::convertSize( archiveFileInfo.size() ) );
00181 KMessageBox::information( mParentWidget, text, i18n( "Archiving finished." ) );
00182
00183 if ( mDeleteFoldersAfterCompletion ) {
00184
00185 if ( archiveFileInfo.size() > 0 && ( mArchivedSize > 0 || mArchivedMessages == 0 ) ) {
00186
00187 FolderUtil::deleteFolder( mRootFolder, mParentWidget );
00188 }
00189 }
00190
00191 deleteLater();
00192 }
00193
00194 void BackupJob::archiveNextMessage()
00195 {
00196 if ( mAborted )
00197 return;
00198
00199 mCurrentMessage = 0;
00200 if ( mPendingMessages.isEmpty() ) {
00201 kdDebug(5006) << "===> All messages done in folder " << mCurrentFolder->name() << endl;
00202 mCurrentFolder->close( "BackupJob" );
00203 mCurrentFolderOpen = false;
00204 archiveNextFolder();
00205 return;
00206 }
00207
00208 unsigned long serNum = mPendingMessages.front();
00209 mPendingMessages.pop_front();
00210
00211 KMFolder *folder;
00212 mMessageIndex = -1;
00213 KMMsgDict::instance()->getLocation( serNum, &folder, &mMessageIndex );
00214 if ( mMessageIndex == -1 ) {
00215 kdWarning(5006) << "Failed to get message location for sernum " << serNum << endl;
00216 abort( i18n( "Unable to retrieve a message for folder '%1'." ).arg( mCurrentFolder->name() ) );
00217 return;
00218 }
00219
00220 Q_ASSERT( folder == mCurrentFolder );
00221 const KMMsgBase *base = mCurrentFolder->getMsgBase( mMessageIndex );
00222 mUnget = base && !base->isMessage();
00223 KMMessage *message = mCurrentFolder->getMsg( mMessageIndex );
00224 if ( !message ) {
00225 kdWarning(5006) << "Failed to retrieve message with index " << mMessageIndex << endl;
00226 abort( i18n( "Unable to retrieve a message for folder '%1'." ).arg( mCurrentFolder->name() ) );
00227 return;
00228 }
00229
00230 kdDebug(5006) << "Going to get next message with subject " << message->subject() << ", "
00231 << mPendingMessages.size() << " messages left in the folder." << endl;
00232
00233 if ( message->isComplete() ) {
00234
00235
00236 mCurrentMessage = message;
00237 TQTimer::singleShot( 0, this, TQT_SLOT( processCurrentMessage() ) );
00238 }
00239 else if ( message->parent() ) {
00240 mCurrentJob = message->parent()->createJob( message );
00241 mCurrentJob->setCancellable( false );
00242 connect( mCurrentJob, TQT_SIGNAL( messageRetrieved( KMMessage* ) ),
00243 this, TQT_SLOT( messageRetrieved( KMMessage* ) ) );
00244 connect( mCurrentJob, TQT_SIGNAL( result( KMail::FolderJob* ) ),
00245 this, TQT_SLOT( folderJobFinished( KMail::FolderJob* ) ) );
00246 mCurrentJob->start();
00247 }
00248 else {
00249 kdWarning(5006) << "Message with subject " << mCurrentMessage->subject()
00250 << " is neither complete nor has a parent!" << endl;
00251 abort( i18n( "Internal error while trying to retrieve a message from folder '%1'." )
00252 .arg( mCurrentFolder->name() ) );
00253 }
00254
00255 mProgressItem->setProgress( ( mProgressItem->progress() + 5 ) );
00256 }
00257
00258 static int fileInfoToUnixPermissions( const TQFileInfo &fileInfo )
00259 {
00260 int perm = 0;
00261 if ( fileInfo.permission( TQFileInfo::ExeOther ) ) perm += S_IXOTH;
00262 if ( fileInfo.permission( TQFileInfo::WriteOther ) ) perm += S_IWOTH;
00263 if ( fileInfo.permission( TQFileInfo::ReadOther ) ) perm += S_IROTH;
00264 if ( fileInfo.permission( TQFileInfo::ExeGroup ) ) perm += S_IXGRP;
00265 if ( fileInfo.permission( TQFileInfo::WriteGroup ) ) perm += S_IWGRP;
00266 if ( fileInfo.permission( TQFileInfo::ReadGroup ) ) perm += S_IRGRP;
00267 if ( fileInfo.permission( TQFileInfo::ExeOwner ) ) perm += S_IXUSR;
00268 if ( fileInfo.permission( TQFileInfo::WriteOwner ) ) perm += S_IWUSR;
00269 if ( fileInfo.permission( TQFileInfo::ReadOwner ) ) perm += S_IRUSR;
00270 return perm;
00271 }
00272
00273 void BackupJob::processCurrentMessage()
00274 {
00275 if ( mAborted )
00276 return;
00277
00278 if ( mCurrentMessage ) {
00279 kdDebug(5006) << "Processing message with subject " << mCurrentMessage->subject() << endl;
00280 const DwString &messageDWString = mCurrentMessage->asDwString();
00281 const uint messageSize = messageDWString.size();
00282 const char *messageString = mCurrentMessage->asDwString().c_str();
00283 TQString messageName;
00284 TQFileInfo fileInfo;
00285 if ( messageName.isEmpty() ) {
00286 messageName = TQString::number( mCurrentMessage->getMsgSerNum() );
00287 if ( mCurrentMessage->storage() ) {
00288 fileInfo.setFile( mCurrentMessage->storage()->location() );
00289
00290 }
00291 }
00292 else {
00293
00294 fileInfo.setFile( mCurrentFolder->location() + "/cur/" + mCurrentMessage->fileName() );
00295 messageName = mCurrentMessage->fileName();
00296 }
00297
00298 const TQString fileName = stripRootPath( mCurrentFolder->location() ) +
00299 "/cur/" + messageName;
00300
00301 TQString user;
00302 TQString group;
00303 mode_t permissions = 0700;
00304 time_t creationTime = time( 0 );
00305 time_t modificationTime = time( 0 );
00306 time_t accessTime = time( 0 );
00307 if ( !fileInfo.fileName().isEmpty() ) {
00308 user = fileInfo.owner();
00309 group = fileInfo.group();
00310 permissions = fileInfoToUnixPermissions( fileInfo );
00311 creationTime = fileInfo.created().toTime_t();
00312 modificationTime = fileInfo.lastModified().toTime_t();
00313 accessTime = fileInfo.lastRead().toTime_t();
00314 }
00315 else {
00316 kdWarning(5006) << "Unable to find file for message " << fileName << endl;
00317 }
00318
00319 if ( !mArchive->writeFile( fileName, user, group, messageSize, permissions, accessTime,
00320 modificationTime, creationTime, messageString ) ) {
00321 abort( i18n( "Failed to write a message into the archive folder '%1'." ).arg( mCurrentFolder->name() ) );
00322 return;
00323 }
00324
00325 if ( mUnget ) {
00326 Q_ASSERT( mMessageIndex >= 0 );
00327 mCurrentFolder->unGetMsg( mMessageIndex );
00328 }
00329
00330 mArchivedMessages++;
00331 mArchivedSize += messageSize;
00332 }
00333 else {
00334
00335
00336 kdWarning(5006) << "Unable to download a message for folder " << mCurrentFolder->name() << endl;
00337 }
00338 archiveNextMessage();
00339 }
00340
00341 void BackupJob::messageRetrieved( KMMessage *message )
00342 {
00343 mCurrentMessage = message;
00344 processCurrentMessage();
00345 }
00346
00347 void BackupJob::folderJobFinished( KMail::FolderJob *job )
00348 {
00349 if ( mAborted )
00350 return;
00351
00352
00353
00354 if ( job == mCurrentJob ) {
00355 mCurrentJob = 0;
00356 }
00357
00358 if ( job->error() ) {
00359 if ( mCurrentFolder )
00360 abort( i18n( "Downloading a message in folder '%1' failed." ).arg( mCurrentFolder->name() ) );
00361 else
00362 abort( i18n( "Downloading a message in the current folder failed." ) );
00363 }
00364 }
00365
00366 bool BackupJob::writeDirHelper( const TQString &directoryPath, const TQString &permissionPath )
00367 {
00368 TQFileInfo fileInfo( permissionPath );
00369 TQString user = fileInfo.owner();
00370 TQString group = fileInfo.group();
00371 mode_t permissions = fileInfoToUnixPermissions( fileInfo );
00372 time_t creationTime = fileInfo.created().toTime_t();
00373 time_t modificationTime = fileInfo.lastModified().toTime_t();
00374 time_t accessTime = fileInfo.lastRead().toTime_t();
00375 return mArchive->writeDir( stripRootPath( directoryPath ), user, group, permissions, accessTime,
00376 modificationTime, creationTime );
00377 }
00378
00379 void BackupJob::archiveNextFolder()
00380 {
00381 if ( mAborted )
00382 return;
00383
00384 if ( mPendingFolders.isEmpty() ) {
00385 finish();
00386 return;
00387 }
00388
00389 mCurrentFolder = mPendingFolders.take( 0 );
00390 kdDebug(5006) << "===> Archiving next folder: " << mCurrentFolder->name() << endl;
00391 mProgressItem->setStatus( i18n( "Archiving folder %1" ).arg( mCurrentFolder->name() ) );
00392 if ( mCurrentFolder->open( "BackupJob" ) != 0 ) {
00393 abort( i18n( "Unable to open folder '%1'.").arg( mCurrentFolder->name() ) );
00394 return;
00395 }
00396 mCurrentFolderOpen = true;
00397
00398 const TQString folderName = mCurrentFolder->name();
00399 bool success = true;
00400 if ( hasChildren( mCurrentFolder ) ) {
00401 if ( !writeDirHelper( mCurrentFolder->subdirLocation(), mCurrentFolder->subdirLocation() ) )
00402 success = false;
00403 }
00404 if ( !writeDirHelper( mCurrentFolder->location(), mCurrentFolder->location() ) )
00405 success = false;
00406 if ( !writeDirHelper( mCurrentFolder->location() + "/cur", mCurrentFolder->location() ) )
00407 success = false;
00408 if ( !writeDirHelper( mCurrentFolder->location() + "/new", mCurrentFolder->location() ) )
00409 success = false;
00410 if ( !writeDirHelper( mCurrentFolder->location() + "/tmp", mCurrentFolder->location() ) )
00411 success = false;
00412 if ( !success ) {
00413 abort( i18n( "Unable to create folder structure for folder '%1' within archive file." )
00414 .arg( mCurrentFolder->name() ) );
00415 return;
00416 }
00417
00418 for ( int i = 0; i < mCurrentFolder->count( false ); i++ ) {
00419 unsigned long serNum = KMMsgDict::instance()->getMsgSerNum( mCurrentFolder, i );
00420 if ( serNum == 0 ) {
00421
00422 kdWarning(5006) << "Got serial number zero in " << mCurrentFolder->name()
00423 << " at index " << i << "!" << endl;
00424
00425 abort( i18n( "Unable to backup messages in folder '%1', the index file is corrupted." )
00426 .arg( mCurrentFolder->name() ) );
00427 return;
00428 }
00429 else
00430 mPendingMessages.append( serNum );
00431 }
00432 archiveNextMessage();
00433 }
00434
00435
00436
00437
00438
00439
00440
00441
00442
00443
00444
00445
00446
00447
00448
00449
00450
00451
00452
00453 void BackupJob::start()
00454 {
00455 Q_ASSERT( !mMailArchivePath.isEmpty() );
00456 Q_ASSERT( mRootFolder );
00457
00458 queueFolders( mRootFolder );
00459
00460 switch ( mArchiveType ) {
00461 case Zip: {
00462 KZip *zip = new KZip( mMailArchivePath.path() );
00463 zip->setCompression( KZip::DeflateCompression );
00464 mArchive = zip;
00465 break;
00466 }
00467 case Tar: {
00468 mArchive = new KTar( mMailArchivePath.path(), "application/x-tar" );
00469 break;
00470 }
00471 case TarGz: {
00472 mArchive = new KTar( mMailArchivePath.path(), "application/x-gzip" );
00473 break;
00474 }
00475 case TarBz2: {
00476 mArchive = new KTar( mMailArchivePath.path(), "application/x-bzip2" );
00477 break;
00478 }
00479 }
00480
00481 kdDebug(5006) << "Starting backup." << endl;
00482 if ( !mArchive->open( IO_WriteOnly ) ) {
00483 abort( i18n( "Unable to open archive for writing." ) );
00484 return;
00485 }
00486
00487 mProgressItem = KPIM::ProgressManager::createProgressItem(
00488 "BackupJob",
00489 i18n( "Archiving" ),
00490 TQString(),
00491 true );
00492 mProgressItem->setUsesBusyIndicator( true );
00493 connect( mProgressItem, TQT_SIGNAL(progressItemCanceled(KPIM::ProgressItem*)),
00494 this, TQT_SLOT(cancelJob()) );
00495
00496 archiveNextFolder();
00497 }
00498
00499 #include "backupjob.moc"
00500