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 
00025 #include "progressmanager.h"
00026 
00027 #include <kdebug.h>
00028 #include <kzip.h>
00029 #include <ktar.h>
00030 #include <klocale.h>
00031 #include <kmessagebox.h>
00032 #include <kmimetype.h>
00033 
00034 #include <qwidget.h>
00035 #include <qtimer.h>
00036 #include <qfile.h>
00037 
00038 using namespace KMail;
00039 
00040 ImportJob::ImportJob( QWidget *parentWidget )
00041   : QObject( parentWidget ),
00042     mArchive( 0 ),
00043     mRootFolder( 0 ),
00044     mParentWidget( parentWidget ),
00045     mNumberOfImportedMessages( 0 ),
00046     mCurrentFolder( 0 ),
00047     mProgressItem( 0 ),
00048     mAborted( false )
00049 {
00050 }
00051 
00052 ImportJob::~ImportJob()
00053 {
00054   if ( mArchive && mArchive->isOpened() ) {
00055     mArchive->close();
00056   }
00057   delete mArchive;
00058   mArchive = 0;
00059 }
00060 
00061 void ImportJob::setFile( const KURL &archiveFile )
00062 {
00063   mArchiveFile = archiveFile;
00064 }
00065 
00066 void ImportJob::setRootFolder( KMFolder *rootFolder )
00067 {
00068   mRootFolder = rootFolder;
00069 }
00070 
00071 void ImportJob::finish()
00072 {
00073   kdDebug(5006) << "Finished import job." << endl;
00074   mProgressItem->setComplete();
00075   mProgressItem = 0;
00076   QString text = i18n( "Importing the archive file '%1' into the folder '%2' succeeded." )
00077                      .arg( mArchiveFile.path() ).arg( mRootFolder->name() );
00078   text += "\n" + i18n( "%1 messages were imported." ).arg( mNumberOfImportedMessages );
00079   KMessageBox::information( mParentWidget, text, i18n( "Import finished." ) );
00080   deleteLater();
00081 }
00082 
00083 void ImportJob::cancelJob()
00084 {
00085   abort( i18n( "The operation was cancelled by the user." ) );
00086 }
00087 
00088 void ImportJob::abort( const QString &errorMessage )
00089 {
00090   if ( mAborted )
00091     return;
00092 
00093   mAborted = true;
00094   QString text = i18n( "Failed to import the archive into folder '%1'." ).arg( mRootFolder->name() );
00095   text += "\n" + errorMessage;
00096   if ( mProgressItem ) {
00097     mProgressItem->setComplete();
00098     mProgressItem = 0;
00099     // The progressmanager will delete it
00100   }
00101   KMessageBox::sorry( mParentWidget, text, i18n( "Importing archive failed." ) );
00102   deleteLater();
00103 }
00104 
00105 KMFolder * ImportJob::createSubFolder( KMFolder *parent, const QString &folderName, mode_t permissions )
00106 {
00107   KMFolder *newFolder = FolderUtil::createSubFolder( parent, parent->child(), folderName, QString(),
00108                                                      KMFolderTypeMaildir );
00109   if ( !newFolder ) {
00110     abort( i18n( "Unable to create subfolder for folder '%1'." ).arg( parent->name() ) );
00111     return 0;
00112   }
00113   else {
00114     newFolder->createChildFolder(); // TODO: Just creating a child folder here is wasteful, only do
00115                                     //       that if really needed. We do it here so we can set the
00116                                     //       permissions
00117     chmod( newFolder->location().latin1(), permissions );
00118     chmod( newFolder->subdirLocation().latin1(), permissions );
00119     // TODO: chown?
00120     // TODO: what about subdirectories like "cur"?
00121     return newFolder;
00122   }
00123 }
00124 
00125 void ImportJob::enqueueMessages( const KArchiveDirectory *dir, KMFolder *folder )
00126 {
00127   const KArchiveDirectory *messageDir = dynamic_cast<const KArchiveDirectory*>( dir->entry( "cur" ) );
00128   if ( messageDir ) {
00129     Messages messagesToQueue;
00130     messagesToQueue.parent = folder;
00131     const QStringList entries = messageDir->entries();
00132     for ( uint i = 0; i < entries.size(); i++ ) {
00133       const KArchiveEntry *entry = messageDir->entry( entries[i] );
00134       Q_ASSERT( entry );
00135       if ( entry->isDirectory() ) {
00136         kdWarning(5006) << "Unexpected subdirectory in archive folder " << dir->name() << endl;
00137       }
00138       else {
00139         kdDebug(5006) << "Queueing message " << entry->name() << endl;
00140         const KArchiveFile *file = static_cast<const KArchiveFile*>( entry );
00141         messagesToQueue.files.append( file );
00142       }
00143     }
00144     mQueuedMessages.append( messagesToQueue );
00145   }
00146   else {
00147     kdWarning(5006) << "No 'cur' subdirectory for archive directory " << dir->name() << endl;
00148   }
00149 }
00150 
00151 void ImportJob::importNextMessage()
00152 {
00153   if ( mAborted )
00154     return;
00155 
00156   if ( mQueuedMessages.isEmpty() ) {
00157     kdDebug(5006) << "importNextMessage(): Processed all messages in the queue." << endl;
00158     if ( mCurrentFolder ) {
00159       mCurrentFolder->close( "ImportJob" );
00160     }
00161     mCurrentFolder = 0;
00162     importNextDirectory();
00163     return;
00164   }
00165 
00166   Messages &messages = mQueuedMessages.front();
00167   if ( messages.files.isEmpty() ) {
00168     mQueuedMessages.pop_front();
00169     importNextMessage();
00170     return;
00171   }
00172 
00173   KMFolder *folder = messages.parent;
00174   if ( folder != mCurrentFolder ) {
00175     kdDebug(5006) << "importNextMessage(): Processed all messages in the current folder of the queue." << endl;
00176     if ( mCurrentFolder ) {
00177       mCurrentFolder->close( "ImportJob" );
00178     }
00179     mCurrentFolder = folder;
00180     if ( mCurrentFolder->open( "ImportJob" ) != 0 ) {
00181       abort( i18n( "Unable to open folder '%1'." ).arg( mCurrentFolder->name() ) );
00182       return;
00183     }
00184     kdDebug(5006) << "importNextMessage(): Current folder of queue is now: " << mCurrentFolder->name() << endl;
00185     mProgressItem->setStatus( i18n( "Importing folder %1" ).arg( mCurrentFolder->name() ) );
00186   }
00187 
00188   mProgressItem->setProgress( ( mProgressItem->progress() + 5 ) );
00189 
00190   const KArchiveFile *file = messages.files.first();
00191   Q_ASSERT( file );
00192   messages.files.removeFirst();
00193 
00194   KMMessage *newMessage = new KMMessage();
00195   newMessage->fromByteArray( file->data(), true /* setStatus */ );
00196   int retIndex;
00197   if ( mCurrentFolder->addMsg( newMessage, &retIndex ) != 0 ) {
00198     abort( i18n( "Failed to add a message to the folder '%1'." ).arg( mCurrentFolder->name() ) );
00199     return;
00200   }
00201   else {
00202     mNumberOfImportedMessages++;
00203     if ( mCurrentFolder->folderType() == KMFolderTypeMaildir ||
00204          mCurrentFolder->folderType() == KMFolderTypeCachedImap ) {
00205       const QString messageFile = mCurrentFolder->location() + "/cur/" + newMessage->fileName();
00206       // TODO: what if the file is not in the "cur" subdirectory?
00207       if ( QFile::exists( messageFile ) ) {
00208         chmod( messageFile.latin1(), file->permissions() );
00209         // TODO: changing user/group he requires a bit more work, requires converting the strings
00210         //       to uid_t and gid_t
00211         //getpwnam()
00212         //chown( messageFile, 
00213       }
00214       else {
00215         kdWarning(5006) << "Unable to change permissions for newly created file: " << messageFile << endl;
00216       }
00217     }
00218     // TODO: Else?
00219     kdDebug(5006) << "Added message with subject " /*<< newMessage->subject()*/ // < this causes a pure virtual method to be called...
00220                   << " to folder " << mCurrentFolder->name() << " at index " << retIndex << endl;
00221   }
00222   QTimer::singleShot( 0, this, SLOT( importNextMessage() ) );
00223 }
00224 
00225 // Input: .inbox.directory
00226 // Output: inbox
00227 // Can also return an empty string if this is no valid dir name
00228 static QString folderNameForDirectoryName( const QString &dirName )
00229 {
00230   Q_ASSERT( dirName.startsWith( "." ) );
00231   const QString end = ".directory";
00232   const int expectedIndex = dirName.length() - end.length();
00233   if ( dirName.lower().find( end ) != expectedIndex )
00234     return QString();
00235   QString returnName = dirName.left( dirName.length() - end.length() );
00236   returnName = returnName.right( returnName.length() - 1 );
00237   return returnName;
00238 }
00239 
00240 KMFolder* ImportJob::getOrCreateSubFolder( KMFolder *parentFolder, const QString &subFolderName,
00241                                            mode_t subFolderPermissions )
00242 {
00243   if ( !parentFolder->createChildFolder() ) {
00244     abort( i18n( "Unable to create subfolder for folder '%1'." ).arg( parentFolder->name() ) );
00245     return 0;
00246   }
00247 
00248   KMFolder *subFolder = 0;
00249   subFolder = dynamic_cast<KMFolder*>( parentFolder->child()->hasNamedFolder( subFolderName ) );
00250 
00251   if ( !subFolder ) {
00252     subFolder = createSubFolder( parentFolder, subFolderName, subFolderPermissions );
00253   }
00254   return subFolder;
00255 }
00256 
00257 void ImportJob::importNextDirectory()
00258 {
00259   if ( mAborted )
00260     return;
00261 
00262   if ( mQueuedDirectories.isEmpty() ) {
00263     finish();
00264     return;
00265   }
00266 
00267   Folder folder = mQueuedDirectories.first();
00268   KMFolder *currentFolder = folder.parent;
00269   mQueuedDirectories.pop_front();
00270   kdDebug(5006) << "importNextDirectory(): Working on directory " << folder.archiveDir->name() << endl;
00271 
00272   QStringList entries = folder.archiveDir->entries();
00273   for ( uint i = 0; i < entries.size(); i++ ) {
00274     const KArchiveEntry *entry = folder.archiveDir->entry( entries[i] );
00275     Q_ASSERT( entry );
00276     kdDebug(5006) << "Queueing entry " << entry->name() << endl;
00277     if ( entry->isDirectory() ) {
00278       const KArchiveDirectory *dir = static_cast<const KArchiveDirectory*>( entry );
00279       if ( !dir->name().startsWith( "." ) ) {
00280 
00281         kdDebug(5006) << "Queueing messages in folder " << entry->name() << endl;
00282         KMFolder *subFolder = getOrCreateSubFolder( currentFolder, entry->name(), entry->permissions() );
00283         if ( !subFolder )
00284           return;
00285 
00286         enqueueMessages( dir, subFolder );
00287       }
00288 
00289       // Entry starts with a dot, so we assume it is a subdirectory
00290       else {
00291 
00292         const QString folderName = folderNameForDirectoryName( entry->name() );
00293         if ( folderName.isEmpty() ) {
00294           abort( i18n( "Unexpected subdirectory named '%1'." ).arg( entry->name() ) );
00295           return;
00296         }
00297         KMFolder *subFolder = getOrCreateSubFolder( currentFolder, folderName, entry->permissions() );
00298         if ( !subFolder )
00299           return;
00300 
00301         Folder newFolder;
00302         newFolder.archiveDir = dir;
00303         newFolder.parent = subFolder;
00304         kdDebug(5006) << "Enqueueing directory " << entry->name() << endl;
00305         mQueuedDirectories.push_back( newFolder );
00306       }
00307     }
00308   }
00309 
00310   importNextMessage();
00311 }
00312 
00313 // TODO:
00314 // BUGS:
00315 //    Online IMAP can fail spectacular, for example when cancelling upload
00316 //    Online IMAP: Inform that messages are still being uploaded on finish()!
00317 void ImportJob::start()
00318 {
00319   Q_ASSERT( mRootFolder );
00320   Q_ASSERT( mArchiveFile.isValid() );
00321 
00322   KMimeType::Ptr mimeType = KMimeType::findByURL( mArchiveFile, 0, true /* local file */ );
00323   if ( !mimeType->patterns().grep( "tar", false /* no case-sensitive */ ).isEmpty() )
00324     mArchive = new KTar( mArchiveFile.path() );
00325   else if ( !mimeType->patterns().grep( "zip", false ).isEmpty() )
00326     mArchive = new KZip( mArchiveFile.path() );
00327   else {
00328     abort( i18n( "The file '%1' does not appear to be a valid archive." ).arg( mArchiveFile.path() ) );
00329     return;
00330   }
00331 
00332   if ( !mArchive->open( IO_ReadOnly ) ) {
00333     abort( i18n( "Unable to open archive file '%1'" ).arg( mArchiveFile.path() ) );
00334     return;
00335   }
00336 
00337   mProgressItem = KPIM::ProgressManager::createProgressItem(
00338       "ImportJob",
00339       i18n( "Importing Archive" ),
00340       QString(),
00341       true );
00342   mProgressItem->setUsesBusyIndicator( true );
00343   connect( mProgressItem, SIGNAL(progressItemCanceled(KPIM::ProgressItem*)),
00344            this, SLOT(cancelJob()) );
00345 
00346   Folder nextFolder;
00347   nextFolder.archiveDir = mArchive->directory();
00348   nextFolder.parent = mRootFolder;
00349   mQueuedDirectories.push_back( nextFolder );
00350   importNextDirectory();
00351 }
00352 
00353 #include "importjob.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys