mirror of
https://bitbucket.org/smil3y/kde-playground.git
synced 2025-02-23 10:22:50 +00:00
445 lines
13 KiB
C++
445 lines
13 KiB
C++
/*
|
|
Copyright (c) 2006 - 2007 Volker Krause <vkrause@kde.org>
|
|
|
|
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 "collectionfetchjob.h"
|
|
|
|
#include "imapparser_p.h"
|
|
#include "job_p.h"
|
|
#include "protocol_p.h"
|
|
#include "protocolhelper_p.h"
|
|
#include "entity_p.h"
|
|
#include "collectionfetchscope.h"
|
|
#include "collectionutils_p.h"
|
|
|
|
#include <kdebug.h>
|
|
#include <KLocalizedString>
|
|
|
|
#include <QtCore/QHash>
|
|
#include <QtCore/QStringList>
|
|
#include <QtCore/QTimer>
|
|
|
|
using namespace Akonadi;
|
|
|
|
class Akonadi::CollectionFetchJobPrivate : public JobPrivate
|
|
{
|
|
public:
|
|
CollectionFetchJobPrivate(CollectionFetchJob *parent)
|
|
: JobPrivate(parent)
|
|
, mEmitTimer(0)
|
|
, mBasePrefetch(false)
|
|
{
|
|
|
|
}
|
|
|
|
void init()
|
|
{
|
|
mEmitTimer = new QTimer(q_ptr);
|
|
mEmitTimer->setSingleShot(true);
|
|
mEmitTimer->setInterval(100);
|
|
q_ptr->connect(mEmitTimer, SIGNAL(timeout()), q_ptr, SLOT(timeout()));
|
|
}
|
|
|
|
Q_DECLARE_PUBLIC(CollectionFetchJob)
|
|
|
|
CollectionFetchJob::Type mType;
|
|
Collection mBase;
|
|
Collection::List mBaseList;
|
|
Collection::List mCollections;
|
|
CollectionFetchScope mScope;
|
|
Collection::List mPendingCollections;
|
|
QTimer *mEmitTimer;
|
|
bool mBasePrefetch;
|
|
Collection::List mPrefetchList;
|
|
|
|
void aboutToFinish()
|
|
{
|
|
timeout();
|
|
}
|
|
|
|
void timeout()
|
|
{
|
|
Q_Q(CollectionFetchJob);
|
|
|
|
mEmitTimer->stop(); // in case we are called by result()
|
|
if (!mPendingCollections.isEmpty()) {
|
|
if (!q->error()) {
|
|
emit q->collectionsReceived(mPendingCollections);
|
|
}
|
|
mPendingCollections.clear();
|
|
}
|
|
}
|
|
|
|
void subJobCollectionReceived(const Akonadi::Collection::List &collections)
|
|
{
|
|
mPendingCollections += collections;
|
|
if (!mEmitTimer->isActive()) {
|
|
mEmitTimer->start();
|
|
}
|
|
}
|
|
|
|
QString jobDebuggingString() const
|
|
{
|
|
if (mBase.isValid()) {
|
|
return QString::fromLatin1("Collection Id %1").arg(mBase.id());
|
|
} else if (CollectionUtils::hasValidHierarchicalRID(mBase)) {
|
|
return QString::fromUtf8(QByteArray(QByteArray("(") + ProtocolHelper::hierarchicalRidToByteArray(mBase) + QByteArray(")")));
|
|
} else {
|
|
return QString::fromLatin1("Collection RemoteId %1").arg(mBase.remoteId());
|
|
}
|
|
}
|
|
};
|
|
|
|
CollectionFetchJob::CollectionFetchJob(const Collection &collection, Type type, QObject *parent)
|
|
: Job(new CollectionFetchJobPrivate(this), parent)
|
|
{
|
|
Q_D(CollectionFetchJob);
|
|
d->init();
|
|
|
|
d->mBase = collection;
|
|
d->mType = type;
|
|
}
|
|
|
|
CollectionFetchJob::CollectionFetchJob(const Collection::List &cols, QObject *parent)
|
|
: Job(new CollectionFetchJobPrivate(this), parent)
|
|
{
|
|
Q_D(CollectionFetchJob);
|
|
d->init();
|
|
|
|
Q_ASSERT(!cols.isEmpty());
|
|
if (cols.size() == 1) {
|
|
d->mBase = cols.first();
|
|
} else {
|
|
d->mBaseList = cols;
|
|
}
|
|
d->mType = CollectionFetchJob::Base;
|
|
}
|
|
|
|
CollectionFetchJob::CollectionFetchJob(const Collection::List &cols, Type type, QObject *parent)
|
|
: Job(new CollectionFetchJobPrivate(this), parent)
|
|
{
|
|
Q_D(CollectionFetchJob);
|
|
d->init();
|
|
|
|
Q_ASSERT(!cols.isEmpty());
|
|
if (cols.size() == 1) {
|
|
d->mBase = cols.first();
|
|
} else {
|
|
d->mBaseList = cols;
|
|
}
|
|
d->mType = type;
|
|
}
|
|
|
|
CollectionFetchJob::CollectionFetchJob(const QList<Collection::Id> &cols, Type type, QObject *parent)
|
|
: Job(new CollectionFetchJobPrivate(this), parent)
|
|
{
|
|
Q_D(CollectionFetchJob);
|
|
d->init();
|
|
|
|
Q_ASSERT(!cols.isEmpty());
|
|
if (cols.size() == 1) {
|
|
d->mBase = Collection(cols.first());
|
|
} else {
|
|
foreach (Collection::Id id, cols) {
|
|
d->mBaseList.append(Collection(id));
|
|
}
|
|
}
|
|
d->mType = type;
|
|
}
|
|
|
|
CollectionFetchJob::~CollectionFetchJob()
|
|
{
|
|
}
|
|
|
|
Akonadi::Collection::List CollectionFetchJob::collections() const
|
|
{
|
|
Q_D(const CollectionFetchJob);
|
|
|
|
return d->mCollections;
|
|
}
|
|
|
|
void CollectionFetchJob::doStart()
|
|
{
|
|
Q_D(CollectionFetchJob);
|
|
|
|
if (!d->mBaseList.isEmpty()) {
|
|
if (d->mType == Recursive) {
|
|
// Because doStart starts several subjobs and @p cols could contain descendants of
|
|
// other elements in the list, if type is Recusrive, we could end up with duplicates in the result.
|
|
// To fix this we require an initial fetch of @p cols with Base and RetrieveAncestors,
|
|
// Iterate over that result removing intersections and then perform the Recursive fetch on
|
|
// the remainder.
|
|
d->mBasePrefetch = true;
|
|
// No need to connect to the collectionsReceived signal here. This job is internal. The
|
|
// result needs to be filtered through filterDescendants before it is useful.
|
|
new CollectionFetchJob(d->mBaseList, NonOverlappingRoots, this);
|
|
} else if (d->mType == NonOverlappingRoots) {
|
|
foreach (const Collection &col, d->mBaseList) {
|
|
// No need to connect to the collectionsReceived signal here. This job is internal. The (aggregated)
|
|
// result needs to be filtered through filterDescendants before it is useful.
|
|
CollectionFetchJob *subJob = new CollectionFetchJob(col, Base, this);
|
|
subJob->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::All);
|
|
}
|
|
} else {
|
|
foreach (const Collection &col, d->mBaseList) {
|
|
CollectionFetchJob *subJob = new CollectionFetchJob(col, d->mType, this);
|
|
connect(subJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)), SLOT(subJobCollectionReceived(Akonadi::Collection::List)));
|
|
subJob->setFetchScope(fetchScope());
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!d->mBase.isValid() && d->mBase.remoteId().isEmpty()) {
|
|
setError(Unknown);
|
|
setErrorText(i18n("Invalid collection given."));
|
|
emitResult();
|
|
return;
|
|
}
|
|
|
|
QByteArray command = d->newTag();
|
|
if (!d->mBase.isValid()) {
|
|
if (CollectionUtils::hasValidHierarchicalRID(d->mBase)) {
|
|
command += " HRID";
|
|
} else {
|
|
command += " " AKONADI_CMD_RID;
|
|
}
|
|
}
|
|
command += " LIST ";
|
|
|
|
if (d->mBase.isValid()) {
|
|
command += QByteArray::number(d->mBase.id());
|
|
} else if (CollectionUtils::hasValidHierarchicalRID(d->mBase)) {
|
|
command += '(' + ProtocolHelper::hierarchicalRidToByteArray(d->mBase) + ')';
|
|
} else {
|
|
command += ImapParser::quote(d->mBase.remoteId().toUtf8());
|
|
}
|
|
|
|
command += ' ';
|
|
switch (d->mType) {
|
|
case Base:
|
|
command += "0 (";
|
|
break;
|
|
case FirstLevel:
|
|
command += "1 (";
|
|
break;
|
|
case Recursive:
|
|
command += "INF (";
|
|
break;
|
|
default:
|
|
Q_ASSERT(false);
|
|
}
|
|
|
|
QList<QByteArray> filter;
|
|
if (!d->mScope.resource().isEmpty()) {
|
|
filter.append("RESOURCE");
|
|
// FIXME: Does this need to be quoted??
|
|
filter.append(d->mScope.resource().toUtf8());
|
|
}
|
|
|
|
if (!d->mScope.contentMimeTypes().isEmpty()) {
|
|
filter.append("MIMETYPE");
|
|
QList<QByteArray> mts;
|
|
foreach (const QString &mt, d->mScope.contentMimeTypes()) {
|
|
// FIXME: Does this need to be quoted??
|
|
mts.append(mt.toUtf8());
|
|
}
|
|
filter.append('(' + ImapParser::join(mts, " ") + ')');
|
|
}
|
|
|
|
switch (d->mScope.listFilter()) {
|
|
case CollectionFetchScope::Display:
|
|
filter.append("DISPLAY TRUE");
|
|
break;
|
|
case CollectionFetchScope::Sync:
|
|
filter.append("SYNC TRUE");
|
|
break;
|
|
case CollectionFetchScope::Index:
|
|
filter.append("INDEX TRUE");
|
|
break;
|
|
case CollectionFetchScope::Enabled:
|
|
filter.append("ENABLED TRUE");
|
|
break;
|
|
case CollectionFetchScope::NoFilter:
|
|
break;
|
|
default:
|
|
Q_ASSERT(false);
|
|
}
|
|
|
|
QList<QByteArray> options;
|
|
if (d->mScope.includeStatistics()) {
|
|
options.append("STATISTICS");
|
|
options.append("true");
|
|
}
|
|
if (d->mScope.ancestorRetrieval() != CollectionFetchScope::None) {
|
|
options.append("ANCESTORS");
|
|
switch (d->mScope.ancestorRetrieval()) {
|
|
case CollectionFetchScope::None:
|
|
options.append("0");
|
|
break;
|
|
case CollectionFetchScope::Parent:
|
|
options.append("1");
|
|
break;
|
|
case CollectionFetchScope::All:
|
|
options.append("INF");
|
|
break;
|
|
default:
|
|
Q_ASSERT(false);
|
|
}
|
|
}
|
|
|
|
command += ImapParser::join(filter, " ") + ") (" + ImapParser::join(options, " ") + ")\n";
|
|
d->writeData(command);
|
|
}
|
|
|
|
void CollectionFetchJob::doHandleResponse(const QByteArray &tag, const QByteArray &data)
|
|
{
|
|
Q_D(CollectionFetchJob);
|
|
|
|
if (d->mBasePrefetch || d->mType == NonOverlappingRoots) {
|
|
return;
|
|
}
|
|
|
|
if (tag == "*") {
|
|
Collection collection;
|
|
ProtocolHelper::parseCollection(data, collection);
|
|
if (!collection.isValid()) {
|
|
return;
|
|
}
|
|
|
|
collection.d_ptr->resetChangeLog();
|
|
d->mCollections.append(collection);
|
|
d->mPendingCollections.append(collection);
|
|
if (!d->mEmitTimer->isActive()) {
|
|
d->mEmitTimer->start();
|
|
}
|
|
return;
|
|
}
|
|
kDebug() << "Unhandled server response" << tag << data;
|
|
}
|
|
|
|
void CollectionFetchJob::setResource(const QString &resource)
|
|
{
|
|
Q_D(CollectionFetchJob);
|
|
|
|
d->mScope.setResource(resource);
|
|
}
|
|
|
|
static Collection::List filterDescendants(const Collection::List &list)
|
|
{
|
|
Collection::List result;
|
|
|
|
QVector<QList<Collection::Id> > ids;
|
|
foreach (const Collection &collection, list) {
|
|
QList<Collection::Id> ancestors;
|
|
Collection parent = collection.parentCollection();
|
|
ancestors << parent.id();
|
|
if (parent != Collection::root()) {
|
|
while (parent.parentCollection() != Collection::root()) {
|
|
parent = parent.parentCollection();
|
|
QList<Collection::Id>::iterator i = qLowerBound(ancestors.begin(), ancestors.end(), parent.id());
|
|
ancestors.insert(i, parent.id());
|
|
}
|
|
}
|
|
ids << ancestors;
|
|
}
|
|
|
|
QSet<Collection::Id> excludeList;
|
|
foreach (const Collection &collection, list) {
|
|
int i = 0;
|
|
foreach (const QList<Collection::Id> &ancestors, ids) {
|
|
if (qBinaryFind(ancestors, collection.id()) != ancestors.end()) {
|
|
excludeList.insert(list.at(i).id());
|
|
}
|
|
++i;
|
|
}
|
|
}
|
|
|
|
foreach (const Collection &collection, list) {
|
|
if (!excludeList.contains(collection.id())) {
|
|
result.append(collection);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void CollectionFetchJob::slotResult(KJob *job)
|
|
{
|
|
Q_D(CollectionFetchJob);
|
|
|
|
CollectionFetchJob *list = qobject_cast<CollectionFetchJob *>(job);
|
|
Q_ASSERT(job);
|
|
if (d->mBasePrefetch) {
|
|
d->mBasePrefetch = false;
|
|
const Collection::List roots = list->collections();
|
|
Job::slotResult(job);
|
|
Q_ASSERT(!hasSubjobs());
|
|
if (!job->error()) {
|
|
foreach (const Collection &col, roots) {
|
|
CollectionFetchJob *subJob = new CollectionFetchJob(col, d->mType, this);
|
|
connect(subJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)), SLOT(subJobCollectionReceived(Akonadi::Collection::List)));
|
|
subJob->setFetchScope(fetchScope());
|
|
}
|
|
}
|
|
// No result yet.
|
|
} else if (d->mType == NonOverlappingRoots) {
|
|
d->mPrefetchList += list->collections();
|
|
Job::slotResult(job);
|
|
if (!job->error() && !hasSubjobs()) {
|
|
const Collection::List result = filterDescendants(d->mPrefetchList);
|
|
d->mPendingCollections += result;
|
|
d->mCollections = result;
|
|
d->delayedEmitResult();
|
|
}
|
|
} else {
|
|
d->mCollections += list->collections();
|
|
Job::slotResult(job);
|
|
if (!job->error() && !hasSubjobs()) {
|
|
d->delayedEmitResult();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CollectionFetchJob::includeUnsubscribed(bool include)
|
|
{
|
|
Q_D(CollectionFetchJob);
|
|
|
|
d->mScope.setIncludeUnsubscribed(include);
|
|
}
|
|
|
|
void CollectionFetchJob::includeStatistics(bool include)
|
|
{
|
|
Q_D(CollectionFetchJob);
|
|
|
|
d->mScope.setIncludeStatistics(include);
|
|
}
|
|
|
|
void CollectionFetchJob::setFetchScope(const CollectionFetchScope &scope)
|
|
{
|
|
Q_D(CollectionFetchJob);
|
|
d->mScope = scope;
|
|
}
|
|
|
|
CollectionFetchScope &CollectionFetchJob::fetchScope()
|
|
{
|
|
Q_D(CollectionFetchJob);
|
|
return d->mScope;
|
|
}
|
|
|
|
#include "moc_collectionfetchjob.cpp"
|