kmail

importjob.cpp

00001 /* Copyright 2009 Klarälvdalens Datakonsult AB
00002 
00003    This program is free software; you can redistribute it and/or
00004    modify it under the terms of the GNU General Public License as
00005    published by the Free Software Foundation; either version 2 of
00006    the License or (at your option) version 3 or any later version
00007    accepted by the membership of KDE e.V. (or its successor approved
00008    by the membership of KDE e.V.), which shall act as a proxy
00009    defined in Section 14 of version 3 of the license.
00010 
00011    This program is distributed in the hope that it will be useful,
00012    but WITHOUT ANY WARRANTY; without even the implied warranty of
00013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014    GNU General Public License for more details.
00015 
00016    You should have received a copy of the GNU General Public License
00017    along with this program.  If not, see <http://www.gnu.org/licenses/>.
00018 */
00019 #include "importjob.h"
00020 
00021 #include "kmfolder.h"
00022 #include "folderutil.h"
00023 #include "kmfolderdir.h"
00024 #include "kmfolderimap.h"
00025 #include "imapjob.h"
00026 
00027 #include "progressmanager.h"
00028 
00029 #include <kdebug.h>
00030 #include <kzip.h>
00031 #include <ktar.h>
00032 #include <klocale.h>
00033 #include <kmessagebox.h>
00034 #include <kmimetype.h>
00035 
00036 #include <qwidget.h>
00037 #include <qtimer.h>
00038 #include <qfile.h>
00039 
00040 using namespace KMail;
00041 
00042 ImportJob::ImportJob( QWidget *parentWidget )
00043   : QObject( parentWidget ),
00044     mArchive( 0 ),
00045     mRootFolder( 0 ),
00046     mParentWidget( parentWidget ),
00047     mNumberOfImportedMessages( 0 ),
00048     mCurrentFolder( 0 ),
00049     mCurrentMessage( 0 ),
00050     mCurrentMessageFile( 0 ),
00051     mProgressItem( 0 ),
00052     mAborted( false )
00053 {
00054 }
00055 
00056 ImportJob::~ImportJob()
00057 {
00058   if ( mArchive && mArchive->isOpened() ) {
00059     mArchive->close();
00060   }
00061   delete mArchive;
00062   mArchive = 0;
00063 }
00064 
00065 void ImportJob::setFile( const KURL &archiveFile )
00066 {
00067   mArchiveFile = archiveFile;
00068 }
00069 
00070 void ImportJob::setRootFolder( KMFolder *rootFolder )
00071 {
00072   mRootFolder = rootFolder;
00073 }
00074 
00075 void ImportJob::finish()
00076 {
00077   kdDebug(5006) << "Finished import job." << endl;
00078   mProgressItem->setComplete();
00079   mProgressItem = 0;
00080   QString text = i18n( "Importing the archive file '%1' into the folder '%2' succeeded." )
00081                      .arg( mArchiveFile.path() ).arg( mRootFolder->name() );
00082   text += "\n" + i18n( "1 message was imported.", "%n messages were imported.", mNumberOfImportedMessages );
00083   KMessageBox::information( mParentWidget, text, i18n( "Import finished." ) );
00084   deleteLater();
00085 }
00086 
00087 void ImportJob::cancelJob()
00088 {
00089   abort( i18n( "The operation was canceled by the user." ) );
00090 }
00091 
00092 void ImportJob::abort( const QString &errorMessage )
00093 {
00094   if ( mAborted )
00095     return;
00096 
00097   mAborted = true;
00098   QString text = i18n( "Failed to import the archive into folder '%1'." ).arg( mRootFolder->name() );
00099   text += "\n" + errorMessage;
00100   if ( mProgressItem ) {
00101     mProgressItem->setComplete();
00102     mProgressItem = 0;
00103     // The progressmanager will delete it
00104   }
00105   KMessageBox::sorry( mParentWidget, text, i18n( "Importing archive failed." ) );
00106   deleteLater();
00107 }
00108 
00109 KMFolder * ImportJob::createSubFolder( KMFolder *parent, const QString &folderName, mode_t permissions )
00110 {
00111   KMFolder *newFolder = FolderUtil::createSubFolder( parent, parent->child(), folderName, QString(),
00112                                                      KMFolderTypeMaildir );
00113   if ( !newFolder ) {
00114     abort( i18n( "Unable to create subfolder for folder '%1'." ).arg( parent->name() ) );
00115     return 0;
00116   }
00117   else {
00118     newFolder->createChildFolder(); // TODO: Just creating a child folder here is wasteful, only do
00119                                     //       that if really needed. We do it here so we can set the
00120                                     //       permissions
00121     chmod( newFolder->location().latin1(), permissions );
00122     chmod( newFolder->subdirLocation().latin1(), permissions );
00123     // TODO: chown?
00124     // TODO: what about subdirectories like "cur"?
00125     return newFolder;
00126   }
00127 }
00128 
00129 void ImportJob::enqueueMessages( const KArchiveDirectory *dir, KMFolder *folder )
00130 {
00131   const KArchiveDirectory *messageDir = dynamic_cast<const KArchiveDirectory*>( dir->entry( "cur" ) );
00132   if ( messageDir ) {
00133     Messages messagesToQueue;
00134     messagesToQueue.parent = folder;
00135     const QStringList entries = messageDir->entries();
00136     for ( uint i = 0; i < entries.size(); i++ ) {
00137       const KArchiveEntry *entry = messageDir->entry( entries[i] );
00138       Q_ASSERT( entry );
00139       if ( entry->isDirectory() ) {
00140         kdWarning(5006) << "Unexpected subdirectory in archive folder " << dir->name() << endl;
00141       }
00142       else {
00143         kdDebug(5006) << "Queueing message " << entry->name() << endl;
00144         const KArchiveFile *file = static_cast<const KArchiveFile*>( entry );
00145         messagesToQueue.files.append( file );
00146       }
00147     }
00148     mQueuedMessages.append( messagesToQueue );
00149   }
00150   else {
00151     kdWarning(5006) << "No 'cur' subdirectory for archive directory " << dir->name() << endl;
00152   }
00153 }
00154 
00155 void ImportJob::messageAdded()
00156 {
00157   mNumberOfImportedMessages++;
00158   if ( mCurrentFolder->folderType() == KMFolderTypeMaildir ||
00159        mCurrentFolder->folderType() == KMFolderTypeCachedImap ) {
00160     const QString messageFile = mCurrentFolder->location() + "/cur/" + mCurrentMessage->fileName();
00161     // TODO: what if the file is not in the "cur" subdirectory?
00162     if ( QFile::exists( messageFile ) ) {
00163       chmod( messageFile.latin1(), mCurrentMessageFile->permissions() );
00164       // TODO: changing user/group he requires a bit more work, requires converting the strings
00165       //       to uid_t and gid_t
00166       //getpwnam()
00167       //chown( messageFile,
00168     }
00169     else {
00170       kdWarning(5006) << "Unable to change permissions for newly created file: " << messageFile << endl;
00171     }
00172   }
00173   // TODO: Else?
00174 
00175   mCurrentMessage = 0;
00176   mCurrentMessageFile = 0;
00177   QTimer::singleShot( 0, this, SLOT( importNextMessage() ) );
00178 }
00179 
00180 void ImportJob::importNextMessage()
00181 {
00182   if ( mAborted )
00183     return;
00184 
00185   if ( mQueuedMessages.isEmpty() ) {
00186     kdDebug(5006) << "importNextMessage(): Processed all messages in the queue." << endl;
00187     if ( mCurrentFolder ) {
00188       mCurrentFolder->close( "ImportJob" );
00189     }
00190     mCurrentFolder = 0;
00191     importNextDirectory();
00192     return;
00193   }
00194 
00195   Messages &messages = mQueuedMessages.front();
00196   if ( messages.files.isEmpty() ) {
00197     mQueuedMessages.pop_front();
00198     importNextMessage();
00199     return;
00200   }
00201 
00202   KMFolder *folder = messages.parent;
00203   if ( folder != mCurrentFolder ) {
00204     kdDebug(5006) << "importNextMessage(): Processed all messages in the current folder of the queue." << endl;
00205     if ( mCurrentFolder ) {
00206       mCurrentFolder->close( "ImportJob" );
00207     }
00208     mCurrentFolder = folder;
00209     if ( mCurrentFolder->open( "ImportJob" ) != 0 ) {
00210       abort( i18n( "Unable to open folder '%1'." ).arg( mCurrentFolder->name() ) );
00211       return;
00212     }
00213     kdDebug(5006) << "importNextMessage(): Current folder of queue is now: " << mCurrentFolder->name() << endl;
00214     mProgressItem->setStatus( i18n( "Importing folder %1" ).arg( mCurrentFolder->name() ) );
00215   }
00216 
00217   mProgressItem->setProgress( ( mProgressItem->progress() + 5 ) );
00218 
00219   mCurrentMessageFile = messages.files.first();
00220   Q_ASSERT( mCurrentMessageFile );
00221   messages.files.removeFirst();
00222 
00223   mCurrentMessage = new KMMessage();
00224   mCurrentMessage->fromByteArray( mCurrentMessageFile->data(), true /* setStatus */ );
00225   int retIndex;
00226 
00227   // If this is not an IMAP folder, we can add the message directly. Otherwise, the whole thing is
00228   // async, for online IMAP. While addMsg() fakes a sync call, we rather do it the async way here
00229   // ourselves, as otherwise the whole thing gets pretty much messed up with regards to folder
00230   // refcounting. Furthermore, the completion dialog would be shown before the messages are actually
00231   // uploaded.
00232   if ( mCurrentFolder->folderType() != KMFolderTypeImap ) {
00233     if ( mCurrentFolder->addMsg( mCurrentMessage, &retIndex ) != 0 ) {
00234       abort( i18n( "Failed to add a message to the folder '%1'." ).arg( mCurrentFolder->name() ) );
00235       return;
00236     }
00237     messageAdded();
00238   }
00239   else {
00240     ImapJob *imapJob = new ImapJob( mCurrentMessage, ImapJob::tPutMessage,
00241                                     dynamic_cast<KMFolderImap*>( mCurrentFolder->storage() ) );
00242     connect( imapJob, SIGNAL(result(KMail::FolderJob*)),
00243              SLOT(messagePutResult(KMail::FolderJob*)) );
00244     imapJob->start();
00245   }
00246 }
00247 
00248 void ImportJob::messagePutResult( KMail::FolderJob *job )
00249 {
00250   if ( mAborted )
00251     return;
00252 
00253   if ( job->error() ) {
00254     abort( i18n( "Failed to upload a message to the IMAP server." ) );
00255     return;
00256   } else {
00257 
00258     KMFolderImap *imap = dynamic_cast<KMFolderImap*>( mCurrentFolder->storage() );
00259     Q_ASSERT( imap );
00260 
00261     // Ok, we uploaded the message, but we still need to add it to the folder. Use addMsgQuiet(),
00262     // otherwise it will be uploaded again.
00263     imap->addMsgQuiet( mCurrentMessage );
00264     messageAdded();
00265   }
00266 }
00267 
00268 // Input: .inbox.directory
00269 // Output: inbox
00270 // Can also return an empty string if this is no valid dir name
00271 static QString folderNameForDirectoryName( const QString &dirName )
00272 {
00273   Q_ASSERT( dirName.startsWith( "." ) );
00274   const QString end = ".directory";
00275   const int expectedIndex = dirName.length() - end.length();
00276   if ( dirName.lower().find( end ) != expectedIndex )
00277     return QString();
00278   QString returnName = dirName.left( dirName.length() - end.length() );
00279   returnName = returnName.right( returnName.length() - 1 );
00280   return returnName;
00281 }
00282 
00283 KMFolder* ImportJob::getOrCreateSubFolder( KMFolder *parentFolder, const QString &subFolderName,
00284                                            mode_t subFolderPermissions )
00285 {
00286   if ( !parentFolder->createChildFolder() ) {
00287     abort( i18n( "Unable to create subfolder for folder '%1'." ).arg( parentFolder->name() ) );
00288     return 0;
00289   }
00290 
00291   KMFolder *subFolder = 0;
00292   subFolder = dynamic_cast<KMFolder*>( parentFolder->child()->hasNamedFolder( subFolderName ) );
00293 
00294   if ( !subFolder ) {
00295     subFolder = createSubFolder( parentFolder, subFolderName, subFolderPermissions );
00296   }
00297   return subFolder;
00298 }
00299 
00300 void ImportJob::importNextDirectory()
00301 {
00302   if ( mAborted )
00303     return;
00304 
00305   if ( mQueuedDirectories.isEmpty() ) {
00306     finish();
00307     return;
00308   }
00309 
00310   Folder folder = mQueuedDirectories.first();
00311   KMFolder *currentFolder = folder.parent;
00312   mQueuedDirectories.pop_front();
00313   kdDebug(5006) << "importNextDirectory(): Working on directory " << folder.archiveDir->name() << endl;
00314 
00315   QStringList entries = folder.archiveDir->entries();
00316   for ( uint i = 0; i < entries.size(); i++ ) {
00317     const KArchiveEntry *entry = folder.archiveDir->entry( entries[i] );
00318     Q_ASSERT( entry );
00319     kdDebug(5006) << "Queueing entry " << entry->name() << endl;
00320     if ( entry->isDirectory() ) {
00321       const KArchiveDirectory *dir = static_cast<const KArchiveDirectory*>( entry );
00322       if ( !dir->name().startsWith( "." ) ) {
00323 
00324         kdDebug(5006) << "Queueing messages in folder " << entry->name() << endl;
00325         KMFolder *subFolder = getOrCreateSubFolder( currentFolder, entry->name(), entry->permissions() );
00326         if ( !subFolder )
00327           return;
00328 
00329         enqueueMessages( dir, subFolder );
00330       }
00331 
00332       // Entry starts with a dot, so we assume it is a subdirectory
00333       else {
00334 
00335         const QString folderName = folderNameForDirectoryName( entry->name() );
00336         if ( folderName.isEmpty() ) {
00337           abort( i18n( "Unexpected subdirectory named '%1'." ).arg( entry->name() ) );
00338           return;
00339         }
00340         KMFolder *subFolder = getOrCreateSubFolder( currentFolder, folderName, entry->permissions() );
00341         if ( !subFolder )
00342           return;
00343 
00344         Folder newFolder;
00345         newFolder.archiveDir = dir;
00346         newFolder.parent = subFolder;
00347         kdDebug(5006) << "Enqueueing directory " << entry->name() << endl;
00348         mQueuedDirectories.push_back( newFolder );
00349       }
00350     }
00351   }
00352 
00353   importNextMessage();
00354 }
00355 
00356 // TODO:
00357 // BUGS:
00358 //    Online IMAP can fail spectacular, for example when cancelling upload
00359 //    Online IMAP: Inform that messages are still being uploaded on finish()!
00360 void ImportJob::start()
00361 {
00362   Q_ASSERT( mRootFolder );
00363   Q_ASSERT( mArchiveFile.isValid() );
00364 
00365   KMimeType::Ptr mimeType = KMimeType::findByURL( mArchiveFile, 0, true /* local file */ );
00366   if ( !mimeType->patterns().grep( "tar", false /* no case-sensitive */ ).isEmpty() )
00367     mArchive = new KTar( mArchiveFile.path() );
00368   else if ( !mimeType->patterns().grep( "zip", false ).isEmpty() )
00369     mArchive = new KZip( mArchiveFile.path() );
00370   else {
00371     abort( i18n( "The file '%1' does not appear to be a valid archive." ).arg( mArchiveFile.path() ) );
00372     return;
00373   }
00374 
00375   if ( !mArchive->open( IO_ReadOnly ) ) {
00376     abort( i18n( "Unable to open archive file '%1'" ).arg( mArchiveFile.path() ) );
00377     return;
00378   }
00379 
00380   mProgressItem = KPIM::ProgressManager::createProgressItem(
00381       "ImportJob",
00382       i18n( "Importing Archive" ),
00383       QString(),
00384       true );
00385   mProgressItem->setUsesBusyIndicator( true );
00386   connect( mProgressItem, SIGNAL(progressItemCanceled(KPIM::ProgressItem*)),
00387            this, SLOT(cancelJob()) );
00388 
00389   Folder nextFolder;
00390   nextFolder.archiveDir = mArchive->directory();
00391   nextFolder.parent = mRootFolder;
00392   mQueuedDirectories.push_back( nextFolder );
00393   importNextDirectory();
00394 }
00395 
00396 #include "importjob.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys