kde-playground/kdepim-runtime/resources/mixedmaildir/mixedmaildirstore.cpp
2015-04-14 22:08:21 +00:00

2366 lines
83 KiB
C++

/* This file is part of the KDE project
Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
Author: Kevin Krammer, krake@kdab.com
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "mixedmaildirstore.h"
#include "kmindexreader/kmindexreader.h"
#include "filestore/collectioncreatejob.h"
#include "filestore/collectiondeletejob.h"
#include "filestore/collectionfetchjob.h"
#include "filestore/collectionmovejob.h"
#include "filestore/collectionmodifyjob.h"
#include "filestore/entitycompactchangeattribute.h"
#include "filestore/itemcreatejob.h"
#include "filestore/itemdeletejob.h"
#include "filestore/itemfetchjob.h"
#include "filestore/itemmodifyjob.h"
#include "filestore/itemmovejob.h"
#include "filestore/storecompactjob.h"
#include "libmaildir/maildir.h"
#include <kmbox/mbox.h>
#include <kmime/kmime_message.h>
#include <akonadi/kmime/messageparts.h>
#include <akonadi/cachepolicy.h>
#include <akonadi/itemfetchscope.h>
#include <kpimutils/kfileio.h>
#include <KLocalizedString>
#include <QDir>
#include <QFileInfo>
#include <QStringList>
using namespace Akonadi;
using KPIM::Maildir;
using namespace KMBox;
static bool fullEntryCompare( const KMBox::MBoxEntry &left, const KMBox::MBoxEntry &right )
{
return ( left.messageOffset() == right.messageOffset() &&
left.separatorSize() == right.separatorSize() &&
left.messageSize() == right.messageSize() );
}
static bool indexFileForFolder( const QFileInfo &folderDirInfo, QFileInfo &indexFileInfo )
{
indexFileInfo = QFileInfo( folderDirInfo.dir(), QString::fromUtf8( ".%1.index" ).arg( folderDirInfo.fileName() ) );
if ( !indexFileInfo.exists() || !indexFileInfo.isReadable() ) {
kDebug( KDE_DEFAULT_DEBUG_AREA ) << "No index file" << indexFileInfo.absoluteFilePath() << "or not readable";
return false;
}
return true;
}
class MBoxContext
{
public:
MBoxContext() : mRevision( 0 ), mIndexDataLoaded( false ), mHasIndexData( false ) {}
QString fileName() const
{
return mMBox.fileName();
}
bool load( const QString &fileName )
{
mModificationTime = QFileInfo( fileName ).lastModified();
// in case of reload, check if anything changed, otherwise keep deleted entries
if ( !mDeletedOffsets.isEmpty() && fileName == mMBox.fileName() ) {
const KMBox::MBoxEntry::List currentEntryList = mMBox.entries();
if ( mMBox.load( fileName ) ) {
const KMBox::MBoxEntry::List newEntryList = mMBox.entries();
if ( !std::equal( currentEntryList.begin(), currentEntryList.end(), newEntryList.begin(), fullEntryCompare ) ) {
mDeletedOffsets.clear();
}
return true;
}
return false;
}
mDeletedOffsets.clear();
return mMBox.load( fileName );
}
QDateTime modificationTime() const { return mModificationTime; }
KMBox::MBoxEntry::List entryList() const
{
KMBox::MBoxEntry::List result;
Q_FOREACH( const KMBox::MBoxEntry &entry, mMBox.entries() ) {
if ( !mDeletedOffsets.contains( entry.messageOffset() ) ) {
result << entry;
}
}
return result;
}
QByteArray readRawEntry( quint64 offset )
{
return mMBox.readRawMessage( KMBox::MBoxEntry( offset ) );
}
QByteArray readEntryHeaders( quint64 offset )
{
return mMBox.readMessageHeaders( KMBox::MBoxEntry( offset ) );
}
qint64 appendEntry( const KMime::Message::Ptr &entry )
{
const KMBox::MBoxEntry result = mMBox.appendMessage( entry );
if ( result.isValid() && mHasIndexData ) {
mIndexData.insert( result.messageOffset(), KMIndexDataPtr( new KMIndexData ) );
Q_ASSERT( mIndexData.value( result.messageOffset() )->isEmpty() );
}
return result.messageOffset();
}
void deleteEntry( quint64 offset )
{
mDeletedOffsets << offset;
}
bool isValidOffset( quint64 offset ) const
{
if ( mDeletedOffsets.contains( offset ) ) {
return false;
}
Q_FOREACH( const KMBox::MBoxEntry &entry, mMBox.entries() ) {
if ( entry.messageOffset() == offset ) {
return true;
}
}
return false;
}
bool save()
{
bool ret = mMBox.save();
mModificationTime = QDateTime::currentDateTime();
return ret;
}
int purge( QList<KMBox::MBoxEntry::Pair> &movedEntries )
{
const int deleteCount = mDeletedOffsets.count();
KMBox::MBoxEntry::List deletedEntries;
Q_FOREACH( quint64 offset, mDeletedOffsets )
deletedEntries << KMBox::MBoxEntry( offset );
const bool result = mMBox.purge( deletedEntries, &movedEntries );
if ( mHasIndexData ) {
// keep copy of original for lookup
const IndexDataHash indexData = mIndexData;
// delete index data for removed entries
Q_FOREACH( quint64 offset, mDeletedOffsets ) {
mIndexData.remove( offset );
}
// delete index data for changed entries
// readded below in an extra loop to handled cases where a new index is equal to an
// old index of a different entry
Q_FOREACH( const KMBox::MBoxEntry::Pair &entry, movedEntries ) {
mIndexData.remove( entry.first.messageOffset() );
}
// readd index data for changed entries at their new position
Q_FOREACH( const KMBox::MBoxEntry::Pair &entry, movedEntries ) {
const KMIndexDataPtr data = indexData.value( entry.first.messageOffset() );
mIndexData.insert( entry.second.messageOffset(), data );
}
}
mDeletedOffsets.clear();
mModificationTime = QDateTime::currentDateTime();
return ( result ? deleteCount : -1 );
}
MBox &mbox()
{
return mMBox;
}
const MBox &mbox() const
{
return mMBox;
}
bool hasDeletedOffsets() const
{
return !mDeletedOffsets.isEmpty();
}
void readIndexData();
KMIndexDataPtr indexData( quint64 offset ) const {
if ( mHasIndexData ) {
if ( !mDeletedOffsets.contains( offset ) ) {
IndexDataHash::const_iterator it = mIndexData.constFind( offset );
if ( it != mIndexData.constEnd() ) {
return it.value();
}
}
}
return KMIndexDataPtr();
}
bool hasIndexData() const {
return mHasIndexData;
}
void updatePath( const QString &newPath ) {
// TODO FIXME there has to be a better of doing that
mMBox.load( newPath );
}
public:
Collection mCollection;
qint64 mRevision;
private:
QSet<quint64> mDeletedOffsets;
MBox mMBox;
QDateTime mModificationTime;
typedef QHash<quint64, KMIndexDataPtr> IndexDataHash;
IndexDataHash mIndexData;
bool mIndexDataLoaded;
bool mHasIndexData;
};
typedef boost::shared_ptr<MBoxContext> MBoxPtr;
void MBoxContext::readIndexData()
{
if ( mIndexDataLoaded ) {
return;
}
mIndexDataLoaded = true;
const QFileInfo mboxFileInfo( mMBox.fileName() );
QFileInfo indexFileInfo;
if ( !indexFileForFolder( mboxFileInfo, indexFileInfo ) ) {
return;
}
if ( mboxFileInfo.lastModified() > indexFileInfo.lastModified() ) {
kDebug( KDE_DEFAULT_DEBUG_AREA ) << "MBox file " << mboxFileInfo.absoluteFilePath()
<< "newer than the index: mbox modified at"
<< mboxFileInfo.lastModified() << ", index modified at"
<< indexFileInfo.lastModified();
return;
}
KMIndexReader indexReader( indexFileInfo.absoluteFilePath() );
if ( indexReader.error() || !indexReader.readIndex() ) {
kError() << "Index file" << indexFileInfo.path() << "could not be read";
return;
}
mHasIndexData = true;
const KMBox::MBoxEntry::List entries = mMBox.entries();
Q_FOREACH( const KMBox::MBoxEntry &entry, entries ) {
const quint64 indexOffset = entry.messageOffset() + entry.separatorSize();
const KMIndexDataPtr data = indexReader.dataByOffset( indexOffset );
if ( data != 0 ) {
mIndexData.insert( entry.messageOffset(), data );
}
}
kDebug( KDE_DEFAULT_DEBUG_AREA ) << "Read" << mIndexData.count() << "index entries from"
<< indexFileInfo.absoluteFilePath();
}
class MaildirContext
{
public:
MaildirContext( const QString &path, bool isTopLevel )
: mMaildir( path, isTopLevel ), mIndexDataLoaded( false ), mHasIndexData( false )
{
}
MaildirContext( const Maildir &md )
: mMaildir( md ), mIndexDataLoaded( false ), mHasIndexData( false )
{
}
QStringList entryList() const {
return mMaildir.entryList();
}
QString addEntry( const QByteArray &data ) {
const QString result = mMaildir.addEntry( data );
if ( !result.isEmpty() && mHasIndexData ) {
mIndexData.insert( result, KMIndexDataPtr( new KMIndexData ) );
Q_ASSERT( mIndexData.value( result )->isEmpty() );
} else {
//TODO: use the error string?
kWarning() << mMaildir.lastError();
}
return result;
}
void writeEntry( const QString &key, const QByteArray &data ) {
mMaildir.writeEntry( key, data ); //TODO: error handling
if ( mHasIndexData ) {
mIndexData.insert( key, KMIndexDataPtr( new KMIndexData ) );
}
}
bool removeEntry( const QString &key ) {
const bool result = mMaildir.removeEntry( key );
if ( result && mHasIndexData ) {
mIndexData.remove( key );
}
return result;
}
QString moveEntryTo( const QString &key, MaildirContext &destination ) {
const QString result = mMaildir.moveEntryTo( key, destination.mMaildir );
if ( !result.isEmpty() ) {
if ( mHasIndexData ) {
mIndexData.remove( key );
}
if ( destination.mHasIndexData ) {
destination.mIndexData.insert( result, KMIndexDataPtr( new KMIndexData ) );
}
} else {
//TODO error handling?
kWarning() << mMaildir.lastError();
}
return result;
}
QByteArray readEntryHeaders( const QString &key ) const {
return mMaildir.readEntryHeaders( key );
}
QByteArray readEntry( const QString &key ) const {
return mMaildir.readEntry( key );
}
bool isValid( QString &error ) const {
bool result = mMaildir.isValid();
if ( !result ) {
error = mMaildir.lastError();
}
return result;
}
bool isValidEntry( const QString &entry ) const {
return !mMaildir.findRealKey( entry ).isEmpty();
}
void readIndexData();
KMIndexDataPtr indexData( const QString &fileName ) const {
if ( mHasIndexData ) {
IndexDataHash::const_iterator it = mIndexData.constFind( fileName );
if ( it != mIndexData.constEnd() ) {
return it.value();
}
}
return KMIndexDataPtr();
}
bool hasIndexData() const {
return mHasIndexData;
}
void updatePath( const QString &newPath ) {
mMaildir = Maildir( newPath, mMaildir.isRoot() );
}
const Maildir & maildir() const {
return mMaildir;
}
private:
Maildir mMaildir;
typedef QHash<QString, KMIndexDataPtr> IndexDataHash;
IndexDataHash mIndexData;
bool mIndexDataLoaded;
bool mHasIndexData;
};
void MaildirContext::readIndexData()
{
if ( mIndexDataLoaded ) {
return;
}
mIndexDataLoaded = true;
const QFileInfo maildirFileInfo( mMaildir.path() );
QFileInfo indexFileInfo;
if ( !indexFileForFolder( maildirFileInfo, indexFileInfo ) ) {
return;
}
const QDir maildirBaseDir( maildirFileInfo.absoluteFilePath() );
const QFileInfo curDirFileInfo( maildirBaseDir, QLatin1String( "cur" ) );
const QFileInfo newDirFileInfo( maildirBaseDir, QLatin1String( "new" ) );
if ( curDirFileInfo.lastModified() > indexFileInfo.lastModified() ) {
kDebug( KDE_DEFAULT_DEBUG_AREA ) << "Maildir " << maildirFileInfo.absoluteFilePath()
<< "\"cur\" directory newer than the index: cur modified at"
<< curDirFileInfo.lastModified() << ", index modified at"
<< indexFileInfo.lastModified();
return;
}
if ( newDirFileInfo.lastModified() > indexFileInfo.lastModified() ) {
kDebug( KDE_DEFAULT_DEBUG_AREA ) << "Maildir \"new\" directory newer than the index: cur modified at"
<< newDirFileInfo.lastModified() << ", index modified at"
<< indexFileInfo.lastModified();
return;
}
KMIndexReader indexReader( indexFileInfo.absoluteFilePath() );
if ( indexReader.error() || !indexReader.readIndex() ) {
kError() << "Index file" << indexFileInfo.path() << "could not be read";
return;
}
mHasIndexData = true;
const QStringList entries = mMaildir.entryList();
Q_FOREACH( const QString &entry, entries ) {
const KMIndexDataPtr data = indexReader.dataByFileName( entry );
if ( data != 0 ) {
mIndexData.insert( entry, data );
}
}
kDebug( KDE_DEFAULT_DEBUG_AREA ) << "Read" << mIndexData.count() << "index entries from"
<< indexFileInfo.absoluteFilePath();
}
typedef boost::shared_ptr<MaildirContext> MaildirPtr;
class MixedMaildirStore::Private : public FileStore::Job::Visitor
{
MixedMaildirStore *const q;
public:
enum FolderType
{
InvalidFolder,
TopLevelFolder,
MaildirFolder,
MBoxFolder
};
Private( MixedMaildirStore *parent ) : q( parent )
{
}
FolderType folderForCollection( const Collection &col, QString &path, QString &errorText ) const;
MBoxPtr getOrCreateMBoxPtr( const QString &path );
MaildirPtr getOrCreateMaildirPtr( const QString &path, bool isTopLevel );
void fillMBoxCollectionDetails( const MBoxPtr &mbox, Collection &collection );
void fillMaildirCollectionDetails( const Maildir &md, Collection &collection );
void fillMaildirTreeDetails( const Maildir &md, const Collection &collection, Collection::List &collections, bool recurse );
void listCollection( FileStore::Job *job, MBoxPtr &mbox, const Collection &collection, Item::List &items );
void listCollection( FileStore::Job *job, MaildirPtr &md, const Collection &collection, Item::List &items );
bool fillItem( MBoxPtr &mbox, bool includeHeaders, bool includeBody, Item &item ) const;
bool fillItem( const MaildirPtr &md, bool includeHeaders, bool includeBody, Item &item ) const;
void updateContextHashes( const QString &oldPath, const QString &newPath );
public: // visitor interface implementation
bool visit( FileStore::Job *job );
bool visit( FileStore::CollectionCreateJob *job );
bool visit( FileStore::CollectionDeleteJob *job );
bool visit( FileStore::CollectionFetchJob *job );
bool visit( FileStore::CollectionModifyJob *job );
bool visit( FileStore::CollectionMoveJob *job );
bool visit( FileStore::ItemCreateJob *job );
bool visit( FileStore::ItemDeleteJob *job );
bool visit( FileStore::ItemFetchJob *job );
bool visit( FileStore::ItemModifyJob *job );
bool visit( FileStore::ItemMoveJob *job );
bool visit( FileStore::StoreCompactJob *job );
public:
typedef QHash<QString, MBoxPtr> MBoxHash;
MBoxHash mMBoxes;
typedef QHash<QString, MaildirPtr> MaildirHash;
MaildirHash mMaildirs;
};
MixedMaildirStore::Private::FolderType MixedMaildirStore::Private::folderForCollection( const Collection &col, QString &path, QString &errorText ) const
{
path.clear();
errorText.clear();
if ( col.remoteId().isEmpty() ) {
errorText = i18nc( "@info:status", "Given folder name is empty" );
kWarning() << "Incomplete ancestor chain for collection.";
Q_ASSERT( !col.remoteId().isEmpty() ); // abort! Look at backtrace to see where we came from.
return InvalidFolder;
}
if ( col.parentCollection() == Collection::root() ) {
path = q->path();
if ( col.remoteId() != path )
kWarning() << "RID mismatch, is" << col.remoteId() << "expected" << path;
return TopLevelFolder;
}
FolderType type = folderForCollection( col.parentCollection(), path, errorText );
switch ( type ) {
case InvalidFolder: return InvalidFolder;
case TopLevelFolder: // fall through
case MaildirFolder: {
const Maildir parentMd( path, type == TopLevelFolder );
const Maildir subFolder = parentMd.subFolder( col.remoteId() );
if ( subFolder.isValid( false ) ) {
path = subFolder.path();
return MaildirFolder;
}
const QString subDirPath =
(type == TopLevelFolder ? path : Maildir::subDirPathForFolderPath( path ) );
QFileInfo fileInfo( QDir( subDirPath ), col.remoteId() );
if ( fileInfo.isFile() ) {
path = fileInfo.absoluteFilePath();
return MBoxFolder;
}
errorText = i18nc( "@info:status", "Folder %1 does not seem to be a valid email folder", fileInfo.absoluteFilePath() );
return InvalidFolder;
}
case MBoxFolder: {
const QString subDirPath = Maildir::subDirPathForFolderPath( path );
QFileInfo fileInfo( QDir( subDirPath ), col.remoteId() );
if ( fileInfo.isFile() ) {
path = fileInfo.absoluteFilePath();
return MBoxFolder;
}
const Maildir subFolder( fileInfo.absoluteFilePath(), false );
if ( subFolder.isValid( false ) ) {
path = subFolder.path();
return MaildirFolder;
}
errorText = i18nc( "@info:status", "Folder %1 does not seem to be a valid email folder", fileInfo.absoluteFilePath() );
return InvalidFolder;
}
}
return InvalidFolder;
}
MBoxPtr MixedMaildirStore::Private::getOrCreateMBoxPtr( const QString &path )
{
MBoxPtr mbox;
const MBoxHash::const_iterator it = mMBoxes.constFind( path );
if ( it == mMBoxes.constEnd() ) {
mbox = MBoxPtr( new MBoxContext );
mMBoxes.insert( path, mbox );
} else {
mbox = it.value();
}
return mbox;
}
MaildirPtr MixedMaildirStore::Private::getOrCreateMaildirPtr( const QString &path, bool isTopLevel )
{
MaildirPtr md;
const MaildirHash::const_iterator it = mMaildirs.constFind( path );
if ( it == mMaildirs.constEnd() ) {
md = MaildirPtr( new MaildirContext( path, isTopLevel ) );
mMaildirs.insert( path, md );
} else {
md = it.value();
}
return md;
}
void MixedMaildirStore::Private::fillMBoxCollectionDetails( const MBoxPtr &mbox, Collection &collection )
{
collection.setContentMimeTypes( QStringList() << Collection::mimeType()
<< KMime::Message::mimeType() );
const QFileInfo fileInfo( mbox->fileName() );
if ( fileInfo.isWritable() ) {
collection.setRights( Collection::CanCreateItem |
Collection::CanChangeItem |
Collection::CanDeleteItem |
Collection::CanCreateCollection |
Collection::CanChangeCollection |
Collection::CanDeleteCollection );
} else {
collection.setRights( Collection::ReadOnly );
}
if ( mbox->mRevision > 0 ) {
collection.setRemoteRevision( QString::number( mbox->mRevision ) );
}
}
void MixedMaildirStore::Private::fillMaildirCollectionDetails( const Maildir &md, Collection &collection )
{
collection.setContentMimeTypes( QStringList() << Collection::mimeType()
<< KMime::Message::mimeType() );
const QFileInfo fileInfo( md.path() );
if ( fileInfo.isWritable() ) {
collection.setRights( Collection::CanCreateItem |
Collection::CanChangeItem |
Collection::CanDeleteItem |
Collection::CanCreateCollection |
Collection::CanChangeCollection |
Collection::CanDeleteCollection );
} else {
collection.setRights( Collection::ReadOnly );
}
}
void MixedMaildirStore::Private::fillMaildirTreeDetails( const Maildir &md, const Collection &collection, Collection::List &collections, bool recurse )
{
if ( md.path().isEmpty() ) {
return;
}
const QStringList maildirSubFolders = md.subFolderList();
Q_FOREACH( const QString &subFolder, maildirSubFolders ) {
const Maildir subMd = md.subFolder( subFolder );
if ( !mMaildirs.contains( subMd.path() ) ) {
const MaildirPtr mdPtr = MaildirPtr( new MaildirContext( subMd ) );
mMaildirs.insert( subMd.path(), mdPtr );
}
Collection col;
col.setRemoteId( subFolder );
col.setName( subFolder );
col.setParentCollection( collection );
fillMaildirCollectionDetails( subMd, col );
collections << col;
if ( recurse ) {
fillMaildirTreeDetails( subMd, col, collections, true );
}
}
const QDir dir( md.isRoot() ? md.path() : Maildir::subDirPathForFolderPath( md.path() ) );
const QFileInfoList fileInfos = dir.entryInfoList( QDir::Files );
Q_FOREACH( const QFileInfo &fileInfo, fileInfos ) {
if ( fileInfo.isHidden() || !fileInfo.isReadable() ) {
continue;
}
const QString mboxPath = fileInfo.absoluteFilePath();
MBoxPtr mbox = getOrCreateMBoxPtr( mboxPath );
if ( mbox->load( mboxPath ) ) {
const QString subFolder = fileInfo.fileName();
Collection col;
col.setRemoteId( subFolder );
col.setName( subFolder );
col.setParentCollection( collection );
mbox->mCollection = col;
fillMBoxCollectionDetails( mbox, col );
collections << col;
if ( recurse ) {
const QString subDirPath = Maildir::subDirPathForFolderPath( fileInfo.absoluteFilePath() );
const Maildir subMd( subDirPath, true );
fillMaildirTreeDetails( subMd, col, collections, true );
}
} else {
mMBoxes.remove( fileInfo.absoluteFilePath() );
}
}
}
void MixedMaildirStore::Private::listCollection( FileStore::Job *job, MBoxPtr &mbox, const Collection &collection, Item::List &items )
{
mbox->readIndexData();
QHash<QString, QVariant> uidHash;
QHash<QString, QVariant> tagListHash;
const KMBox::MBoxEntry::List entryList = mbox->entryList();
Q_FOREACH( const KMBox::MBoxEntry &entry, entryList ) {
Item item;
item.setMimeType( KMime::Message::mimeType() );
item.setRemoteId( QString::number( entry.messageOffset() ) );
item.setParentCollection( collection );
if ( mbox->hasIndexData() ) {
const KMIndexDataPtr indexData = mbox->indexData( entry.messageOffset() );
if ( indexData != 0 && !indexData->isEmpty() ) {
item.setFlags( indexData->status().statusFlags() );
quint64 uid = indexData->uid();
if ( uid != 0 ) {
kDebug() << "item" << item.remoteId() << "has UID" << uid;
uidHash.insert( item.remoteId(), QString::number( uid ) );
}
const QStringList tagList = indexData->tagList();
if ( !tagList.isEmpty() ) {
kDebug() << "item" << item.remoteId() << "has"
<< tagList.count() << "tags:" << tagList;
tagListHash.insert( item.remoteId(), tagList );
}
} else if ( indexData == 0 ) {
Akonadi::MessageStatus status;
status.setDeleted( true ),
item.setFlags( status.statusFlags() );
kDebug() << "no index for item" << item.remoteId() << "in MBox" << mbox->fileName()
<< "so it has been deleted but not purged. Marking it as"
<< item.flags();
}
}
items << item;
}
if ( mbox->hasIndexData() ) {
QVariant var;
if ( !uidHash.isEmpty() ) {
var = QVariant::fromValue< QHash<QString, QVariant> >( uidHash );
job->setProperty( "remoteIdToIndexUid", var );
}
if ( !tagListHash.isEmpty() ) {
var = QVariant::fromValue< QHash<QString, QVariant> >( tagListHash );
job->setProperty( "remoteIdToTagList", var );
}
}
}
void MixedMaildirStore::Private::listCollection( FileStore::Job *job, MaildirPtr &md, const Collection &collection, Item::List &items )
{
md->readIndexData();
QHash<QString, QVariant> uidHash;
QHash<QString, QVariant> tagListHash;
const QStringList entryList = md->entryList();
Q_FOREACH( const QString &entry, entryList ) {
Item item;
item.setMimeType( KMime::Message::mimeType() );
item.setRemoteId( entry );
item.setParentCollection( collection );
if ( md->hasIndexData() ) {
const KMIndexDataPtr indexData = md->indexData( entry );
if ( indexData != 0 && !indexData->isEmpty() ) {
item.setFlags( indexData->status().statusFlags() );
const quint64 uid = indexData->uid();
if ( uid != 0 ) {
kDebug() << "item" << item.remoteId() << "has UID" << uid;
uidHash.insert( item.remoteId(), QString::number( uid ) );
}
const QStringList tagList = indexData->tagList();
if ( !tagList.isEmpty() ) {
kDebug() << "item" << item.remoteId() << "has"
<< tagList.count() << "tags:" << tagList;
tagListHash.insert( item.remoteId(), tagList );
}
}
}
Akonadi::Item::Flags flags = md->maildir().readEntryFlags( entry );
Q_FOREACH( const Akonadi::Item::Flag& flag, flags ) {
item.setFlag(flag);
}
items << item;
}
if ( md->hasIndexData() ) {
QVariant var;
if ( !uidHash.isEmpty() ) {
var = QVariant::fromValue< QHash<QString, QVariant> >( uidHash );
job->setProperty( "remoteIdToIndexUid", var );
}
if ( !tagListHash.isEmpty() ) {
var = QVariant::fromValue< QHash<QString, QVariant> >( tagListHash );
job->setProperty( "remoteIdToTagList", var );
}
}
}
bool MixedMaildirStore::Private::fillItem( MBoxPtr &mbox, bool includeHeaders, bool includeBody, Item &item ) const
{
// kDebug( KDE_DEFAULT_DEBUG_AREA ) << "Filling item" << item.remoteId() << "from MBox: includeBody=" << includeBody;
bool ok = false;
const quint64 offset = item.remoteId().toULongLong( &ok );
if ( !ok || !mbox->isValidOffset( offset ) ) {
return false;
}
item.setModificationTime( mbox->modificationTime() );
// TODO: size?
if ( includeHeaders || includeBody ) {
KMime::Message::Ptr messagePtr( new KMime::Message() );
if ( includeBody ) {
const QByteArray data = mbox->readRawEntry( offset );
messagePtr->setContent( KMime::CRLFtoLF( data ) );
} else {
const QByteArray data = mbox->readEntryHeaders( offset );
messagePtr->setHead( KMime::CRLFtoLF( data ) );
}
messagePtr->parse();
item.setPayload<KMime::Message::Ptr>( messagePtr );
}
return true;
}
bool MixedMaildirStore::Private::fillItem( const MaildirPtr &md, bool includeHeaders, bool includeBody, Item &item ) const
{
/* kDebug( KDE_DEFAULT_DEBUG_AREA ) << "Filling item" << item.remoteId() << "from Maildir: includeBody=" << includeBody;*/
const qint64 entrySize = md->maildir().size( item.remoteId() );
if ( entrySize < 0 )
return false;
item.setSize( entrySize );
item.setModificationTime( md->maildir().lastModified( item.remoteId() ) );
if ( includeHeaders || includeBody ) {
KMime::Message::Ptr messagePtr( new KMime::Message() );
if ( includeBody ) {
const QByteArray data = md->readEntry( item.remoteId() );
if ( data.isEmpty() ) {
return false;
}
messagePtr->setContent( KMime::CRLFtoLF( data ) );
} else {
const QByteArray data = md->readEntryHeaders( item.remoteId() );
if ( data.isEmpty() ) {
return false;
}
messagePtr->setHead( KMime::CRLFtoLF( data ) );
}
messagePtr->parse();
item.setPayload<KMime::Message::Ptr>( messagePtr );
}
return true;
}
void MixedMaildirStore::Private::updateContextHashes( const QString &oldPath, const QString &newPath )
{
//kDebug() << "oldPath=" << oldPath << "newPath=" << newPath;
const QString oldSubDirPath = Maildir::subDirPathForFolderPath( oldPath );
const QString newSubDirPath = Maildir::subDirPathForFolderPath( newPath );
MBoxHash mboxes;
MBoxHash::const_iterator mboxIt = mMBoxes.constBegin();
MBoxHash::const_iterator mboxEndIt = mMBoxes.constEnd();
for ( ; mboxIt != mboxEndIt; ++mboxIt ) {
QString key = mboxIt.key();
MBoxPtr mboxPtr = mboxIt.value();
if ( key == oldPath ) {
key = newPath;
} else if ( key.startsWith( oldSubDirPath ) ) {
if ( mboxPtr->hasIndexData() || mboxPtr->mRevision > 0 ) {
key.replace( oldSubDirPath, newSubDirPath );
} else {
// if there is no index data yet, just discard this context
key.clear();
}
}
if ( !key.isEmpty() ) {
mboxPtr->updatePath( key );
mboxes.insert( key, mboxPtr );
}
}
//kDebug() << "mbox: old keys=" << mMBoxes.keys() << "new keys" << mboxes.keys();
mMBoxes = mboxes;
MaildirHash maildirs;
MaildirHash::const_iterator mdIt = mMaildirs.constBegin();
MaildirHash::const_iterator mdEndIt = mMaildirs.constEnd();
for ( ; mdIt != mdEndIt; ++mdIt ) {
QString key = mdIt.key();
MaildirPtr mdPtr = mdIt.value();
if ( key == oldPath ) {
key = newPath;
} else if ( key.startsWith( oldSubDirPath ) ) {
if ( mdPtr->hasIndexData() ) {
key.replace( oldSubDirPath, newSubDirPath );
} else {
// if there is no index data yet, just discard this context
key.clear();
}
}
if ( !key.isEmpty() ) {
mdPtr->updatePath( key );
maildirs.insert( key, mdPtr );
}
}
//kDebug() << "maildir: old keys=" << mMaildirs.keys() << "new keys" << maildirs.keys();
mMaildirs = maildirs;
}
bool MixedMaildirStore::Private::visit( FileStore::Job *job )
{
const QString message = i18nc( "@info:status", "Unhandled operation %1", QLatin1String(job->metaObject()->className()) );
kError() << message;
q->notifyError( FileStore::Job::InvalidJobContext, message );
return false;
}
bool MixedMaildirStore::Private::visit( FileStore::CollectionCreateJob *job )
{
QString path;
QString errorText;
const FolderType folderType = folderForCollection( job->targetParent(), path, errorText );
if ( folderType == InvalidFolder ) {
errorText = i18nc( "@info:status", "Cannot create folder %1 inside folder %2",
job->collection().name(), job->targetParent().name() );
kError() << errorText << "FolderType=" << folderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
const QString collectionName = job->collection().name().replace( QDir::separator(), QString() );
Maildir md;
if ( folderType == MBoxFolder ) {
const QString subDirPath = Maildir::subDirPathForFolderPath( path );
const QDir dir( subDirPath );
const QFileInfo dirInfo( dir, collectionName );
if ( dirInfo.exists() && !dirInfo.isDir() ) {
errorText = i18nc( "@info:status", "Cannot create folder %1 inside folder %2",
job->collection().name(), job->targetParent().name() );
kError() << errorText << "FolderType=" << folderType << ", dirInfo exists and it not a dir";
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
if ( !dir.mkpath( collectionName ) ) {
errorText = i18nc( "@info:status", "Cannot create folder %1 inside folder %2",
job->collection().name(), job->targetParent().name() );
kError() << errorText << "FolderType=" << folderType << ", mkpath failed";
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
md = Maildir( dirInfo.absoluteFilePath(), false );
if ( !md.create() ) {
errorText = i18nc( "@info:status", "Cannot create folder %1 inside folder %2",
job->collection().name(), job->targetParent().name() );
kError() << errorText << "FolderType=" << folderType << ", maildir create failed";
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
const MaildirPtr mdPtr( new MaildirContext( md ) );
mMaildirs.insert( md.path(), mdPtr );
} else {
Maildir parentMd( path, folderType == TopLevelFolder );
if ( parentMd.addSubFolder( collectionName ).isEmpty() ) {
errorText = i18nc( "@info:status", "Cannot create folder %1 inside folder %2",
job->collection().name(), job->targetParent().name() );
kError() << errorText << "FolderType=" << folderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
md = Maildir( parentMd.subFolder( collectionName ) );
const MaildirPtr mdPtr( new MaildirContext( md ) );
mMaildirs.insert( md.path(), mdPtr );
}
Collection collection = job->collection();
collection.setRemoteId( collectionName );
collection.setName( collectionName );
collection.setParentCollection( job->targetParent() );
fillMaildirCollectionDetails( md, collection );
q->notifyCollectionsProcessed( Collection::List() << collection );
return true;
}
bool MixedMaildirStore::Private::visit( FileStore::CollectionDeleteJob *job )
{
QString path;
QString errorText;
const FolderType folderType = folderForCollection( job->collection(), path, errorText );
if ( folderType == InvalidFolder ) {
errorText = i18nc( "@info:status", "Cannot remove folder %1 from folder %2",
job->collection().name(), job->collection().parentCollection().name() );
kError() << errorText << "FolderType=" << folderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
QString parentPath;
const FolderType parentFolderType = folderForCollection( job->collection().parentCollection(), parentPath, errorText );
if ( parentFolderType == InvalidFolder ) {
errorText = i18nc( "@info:status", "Cannot remove folder %1 from folder %2",
job->collection().name(), job->collection().parentCollection().name() );
kError() << errorText << "Parent FolderType=" << parentFolderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
if ( folderType == MBoxFolder ) {
if ( !QFile::remove( path ) ) {
errorText = i18nc( "@info:status", "Cannot remove folder %1 from folder %2",
job->collection().name(), job->collection().parentCollection().name() );
kError() << errorText << "FolderType=" << folderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
} else {
if ( !KPIMUtils::removeDirAndContentsRecursively( path ) ) {
errorText = i18nc( "@info:status", "Cannot remove folder %1 from folder %2",
job->collection().name(), job->collection().parentCollection().name() );
kError() << errorText << "FolderType=" << folderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
}
const QString subDirPath = Maildir::subDirPathForFolderPath( path );
KPIMUtils::removeDirAndContentsRecursively( subDirPath );
q->notifyCollectionsProcessed( Collection::List() << job->collection() );
return true;
}
bool MixedMaildirStore::Private::visit( FileStore::CollectionFetchJob *job )
{
QString path;
QString errorText;
const FolderType folderType = folderForCollection( job->collection(), path, errorText );
if ( folderType == InvalidFolder ) {
kError() << errorText << "collection:" << job->collection();
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
Collection::List collections;
Collection collection = job->collection();
if ( job->type() == FileStore::CollectionFetchJob::Base ) {
collection.setName( collection.remoteId() );
if ( folderType == MBoxFolder ) {
MBoxPtr mbox;
MBoxHash::const_iterator findIt = mMBoxes.constFind( path );
if ( findIt == mMBoxes.constEnd() ) {
mbox = MBoxPtr( new MBoxContext );
if ( !mbox->load( path ) ) {
errorText = i18nc( "@info:status", "Failed to load MBox folder %1", path );
kError() << errorText << "collection=" << collection;
q->notifyError( FileStore::Job::InvalidJobContext, errorText ); // TODO should be a different error code
return false;
}
mbox->mCollection = collection;
mMBoxes.insert( path, mbox );
} else {
mbox = findIt.value();
}
fillMBoxCollectionDetails( mbox, collection );
} else {
const Maildir md( path, folderType == TopLevelFolder );
fillMaildirCollectionDetails( md, collection );
}
collections << collection;
} else {
// if the base is an mbox, use its sub folder dir like a top level maildir
if ( folderType == MBoxFolder ) {
path = Maildir::subDirPathForFolderPath( path );
}
const Maildir md( path, folderType != MaildirFolder );
fillMaildirTreeDetails( md, collection, collections,
job->type() == FileStore::CollectionFetchJob::Recursive );
}
if ( !collections.isEmpty() ) {
q->notifyCollectionsProcessed( collections );
}
return true;
}
static Collection updateMBoxCollectionTree( const Collection &collection, const Collection &oldParent, const Collection &newParent )
{
if ( collection == oldParent ) {
return newParent;
}
if ( collection == Collection::root() ) {
return collection;
}
Collection updatedCollection = collection;
updatedCollection.setParentCollection( updateMBoxCollectionTree( collection.parentCollection(), oldParent, newParent ) );
return updatedCollection;
}
bool MixedMaildirStore::Private::visit( FileStore::CollectionModifyJob *job )
{
const Collection collection = job->collection();
const QString collectionName = collection.name().replace( QDir::separator(), QString() );
// we also only do renames
if ( collection.remoteId() == collection.name() ) {
kWarning() << "CollectionModifyJob with name still identical to remoteId. Ignoring";
return true;
}
QString path;
QString errorText;
const FolderType folderType = folderForCollection( collection, path, errorText );
if ( folderType == InvalidFolder ) {
errorText = i18nc( "@info:status", "Cannot rename folder %1",
collection.name() );
kError() << errorText << "FolderType=" << folderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
const QFileInfo fileInfo( path );
const QFileInfo subDirInfo = Maildir::subDirPathForFolderPath( path );
QDir parentDir( path );
parentDir.cdUp();
const QFileInfo targetFileInfo( parentDir, collectionName );
const QFileInfo targetSubDirInfo = Maildir::subDirPathForFolderPath( targetFileInfo.absoluteFilePath() );
if ( targetFileInfo.exists() || ( subDirInfo.exists() && targetSubDirInfo.exists() ) ) {
errorText = i18nc( "@info:status", "Cannot rename folder %1",
collection.name() );
kError() << errorText << "FolderType=" << folderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
// if there is an index, make sure it is read before renaming
// do not rename index as it could already be out of date
bool indexInvalidated = false;
if ( folderType == MBoxFolder ) {
// TODO would be nice if getOrCreateMBoxPtr() could be used instead, like below for Maildir
MBoxPtr mbox;
MBoxHash::const_iterator findIt = mMBoxes.constFind( path );
if ( findIt == mMBoxes.constEnd() ) {
mbox = MBoxPtr( new MBoxContext );
if ( !mbox->load( path ) ) {
kWarning() << "Failed to load mbox" << path;
}
mbox->mCollection = collection;
mMBoxes.insert( path, mbox );
} else {
mbox = findIt.value();
}
mbox->readIndexData();
indexInvalidated = mbox->hasIndexData();
} else if ( folderType == MaildirFolder ) {
MaildirPtr md = getOrCreateMaildirPtr( path, false );
md->readIndexData();
indexInvalidated = md->hasIndexData();
}
if ( !parentDir.rename( fileInfo.absoluteFilePath(), targetFileInfo.fileName() ) ) {
errorText = i18nc( "@info:status", "Cannot rename folder %1",
collection.name() );
kError() << errorText << "FolderType=" << folderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
if ( subDirInfo.exists() ) {
if ( !parentDir.rename( subDirInfo.absoluteFilePath(), targetSubDirInfo.fileName() ) ) {
errorText = i18nc( "@info:status", "Cannot rename folder %1",
collection.name() );
kError() << errorText << "FolderType=" << folderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
// try to recover the previous rename
parentDir.rename( targetFileInfo.absoluteFilePath(), fileInfo.fileName() );
return false;
}
}
// update context hashes
updateContextHashes( fileInfo.absoluteFilePath(), targetFileInfo.absoluteFilePath() );
Collection renamedCollection = collection;
// when renaming top level folder, change path of store
if ( folderType == TopLevelFolder ) {
// backup caches, setTopLevelCollection() clears them
const MBoxHash mboxes = mMBoxes;
const MaildirHash maildirs = mMaildirs;
q->setPath( targetFileInfo.absoluteFilePath() );
// restore caches
mMBoxes = mboxes;
mMaildirs = maildirs;
renamedCollection = q->topLevelCollection();
} else {
renamedCollection.setRemoteId( collectionName );
renamedCollection.setName( collectionName );
}
// update collections in MBox contexts so they stay usable for purge
Q_FOREACH( const MBoxPtr &mbox, mMBoxes ) {
if ( mbox->mCollection.isValid() ) {
MBoxPtr updatedMBox = mbox;
updatedMBox->mCollection = updateMBoxCollectionTree( mbox->mCollection, collection, renamedCollection );
}
}
if ( indexInvalidated ) {
const QVariant var = QVariant::fromValue<Collection::List>( Collection::List() << renamedCollection );
job->setProperty( "onDiskIndexInvalidated", var );
}
q->notifyCollectionsProcessed( Collection::List() << renamedCollection );
return true;
}
bool MixedMaildirStore::Private::visit( FileStore::CollectionMoveJob *job )
{
QString errorText;
const Collection moveCollection = job->collection();
const Collection targetCollection = job->targetParent();
QString movePath;
const FolderType moveFolderType = folderForCollection( moveCollection, movePath, errorText );
if ( moveFolderType == InvalidFolder || moveFolderType == TopLevelFolder ) {
errorText = i18nc( "@info:status", "Cannot move folder %1 from folder %2 to folder %3",
moveCollection.name(), moveCollection.parentCollection().name(), targetCollection.name() );
kError() << errorText << "FolderType=" << moveFolderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
// kDebug( KDE_DEFAULT_DEBUG_AREA ) << "moveCollection" << moveCollection.remoteId()
// << "movePath=" << movePath
// << "moveType=" << moveFolderType;
QString targetPath;
const FolderType targetFolderType = folderForCollection( targetCollection, targetPath, errorText );
if ( targetFolderType == InvalidFolder ) {
errorText = i18nc( "@info:status", "Cannot move folder %1 from folder %2 to folder %3",
moveCollection.name(), moveCollection.parentCollection().name(), targetCollection.name() );
kError() << errorText << "FolderType=" << targetFolderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
// kDebug( KDE_DEFAULT_DEBUG_AREA ) << "targetCollection" << targetCollection.remoteId()
// << "targetPath=" << targetPath
// << "targetType=" << targetFolderType;
const QFileInfo targetSubDirInfo( Maildir::subDirPathForFolderPath( targetPath ) );
// if target is not the top level folder, make sure the sub folder directory exists
if ( targetFolderType != TopLevelFolder ) {
if ( !targetSubDirInfo.exists() ) {
QDir topDir( q->path() );
if ( !topDir.mkpath( targetSubDirInfo.absoluteFilePath() ) ) {
errorText = i18nc( "@info:status", "Cannot move folder %1 from folder %2 to folder %3",
moveCollection.name(), moveCollection.parentCollection().name(), targetCollection.name() );
kError() << errorText << "MoveFolderType=" << moveFolderType
<< "TargetFolderType=" << targetFolderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
}
}
bool indexInvalidated = false;
QString movedPath;
if ( moveFolderType == MBoxFolder ) {
// TODO would be nice if getOrCreateMBoxPtr() could be used instead, like below for Maildir
MBoxPtr mbox;
MBoxHash::const_iterator findIt = mMBoxes.constFind( movePath );
if ( findIt == mMBoxes.constEnd() ) {
mbox = MBoxPtr( new MBoxContext );
if ( !mbox->load( movePath ) ) {
kWarning() << "Failed to load mbox" << movePath;
}
mbox->mCollection = moveCollection;
mMBoxes.insert( movePath, mbox );
} else {
mbox = findIt.value();
}
mbox->readIndexData();
indexInvalidated = mbox->hasIndexData();
const QFileInfo moveFileInfo( movePath );
const QFileInfo moveSubDirInfo( Maildir::subDirPathForFolderPath( movePath ) );
const QFileInfo targetFileInfo( targetPath );
QDir targetDir( targetFolderType == TopLevelFolder ?
targetPath : Maildir::subDirPathForFolderPath( targetPath ) );
if ( targetDir.exists( moveFileInfo.fileName() ) ||
!targetDir.rename( moveFileInfo.absoluteFilePath(), moveFileInfo.fileName() ) ) {
errorText = i18nc( "@info:status", "Cannot move folder %1 from folder %2 to folder %3",
moveCollection.name(), moveCollection.parentCollection().name(), targetCollection.name() );
kError() << errorText << "MoveFolderType=" << moveFolderType
<< "TargetFolderType=" << targetFolderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
if ( moveSubDirInfo.exists() ) {
if ( targetDir.exists( moveSubDirInfo.fileName() ) ||
!targetDir.rename( moveSubDirInfo.absoluteFilePath(), moveSubDirInfo.fileName() ) ) {
errorText = i18nc( "@info:status", "Cannot move folder %1 from folder %2 to folder %3",
moveCollection.name(), moveCollection.parentCollection().name(), targetCollection.name() );
kError() << errorText << "MoveFolderType=" << moveFolderType
<< "TargetFolderType=" << targetFolderType;
// try to revert the other rename
QDir sourceDir( moveFileInfo.absolutePath() );
sourceDir.cdUp();
sourceDir.rename( targetFileInfo.absoluteFilePath(), moveFileInfo.fileName() );
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
}
movedPath = QFileInfo( targetDir, moveFileInfo.fileName() ).absoluteFilePath();
} else {
MaildirPtr md = getOrCreateMaildirPtr( movePath, false );
md->readIndexData();
indexInvalidated = md->hasIndexData();
Maildir moveMd( movePath, false );
// for moving purpose we can treat the MBox target's subDirPath like a top level maildir
Maildir targetMd;
if ( targetFolderType == MBoxFolder ) {
targetMd = Maildir( targetSubDirInfo.absoluteFilePath(), true );
} else {
targetMd = Maildir( targetPath, targetFolderType == TopLevelFolder );
}
if ( !moveMd.moveTo( targetMd ) ) {
errorText = i18nc( "@info:status", "Cannot move folder %1 from folder %2 to folder %3",
moveCollection.name(), moveCollection.parentCollection().name(), targetCollection.name() );
kError() << errorText << "MoveFolderType=" << moveFolderType
<< "TargetFolderType=" << targetFolderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
movedPath = targetMd.subFolder( moveCollection.remoteId() ).path();
}
// update context hashes
updateContextHashes( movePath, movedPath );
Collection movedCollection = moveCollection;
movedCollection.setParentCollection( targetCollection );
// update collections in MBox contexts so they stay usable for purge
Q_FOREACH( const MBoxPtr &mbox, mMBoxes ) {
if ( mbox->mCollection.isValid() ) {
MBoxPtr updatedMBox = mbox;
updatedMBox->mCollection = updateMBoxCollectionTree( mbox->mCollection, moveCollection, movedCollection );
}
}
if ( indexInvalidated ) {
const QVariant var = QVariant::fromValue<Collection::List>( Collection::List() << movedCollection );
job->setProperty( "onDiskIndexInvalidated", var );
}
q->notifyCollectionsProcessed( Collection::List() << movedCollection );
return true;
}
bool MixedMaildirStore::Private::visit( FileStore::ItemCreateJob *job )
{
QString path;
QString errorText;
const FolderType folderType = folderForCollection( job->collection(), path, errorText );
if ( folderType == InvalidFolder || folderType == TopLevelFolder ) {
errorText = i18nc( "@info:status", "Cannot add emails to folder %1",
job->collection().name() );
kError() << errorText << "FolderType=" << folderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
Item item = job->item();
if ( folderType == MBoxFolder ) {
MBoxPtr mbox;
MBoxHash::const_iterator findIt = mMBoxes.constFind( path );
if ( findIt == mMBoxes.constEnd() ) {
mbox = MBoxPtr( new MBoxContext );
if ( !mbox->load( path ) ) {
errorText = i18nc( "@info:status", "Cannot add emails to folder %1",
job->collection().name() );
kError() << errorText << "FolderType=" << folderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
mbox->mCollection = job->collection();
mMBoxes.insert( path, mbox );
} else {
mbox = findIt.value();
}
// make sure to read the index (if available) before modifying the data, which would
// make the index invalid
mbox->readIndexData();
// if there is index data now, we let the job creator know that the on-disk index
// became invalid
if ( mbox->hasIndexData() ) {
const QVariant var = QVariant::fromValue<Collection::List>( Collection::List() << job->collection() );
job->setProperty( "onDiskIndexInvalidated", var );
}
qint64 result = mbox->appendEntry( item.payload<KMime::Message::Ptr>() );
if ( result < 0 ) {
errorText = i18nc( "@info:status", "Cannot add emails to folder %1",
job->collection().name() );
kError() << errorText << "FolderType=" << folderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
mbox->save();
item.setRemoteId( QString::number( result ) );
} else {
MaildirPtr mdPtr;
MaildirHash::const_iterator findIt = mMaildirs.constFind( path );
if ( findIt == mMaildirs.constEnd() ) {
mdPtr = MaildirPtr( new MaildirContext( path, false ) );
mMaildirs.insert( path, mdPtr );
} else {
mdPtr = findIt.value();
}
// make sure to read the index (if available) before modifying the data, which would
// make the index invalid
mdPtr->readIndexData();
// if there is index data now, we let the job creator know that the on-disk index
// became invalid
if ( mdPtr->hasIndexData() ) {
const QVariant var = QVariant::fromValue<Collection::List>( Collection::List() << job->collection() );
job->setProperty( "onDiskIndexInvalidated", var );
}
const QString result = mdPtr->addEntry( item.payload<KMime::Message::Ptr>()->encodedContent() );
if ( result.isEmpty() ) {
errorText = i18nc( "@info:status", "Cannot add emails to folder %1",
job->collection().name() );
kError() << errorText << "FolderType=" << folderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
item.setRemoteId( result );
}
item.setParentCollection( job->collection() );
q->notifyItemsProcessed( Item::List() << item );
return true;
}
bool MixedMaildirStore::Private::visit( FileStore::ItemDeleteJob *job )
{
const Item item = job->item();
const Collection collection = item.parentCollection();
QString path;
QString errorText;
const FolderType folderType = folderForCollection( collection, path, errorText );
if ( folderType == InvalidFolder || folderType == TopLevelFolder ) {
errorText = i18nc( "@info:status", "Cannot remove emails from folder %1",
collection.name() );
kError() << errorText << "FolderType=" << folderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
if ( folderType == MBoxFolder ) {
MBoxPtr mbox;
MBoxHash::const_iterator findIt = mMBoxes.constFind( path );
if ( findIt == mMBoxes.constEnd() ) {
mbox = MBoxPtr( new MBoxContext );
if ( !mbox->load( path ) ) {
errorText = i18nc( "@info:status", "Cannot remove emails from folder %1",
collection.name() );
kError() << errorText << "FolderType=" << folderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
mMBoxes.insert( path, mbox );
} else {
mbox = findIt.value();
}
bool ok = false;
qint64 offset = item.remoteId().toLongLong( &ok );
if ( !ok || offset < 0 || !mbox->isValidOffset( offset ) ) {
errorText = i18nc( "@info:status", "Cannot remove emails from folder %1",
collection.name() );
kError() << errorText << "FolderType=" << folderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
mbox->mCollection = collection;
mbox->deleteEntry( offset );
job->setProperty( "compactStore", true );
} else {
MaildirPtr mdPtr;
MaildirHash::const_iterator findIt = mMaildirs.constFind( path );
if ( findIt == mMaildirs.constEnd() ) {
mdPtr = MaildirPtr( new MaildirContext( path, false ) );
mMaildirs.insert( path, mdPtr );
} else {
mdPtr = findIt.value();
}
// make sure to read the index (if available) before modifying the data, which would
// make the index invalid
mdPtr->readIndexData();
// if there is index data now, we let the job creator know that the on-disk index
// became invalid
if ( mdPtr->hasIndexData() ) {
const QVariant var = QVariant::fromValue<Collection::List>( Collection::List() << collection );
job->setProperty( "onDiskIndexInvalidated", var );
}
if ( !mdPtr->removeEntry( item.remoteId() ) ) {
errorText = i18nc( "@info:status", "Cannot remove emails from folder %1",
collection.name() );
kError() << errorText << "FolderType=" << folderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
}
q->notifyItemsProcessed( Item::List() << item );
return true;
}
bool MixedMaildirStore::Private::visit( FileStore::ItemFetchJob *job )
{
ItemFetchScope scope = job->fetchScope();
const bool includeBody = scope.fullPayload() ||
scope.payloadParts().contains( MessagePart::Body );
const bool includeHeaders = scope.payloadParts().contains( MessagePart::Header ) ||
scope.payloadParts().contains( MessagePart::Envelope );
const bool fetchSingleItem = job->collection().remoteId().isEmpty();
const Collection collection = fetchSingleItem ? job->item().parentCollection() : job->collection();
QString path;
QString errorText;
Q_ASSERT( !collection.remoteId().isEmpty() );
const FolderType folderType = folderForCollection( collection, path, errorText );
if ( folderType == InvalidFolder ) {
kError() << errorText << "collection:" << job->collection();
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
if ( folderType == MBoxFolder ) {
MBoxHash::iterator findIt = mMBoxes.find( path );
if ( findIt == mMBoxes.end() || !fetchSingleItem ) {
MBoxPtr mbox = findIt != mMBoxes.end() ? findIt.value() : MBoxPtr( new MBoxContext );
if ( !mbox->load( path ) ) {
errorText = i18nc( "@info:status", "Failed to load MBox folder %1", path );
kError() << errorText << "collection=" << collection;
q->notifyError( FileStore::Job::InvalidJobContext, errorText ); // TODO should be a different error code
if ( findIt != mMBoxes.end() ) {
mMBoxes.erase( findIt );
}
return false;
}
if ( findIt == mMBoxes.end() ) {
findIt = mMBoxes.insert( path, mbox );
}
}
Item::List items;
if ( fetchSingleItem ) {
items << job->item();
} else {
listCollection( job, findIt.value(), collection, items );
}
Item::List::iterator it = items.begin();
Item::List::iterator endIt = items.end();
for ( ; it != endIt; ++it ) {
if ( !fillItem( findIt.value(), includeHeaders, includeBody, *it ) ) {
const QString errorText =
i18nc( "@info:status", "Error while reading mails from folder %1", collection.name() );
q->notifyError( FileStore::Job::InvalidJobContext, errorText ); // TODO should be a different error code
kError() << "Failed to read item" << (*it).remoteId() << "in MBox file" << path;
return false;
}
}
if ( !items.isEmpty() ) {
q->notifyItemsProcessed( items );
}
} else {
MaildirPtr mdPtr;
MaildirHash::const_iterator mdIt = mMaildirs.constFind( path );
if ( mdIt == mMaildirs.constEnd() ) {
mdPtr = MaildirPtr( new MaildirContext( path, folderType == TopLevelFolder ) );
mMaildirs.insert( path, mdPtr );
} else {
mdPtr = mdIt.value();
}
if ( !mdPtr->isValid( errorText ) ) {
errorText = i18nc( "@info:status", "Failed to load Maildirs folder %1", path );
kError() << errorText << "collection=" << collection;
q->notifyError( FileStore::Job::InvalidJobContext, errorText ); // TODO should be a different error code
return false;
}
Item::List items;
if ( fetchSingleItem ) {
items << job->item();
} else {
listCollection( job, mdPtr, collection, items );
}
Item::List::iterator it = items.begin();
Item::List::iterator endIt = items.end();
for ( ; it != endIt; ++it ) {
if ( !fillItem( mdPtr, includeHeaders, includeBody, *it ) ) {
const QString errorText =
i18nc( "@info:status", "Error while reading mails from folder %1", collection.name() );
q->notifyError( FileStore::Job::InvalidJobContext, errorText ); // TODO should be a different error code
kError() << "Failed to read item" << (*it).remoteId() << "in Maildir" << path;
return false;
}
}
if ( !items.isEmpty() ) {
q->notifyItemsProcessed( items );
}
}
return true;
}
bool MixedMaildirStore::Private::visit( FileStore::ItemModifyJob *job )
{
const QSet<QByteArray> parts = job->parts();
bool payloadChanged = false;
bool flagsChanged = false;
Q_FOREACH( const QByteArray &part, parts ) {
if ( part.startsWith( "PLD:" ) ) {
payloadChanged = true;
}
if ( part.contains( "FLAGS" ) ) {
flagsChanged = true;
}
}
const bool nothingChanged = ( !payloadChanged && !flagsChanged );
const bool payloadChangedButIgnored = payloadChanged && job->ignorePayload();
const bool ignoreModifyIfValid = nothingChanged ||
( payloadChangedButIgnored && !flagsChanged );
Item item = job->item();
const Collection collection = item.parentCollection();
QString path;
QString errorText;
const FolderType folderType = folderForCollection( collection, path, errorText );
if ( folderType == InvalidFolder || folderType == TopLevelFolder ) {
errorText = i18nc( "@info:status", "Cannot modify emails in folder %1",
collection.name() );
kError() << errorText << "FolderType=" << folderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
if ( folderType == MBoxFolder ) {
MBoxPtr mbox;
MBoxHash::const_iterator findIt = mMBoxes.constFind( path );
if ( findIt == mMBoxes.constEnd() ) {
mbox = MBoxPtr( new MBoxContext );
if ( !mbox->load( path ) ) {
errorText = i18nc( "@info:status", "Cannot modify emails in folder %1",
collection.name() );
kError() << errorText << "FolderType=" << folderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
mMBoxes.insert( path, mbox );
} else {
mbox = findIt.value();
}
bool ok = false;
qint64 offset = item.remoteId().toLongLong( &ok );
if ( !ok || offset < 0 || !mbox->isValidOffset( offset ) ) {
errorText = i18nc( "@info:status", "Cannot modify emails in folder %1",
collection.name() );
kError() << errorText << "FolderType=" << folderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
// if we can ignore payload, or we have nothing else to change, then we are finished
if ( ignoreModifyIfValid ) {
kDebug( KDE_DEFAULT_DEBUG_AREA ) << "ItemModifyJob for item" << item.remoteId()
<< "in collection" << collection.remoteId()
<< "skipped: nothing of interest changed (" << nothingChanged
<< ") or only payload changed but should be ignored ("
<< ( payloadChanged && !flagsChanged && job->ignorePayload() )
<< "). Modified parts:" << parts;
q->notifyItemsProcessed( Item::List() << job->item() );
return true;
}
// mbox can only change payload, ignore any other change
if ( !payloadChanged ) {
q->notifyItemsProcessed( Item::List() << item );
return true;
}
// make sure to read the index (if available) before modifying the data, which would
// make the index invalid
mbox->readIndexData();
// if there is index data now, we let the job creator know that the on-disk index
// became invalid
if ( mbox->hasIndexData() ) {
const QVariant var = QVariant::fromValue<Collection::List>( Collection::List() << collection );
job->setProperty( "onDiskIndexInvalidated", var );
}
qint64 newOffset = mbox->appendEntry( item.payload<KMime::Message::Ptr>() );
if ( newOffset < 0 ) {
errorText = i18nc( "@info:status", "Cannot modify emails in folder %1",
collection.name() );
kError() << errorText << "FolderType=" << folderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
if ( newOffset > 0 ) {
mbox->mCollection = collection;
mbox->deleteEntry( offset );
job->setProperty( "compactStore", true );
}
mbox->save();
item.setRemoteId( QString::number( newOffset ) );
} else {
MaildirPtr mdPtr;
MaildirHash::const_iterator findIt = mMaildirs.constFind( path );
if ( findIt == mMaildirs.constEnd() ) {
mdPtr = MaildirPtr( new MaildirContext( path, false ) );
mMaildirs.insert( path, mdPtr );
} else {
mdPtr = findIt.value();
}
if ( !mdPtr->isValidEntry( item.remoteId() ) ) {
errorText = i18nc( "@info:status", "Cannot modify emails in folder %1",
collection.name() );
kError() << errorText << "FolderType=" << folderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
// if we can ignore payload, or we have nothing else to change, then we are finished
if ( ignoreModifyIfValid ) {
kDebug( KDE_DEFAULT_DEBUG_AREA ) << "ItemModifyJob for item" << item.remoteId()
<< "in collection" << collection.remoteId()
<< "skipped: nothing of interest changed (" << nothingChanged
<< ") or only payload changed but should be ignored ("
<< ( payloadChanged && !flagsChanged && job->ignorePayload() )
<< "). Modified parts:" << parts;
q->notifyItemsProcessed( Item::List() << job->item() );
return true;
}
// make sure to read the index (if available) before modifying the data, which would
// make the index invalid
mdPtr->readIndexData();
// if there is index data now, we let the job creator know that the on-disk index
// became invalid
if ( mdPtr->hasIndexData() ) {
const QVariant var = QVariant::fromValue<Collection::List>( Collection::List() << collection );
job->setProperty( "onDiskIndexInvalidated", var );
}
QString newKey = item.remoteId();
if ( flagsChanged ) {
Maildir md( mdPtr->maildir() );
newKey = md.changeEntryFlags( item.remoteId(), item.flags() );
if ( newKey.isEmpty() ) {
errorText = i18nc( "@info:status", "Cannot modify emails in folder %1. %2",
collection.name(), md.lastError() );
kError() << errorText << "FolderType=" << folderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
item.setRemoteId( newKey );
}
if ( payloadChanged ) {
mdPtr->writeEntry( newKey, item.payload<KMime::Message::Ptr>()->encodedContent() );
}
}
q->notifyItemsProcessed( Item::List() << item );
return true;
}
bool MixedMaildirStore::Private::visit( FileStore::ItemMoveJob *job )
{
QString errorText;
QString sourcePath;
const Collection sourceCollection = job->item().parentCollection();
const FolderType sourceFolderType = folderForCollection( sourceCollection, sourcePath, errorText );
if ( sourceFolderType == InvalidFolder || sourceFolderType == TopLevelFolder ) {
errorText = i18nc( "@info:status", "Cannot move emails from folder %1",
sourceCollection.name() );
kError() << errorText << "FolderType=" << sourceFolderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
// kDebug( KDE_DEFAULT_DEBUG_AREA ) << "sourceCollection" << sourceCollection.remoteId()
// << "sourcePath=" << sourcePath
// << "sourceType=" << sourceFolderType;
QString targetPath;
const Collection targetCollection = job->targetParent();
const FolderType targetFolderType = folderForCollection( targetCollection, targetPath, errorText );
if ( targetFolderType == InvalidFolder || targetFolderType == TopLevelFolder ) {
errorText = i18nc( "@info:status", "Cannot move emails to folder %1",
targetCollection.name() );
kError() << errorText << "FolderType=" << targetFolderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
// kDebug( KDE_DEFAULT_DEBUG_AREA ) << "targetCollection" << targetCollection.remoteId()
// << "targetPath=" << targetPath
// << "targetType=" << targetFolderType;
Item item = job->item();
if ( sourceFolderType == MBoxFolder ) {
/* kDebug( KDE_DEFAULT_DEBUG_AREA ) << "source is MBox";*/
bool ok= false;
quint64 offset = item.remoteId().toULongLong( &ok );
if ( !ok ) {
errorText = i18nc( "@info:status", "Cannot move emails from folder %1",
sourceCollection.name() );
kError() << errorText << "FolderType=" << sourceFolderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
MBoxPtr mbox;
MBoxHash::const_iterator findIt = mMBoxes.constFind( sourcePath );
if ( findIt == mMBoxes.constEnd() ) {
mbox = MBoxPtr( new MBoxContext );
if ( !mbox->load( sourcePath ) ) {
errorText = i18nc( "@info:status", "Cannot move emails to folder %1",
sourceCollection.name() );
kError() << errorText << "FolderType=" << sourceFolderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
mbox->mCollection = sourceCollection;
mMBoxes.insert( sourcePath, mbox );
} else {
mbox = findIt.value();
}
if ( !mbox->isValidOffset( offset ) ) {
errorText = i18nc( "@info:status", "Cannot move emails from folder %1",
sourceCollection.name() );
kError() << errorText << "FolderType=" << sourceFolderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
if ( !item.hasPayload<KMime::Message::Ptr>() ||
!item.loadedPayloadParts().contains( MessagePart::Body ) ) {
if ( !fillItem( mbox, true, true, item ) ) {
errorText = i18nc( "@info:status", "Cannot move email from folder %1",
sourceCollection.name() );
kError() << errorText << "FolderType=" << sourceFolderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
}
Collection::List collections;
// make sure to read the index (if available) before modifying the data, which would
// make the index invalid
mbox->readIndexData();
if ( mbox->hasIndexData() ) {
collections << sourceCollection;
}
if ( targetFolderType == MBoxFolder ) {
/* kDebug( KDE_DEFAULT_DEBUG_AREA ) << "target is MBox";*/
MBoxPtr targetMBox;
MBoxHash::const_iterator findIt = mMBoxes.constFind( targetPath );
if ( findIt == mMBoxes.constEnd() ) {
targetMBox = MBoxPtr( new MBoxContext );
if ( !targetMBox->load( targetPath ) ) {
errorText = i18nc( "@info:status", "Cannot move emails to folder %1",
targetCollection.name() );
kError() << errorText << "FolderType=" << targetFolderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
targetMBox->mCollection = targetCollection;
mMBoxes.insert( targetPath, targetMBox );
} else {
targetMBox = findIt.value();
}
// make sure to read the index (if available) before modifying the data, which would
// make the index invalid
targetMBox->readIndexData();
// if there is index data now, we let the job creator know that the on-disk index
// became invalid
if ( targetMBox->hasIndexData() ) {
collections << targetCollection;
}
qint64 remoteId = targetMBox->appendEntry( item.payload<KMime::Message::Ptr>() );
if ( remoteId < 0 ) {
errorText = i18nc( "@info:status", "Cannot move emails to folder %1",
targetCollection.name() );
kError() << errorText << "FolderType=" << targetFolderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
if ( !targetMBox->save() ) {
errorText = i18nc( "@info:status", "Cannot move emails to folder %1",
targetCollection.name() );
kError() << errorText << "FolderType=" << targetFolderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
item.setRemoteId( QString::number( remoteId ) );
} else {
/* kDebug( KDE_DEFAULT_DEBUG_AREA ) << "target is Maildir";*/
MaildirPtr targetMdPtr = getOrCreateMaildirPtr( targetPath, false );
// make sure to read the index (if available) before modifying the data, which would
// make the index invalid
targetMdPtr->readIndexData();
// if there is index data now, we let the job creator know that the on-disk index
// became invalid
if ( targetMdPtr->hasIndexData() ) {
collections << targetCollection;
}
const QString remoteId = targetMdPtr->addEntry( mbox->readRawEntry( offset ) );
if ( remoteId.isEmpty() ) {
errorText = i18nc( "@info:status", "Cannot move email from folder %1 to folder %2",
sourceCollection.name(), targetCollection.name() );
kError() << errorText << "SourceFolderType=" << sourceFolderType << "TargetFolderType=" << targetFolderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
item.setRemoteId( remoteId );
}
if ( !collections.isEmpty() ) {
const QVariant var = QVariant::fromValue<Collection::List>( collections );
job->setProperty( "onDiskIndexInvalidated", var );
}
mbox->mCollection = sourceCollection;
mbox->deleteEntry( offset );
job->setProperty( "compactStore", true );
} else {
/* kDebug( KDE_DEFAULT_DEBUG_AREA ) << "source is Maildir";*/
MaildirPtr sourceMdPtr = getOrCreateMaildirPtr( sourcePath, false );
if ( !sourceMdPtr->isValidEntry( item.remoteId() ) ) {
errorText = i18nc( "@info:status", "Cannot move email from folder %1",
sourceCollection.name() );
kError() << errorText << "FolderType=" << sourceFolderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
Collection::List collections;
// make sure to read the index (if available) before modifying the data, which would
// make the index invalid
sourceMdPtr->readIndexData();
// if there is index data now, we let the job creator know that the on-disk index
// became invalid
if ( sourceMdPtr->hasIndexData() ) {
collections << sourceCollection;
}
if ( targetFolderType == MBoxFolder ) {
/* kDebug( KDE_DEFAULT_DEBUG_AREA ) << "target is MBox";*/
if ( !item.hasPayload<KMime::Message::Ptr>() ||
!item.loadedPayloadParts().contains( MessagePart::Body ) ) {
if ( !fillItem( sourceMdPtr, true, true, item ) ) {
errorText = i18nc( "@info:status", "Cannot move email from folder %1",
sourceCollection.name() );
kError() << errorText << "FolderType=" << sourceFolderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
}
MBoxPtr mbox;
MBoxHash::const_iterator findIt = mMBoxes.constFind( targetPath );
if ( findIt == mMBoxes.constEnd() ) {
mbox = MBoxPtr( new MBoxContext );
if ( !mbox->load( targetPath ) ) {
errorText = i18nc( "@info:status", "Cannot move emails to folder %1",
targetCollection.name() );
kError() << errorText << "FolderType=" << targetFolderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
mbox->mCollection = targetCollection;
mMBoxes.insert( targetPath, mbox );
} else {
mbox = findIt.value();
}
// make sure to read the index (if available) before modifying the data, which would
// make the index invalid
mbox->readIndexData();
// if there is index data now, we let the job creator know that the on-disk index
// became invalid
if ( mbox->hasIndexData() ) {
collections << targetCollection;
}
const qint64 remoteId = mbox->appendEntry( item.payload<KMime::Message::Ptr>() );
if ( remoteId < 0 ) {
errorText = i18nc( "@info:status", "Cannot move emails to folder %1",
targetCollection.name() );
kError() << errorText << "FolderType=" << targetFolderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
sourceMdPtr->removeEntry( item.remoteId() );
mbox->save();
item.setRemoteId( QString::number( remoteId ) );
} else {
/* kDebug( KDE_DEFAULT_DEBUG_AREA ) << "target is Maildir";*/
MaildirPtr targetMdPtr = getOrCreateMaildirPtr( targetPath, false );
// make sure to read the index (if available) before modifying the data, which would
// make the index invalid
targetMdPtr->readIndexData();
// if there is index data now, we let the job creator know that the on-disk index
// became invalid
if ( targetMdPtr->hasIndexData() ) {
collections << targetCollection;
}
const QString remoteId = sourceMdPtr->moveEntryTo( item.remoteId(), *targetMdPtr );
if ( remoteId.isEmpty() ) {
errorText = i18nc( "@info:status", "Cannot move email from folder %1 to folder %2",
sourceCollection.name(), targetCollection.name() );
kError() << errorText << "SourceFolderType=" << sourceFolderType << "TargetFolderType=" << targetFolderType;
q->notifyError( FileStore::Job::InvalidJobContext, errorText );
return false;
}
item.setRemoteId( remoteId );
}
if ( !collections.isEmpty() ) {
const QVariant var = QVariant::fromValue<Collection::List>( collections );
job->setProperty( "onDiskIndexInvalidated", var );
}
}
item.setParentCollection( targetCollection );
q->notifyItemsProcessed( Item::List() << item );
return true;
}
bool MixedMaildirStore::Private::visit( FileStore::StoreCompactJob *job )
{
Q_UNUSED( job );
Collection::List collections;
MBoxHash::const_iterator it = mMBoxes.constBegin();
MBoxHash::const_iterator endIt = mMBoxes.constEnd();
for ( ; it != endIt; ++it ) {
MBoxPtr mbox = it.value();
if ( !mbox->hasDeletedOffsets() ) {
continue;
}
// make sure to read the index (if available) before modifying the data, which would
// make the index invalid
mbox->readIndexData();
QList<KMBox::MBoxEntry::Pair> movedEntries;
const int result = mbox->purge( movedEntries );
if ( result > 0 ) {
if ( movedEntries.count() > 0 ) {
qint64 revision = mbox->mCollection.remoteRevision().toLongLong();
kDebug() << "purge of" << mbox->mCollection.name() << "caused item move: oldRevision="
<< revision << "(stored)," << mbox->mRevision << "(local)";
revision = qMax( revision, mbox->mRevision ) + 1;
const QString remoteRevision = QString::number( revision );
Collection collection = mbox->mCollection;
collection.attribute<FileStore::EntityCompactChangeAttribute>( Entity::AddIfMissing )->setRemoteRevision( remoteRevision );
q->notifyCollectionsProcessed( Collection::List() << collection );
mbox->mCollection.setRemoteRevision( remoteRevision );
mbox->mRevision = revision;
}
Item::List items;
Q_FOREACH( const KMBox::MBoxEntry::Pair &offsetPair, movedEntries ) {
const QString oldRemoteId( QString::number( offsetPair.first.messageOffset() ) );
const QString newRemoteId( QString::number( offsetPair.second.messageOffset() ) );
Item item;
item.setRemoteId( oldRemoteId );
item.setParentCollection( mbox->mCollection );
item.attribute<FileStore::EntityCompactChangeAttribute>( Entity::AddIfMissing )->setRemoteId( newRemoteId );
items << item;
}
// if there is index data, we let the job creator know that the on-disk index
// became invalid
if ( mbox->hasIndexData() ) {
collections << mbox->mCollection;
}
if ( !items.isEmpty() ) {
q->notifyItemsProcessed( items );
}
}
}
if ( !collections.isEmpty() ) {
const QVariant var = QVariant::fromValue<Collection::List>( collections );
job->setProperty( "onDiskIndexInvalidated", var );
}
return true;
}
MixedMaildirStore::MixedMaildirStore() : FileStore::AbstractLocalStore(), d( new Private( this ) )
{
}
MixedMaildirStore::~MixedMaildirStore()
{
delete d;
}
void MixedMaildirStore::setTopLevelCollection( const Collection &collection )
{
QStringList contentMimeTypes;
contentMimeTypes << Collection::mimeType();
Collection::Rights rights;
// TODO check if read-only?
rights = Collection::CanCreateCollection | Collection::CanChangeCollection | Collection::CanDeleteCollection;
CachePolicy cachePolicy;
cachePolicy.setInheritFromParent( false );
cachePolicy.setLocalParts( QStringList() << QLatin1String(MessagePart::Envelope) );
cachePolicy.setSyncOnDemand( true );
cachePolicy.setCacheTimeout( 1 );
Collection modifiedCollection = collection;
modifiedCollection.setContentMimeTypes( contentMimeTypes );
modifiedCollection.setRights( rights );
modifiedCollection.setParentCollection( Collection::root() );
modifiedCollection.setCachePolicy( cachePolicy );
// clear caches
d->mMBoxes.clear();
d->mMaildirs.clear();
FileStore::AbstractLocalStore::setTopLevelCollection( modifiedCollection );
}
void MixedMaildirStore::processJob( FileStore::Job *job )
{
if ( !job->accept( d ) ) {
// check that an error has been set
if ( job->error() == 0 || job->errorString().isEmpty() ) {
kError() << "visitor did not set either error code or error string when returning false";
Q_ASSERT( job->error() == 0 || job->errorString().isEmpty() );
}
} else {
// check that no error has been set
if ( job->error() != 0 || !job->errorString().isEmpty() ) {
kError() << "visitor did set either error code or error string when returning true";
Q_ASSERT( job->error() != 0 || !job->errorString().isEmpty() );
}
}
}
void MixedMaildirStore::checkCollectionMove( FileStore::CollectionMoveJob *job, int &errorCode, QString &errorText ) const
{
// check if the target is not the collection itself or one if its children
Collection targetCollection = job->targetParent();
while ( targetCollection.isValid() ) {
if ( targetCollection == job->collection() ) {
errorCode = FileStore::Job::InvalidJobContext;
errorText = i18nc( "@info:status", "Cannot move folder %1 into one of its own subfolder tree", job->collection().name() );
return;
}
targetCollection = targetCollection.parentCollection();
}
}
void MixedMaildirStore::checkItemCreate( FileStore::ItemCreateJob *job, int &errorCode, QString &errorText ) const
{
if ( !job->item().hasPayload<KMime::Message::Ptr>() ) {
errorCode = FileStore::Job::InvalidJobContext;
errorText = i18nc( "@info:status", "Cannot add email to folder %1 because there is no email content", job->collection().name() );
}
}
void MixedMaildirStore::checkItemModify( FileStore::ItemModifyJob *job, int &errorCode, QString &errorText ) const
{
if ( !job->ignorePayload() && !job->item().hasPayload<KMime::Message::Ptr>() ) {
errorCode = FileStore::Job::InvalidJobContext;
errorText = i18nc( "@info:status", "Cannot modify email in folder %1 because there is no email content", job->item().parentCollection().name() );
}
}
void MixedMaildirStore::checkItemFetch( FileStore::ItemFetchJob *job, int &errorCode, QString &errorText ) const
{
Q_UNUSED( errorCode );
Q_UNUSED( errorText );
const bool fetchSingleItem = job->collection().remoteId().isEmpty();
if ( fetchSingleItem ) {
Collection coll = job->item().parentCollection();
Q_ASSERT( !coll.remoteId().isEmpty() );
}
}
// kate: space-indent on; indent-width 2; replace-tabs on;