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 "kmessagebox.h"
00032
00033 #include "qfile.h"
00034 #include "qfileinfo.h"
00035 #include "qstringlist.h"
00036
00037 using namespace KMail;
00038
00039 BackupJob::BackupJob( QWidget *parent )
00040 : QObject( 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 QString BackupJob::stripRootPath( const QString &path ) const
00087 {
00088 QString 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 cancelled by the user." ) );
00124 }
00125
00126 void BackupJob::abort( const QString &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 QString 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 QFileInfo archiveFileInfo( mMailArchivePath.path() );
00173 QString 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 messages with the total size of %2 were archived." )
00177 .arg( mArchivedMessages ).arg( KIO::convertSize( mArchivedSize ) );
00178 text += "\n" + i18n( "The archive file has a size of %1." )
00179 .arg( KIO::convertSize( archiveFileInfo.size() ) );
00180 KMessageBox::information( mParentWidget, text, i18n( "Archiving finished." ) );
00181
00182 if ( mDeleteFoldersAfterCompletion ) {
00183
00184 if ( archiveFileInfo.size() > 0 && mArchivedMessages > 0 && mArchivedSize > 0 ) {
00185
00186 FolderUtil::deleteFolder( mRootFolder, mParentWidget );
00187 }
00188 }
00189
00190 deleteLater();
00191 }
00192
00193 void BackupJob::archiveNextMessage()
00194 {
00195 if ( mAborted )
00196 return;
00197
00198 mCurrentMessage = 0;
00199 if ( mPendingMessages.isEmpty() ) {
00200 kdDebug(5006) << "===> All messages done in folder " << mCurrentFolder->name() << endl;
00201 mCurrentFolder->close( "BackupJob" );
00202 mCurrentFolderOpen = false;
00203 archiveNextFolder();
00204 return;
00205 }
00206
00207 unsigned long serNum = mPendingMessages.front();
00208 mPendingMessages.pop_front();
00209
00210 KMFolder *folder;
00211 int index = -1;
00212 KMMsgDict::instance()->getLocation( serNum, &folder, &index );
00213 if ( index == -1 ) {
00214 kdWarning(5006) << "Failed to get message location for sernum " << serNum << endl;
00215 abort( i18n( "Unable to retrieve a message for folder '%1'." ).arg( mCurrentFolder->name() ) );
00216 return;
00217 }
00218
00219 Q_ASSERT( folder == mCurrentFolder );
00220 KMMessage *message = mCurrentFolder->getMsg( index );
00221 if ( !message ) {
00222 kdWarning(5006) << "Failed to retrieve message with index " << index << endl;
00223 abort( i18n( "Unable to retrieve a message for folder '%1'." ).arg( mCurrentFolder->name() ) );
00224 return;
00225 }
00226
00227 kdDebug(5006) << "Going to get next message with subject " << message->subject() << ", "
00228 << mPendingMessages.size() << " messages left in the folder." << endl;
00229
00230 if ( message->isComplete() ) {
00231
00232
00233 mCurrentMessage = message;
00234 QTimer::singleShot( 0, this, SLOT( processCurrentMessage() ) );
00235 }
00236 else if ( message->parent() ) {
00237 mCurrentJob = message->parent()->createJob( message );
00238 mCurrentJob->setCancellable( false );
00239 connect( mCurrentJob, SIGNAL( messageRetrieved( KMMessage* ) ),
00240 this, SLOT( messageRetrieved( KMMessage* ) ) );
00241 connect( mCurrentJob, SIGNAL( result( KMail::FolderJob* ) ),
00242 this, SLOT( folderJobFinished( KMail::FolderJob* ) ) );
00243 mCurrentJob->start();
00244 }
00245 else {
00246 kdWarning(5006) << "Message with subject " << mCurrentMessage->subject()
00247 << " is neither complete nor has a parent!" << endl;
00248 abort( i18n( "Internal error while trying to retrieve a message from folder '%1'." )
00249 .arg( mCurrentFolder->name() ) );
00250 }
00251
00252 mProgressItem->setProgress( ( mProgressItem->progress() + 5 ) );
00253 }
00254
00255 static int fileInfoToUnixPermissions( const QFileInfo &fileInfo )
00256 {
00257 int perm = 0;
00258 if ( fileInfo.permission( QFileInfo::ExeOther ) ) perm += S_IXOTH;
00259 if ( fileInfo.permission( QFileInfo::WriteOther ) ) perm += S_IWOTH;
00260 if ( fileInfo.permission( QFileInfo::ReadOther ) ) perm += S_IROTH;
00261 if ( fileInfo.permission( QFileInfo::ExeGroup ) ) perm += S_IXGRP;
00262 if ( fileInfo.permission( QFileInfo::WriteGroup ) ) perm += S_IWGRP;
00263 if ( fileInfo.permission( QFileInfo::ReadGroup ) ) perm += S_IRGRP;
00264 if ( fileInfo.permission( QFileInfo::ExeOwner ) ) perm += S_IXUSR;
00265 if ( fileInfo.permission( QFileInfo::WriteOwner ) ) perm += S_IWUSR;
00266 if ( fileInfo.permission( QFileInfo::ReadOwner ) ) perm += S_IRUSR;
00267 return perm;
00268 }
00269
00270 void BackupJob::processCurrentMessage()
00271 {
00272 if ( mAborted )
00273 return;
00274
00275 if ( mCurrentMessage ) {
00276 kdDebug(5006) << "Processing message with subject " << mCurrentMessage->subject() << endl;
00277 const DwString &messageDWString = mCurrentMessage->asDwString();
00278 const uint messageSize = messageDWString.size();
00279 const char *messageString = mCurrentMessage->asDwString().c_str();
00280 QString messageName;
00281 QFileInfo fileInfo;
00282 if ( messageName.isEmpty() ) {
00283 messageName = QString::number( mCurrentMessage->getMsgSerNum() );
00284 if ( mCurrentMessage->storage() ) {
00285 fileInfo.setFile( mCurrentMessage->storage()->location() );
00286
00287 }
00288 }
00289 else {
00290
00291 fileInfo.setFile( mCurrentFolder->location() + "/cur/" + mCurrentMessage->fileName() );
00292 messageName = mCurrentMessage->fileName();
00293 }
00294
00295 const QString fileName = stripRootPath( mCurrentFolder->location() ) +
00296 "/cur/" + messageName;
00297
00298 QString user;
00299 QString group;
00300 mode_t permissions = 0700;
00301 time_t creationTime = time( 0 );
00302 time_t modificationTime = time( 0 );
00303 time_t accessTime = time( 0 );
00304 if ( !fileInfo.fileName().isEmpty() ) {
00305 user = fileInfo.owner();
00306 group = fileInfo.group();
00307 permissions = fileInfoToUnixPermissions( fileInfo );
00308 creationTime = fileInfo.created().toTime_t();
00309 modificationTime = fileInfo.lastModified().toTime_t();
00310 accessTime = fileInfo.lastRead().toTime_t();
00311 }
00312 else {
00313 kdWarning(5006) << "Unable to find file for message " << fileName << endl;
00314 }
00315
00316 if ( !mArchive->writeFile( fileName, user, group, messageSize, permissions, accessTime,
00317 modificationTime, creationTime, messageString ) ) {
00318 abort( i18n( "Failed to write a message into the archive folder '%1'." ).arg( mCurrentFolder->name() ) );
00319 return;
00320 }
00321
00322 mArchivedMessages++;
00323 mArchivedSize += messageSize;
00324 }
00325 else {
00326
00327
00328 kdWarning(5006) << "Unable to download a message for folder " << mCurrentFolder->name() << endl;
00329 }
00330 archiveNextMessage();
00331 }
00332
00333 void BackupJob::messageRetrieved( KMMessage *message )
00334 {
00335 mCurrentMessage = message;
00336 processCurrentMessage();
00337 }
00338
00339 void BackupJob::folderJobFinished( KMail::FolderJob *job )
00340 {
00341 if ( mAborted )
00342 return;
00343
00344
00345
00346 if ( job == mCurrentJob ) {
00347 mCurrentJob = 0;
00348 }
00349
00350 if ( job->error() ) {
00351 if ( mCurrentFolder )
00352 abort( i18n( "Downloading a message in folder '%1' failed." ).arg( mCurrentFolder->name() ) );
00353 else
00354 abort( i18n( "Downloading a message in the current folder failed." ) );
00355 }
00356 }
00357
00358 bool BackupJob::writeDirHelper( const QString &directoryPath, const QString &permissionPath )
00359 {
00360 QFileInfo fileInfo( permissionPath );
00361 QString user = fileInfo.owner();
00362 QString group = fileInfo.group();
00363 mode_t permissions = fileInfoToUnixPermissions( fileInfo );
00364 time_t creationTime = fileInfo.created().toTime_t();
00365 time_t modificationTime = fileInfo.lastModified().toTime_t();
00366 time_t accessTime = fileInfo.lastRead().toTime_t();
00367 return mArchive->writeDir( stripRootPath( directoryPath ), user, group, permissions, accessTime,
00368 modificationTime, creationTime );
00369 }
00370
00371 void BackupJob::archiveNextFolder()
00372 {
00373 if ( mAborted )
00374 return;
00375
00376 if ( mPendingFolders.isEmpty() ) {
00377 finish();
00378 return;
00379 }
00380
00381 mCurrentFolder = mPendingFolders.take( 0 );
00382 kdDebug(5006) << "===> Archiving next folder: " << mCurrentFolder->name() << endl;
00383 mProgressItem->setStatus( i18n( "Archiving folder %1" ).arg( mCurrentFolder->name() ) );
00384 if ( mCurrentFolder->open( "BackupJob" ) != 0 ) {
00385 abort( i18n( "Unable to open folder '%1'.").arg( mCurrentFolder->name() ) );
00386 return;
00387 }
00388 mCurrentFolderOpen = true;
00389
00390 const QString folderName = mCurrentFolder->name();
00391 bool success = true;
00392 if ( hasChildren( mCurrentFolder ) ) {
00393 if ( !writeDirHelper( mCurrentFolder->subdirLocation(), mCurrentFolder->subdirLocation() ) )
00394 success = false;
00395 }
00396 if ( !writeDirHelper( mCurrentFolder->location(), mCurrentFolder->location() ) )
00397 success = false;
00398 if ( !writeDirHelper( mCurrentFolder->location() + "/cur", mCurrentFolder->location() ) )
00399 success = false;
00400 if ( !success ) {
00401 abort( i18n( "Unable to create folder structure for folder '%1' within archive file." )
00402 .arg( mCurrentFolder->name() ) );
00403 return;
00404 }
00405
00406 for ( int i = 0; i < mCurrentFolder->count( false ); i++ ) {
00407 unsigned long serNum = KMMsgDict::instance()->getMsgSerNum( mCurrentFolder, i );
00408 if ( serNum == 0 ) {
00409
00410 kdWarning(5006) << "Got serial number zero in " << mCurrentFolder->name()
00411 << " at index " << i << "!" << endl;
00412
00413 abort( i18n( "Unable to backup messages in folder '%1', the index file is corrupted." )
00414 .arg( mCurrentFolder->name() ) );
00415 return;
00416 }
00417 else
00418 mPendingMessages.append( serNum );
00419 }
00420 archiveNextMessage();
00421 }
00422
00423
00424
00425
00426
00427
00428
00429
00430
00431
00432
00433
00434
00435
00436
00437
00438
00439
00440
00441 void BackupJob::start()
00442 {
00443 Q_ASSERT( !mMailArchivePath.isEmpty() );
00444 Q_ASSERT( mRootFolder );
00445
00446 queueFolders( mRootFolder );
00447
00448 switch ( mArchiveType ) {
00449 case Zip: {
00450 KZip *zip = new KZip( mMailArchivePath.path() );
00451 zip->setCompression( KZip::DeflateCompression );
00452 mArchive = zip;
00453 break;
00454 }
00455 case Tar: {
00456 mArchive = new KTar( mMailArchivePath.path(), "application/x-tar" );
00457 break;
00458 }
00459 case TarGz: {
00460 mArchive = new KTar( mMailArchivePath.path(), "application/x-gzip" );
00461 break;
00462 }
00463 case TarBz2: {
00464 mArchive = new KTar( mMailArchivePath.path(), "application/x-bzip2" );
00465 break;
00466 }
00467 }
00468
00469 kdDebug(5006) << "Starting backup." << endl;
00470 if ( !mArchive->open( IO_WriteOnly ) ) {
00471 abort( i18n( "Unable to open archive for writing." ) );
00472 return;
00473 }
00474
00475 mProgressItem = KPIM::ProgressManager::createProgressItem(
00476 "BackupJob",
00477 i18n( "Archiving" ),
00478 QString(),
00479 true );
00480 mProgressItem->setUsesBusyIndicator( true );
00481 connect( mProgressItem, SIGNAL(progressItemCanceled(KPIM::ProgressItem*)),
00482 this, SLOT(cancelJob()) );
00483
00484 archiveNextFolder();
00485 }
00486
00487 #include "backupjob.moc"
00488