mirror of
https://bitbucket.org/smil3y/kde-playground.git
synced 2025-02-23 18:32:51 +00:00
2366 lines
83 KiB
C++
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;
|