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 <tdelocale.h> 00033 #include <tdemessagebox.h> 00034 #include <kmimetype.h> 00035 00036 #include <tqwidget.h> 00037 #include <tqtimer.h> 00038 #include <tqfile.h> 00039 00040 using namespace KMail; 00041 00042 KMail::ImportJob::ImportJob( TQWidget *parentWidget ) 00043 : TQObject( 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 KMail::ImportJob::~ImportJob() 00057 { 00058 if ( mArchive && mArchive->isOpened() ) { 00059 mArchive->close(); 00060 } 00061 delete mArchive; 00062 mArchive = 0; 00063 } 00064 00065 void KMail::ImportJob::setFile( const KURL &archiveFile ) 00066 { 00067 mArchiveFile = archiveFile; 00068 } 00069 00070 void KMail::ImportJob::setRootFolder( KMFolder *rootFolder ) 00071 { 00072 mRootFolder = rootFolder; 00073 } 00074 00075 void KMail::ImportJob::finish() 00076 { 00077 kdDebug(5006) << "Finished import job." << endl; 00078 mProgressItem->setComplete(); 00079 mProgressItem = 0; 00080 TQString 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 KMail::ImportJob::cancelJob() 00088 { 00089 abort( i18n( "The operation was canceled by the user." ) ); 00090 } 00091 00092 void KMail::ImportJob::abort( const TQString &errorMessage ) 00093 { 00094 if ( mAborted ) 00095 return; 00096 00097 mAborted = true; 00098 TQString 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 * KMail::ImportJob::createSubFolder( KMFolder *parent, const TQString &folderName, mode_t permissions ) 00110 { 00111 KMFolder *newFolder = FolderUtil::createSubFolder( parent, parent->child(), folderName, TQString(), 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 | S_IXUSR ); 00122 chmod( newFolder->subdirLocation().latin1(), permissions | S_IXUSR ); 00123 // TODO: chown? 00124 // TODO: what about subdirectories like "cur"? 00125 return newFolder; 00126 } 00127 } 00128 00129 void KMail::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 TQStringList 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 KMail::ImportJob::messageAdded() 00156 { 00157 mNumberOfImportedMessages++; 00158 if ( mCurrentFolder->folderType() == KMFolderTypeMaildir || 00159 mCurrentFolder->folderType() == KMFolderTypeCachedImap ) { 00160 const TQString messageFile = mCurrentFolder->location() + "/cur/" + mCurrentMessage->fileName(); 00161 // TODO: what if the file is not in the "cur" subdirectory? 00162 if ( TQFile::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 TQTimer::singleShot( 0, this, TQT_SLOT( importNextMessage() ) ); 00178 } 00179 00180 void KMail::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, TQT_SIGNAL(result(KMail::FolderJob*)), 00243 TQT_SLOT(messagePutResult(KMail::FolderJob*)) ); 00244 imapJob->start(); 00245 } 00246 } 00247 00248 void KMail::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 TQString folderNameForDirectoryName( const TQString &dirName ) 00272 { 00273 Q_ASSERT( dirName.startsWith( "." ) ); 00274 const TQString end = ".directory"; 00275 const int expectedIndex = dirName.length() - end.length(); 00276 if ( dirName.lower().find( end ) != expectedIndex ) 00277 return TQString(); 00278 TQString returnName = dirName.left( dirName.length() - end.length() ); 00279 returnName = returnName.right( returnName.length() - 1 ); 00280 return returnName; 00281 } 00282 00283 KMFolder* KMail::ImportJob::getOrCreateSubFolder( KMFolder *parentFolder, const TQString &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 KMail::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 TQStringList 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 TQString 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 KMail::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 TQString(), 00384 true ); 00385 mProgressItem->setUsesBusyIndicator( true ); 00386 connect( mProgressItem, TQT_SIGNAL(progressItemCanceled(KPIM::ProgressItem*)), 00387 this, TQT_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"