diff --git a/src/core/jobs/collectionfetchjob.cpp b/src/core/jobs/collectionfetchjob.cpp index 71def70bb..14c502a62 100644 --- a/src/core/jobs/collectionfetchjob.cpp +++ b/src/core/jobs/collectionfetchjob.cpp @@ -1,426 +1,426 @@ /* Copyright (c) 2006 - 2007 Volker Krause 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 "job_p.h" #include "protocolhelper_p.h" #include "collection_p.h" #include "collectionfetchscope.h" #include "collectionutils.h" #include "private/protocol_p.h" #include "akonadicore_debug.h" #include #include #include #include using namespace Akonadi; class Akonadi::CollectionFetchJobPrivate : public JobPrivate { public: CollectionFetchJobPrivate(CollectionFetchJob *parent) : JobPrivate(parent) , mType(CollectionFetchJob::Base) { } 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 = nullptr; bool mBasePrefetch = false; Collection::List mPrefetchList; void aboutToFinish() override { timeout(); } void timeout() { Q_Q(CollectionFetchJob); mEmitTimer->stop(); // in case we are called by result() if (!mPendingCollections.isEmpty()) { if (!q->error() || mScope.ignoreRetrievalErrors()) { Q_EMIT q->collectionsReceived(mPendingCollections); } mPendingCollections.clear(); } } void subJobCollectionReceived(const Akonadi::Collection::List &collections) { mPendingCollections += collections; if (!mEmitTimer->isActive()) { mEmitTimer->start(); } } QString jobDebuggingString() const override { if (mBase.isValid()) { return QStringLiteral("Collection Id %1").arg(mBase.id()); } else if (CollectionUtils::hasValidHierarchicalRID(mBase)) { //return QLatin1String("(") + ProtocolHelper::hierarchicalRidToScope(mBase).hridChain().join(QLatin1String(", ")) + QLatin1Char(')'); return QStringLiteral("HRID chain"); } else { return QStringLiteral("Collection RemoteId %1").arg(mBase.remoteId()); } } bool jobFailed(KJob *job) { Q_Q(CollectionFetchJob); if (mScope.ignoreRetrievalErrors()) { int error = job->error(); if (error && !q->error()) { q->setError(error); q->setErrorText(job->errorText()); } if (error == Job::ConnectionFailed || error == Job::ProtocolVersionMismatch || error == Job::UserCanceled) { return true; } return false; } else { return job->error(); } } }; 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 &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 { for (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 Recursive, 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) { for (const Collection &col : qAsConst(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 { for (const Collection &col : qAsConst(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; } const auto cmd = Protocol::FetchCollectionsCommandPtr::create(ProtocolHelper::entityToScope(d->mBase)); switch (d->mType) { case Base: cmd->setDepth(Protocol::FetchCollectionsCommand::BaseCollection); break; case Akonadi::CollectionFetchJob::FirstLevel: cmd->setDepth(Protocol::FetchCollectionsCommand::ParentCollection); break; case Akonadi::CollectionFetchJob::Recursive: cmd->setDepth(Protocol::FetchCollectionsCommand::AllCollections); break; default: Q_ASSERT(false); } cmd->setResource(d->mScope.resource()); cmd->setMimeTypes(d->mScope.contentMimeTypes()); switch (d->mScope.listFilter()) { case CollectionFetchScope::Display: cmd->setDisplayPref(true); break; case CollectionFetchScope::Sync: cmd->setSyncPref(true); break; case CollectionFetchScope::Index: cmd->setIndexPref(true); break; case CollectionFetchScope::Enabled: cmd->setEnabled(true); break; case CollectionFetchScope::NoFilter: break; default: Q_ASSERT(false); } cmd->setFetchStats(d->mScope.includeStatistics()); switch (d->mScope.ancestorRetrieval()) { case CollectionFetchScope::None: cmd->setAncestorsDepth(Protocol::Ancestor::NoAncestor); break; case CollectionFetchScope::Parent: cmd->setAncestorsDepth(Protocol::Ancestor::ParentAncestor); break; case CollectionFetchScope::All: cmd->setAncestorsDepth(Protocol::Ancestor::AllAncestors); break; } if (d->mScope.ancestorRetrieval() != CollectionFetchScope::None) { cmd->setAncestorsAttributes(d->mScope.ancestorFetchScope().attributes()); } d->sendCommand(cmd); } bool CollectionFetchJob::doHandleResponse(qint64 tag, const Protocol::CommandPtr &response) { Q_D(CollectionFetchJob); if (d->mBasePrefetch || d->mType == NonOverlappingRoots) { return false; } if (!response->isResponse() || response->type() != Protocol::Command::FetchCollections) { return Job::doHandleResponse(tag, response); } const auto &resp = Protocol::cmdCast(response); // Invalid response (no ID) means this was the last response if (resp.id() == -1) { return true; } Collection collection = ProtocolHelper::parseCollection(resp, true); if (!collection.isValid()) { return false; } collection.d_ptr->resetChangeLog(); d->mCollections.append(collection); d->mPendingCollections.append(collection); if (!d->mEmitTimer->isActive()) { d->mEmitTimer->start(); } return false; } static Collection::List filterDescendants(const Collection::List &list) { Collection::List result; QVector > ids; ids.reserve(list.count()); for (const Collection &collection : list) { QList ancestors; Collection parent = collection.parentCollection(); ancestors << parent.id(); if (parent != Collection::root()) { while (parent.parentCollection() != Collection::root()) { parent = parent.parentCollection(); QList::iterator i = std::lower_bound(ancestors.begin(), ancestors.end(), parent.id()); ancestors.insert(i, parent.id()); } } ids << ancestors; } QSet excludeList; for (const Collection &collection : list) { int i = 0; for (const QList &ancestors : qAsConst(ids)) { - if (qBinaryFind(ancestors, collection.id()) != ancestors.end()) { + if (std::binary_search(ancestors.cbegin(), ancestors.cend(), collection.id())) { excludeList.insert(list.at(i).id()); } ++i; } } for (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(job); Q_ASSERT(job); if (d->mType == NonOverlappingRoots) { d->mPrefetchList += list->collections(); } else if (!d->mBasePrefetch) { d->mCollections += list->collections(); } if (d_ptr->mCurrentSubJob == job && !d->jobFailed(job)) { if (job->error()) { qCWarning(AKONADICORE_LOG) << "Error during CollectionFetchJob: " << job->errorString(); } d_ptr->mCurrentSubJob = nullptr; removeSubjob(job); QTimer::singleShot(0, this, [d]() { d->startNext(); }); } else { Job::slotResult(job); } if (d->mBasePrefetch) { d->mBasePrefetch = false; const Collection::List roots = list->collections(); Q_ASSERT(!hasSubjobs()); if (!job->error()) { for (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) { if (!d->jobFailed(job) && !hasSubjobs()) { const Collection::List result = filterDescendants(d->mPrefetchList); d->mPendingCollections += result; d->mCollections = result; d->delayedEmitResult(); } } else { if (!d->jobFailed(job) && !hasSubjobs()) { d->delayedEmitResult(); } } } 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" diff --git a/src/core/models/collectionfilterproxymodel.cpp b/src/core/models/collectionfilterproxymodel.cpp index eaffdbe56..55a48e9f2 100644 --- a/src/core/models/collectionfilterproxymodel.cpp +++ b/src/core/models/collectionfilterproxymodel.cpp @@ -1,180 +1,180 @@ /* Copyright (c) 2007 Bruno Virlet 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 "collectionfilterproxymodel.h" #include "akonadicore_debug.h" #include "entitytreemodel.h" #include "mimetypechecker.h" #include #include using namespace Akonadi; /** * @internal */ class Q_DECL_HIDDEN CollectionFilterProxyModel::Private { public: Private(CollectionFilterProxyModel *parent) : mParent(parent) { mimeChecker.addWantedMimeType(QStringLiteral("text/uri-list")); } bool collectionAccepted(const QModelIndex &index, bool checkResourceVisibility = true); QVector< QModelIndex > acceptedResources; CollectionFilterProxyModel *mParent = nullptr; MimeTypeChecker mimeChecker; bool mExcludeVirtualCollections = false; }; bool CollectionFilterProxyModel::Private::collectionAccepted(const QModelIndex &index, bool checkResourceVisibility) { // Retrieve supported mimetypes const Collection collection = mParent->sourceModel()->data(index, EntityTreeModel::CollectionRole).value(); if (!collection.isValid()) { return false; } if (collection.isVirtual() && mExcludeVirtualCollections) { return false; } // If this collection directly contains one valid mimetype, it is accepted if (mimeChecker.isWantedCollection(collection)) { // The folder will be accepted, but we need to make sure the resource is visible too. if (checkResourceVisibility) { // find the resource QModelIndex resource = index; while (resource.parent().isValid()) { resource = resource.parent(); } // See if that resource is visible, if not, invalidate the filter. if (resource != index && !acceptedResources.contains(resource)) { qCDebug(AKONADICORE_LOG) << "We got a new collection:" << mParent->sourceModel()->data(index).toString() << "but the resource is not visible:" << mParent->sourceModel()->data(resource).toString(); acceptedResources.clear(); // defer reset, the model might still be supplying new items at this point which crashs mParent->invalidateFilter(); return true; } } // Keep track of all the resources that are visible. if (!index.parent().isValid()) { acceptedResources.append(index); } return true; } // If this collection has a child which contains valid mimetypes, it is accepted - QModelIndex childIndex = index.child(0, 0); + QModelIndex childIndex = mParent->sourceModel()->index(0, 0, index); while (childIndex.isValid()) { if (collectionAccepted(childIndex, false /* don't check visibility of the parent, as we are checking the child now */)) { // Keep track of all the resources that are visible. if (!index.parent().isValid()) { acceptedResources.append(index); } return true; } childIndex = childIndex.sibling(childIndex.row() + 1, 0); } // Or else, no reason to keep this collection. return false; } CollectionFilterProxyModel::CollectionFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent) , d(new Private(this)) { } CollectionFilterProxyModel::~CollectionFilterProxyModel() { delete d; } void CollectionFilterProxyModel::addMimeTypeFilters(const QStringList &typeList) { const QStringList mimeTypes = d->mimeChecker.wantedMimeTypes() + typeList; d->mimeChecker.setWantedMimeTypes(mimeTypes); invalidateFilter(); } void CollectionFilterProxyModel::addMimeTypeFilter(const QString &type) { d->mimeChecker.addWantedMimeType(type); invalidateFilter(); } bool CollectionFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { return d->collectionAccepted(sourceModel()->index(sourceRow, 0, sourceParent)); } QStringList CollectionFilterProxyModel::mimeTypeFilters() const { return d->mimeChecker.wantedMimeTypes(); } void CollectionFilterProxyModel::clearFilters() { d->mimeChecker = MimeTypeChecker(); invalidateFilter(); } void CollectionFilterProxyModel::setExcludeVirtualCollections(bool exclude) { if (exclude != d->mExcludeVirtualCollections) { d->mExcludeVirtualCollections = exclude; invalidateFilter(); } } bool CollectionFilterProxyModel::excludeVirtualCollections() const { return d->mExcludeVirtualCollections; } Qt::ItemFlags CollectionFilterProxyModel::flags(const QModelIndex &index) const { if (!index.isValid()) { // Don't crash return Qt::NoItemFlags; } const Collection collection = sourceModel()->data(mapToSource(index), EntityTreeModel::CollectionRole).value(); // If this collection directly contains one valid mimetype, it is accepted if (d->mimeChecker.isWantedCollection(collection)) { return QSortFilterProxyModel::flags(index); } else { return QSortFilterProxyModel::flags(index) & ~(Qt::ItemIsSelectable); } }