diff options
Diffstat (limited to 'kmail/importjob.cpp')
-rw-r--r-- | kmail/importjob.cpp | 396 |
1 files changed, 396 insertions, 0 deletions
diff --git a/kmail/importjob.cpp b/kmail/importjob.cpp new file mode 100644 index 000000000..3a7de1981 --- /dev/null +++ b/kmail/importjob.cpp @@ -0,0 +1,396 @@ +/* Copyright 2009 Klarälvdalens Datakonsult AB + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License or (at your option) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 of the license. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +#include "importjob.h" + +#include "kmfolder.h" +#include "folderutil.h" +#include "kmfolderdir.h" +#include "kmfolderimap.h" +#include "imapjob.h" + +#include "progressmanager.h" + +#include <kdebug.h> +#include <kzip.h> +#include <ktar.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kmimetype.h> + +#include <tqwidget.h> +#include <tqtimer.h> +#include <tqfile.h> + +using namespace KMail; + +KMail::ImportJob::ImportJob( TQWidget *parentWidget ) + : TQObject( parentWidget ), + mArchive( 0 ), + mRootFolder( 0 ), + mParentWidget( parentWidget ), + mNumberOfImportedMessages( 0 ), + mCurrentFolder( 0 ), + mCurrentMessage( 0 ), + mCurrentMessageFile( 0 ), + mProgressItem( 0 ), + mAborted( false ) +{ +} + +KMail::ImportJob::~ImportJob() +{ + if ( mArchive && mArchive->isOpened() ) { + mArchive->close(); + } + delete mArchive; + mArchive = 0; +} + +void KMail::ImportJob::setFile( const KURL &archiveFile ) +{ + mArchiveFile = archiveFile; +} + +void KMail::ImportJob::setRootFolder( KMFolder *rootFolder ) +{ + mRootFolder = rootFolder; +} + +void KMail::ImportJob::finish() +{ + kdDebug(5006) << "Finished import job." << endl; + mProgressItem->setComplete(); + mProgressItem = 0; + TQString text = i18n( "Importing the archive file '%1' into the folder '%2' succeeded." ) + .arg( mArchiveFile.path() ).arg( mRootFolder->name() ); + text += "\n" + i18n( "1 message was imported.", "%n messages were imported.", mNumberOfImportedMessages ); + KMessageBox::information( mParentWidget, text, i18n( "Import finished." ) ); + deleteLater(); +} + +void KMail::ImportJob::cancelJob() +{ + abort( i18n( "The operation was canceled by the user." ) ); +} + +void KMail::ImportJob::abort( const TQString &errorMessage ) +{ + if ( mAborted ) + return; + + mAborted = true; + TQString text = i18n( "Failed to import the archive into folder '%1'." ).arg( mRootFolder->name() ); + text += "\n" + errorMessage; + if ( mProgressItem ) { + mProgressItem->setComplete(); + mProgressItem = 0; + // The progressmanager will delete it + } + KMessageBox::sorry( mParentWidget, text, i18n( "Importing archive failed." ) ); + deleteLater(); +} + +KMFolder * KMail::ImportJob::createSubFolder( KMFolder *parent, const TQString &folderName, mode_t permissions ) +{ + KMFolder *newFolder = FolderUtil::createSubFolder( parent, parent->child(), folderName, TQString(), + KMFolderTypeMaildir ); + if ( !newFolder ) { + abort( i18n( "Unable to create subfolder for folder '%1'." ).arg( parent->name() ) ); + return 0; + } + else { + newFolder->createChildFolder(); // TODO: Just creating a child folder here is wasteful, only do + // that if really needed. We do it here so we can set the + // permissions + chmod( newFolder->location().latin1(), permissions | S_IXUSR ); + chmod( newFolder->subdirLocation().latin1(), permissions | S_IXUSR ); + // TODO: chown? + // TODO: what about subdirectories like "cur"? + return newFolder; + } +} + +void KMail::ImportJob::enqueueMessages( const KArchiveDirectory *dir, KMFolder *folder ) +{ + const KArchiveDirectory *messageDir = dynamic_cast<const KArchiveDirectory*>( dir->entry( "cur" ) ); + if ( messageDir ) { + Messages messagesToQueue; + messagesToQueue.parent = folder; + const TQStringList entries = messageDir->entries(); + for ( uint i = 0; i < entries.size(); i++ ) { + const KArchiveEntry *entry = messageDir->entry( entries[i] ); + Q_ASSERT( entry ); + if ( entry->isDirectory() ) { + kdWarning(5006) << "Unexpected subdirectory in archive folder " << dir->name() << endl; + } + else { + kdDebug(5006) << "Queueing message " << entry->name() << endl; + const KArchiveFile *file = static_cast<const KArchiveFile*>( entry ); + messagesToQueue.files.append( file ); + } + } + mQueuedMessages.append( messagesToQueue ); + } + else { + kdWarning(5006) << "No 'cur' subdirectory for archive directory " << dir->name() << endl; + } +} + +void KMail::ImportJob::messageAdded() +{ + mNumberOfImportedMessages++; + if ( mCurrentFolder->folderType() == KMFolderTypeMaildir || + mCurrentFolder->folderType() == KMFolderTypeCachedImap ) { + const TQString messageFile = mCurrentFolder->location() + "/cur/" + mCurrentMessage->fileName(); + // TODO: what if the file is not in the "cur" subdirectory? + if ( TQFile::exists( messageFile ) ) { + chmod( messageFile.latin1(), mCurrentMessageFile->permissions() ); + // TODO: changing user/group he requires a bit more work, requires converting the strings + // to uid_t and gid_t + //getpwnam() + //chown( messageFile, + } + else { + kdWarning(5006) << "Unable to change permissions for newly created file: " << messageFile << endl; + } + } + // TODO: Else? + + mCurrentMessage = 0; + mCurrentMessageFile = 0; + TQTimer::singleShot( 0, this, TQT_SLOT( importNextMessage() ) ); +} + +void KMail::ImportJob::importNextMessage() +{ + if ( mAborted ) + return; + + if ( mQueuedMessages.isEmpty() ) { + kdDebug(5006) << "importNextMessage(): Processed all messages in the queue." << endl; + if ( mCurrentFolder ) { + mCurrentFolder->close( "ImportJob" ); + } + mCurrentFolder = 0; + importNextDirectory(); + return; + } + + Messages &messages = mQueuedMessages.front(); + if ( messages.files.isEmpty() ) { + mQueuedMessages.pop_front(); + importNextMessage(); + return; + } + + KMFolder *folder = messages.parent; + if ( folder != mCurrentFolder ) { + kdDebug(5006) << "importNextMessage(): Processed all messages in the current folder of the queue." << endl; + if ( mCurrentFolder ) { + mCurrentFolder->close( "ImportJob" ); + } + mCurrentFolder = folder; + if ( mCurrentFolder->open( "ImportJob" ) != 0 ) { + abort( i18n( "Unable to open folder '%1'." ).arg( mCurrentFolder->name() ) ); + return; + } + kdDebug(5006) << "importNextMessage(): Current folder of queue is now: " << mCurrentFolder->name() << endl; + mProgressItem->setStatus( i18n( "Importing folder %1" ).arg( mCurrentFolder->name() ) ); + } + + mProgressItem->setProgress( ( mProgressItem->progress() + 5 ) ); + + mCurrentMessageFile = messages.files.first(); + Q_ASSERT( mCurrentMessageFile ); + messages.files.removeFirst(); + + mCurrentMessage = new KMMessage(); + mCurrentMessage->fromByteArray( mCurrentMessageFile->data(), true /* setStatus */ ); + int retIndex; + + // If this is not an IMAP folder, we can add the message directly. Otherwise, the whole thing is + // async, for online IMAP. While addMsg() fakes a sync call, we rather do it the async way here + // ourselves, as otherwise the whole thing gets pretty much messed up with regards to folder + // refcounting. Furthermore, the completion dialog would be shown before the messages are actually + // uploaded. + if ( mCurrentFolder->folderType() != KMFolderTypeImap ) { + if ( mCurrentFolder->addMsg( mCurrentMessage, &retIndex ) != 0 ) { + abort( i18n( "Failed to add a message to the folder '%1'." ).arg( mCurrentFolder->name() ) ); + return; + } + messageAdded(); + } + else { + ImapJob *imapJob = new ImapJob( mCurrentMessage, ImapJob::tPutMessage, + dynamic_cast<KMFolderImap*>( mCurrentFolder->storage() ) ); + connect( imapJob, TQT_SIGNAL(result(KMail::FolderJob*)), + TQT_SLOT(messagePutResult(KMail::FolderJob*)) ); + imapJob->start(); + } +} + +void KMail::ImportJob::messagePutResult( KMail::FolderJob *job ) +{ + if ( mAborted ) + return; + + if ( job->error() ) { + abort( i18n( "Failed to upload a message to the IMAP server." ) ); + return; + } else { + + KMFolderImap *imap = dynamic_cast<KMFolderImap*>( mCurrentFolder->storage() ); + Q_ASSERT( imap ); + + // Ok, we uploaded the message, but we still need to add it to the folder. Use addMsgQuiet(), + // otherwise it will be uploaded again. + imap->addMsgQuiet( mCurrentMessage ); + messageAdded(); + } +} + +// Input: .inbox.directory +// Output: inbox +// Can also return an empty string if this is no valid dir name +static TQString folderNameForDirectoryName( const TQString &dirName ) +{ + Q_ASSERT( dirName.startsWith( "." ) ); + const TQString end = ".directory"; + const int expectedIndex = dirName.length() - end.length(); + if ( dirName.lower().find( end ) != expectedIndex ) + return TQString(); + TQString returnName = dirName.left( dirName.length() - end.length() ); + returnName = returnName.right( returnName.length() - 1 ); + return returnName; +} + +KMFolder* KMail::ImportJob::getOrCreateSubFolder( KMFolder *parentFolder, const TQString &subFolderName, + mode_t subFolderPermissions ) +{ + if ( !parentFolder->createChildFolder() ) { + abort( i18n( "Unable to create subfolder for folder '%1'." ).arg( parentFolder->name() ) ); + return 0; + } + + KMFolder *subFolder = 0; + subFolder = dynamic_cast<KMFolder*>( parentFolder->child()->hasNamedFolder( subFolderName ) ); + + if ( !subFolder ) { + subFolder = createSubFolder( parentFolder, subFolderName, subFolderPermissions ); + } + return subFolder; +} + +void KMail::ImportJob::importNextDirectory() +{ + if ( mAborted ) + return; + + if ( mQueuedDirectories.isEmpty() ) { + finish(); + return; + } + + Folder folder = mQueuedDirectories.first(); + KMFolder *currentFolder = folder.parent; + mQueuedDirectories.pop_front(); + kdDebug(5006) << "importNextDirectory(): Working on directory " << folder.archiveDir->name() << endl; + + TQStringList entries = folder.archiveDir->entries(); + for ( uint i = 0; i < entries.size(); i++ ) { + const KArchiveEntry *entry = folder.archiveDir->entry( entries[i] ); + Q_ASSERT( entry ); + kdDebug(5006) << "Queueing entry " << entry->name() << endl; + if ( entry->isDirectory() ) { + const KArchiveDirectory *dir = static_cast<const KArchiveDirectory*>( entry ); + if ( !dir->name().startsWith( "." ) ) { + + kdDebug(5006) << "Queueing messages in folder " << entry->name() << endl; + KMFolder *subFolder = getOrCreateSubFolder( currentFolder, entry->name(), entry->permissions() ); + if ( !subFolder ) + return; + + enqueueMessages( dir, subFolder ); + } + + // Entry starts with a dot, so we assume it is a subdirectory + else { + + const TQString folderName = folderNameForDirectoryName( entry->name() ); + if ( folderName.isEmpty() ) { + abort( i18n( "Unexpected subdirectory named '%1'." ).arg( entry->name() ) ); + return; + } + KMFolder *subFolder = getOrCreateSubFolder( currentFolder, folderName, entry->permissions() ); + if ( !subFolder ) + return; + + Folder newFolder; + newFolder.archiveDir = dir; + newFolder.parent = subFolder; + kdDebug(5006) << "Enqueueing directory " << entry->name() << endl; + mQueuedDirectories.push_back( newFolder ); + } + } + } + + importNextMessage(); +} + +// TODO: +// BUGS: +// Online IMAP can fail spectacular, for example when cancelling upload +// Online IMAP: Inform that messages are still being uploaded on finish()! +void KMail::ImportJob::start() +{ + Q_ASSERT( mRootFolder ); + Q_ASSERT( mArchiveFile.isValid() ); + + KMimeType::Ptr mimeType = KMimeType::findByURL( mArchiveFile, 0, true /* local file */ ); + if ( !mimeType->patterns().grep( "tar", false /* no case-sensitive */ ).isEmpty() ) + mArchive = new KTar( mArchiveFile.path() ); + else if ( !mimeType->patterns().grep( "zip", false ).isEmpty() ) + mArchive = new KZip( mArchiveFile.path() ); + else { + abort( i18n( "The file '%1' does not appear to be a valid archive." ).arg( mArchiveFile.path() ) ); + return; + } + + if ( !mArchive->open( IO_ReadOnly ) ) { + abort( i18n( "Unable to open archive file '%1'" ).arg( mArchiveFile.path() ) ); + return; + } + + mProgressItem = KPIM::ProgressManager::createProgressItem( + "ImportJob", + i18n( "Importing Archive" ), + TQString(), + true ); + mProgressItem->setUsesBusyIndicator( true ); + connect( mProgressItem, TQT_SIGNAL(progressItemCanceled(KPIM::ProgressItem*)), + this, TQT_SLOT(cancelJob()) ); + + Folder nextFolder; + nextFolder.archiveDir = mArchive->directory(); + nextFolder.parent = mRootFolder; + mQueuedDirectories.push_back( nextFolder ); + importNextDirectory(); +} + +#include "importjob.moc" |