diff --git a/autotests/libs/fakeentitycache.h b/autotests/libs/fakeentitycache.h index f6e7d3bab..588498c0b 100644 --- a/autotests/libs/fakeentitycache.h +++ b/autotests/libs/fakeentitycache.h @@ -1,188 +1,188 @@ /* Copyright (c) 2011 Stephen Kelly 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. */ #ifndef FAKEENTITYCACHE_H #define FAKEENTITYCACHE_H #include "monitor_p.h" #include "notificationsource_p.h" #include "collectionfetchscope.h" #include "itemfetchscope.h" #include "akonaditestfake_export.h" #include "private/protocol_p.h" template class FakeEntityCache : public Cache { public: FakeEntityCache(Akonadi::Session *session = nullptr, QObject *parent = nullptr) : Cache(0, session, parent) { } void setData(const QHash &data) { m_data = data; } void insert(T t) { m_data.insert(t.id(), t); } void emitDataAvailable() { - emit Cache::dataAvailable(); + Q_EMIT Cache::dataAvailable(); } T retrieve(typename T::Id id) const override { return m_data.value(id); } void request(typename T::Id id, const typename Cache::FetchScope &scope) override { Q_UNUSED(id) Q_UNUSED(scope) } bool ensureCached(typename T::Id id, const typename Cache::FetchScope &scope) override { Q_UNUSED(scope) return m_data.contains(id); } private: QHash m_data; }; typedef FakeEntityCache FakeCollectionCache; typedef FakeEntityCache FakeItemCache; class AKONADITESTFAKE_EXPORT FakeNotificationSource : public QObject { Q_OBJECT public: explicit FakeNotificationSource(QObject *parent = nullptr) : QObject(parent) { } public Q_SLOTS: void setAllMonitored(bool allMonitored) { Q_UNUSED(allMonitored) } void setMonitoredCollection(qlonglong id, bool monitored) { Q_UNUSED(id) Q_UNUSED(monitored) } void setMonitoredItem(qlonglong id, bool monitored) { Q_UNUSED(id) Q_UNUSED(monitored) } void setMonitoredResource(const QByteArray &resource, bool monitored) { Q_UNUSED(resource) Q_UNUSED(monitored) } void setMonitoredMimeType(const QString &mimeType, bool monitored) { Q_UNUSED(mimeType) Q_UNUSED(monitored) } void setIgnoredSession(const QByteArray &session, bool ignored) { Q_UNUSED(session) Q_UNUSED(ignored) } void setSession(const QByteArray &session) { Q_UNUSED(session); } }; class AKONADITESTFAKE_EXPORT FakeNotificationConnection : public Akonadi::Connection { Q_OBJECT public: explicit FakeNotificationConnection(Akonadi::CommandBuffer *buffer) : Connection(Connection::NotificationConnection, "", buffer) , mBuffer(buffer) {} virtual ~FakeNotificationConnection() {} void emitNotify(const Akonadi::Protocol::ChangeNotificationPtr &ntf) { Akonadi::CommandBufferLocker locker(mBuffer); mBuffer->enqueue(3, ntf); } private: Akonadi::CommandBuffer *mBuffer; }; class FakeMonitorDependeciesFactory : public Akonadi::ChangeNotificationDependenciesFactory { public: FakeMonitorDependeciesFactory(FakeItemCache *itemCache_, FakeCollectionCache *collectionCache_) : Akonadi::ChangeNotificationDependenciesFactory() , itemCache(itemCache_) , collectionCache(collectionCache_) { } Akonadi::Connection *createNotificationConnection(Akonadi::Session *parent, Akonadi::CommandBuffer *buffer) override { auto conn = new FakeNotificationConnection(buffer); addConnection(parent, conn); return conn; } void destroyNotificationConnection(Akonadi::Session *parent, Akonadi::Connection *connection) override { Q_UNUSED(parent); delete connection; } Akonadi::CollectionCache *createCollectionCache(int maxCapacity, Akonadi::Session *session) override { Q_UNUSED(maxCapacity) Q_UNUSED(session) return collectionCache; } Akonadi::ItemCache *createItemCache(int maxCapacity, Akonadi::Session *session) override { Q_UNUSED(maxCapacity) Q_UNUSED(session) return itemCache; } private: FakeItemCache *itemCache = nullptr; FakeCollectionCache *collectionCache = nullptr; }; #endif diff --git a/autotests/libs/fakesession.cpp b/autotests/libs/fakesession.cpp index dfb314f8a..68ed60fe1 100644 --- a/autotests/libs/fakesession.cpp +++ b/autotests/libs/fakesession.cpp @@ -1,92 +1,92 @@ /* Copyright (c) 2009 Stephen Kelly 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 "fakesession.h" #include "session_p.h" #include "job.h" #include "private/protocol_p.h" #include class FakeSessionPrivate : public SessionPrivate { public: FakeSessionPrivate(FakeSession *parent, FakeSession::Mode mode) : SessionPrivate(parent), q_ptr(parent), m_mode(mode) { protocolVersion = Protocol::version(); } /* reimp */ void init(const QByteArray &id) override { // trimmed down version of the real SessionPrivate::init(), without any server access if (!id.isEmpty()) { sessionId = id; } else { sessionId = QCoreApplication::instance()->applicationName().toUtf8() + '-' + QByteArray::number(qrand()); } connected = false; theNextTag = 1; jobRunning = false; reconnect(); } /* reimp */ void reconnect() override { if (m_mode == FakeSession::EndJobsImmediately) { return; } - emit q_ptr->reconnected(); + Q_EMIT q_ptr->reconnected(); connected = true; startNext(); } /* reimp */ void addJob(Job *job) override { - emit q_ptr->jobAdded(job); + Q_EMIT q_ptr->jobAdded(job); // Return immediately so that no actual communication happens with the server and // the started jobs are completed. if (m_mode == FakeSession::EndJobsImmediately) { endJob(job); } else { SessionPrivate::addJob(job); } } FakeSession *q_ptr; FakeSession::Mode m_mode; }; FakeSession::FakeSession(const QByteArray &sessionId, FakeSession::Mode mode, QObject *parent) : Session(new FakeSessionPrivate(this, mode), sessionId, parent) { } void FakeSession::setAsDefaultSession() { d->setDefaultSession(this); } diff --git a/autotests/libs/testresource/knutresource.cpp b/autotests/libs/testresource/knutresource.cpp index 3312c0b9d..9a64b17f9 100644 --- a/autotests/libs/testresource/knutresource.cpp +++ b/autotests/libs/testresource/knutresource.cpp @@ -1,389 +1,389 @@ /* Copyright (c) 2006 Tobias Koenig Copyright (c) 2009 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 "knutresource.h" #include "knutresource_debug.h" #include "settings.h" #include "settingsadaptor.h" #include "xmlwriter.h" #include "xmlreader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; KnutResource::KnutResource(const QString &id) : ResourceBase(id) , mWatcher(new QFileSystemWatcher(this)) , mSettings(new KnutSettings()) { changeRecorder()->itemFetchScope().fetchFullPayload(); changeRecorder()->fetchCollection(true); new SettingsAdaptor(mSettings); KDBusConnectionPool::threadConnection().registerObject(QStringLiteral("/Settings"), mSettings, QDBusConnection::ExportAdaptors); connect(this, &KnutResource::reloadConfiguration, this, &KnutResource::load); connect(mWatcher, &QFileSystemWatcher::fileChanged, this, &KnutResource::load); load(); } KnutResource::~KnutResource() { delete mSettings; } void KnutResource::load() { if (!mWatcher->files().isEmpty()) { mWatcher->removePaths(mWatcher->files()); } // file loading QString fileName = mSettings->dataFile(); if (fileName.isEmpty()) { - emit status(Broken, i18n("No data file selected.")); + Q_EMIT status(Broken, i18n("No data file selected.")); return; } if (!QFile::exists(fileName)) { fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kf5/akonadi_knut_resource/knut-template.xml")); } if (!mDocument.loadFile(fileName)) { - emit status(Broken, mDocument.lastError()); + Q_EMIT status(Broken, mDocument.lastError()); return; } if (mSettings->fileWatchingEnabled()) { mWatcher->addPath(fileName); } - emit status(Idle, i18n("File '%1' loaded successfully.", fileName)); + Q_EMIT status(Idle, i18n("File '%1' loaded successfully.", fileName)); synchronize(); } void KnutResource::save() { if (mSettings->readOnly()) { return; } const QString fileName = mSettings->dataFile(); if (!mDocument.writeToFile(fileName)) { - emit error(mDocument.lastError()); + Q_EMIT error(mDocument.lastError()); return; } } void KnutResource::configure(WId windowId) { QString oldFile = mSettings->dataFile(); if (oldFile.isEmpty()) { oldFile = QDir::homePath(); } // TODO: Use windowId Q_UNUSED(windowId); const QString newFile = QFileDialog::getSaveFileName( nullptr, i18n("Select Data File"), QString(), QStringLiteral("*.xml |") + i18nc("Filedialog filter for Akonadi data file", "Akonadi Knut Data File")); if (newFile.isEmpty() || oldFile == newFile) { return; } mSettings->setDataFile(newFile); mSettings->save(); load(); - emit configurationDialogAccepted(); + Q_EMIT configurationDialogAccepted(); } void KnutResource::retrieveCollections() { const Collection::List collections = mDocument.collections(); collectionsRetrieved(collections); const Tag::List tags = mDocument.tags(); Q_FOREACH (const Tag &tag, tags) { TagCreateJob *createjob = new TagCreateJob(tag); createjob->setMergeIfExisting(true); } } void KnutResource::retrieveItems(const Akonadi::Collection &collection) { Item::List items = mDocument.items(collection, false); if (!mDocument.lastError().isEmpty()) { cancelTask(mDocument.lastError()); return; } itemsRetrieved(items); } #ifdef DO_IT_THE_OLD_WAY bool KnutResource::retrieveItem(const Item &item, const QSet &parts) { Q_UNUSED(parts); const QDomElement itemElem = mDocument.itemElementByRemoteId(item.remoteId()); if (itemElem.isNull()) { cancelTask(i18n("No item found for remoteid %1", item.remoteId())); return false; } Item i = XmlReader::elementToItem(itemElem, true); i.setId(item.id()); itemRetrieved(i); return true; } #endif bool KnutResource::retrieveItems(const Item::List &items, const QSet &parts) { Q_UNUSED(parts); Item::List results; results.reserve(items.size()); for (const auto &item : items) { const QDomElement itemElem = mDocument.itemElementByRemoteId(item.remoteId()); if (itemElem.isNull()) { cancelTask(i18n("No item found for remoteid %1", item.remoteId())); return false; } Item i = XmlReader::elementToItem(itemElem, true); i.setParentCollection(item.parentCollection()); i.setId(item.id()); results.push_back(i); } itemsRetrieved(results); return true; } void KnutResource::collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent) { QDomElement parentElem = mDocument.collectionElementByRemoteId(parent.remoteId()); if (parentElem.isNull()) { - emit error(i18n("Parent collection not found in DOM tree.")); + Q_EMIT error(i18n("Parent collection not found in DOM tree.")); changeProcessed(); return; } Collection c(collection); c.setRemoteId(QUuid::createUuid().toString()); if (XmlWriter::writeCollection(c, parentElem).isNull()) { - emit error(i18n("Unable to write collection.")); + Q_EMIT error(i18n("Unable to write collection.")); changeProcessed(); } else { save(); changeCommitted(c); } } void KnutResource::collectionChanged(const Akonadi::Collection &collection) { QDomElement oldElem = mDocument.collectionElementByRemoteId(collection.remoteId()); if (oldElem.isNull()) { - emit error(i18n("Modified collection not found in DOM tree.")); + Q_EMIT error(i18n("Modified collection not found in DOM tree.")); changeProcessed(); return; } Collection c(collection); QDomElement newElem; newElem = XmlWriter::collectionToElement(c, mDocument.document()); // move all items/collections over to the new node const QDomNodeList children = oldElem.childNodes(); const int numberOfChildren = children.count(); for (int i = 0; i < numberOfChildren; ++i) { const QDomElement child = children.at(i).toElement(); qCDebug(KNUTRESOURCE_LOG) << "reparenting " << child.tagName() << child.attribute(QStringLiteral("rid")); if (child.isNull()) { continue; } if (child.tagName() == QStringLiteral("item") || child.tagName() == QStringLiteral("collection")) { newElem.appendChild(child); // reparents --i; // children, despite being const is modified by the reparenting } } oldElem.parentNode().replaceChild(newElem, oldElem); save(); changeCommitted(c); } void KnutResource::collectionRemoved(const Akonadi::Collection &collection) { const QDomElement colElem = mDocument.collectionElementByRemoteId(collection.remoteId()); if (colElem.isNull()) { - emit error(i18n("Deleted collection not found in DOM tree.")); + Q_EMIT error(i18n("Deleted collection not found in DOM tree.")); changeProcessed(); return; } colElem.parentNode().removeChild(colElem); save(); changeProcessed(); } void KnutResource::itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection) { QDomElement parentElem = mDocument.collectionElementByRemoteId(collection.remoteId()); if (parentElem.isNull()) { - emit error(i18n("Parent collection '%1' not found in DOM tree." , collection.remoteId())); + Q_EMIT error(i18n("Parent collection '%1' not found in DOM tree." , collection.remoteId())); changeProcessed(); return; } Item i(item); i.setRemoteId(QUuid::createUuid().toString()); if (XmlWriter::writeItem(i, parentElem).isNull()) { - emit error(i18n("Unable to write item.")); + Q_EMIT error(i18n("Unable to write item.")); changeProcessed(); } else { save(); changeCommitted(i); } } void KnutResource::itemChanged(const Akonadi::Item &item, const QSet &parts) { Q_UNUSED(parts); const QDomElement oldElem = mDocument.itemElementByRemoteId(item.remoteId()); if (oldElem.isNull()) { - emit error(i18n("Modified item not found in DOM tree.")); + Q_EMIT error(i18n("Modified item not found in DOM tree.")); changeProcessed(); return; } Item i(item); const QDomElement newElem = XmlWriter::itemToElement(i, mDocument.document()); oldElem.parentNode().replaceChild(newElem, oldElem); save(); changeCommitted(i); } void KnutResource::itemRemoved(const Akonadi::Item &item) { const QDomElement itemElem = mDocument.itemElementByRemoteId(item.remoteId()); if (itemElem.isNull()) { - emit error(i18n("Deleted item not found in DOM tree.")); + Q_EMIT error(i18n("Deleted item not found in DOM tree.")); changeProcessed(); return; } itemElem.parentNode().removeChild(itemElem); save(); changeProcessed(); } void KnutResource::itemMoved(const Item &item, const Collection &collectionSource, const Collection &collectionDestination) { const QDomElement oldElem = mDocument.itemElementByRemoteId(item.remoteId()); if (oldElem.isNull()) { qCWarning(KNUTRESOURCE_LOG) << "Moved item not found in DOM tree"; changeProcessed(); return; } QDomElement sourceParentElem = mDocument.collectionElementByRemoteId(collectionSource.remoteId()); if (sourceParentElem.isNull()) { - emit error(i18n("Parent collection '%1' not found in DOM tree.", collectionSource.remoteId())); + Q_EMIT error(i18n("Parent collection '%1' not found in DOM tree.", collectionSource.remoteId())); changeProcessed(); return; } QDomElement destParentElem = mDocument.collectionElementByRemoteId(collectionDestination.remoteId()); if (destParentElem.isNull()) { - emit error(i18n("Parent collection '%1' not found in DOM tree.", collectionDestination.remoteId())); + Q_EMIT error(i18n("Parent collection '%1' not found in DOM tree.", collectionDestination.remoteId())); changeProcessed(); return; } QDomElement itemElem = mDocument.itemElementByRemoteId(item.remoteId()); if (itemElem.isNull()) { - emit error(i18n("No item found for remoteid %1", item.remoteId())); + Q_EMIT error(i18n("No item found for remoteid %1", item.remoteId())); } sourceParentElem.removeChild(itemElem); destParentElem.appendChild(itemElem); if (XmlWriter::writeItem(item, destParentElem).isNull()) { - emit error(i18n("Unable to write item.")); + Q_EMIT error(i18n("Unable to write item.")); } else { save(); } changeProcessed(); } QSet KnutResource::parseQuery(const QString &queryString) { QSet resultSet; Akonadi::SearchQuery query = Akonadi::SearchQuery::fromJSON(queryString.toLatin1()); foreach (const Akonadi::SearchTerm &term, query.term().subTerms()) { if (term.key() == QStringLiteral("resource")) { resultSet << term.value().toInt(); } } return resultSet; } void KnutResource::search(const QString &query, const Collection &collection) { Q_UNUSED(collection); const QVector result = parseQuery(query).toList().toVector(); qCDebug(KNUTRESOURCE_LOG) << "KNUT QUERY:" << query; qCDebug(KNUTRESOURCE_LOG) << "KNUT RESOURCE:" << result; searchFinished(result, Akonadi::AgentSearchInterface::Uid); } void KnutResource::addSearch(const QString &query, const QString &queryLanguage, const Collection &resultCollection) { Q_UNUSED(query); Q_UNUSED(queryLanguage); Q_UNUSED(resultCollection); qCDebug(KNUTRESOURCE_LOG) << "addSearch: query=" << query << ", queryLanguage=" << queryLanguage << ", resultCollection=" << resultCollection.id(); } void KnutResource::removeSearch(const Collection &resultCollection) { Q_UNUSED(resultCollection); qCDebug(KNUTRESOURCE_LOG) << "removeSearch:" << resultCollection.id(); } AKONADI_RESOURCE_MAIN(KnutResource) diff --git a/autotests/libs/testrunner/setup.cpp b/autotests/libs/testrunner/setup.cpp index 2e30c93b4..fbfcf903b 100644 --- a/autotests/libs/testrunner/setup.cpp +++ b/autotests/libs/testrunner/setup.cpp @@ -1,448 +1,448 @@ /* * Copyright (c) 2008 Igor Trindade Oliveira * Copyright (c) 2013 Volker Krause * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "setup.h" #include "config.h" //krazy:exclude=includes #include #include #include #include #include #include #include #include #include #include #include #include #include bool SetupTest::startAkonadiDaemon() { Q_ASSERT(Akonadi::ServerManager::hasInstanceIdentifier()); if (!mAkonadiDaemonProcess) { mAkonadiDaemonProcess = new KProcess(this); connect(mAkonadiDaemonProcess, QOverload::of(&KProcess::finished), this, &SetupTest::slotAkonadiDaemonProcessFinished); } mAkonadiDaemonProcess->setProgram(QStringLiteral("akonadi_control"), { QStringLiteral("--instance"), instanceId() }); mAkonadiDaemonProcess->start(); const bool started = mAkonadiDaemonProcess->waitForStarted(5000); qDebug() << "Started akonadi daemon with pid:" << mAkonadiDaemonProcess->pid(); return started; } void SetupTest::stopAkonadiDaemon() { if (!mAkonadiDaemonProcess) { return; } disconnect(mAkonadiDaemonProcess, SIGNAL(finished(int)), this, nullptr); mAkonadiDaemonProcess->terminate(); const bool finished = mAkonadiDaemonProcess->waitForFinished(5000); if (!finished) { qDebug() << "Problem finishing process."; } mAkonadiDaemonProcess->close(); mAkonadiDaemonProcess->deleteLater(); mAkonadiDaemonProcess = nullptr; } void SetupTest::setupAgents() { if (mAgentsCreated) { return; } mAgentsCreated = true; Config *config = Config::instance(); const auto agents = config->agents(); for (const auto agent : agents) { qDebug() << "Creating agent" << agent.first << "..."; ++mSetupJobCount; Akonadi::AgentInstanceCreateJob *job = new Akonadi::AgentInstanceCreateJob(agent.first, this); job->setProperty("sync", agent.second); connect(job, &Akonadi::AgentInstanceCreateJob::result, this, &SetupTest::agentCreationResult); job->start(); } if (isSetupDone()) { - emit setupDone(); + Q_EMIT setupDone(); } } void SetupTest::agentCreationResult(KJob *job) { qDebug() << "Agent created"; --mSetupJobCount; if (job->error()) { qCritical() << job->errorString(); setupFailed(); return; } const bool needsSync = job->property("sync").toBool(); if (needsSync) { ++mSetupJobCount; qDebug() << "Scheduling Agent sync"; Akonadi::ResourceSynchronizationJob *sync = new Akonadi::ResourceSynchronizationJob( qobject_cast(job)->instance(), this); connect(sync, &Akonadi::ResourceSynchronizationJob::result, this, &SetupTest::synchronizationResult); sync->start(); } if (isSetupDone()) { - emit setupDone(); + Q_EMIT setupDone(); } } void SetupTest::synchronizationResult(KJob *job) { qDebug() << "Sync done"; --mSetupJobCount; if (job->error()) { qCritical() << job->errorString(); setupFailed(); } if (isSetupDone()) { - emit setupDone(); + Q_EMIT setupDone(); } } void SetupTest::serverStateChanged(Akonadi::ServerManager::State state) { if (state == Akonadi::ServerManager::Running) { setupAgents(); } else if (mShuttingDown && state == Akonadi::ServerManager::NotRunning) { shutdownHarder(); } } void SetupTest::copyXdgDirectory(const QString &src, const QString &dst) { qDebug() << "Copying" << src << "to" << dst; const QDir srcDir(src); const auto entries = srcDir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot); for (const auto &fi : entries) { if (fi.isDir()) { if (fi.fileName() == QStringLiteral("akonadi")) { // namespace according to instance identifier #ifdef Q_OS_WIN const bool isXdgConfig = src.contains(QLatin1String("/xdgconfig/")); copyDirectory(fi.absoluteFilePath(), dst + QStringLiteral("/akonadi/") + (isXdgConfig ? QStringLiteral("config/") : QStringLiteral("data/")) + QStringLiteral("instance/") + instanceId()); #else copyDirectory(fi.absoluteFilePath(), dst + QStringLiteral("/akonadi/instance/") + instanceId()); #endif } else { copyDirectory(fi.absoluteFilePath(), dst + QLatin1Char('/') + fi.fileName()); } } else { if (fi.fileName().startsWith(QStringLiteral("akonadi_")) && fi.fileName().endsWith(QStringLiteral("rc"))) { // namespace according to instance identifier const QString baseName = fi.fileName().left(fi.fileName().size() - 2); const QString dstPath = dst + QLatin1Char('/') + Akonadi::ServerManager::addNamespace(baseName) + QStringLiteral("rc"); if (!QFile::copy(fi.absoluteFilePath(), dstPath)) { qWarning() << "Failed to copy" << fi.absoluteFilePath() << "to" << dstPath; } } else { const QString dstPath = dst + QLatin1Char('/') + fi.fileName(); if (!QFile::copy(fi.absoluteFilePath(), dstPath)) { qWarning() << "Failed to copy" << fi.absoluteFilePath() << "to" << dstPath; } } } } } void SetupTest::copyDirectory(const QString &src, const QString &dst) { const QDir srcDir(src); QDir::root().mkpath(dst); const auto entries = srcDir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot); for (const auto &fi : entries) { const QString dstPath = dst + QLatin1Char('/') + fi.fileName(); if (fi.isDir()) { copyDirectory(fi.absoluteFilePath(), dstPath); } else { if (!QFile::copy(fi.absoluteFilePath(), dstPath)) { qWarning() << "Failed to copy" << fi.absoluteFilePath() << "to" << dstPath; } } } } void SetupTest::createTempEnvironment() { qDebug() << "Creating test environment in" << basePath(); const Config *config = Config::instance(); #ifdef Q_OS_WIN // Always copy the generic xdgconfig dir copyXdgDirectory(config->basePath() + QStringLiteral("/xdgconfig"), basePath()); if (!config->xdgConfigHome().isEmpty()) { copyXdgDirectory(config->xdgConfigHome(), basePath()); } copyXdgDirectory(config->xdgDataHome(), basePath()); setEnvironmentVariable("XDG_DATA_HOME", basePath()); setEnvironmentVariable("XDG_CONFIG_HOME", basePath()); writeAkonadiserverrc(basePath() + QStringLiteral("/akonadi/config/instance/%1/akonadiserverrc").arg(instanceId())); #else const QDir tmpDir(basePath()); const QString testRunnerDataDir = QStringLiteral("data"); const QString testRunnerConfigDir = QStringLiteral("config"); const QString testRunnerTmpDir = QStringLiteral("tmp"); tmpDir.mkpath(testRunnerConfigDir); tmpDir.mkpath(testRunnerDataDir); tmpDir.mkpath(testRunnerTmpDir); // Always copy the generic xdgconfig dir copyXdgDirectory(config->basePath() + QStringLiteral("/xdgconfig"), basePath() + testRunnerConfigDir); if (!config->xdgConfigHome().isEmpty()) { copyXdgDirectory(config->xdgConfigHome(), basePath() + testRunnerConfigDir); } copyXdgDirectory(config->xdgDataHome(), basePath() + testRunnerDataDir); setEnvironmentVariable("XDG_DATA_HOME", basePath() + testRunnerDataDir); setEnvironmentVariable("XDG_CONFIG_HOME", basePath() + testRunnerConfigDir); setEnvironmentVariable("TMPDIR", basePath() + testRunnerTmpDir); writeAkonadiserverrc(basePath() + testRunnerConfigDir + QStringLiteral("/akonadi/instance/%1/akonadiserverrc").arg(instanceId())); #endif QString backend; if (Config::instance()->dbBackend() == QLatin1String("pgsql")) { backend = QStringLiteral("postgresql"); } else { backend = Config::instance()->dbBackend(); } setEnvironmentVariable("TESTRUNNER_DB_ENVIRONMENT", backend); } void SetupTest::writeAkonadiserverrc(const QString &path) { QString backend; if (Config::instance()->dbBackend() == QLatin1String("sqlite")) { backend = QStringLiteral("QSQLITE3"); } else if (Config::instance()->dbBackend() == QLatin1String("mysql")) { backend = QStringLiteral("QMYSQL"); } else if (Config::instance()->dbBackend() == QLatin1String("pgsql")) { backend = QStringLiteral("QPSQL"); } else { qCritical("Invalid backend name %s", qPrintable(backend)); return; } QSettings settings(path, QSettings::IniFormat); settings.beginGroup(QStringLiteral("General")); settings.setValue(QStringLiteral("Driver"), backend); settings.endGroup(); settings.beginGroup(QStringLiteral("Search")); settings.setValue(QStringLiteral("Manager"), QStringLiteral("Dummy")); settings.endGroup(); settings.beginGroup(QStringLiteral("Debug")); settings.setValue(QStringLiteral("Tracer"), QStringLiteral("null")); settings.endGroup(); qDebug() << "Written akonadiserverrc to" << settings.fileName(); } void SetupTest::cleanTempEnvironment() { #ifdef Q_OS_WIN QDir(basePath() + QStringLiteral("akonadi/config/instance/") + instanceId()).removeRecursively(); QDir(basePath() + QStringLiteral("akonadi/data/instance/") + instanceId()).removeRecursively(); #else QDir(basePath()).removeRecursively(); #endif } SetupTest::SetupTest() : mAkonadiDaemonProcess(nullptr) , mShuttingDown(false) , mAgentsCreated(false) , mTrackAkonadiProcess(true) , mSetupJobCount(0) , mExitCode(0) { setupInstanceId(); cleanTempEnvironment(); createTempEnvironment(); // switch off agent auto-starting by default, can be re-enabled if really needed inside the config.xml setEnvironmentVariable("AKONADI_DISABLE_AGENT_AUTOSTART", QStringLiteral("true")); setEnvironmentVariable("AKONADI_TESTRUNNER_PID", QString::number(QCoreApplication::instance()->applicationPid())); // enable all debugging, so we get some useful information when test fails setEnvironmentVariable("QT_LOGGING_RULES", QStringLiteral("* = true\n" "qt.* = false\n" "kf5.coreaddons.desktopparser.debug = false")); QHashIterator iter(Config::instance()->envVars()); while (iter.hasNext()) { iter.next(); qDebug() << "Setting environment variable" << iter.key() << "=" << iter.value(); setEnvironmentVariable(iter.key().toLocal8Bit(), iter.value()); } // No kres-migrator please KConfig migratorConfig(basePath() + QStringLiteral("config/kres-migratorrc")); KConfigGroup migrationCfg(&migratorConfig, "Migration"); migrationCfg.writeEntry("Enabled", false); connect(Akonadi::ServerManager::self(), &Akonadi::ServerManager::stateChanged, this, &SetupTest::serverStateChanged); QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.Akonadi.Testrunner-") + QString::number(QCoreApplication::instance()->applicationPid())); QDBusConnection::sessionBus().registerObject(QStringLiteral("/"), this, QDBusConnection::ExportScriptableSlots); } SetupTest::~SetupTest() { cleanTempEnvironment(); } void SetupTest::shutdown() { if (mShuttingDown) { return; } mShuttingDown = true; switch (Akonadi::ServerManager::self()->state()) { case Akonadi::ServerManager::Running: case Akonadi::ServerManager::Starting: case Akonadi::ServerManager::Upgrading: qDebug() << "Shutting down Akonadi control..."; Akonadi::ServerManager::self()->stop(); // safety timeout QTimer::singleShot(30 * 1000, this, &SetupTest::shutdownHarder); break; case Akonadi::ServerManager::NotRunning: case Akonadi::ServerManager::Broken: shutdownHarder(); Q_FALLTHROUGH(); case Akonadi::ServerManager::Stopping: // safety timeout QTimer::singleShot(30 * 1000, this, &SetupTest::shutdownHarder); break; } } void SetupTest::shutdownHarder() { qDebug(); mShuttingDown = false; stopAkonadiDaemon(); QCoreApplication::instance()->exit(mExitCode); } void SetupTest::restartAkonadiServer() { qDebug(); disconnect(mAkonadiDaemonProcess, SIGNAL(finished(int)), this, nullptr); Akonadi::ServerManager::self()->stop(); const bool shutdownResult = mAkonadiDaemonProcess->waitForFinished(); if (!shutdownResult) { qWarning() << "Akonadi control did not shut down in time, killing it."; mAkonadiDaemonProcess->kill(); } // we don't use Control::start() since we want to be able to kill // it forcefully, if necessary, and know the pid startAkonadiDaemon(); // from here on, the server exiting is an error again connect(mAkonadiDaemonProcess, SIGNAL(finished(int)), this, SLOT(slotAkonadiDaemonProcessFinished(int))); } QString SetupTest::basePath() const { #ifdef Q_OS_WIN // On Windows we are forced to share the same data directory as production instances // because there's no way to override QStandardPaths like we can on Unix. // This means that on Windows we rely on Instances providing us the necessary isolation return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); #else QString sysTempDirPath = QDir::tempPath(); #ifdef Q_OS_UNIX // QDir::tempPath() makes sure to use the fully sym-link exploded // absolute path to the temp dir. That is nice, but on OSX it makes // that path really long. MySQL chokes on this, for it's socket path, // so work around that sysTempDirPath = QStringLiteral("/tmp"); #endif const QDir sysTempDir(sysTempDirPath); const QString tempDir = QStringLiteral("/akonadi_testrunner-%1/") .arg(QCoreApplication::instance()->applicationPid()); if (!sysTempDir.exists(tempDir)) { sysTempDir.mkdir(tempDir); } return sysTempDirPath + tempDir; #endif } void SetupTest::slotAkonadiDaemonProcessFinished(int exitCode) { if (mTrackAkonadiProcess || exitCode != EXIT_SUCCESS) { qWarning() << "Akonadi server process was terminated externally!"; - emit serverExited(exitCode); + Q_EMIT serverExited(exitCode); } mAkonadiDaemonProcess = nullptr; } void SetupTest::trackAkonadiProcess(bool track) { mTrackAkonadiProcess = track; } QString SetupTest::instanceId() const { return QStringLiteral("testrunner-") + QString::number(QCoreApplication::instance()->applicationPid()); } void SetupTest::setupInstanceId() { setEnvironmentVariable("AKONADI_INSTANCE", instanceId()); } bool SetupTest::isSetupDone() const { qDebug() << "isSetupDone:" << mSetupJobCount << mExitCode; return mSetupJobCount == 0 && mExitCode == 0; } void SetupTest::setupFailed() { mExitCode = 1; shutdown(); } void SetupTest::setEnvironmentVariable(const QByteArray &name, const QString &value) { mEnvVars.push_back(qMakePair(name, value.toLocal8Bit())); qputenv(name.constData(), value.toLatin1()); } QVector< SetupTest::EnvVar > SetupTest::environmentVariables() const { return mEnvVars; } diff --git a/autotests/libs/testrunner/testrunner.cpp b/autotests/libs/testrunner/testrunner.cpp index c0cbe31c5..306811cd1 100644 --- a/autotests/libs/testrunner/testrunner.cpp +++ b/autotests/libs/testrunner/testrunner.cpp @@ -1,83 +1,83 @@ /* * Copyright (c) 2009 Volker Krause * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "testrunner.h" #include #include TestRunner::TestRunner(const QStringList &args, QObject *parent) : QObject(parent) , mArguments(args) , mExitCode(0) , mProcess(nullptr) { } int TestRunner::exitCode() const { return mExitCode; } void TestRunner::run() { qDebug() << mArguments; mProcess = new KProcess(this); mProcess->setProgram(mArguments); connect(mProcess, QOverload::of(&KProcess::finished), this, &TestRunner::processFinished); connect(mProcess, QOverload::of(&KProcess::error), this, &TestRunner::processError); // environment setup seems to have been done by setuptest globally already mProcess->start(); if (!mProcess->waitForStarted()) { qWarning() << mArguments << "failed to start!"; mExitCode = 255; - emit finished(); + Q_EMIT finished(); } } void TestRunner::triggerTermination(int exitCode) { processFinished(exitCode); } void TestRunner::processFinished(int exitCode) { // Only update the exit code when it is 0. This prevents overwriting a non-zero // value with 0. This can happen when multiple processes finish or triggerTermination // is called after a process has finished. if (mExitCode == 0) { mExitCode = exitCode; qDebug() << exitCode; } - emit finished(); + Q_EMIT finished(); } void TestRunner::processError(QProcess::ProcessError error) { qWarning() << mArguments << "exited with an error:" << error; mExitCode = 255; - emit finished(); + Q_EMIT finished(); } void TestRunner::terminate() { if (mProcess) { mProcess->terminate(); } } diff --git a/src/agentbase/agentbase.cpp b/src/agentbase/agentbase.cpp index 9eb0193e3..2dcb65550 100644 --- a/src/agentbase/agentbase.cpp +++ b/src/agentbase/agentbase.cpp @@ -1,1328 +1,1328 @@ /* Copyright (c) 2006 Till Adam Copyright (c) 2007 Volker Krause Copyright (c) 2007 Bruno Virlet Copyright (c) 2008 Kevin Krammer 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 "agentbase.h" #include "agentbase_p.h" #include "akonadi_version.h" #include "agentmanager.h" #include "changerecorder.h" #include "controladaptor.h" #include "KDBusConnectionPool" #include "itemfetchjob.h" #include "monitor_p.h" #include "servermanager_p.h" #include "session.h" #include "session_p.h" #include "statusadaptor.h" #include "agentconfigurationdialog.h" #include "private/standarddirs_p.h" #include "akonadiagentbase_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined __GLIBC__ # include // for dumping memory information #endif #ifdef Q_OS_WIN #include #include #include #endif using namespace Akonadi; static AgentBase *sAgentBase = nullptr; AgentBase::Observer::Observer() { } AgentBase::Observer::~Observer() { } void AgentBase::Observer::itemAdded(const Item &item, const Collection &collection) { Q_UNUSED(item); Q_UNUSED(collection); if (sAgentBase) { sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::Observer::itemChanged(const Item &item, const QSet &partIdentifiers) { Q_UNUSED(item); Q_UNUSED(partIdentifiers); if (sAgentBase) { sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::Observer::itemRemoved(const Item &item) { Q_UNUSED(item); if (sAgentBase) { sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::Observer::collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent) { Q_UNUSED(collection); Q_UNUSED(parent); if (sAgentBase) { sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::Observer::collectionChanged(const Collection &collection) { Q_UNUSED(collection); if (sAgentBase) { sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::Observer::collectionRemoved(const Collection &collection) { Q_UNUSED(collection); if (sAgentBase) { sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::ObserverV2::itemMoved(const Akonadi::Item &item, const Akonadi::Collection &source, const Akonadi::Collection &dest) { Q_UNUSED(item); Q_UNUSED(source); Q_UNUSED(dest); if (sAgentBase) { sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::ObserverV2::itemLinked(const Akonadi::Item &item, const Akonadi::Collection &collection) { Q_UNUSED(item); Q_UNUSED(collection); if (sAgentBase) { // not implementation, let's disconnect the signal to enable optimizations in Monitor QObject::disconnect(sAgentBase->changeRecorder(), &Monitor::itemLinked, sAgentBase->d_ptr, &AgentBasePrivate::itemLinked); sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::ObserverV2::itemUnlinked(const Akonadi::Item &item, const Akonadi::Collection &collection) { Q_UNUSED(item); Q_UNUSED(collection); if (sAgentBase) { // not implementation, let's disconnect the signal to enable optimizations in Monitor QObject::disconnect(sAgentBase->changeRecorder(), &Monitor::itemUnlinked, sAgentBase->d_ptr, &AgentBasePrivate::itemUnlinked); sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::ObserverV2::collectionMoved(const Akonadi::Collection &collection, const Akonadi::Collection &source, const Akonadi::Collection &dest) { Q_UNUSED(collection); Q_UNUSED(source); Q_UNUSED(dest); if (sAgentBase) { sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::ObserverV2::collectionChanged(const Akonadi::Collection &collection, const QSet &changedAttributes) { Q_UNUSED(changedAttributes); collectionChanged(collection); } void AgentBase::ObserverV3::itemsFlagsChanged(const Akonadi::Item::List &items, const QSet &addedFlags, const QSet &removedFlags) { Q_UNUSED(items); Q_UNUSED(addedFlags); Q_UNUSED(removedFlags); if (sAgentBase) { // not implementation, let's disconnect the signal to enable optimizations in Monitor QObject::disconnect(sAgentBase->changeRecorder(), &Monitor::itemsFlagsChanged, sAgentBase->d_ptr, &AgentBasePrivate::itemsFlagsChanged); sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::ObserverV3::itemsMoved(const Akonadi::Item::List &items, const Collection &sourceCollection, const Collection &destinationCollection) { Q_UNUSED(items); Q_UNUSED(sourceCollection); Q_UNUSED(destinationCollection); if (sAgentBase) { // not implementation, let's disconnect the signal to enable optimizations in Monitor QObject::disconnect(sAgentBase->changeRecorder(), &Monitor::itemsMoved, sAgentBase->d_ptr, &AgentBasePrivate::itemsMoved); sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::ObserverV3::itemsRemoved(const Akonadi::Item::List &items) { Q_UNUSED(items); if (sAgentBase) { // not implementation, let's disconnect the signal to enable optimizations in Monitor QObject::disconnect(sAgentBase->changeRecorder(), &Monitor::itemsRemoved, sAgentBase->d_ptr, &AgentBasePrivate::itemsRemoved); sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::ObserverV3::itemsLinked(const Akonadi::Item::List &items, const Collection &collection) { Q_UNUSED(items); Q_UNUSED(collection); if (sAgentBase) { // not implementation, let's disconnect the signal to enable optimizations in Monitor QObject::disconnect(sAgentBase->changeRecorder(), &Monitor::itemsLinked, sAgentBase->d_ptr, &AgentBasePrivate::itemsLinked); sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::ObserverV3::itemsUnlinked(const Akonadi::Item::List &items, const Collection &collection) { Q_UNUSED(items); Q_UNUSED(collection) if (sAgentBase) { // not implementation, let's disconnect the signal to enable optimizations in Monitor QObject::disconnect(sAgentBase->changeRecorder(), &Monitor::itemsUnlinked, sAgentBase->d_ptr, &AgentBasePrivate::itemsUnlinked); sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::ObserverV4::tagAdded(const Tag &tag) { Q_UNUSED(tag); if (sAgentBase) { // not implementation, let's disconnect the signal to enable optimization in Monitor QObject::disconnect(sAgentBase->changeRecorder(), &Monitor::tagAdded, sAgentBase->d_ptr, &AgentBasePrivate::tagAdded); sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::ObserverV4::tagChanged(const Tag &tag) { Q_UNUSED(tag); if (sAgentBase) { // not implementation, let's disconnect the signal to enable optimization in Monitor QObject::disconnect(sAgentBase->changeRecorder(), &Monitor::tagChanged, sAgentBase->d_ptr, &AgentBasePrivate::tagChanged); sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::ObserverV4::tagRemoved(const Tag &tag) { Q_UNUSED(tag); if (sAgentBase) { // not implementation, let's disconnect the signal to enable optimization in Monitor QObject::disconnect(sAgentBase->changeRecorder(), &Monitor::tagRemoved, sAgentBase->d_ptr, &AgentBasePrivate::tagRemoved); sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::ObserverV4::itemsTagsChanged(const Item::List &items, const QSet &addedTags, const QSet &removedTags) { Q_UNUSED(items); Q_UNUSED(addedTags); Q_UNUSED(removedTags); if (sAgentBase) { // not implementation, let's disconnect the signal to enable optimization in Monitor QObject::disconnect(sAgentBase->changeRecorder(), &Monitor::itemsTagsChanged, sAgentBase->d_ptr, &AgentBasePrivate::itemsTagsChanged); sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::ObserverV4::relationAdded(const Akonadi::Relation &relation) { Q_UNUSED(relation) if (sAgentBase) { // not implementation, let's disconnect the signal to enable optimization in Monitor QObject::disconnect(sAgentBase->changeRecorder(), &Monitor::relationAdded, sAgentBase->d_ptr, &AgentBasePrivate::relationAdded); sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::ObserverV4::relationRemoved(const Akonadi::Relation &relation) { Q_UNUSED(relation) if (sAgentBase) { // not implementation, let's disconnect the signal to enable optimization in Monitor QObject::disconnect(sAgentBase->changeRecorder(), &Monitor::relationRemoved, sAgentBase->d_ptr, &AgentBasePrivate::relationRemoved); sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::ObserverV4::itemsRelationsChanged(const Akonadi::Item::List &items, const Akonadi::Relation::List &addedRelations, const Akonadi::Relation::List &removedRelations) { Q_UNUSED(items) Q_UNUSED(addedRelations) Q_UNUSED(removedRelations) if (sAgentBase) { // not implementation, let's disconnect the signal to enable optimization in Monitor QObject::disconnect(sAgentBase->changeRecorder(), SIGNAL(itemsRelationsChanged(Akonadi::Item::List,Akonadi::Relation::List,Akonadi::Relation::List)), sAgentBase, SLOT(itemsRelationsChanged(Akonadi::Item::List,Akonadi::Relation::List,Akonadi::Relation::List))); sAgentBase->d_ptr->changeProcessed(); } } //@cond PRIVATE AgentBasePrivate::AgentBasePrivate(AgentBase *parent) : q_ptr(parent) , mStatusCode(AgentBase::Idle) , mProgress(0) , mNeedsNetwork(false) , mOnline(false) , mDesiredOnlineState(false) , mSettings(nullptr) , mChangeRecorder(nullptr) , mTracer(nullptr) , mObserver(nullptr) , mPowerInterface(nullptr) , mTemporaryOfflineTimer(nullptr) , mEventLoopLocker(nullptr) , mNetworkManager(nullptr) { Internal::setClientType(Internal::Agent); } AgentBasePrivate::~AgentBasePrivate() { mChangeRecorder->setConfig(nullptr); delete mSettings; } void AgentBasePrivate::init() { Q_Q(AgentBase); Kdelibs4ConfigMigrator migrate(mId); migrate.setConfigFiles(QStringList() << QStringLiteral("%1rc").arg(mId)); migrate.migrate(); /** * Create a default session for this process. */ SessionPrivate::createDefaultSession(mId.toLatin1()); mTracer = new org::freedesktop::Akonadi::Tracer(ServerManager::serviceName(ServerManager::Server), QStringLiteral("/tracing"), KDBusConnectionPool::threadConnection(), q); new Akonadi__ControlAdaptor(q); new Akonadi__StatusAdaptor(q); if (!KDBusConnectionPool::threadConnection().registerObject(QStringLiteral("/"), q, QDBusConnection::ExportAdaptors)) { q->error(i18n("Unable to register object at dbus: %1", KDBusConnectionPool::threadConnection().lastError().message())); } mSettings = new QSettings(ServerManager::agentConfigFilePath(mId), QSettings::IniFormat); mChangeRecorder = new ChangeRecorder(q); mChangeRecorder->setObjectName(QStringLiteral("AgentBaseChangeRecorder")); mChangeRecorder->ignoreSession(Session::defaultSession()); mChangeRecorder->itemFetchScope().setCacheOnly(true); mChangeRecorder->setConfig(mSettings); mDesiredOnlineState = mSettings->value(QStringLiteral("Agent/DesiredOnlineState"), true).toBool(); mOnline = mDesiredOnlineState; // reinitialize the status message now that online state is available mStatusMessage = defaultReadyMessage(); mName = mSettings->value(QStringLiteral("Agent/Name")).toString(); if (mName.isEmpty()) { mName = mSettings->value(QStringLiteral("Resource/Name")).toString(); if (!mName.isEmpty()) { mSettings->remove(QStringLiteral("Resource/Name")); mSettings->setValue(QStringLiteral("Agent/Name"), mName); } } connect(mChangeRecorder, &Monitor::itemAdded, this, &AgentBasePrivate::itemAdded); connect(mChangeRecorder, &Monitor::itemChanged, this, &AgentBasePrivate::itemChanged); connect(mChangeRecorder, &Monitor::collectionAdded, this, &AgentBasePrivate::collectionAdded); connect(mChangeRecorder, SIGNAL(collectionChanged(Akonadi::Collection)), SLOT(collectionChanged(Akonadi::Collection))); connect(mChangeRecorder, SIGNAL(collectionChanged(Akonadi::Collection,QSet)), SLOT(collectionChanged(Akonadi::Collection,QSet))); connect(mChangeRecorder, &Monitor::collectionMoved, this, &AgentBasePrivate::collectionMoved); connect(mChangeRecorder, &Monitor::collectionRemoved, this, &AgentBasePrivate::collectionRemoved); connect(mChangeRecorder, &Monitor::collectionSubscribed, this, &AgentBasePrivate::collectionSubscribed); connect(mChangeRecorder, &Monitor::collectionUnsubscribed, this, &AgentBasePrivate::collectionUnsubscribed); connect(q, SIGNAL(status(int,QString)), q, SLOT(slotStatus(int,QString))); connect(q, &AgentBase::percent, q, [this](int value) { slotPercent(value); }); connect(q, &AgentBase::warning, q, [this](const QString &str) { slotWarning(str); }); connect(q, &AgentBase::error, q, [this](const QString &str) { slotError(str); }); mPowerInterface = new QDBusInterface(QStringLiteral("org.kde.Solid.PowerManagement"), QStringLiteral("/org/kde/Solid/PowerManagement/Actions/SuspendSession"), QStringLiteral("org.kde.Solid.PowerManagement.Actions.SuspendSession"), QDBusConnection::sessionBus(), this); if (mPowerInterface->isValid()) { connect(mPowerInterface, SIGNAL(resumingFromSuspend()), q, SLOT(slotResumedFromSuspend())); } else { delete mPowerInterface; mPowerInterface = nullptr; } // Use reference counting to allow agents to finish internal jobs when the // agent is stopped. mEventLoopLocker = new QEventLoopLocker(); mResourceTypeName = AgentManager::self()->instance(mId).type().name(); setProgramName(); QTimer::singleShot(0, q, [this]{ delayedInit();}); } void AgentBasePrivate::delayedInit() { Q_Q(AgentBase); const QString serviceId = ServerManager::agentServiceName(ServerManager::Agent, mId); if (!KDBusConnectionPool::threadConnection().registerService(serviceId)) { qCCritical(AKONADIAGENTBASE_LOG) << "Unable to register service" << serviceId << "at dbus:" << KDBusConnectionPool::threadConnection().lastError().message(); } q->setOnlineInternal(mDesiredOnlineState); KDBusConnectionPool::threadConnection().registerObject(QStringLiteral("/Debug"), this, QDBusConnection::ExportScriptableSlots); } void AgentBasePrivate::setProgramName() { // ugly, really ugly, if you find another solution, change it and blame me for this code (Andras) QString programName = mResourceTypeName; if (!mName.isEmpty()) { programName = i18nc("Name and type of Akonadi resource", "%1 of type %2", mName, mResourceTypeName); } QGuiApplication::setApplicationDisplayName(programName); } void AgentBasePrivate::itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection) { if (mObserver) { mObserver->itemAdded(item, collection); } else { changeProcessed(); } } void AgentBasePrivate::itemChanged(const Akonadi::Item &item, const QSet &partIdentifiers) { if (mObserver) { mObserver->itemChanged(item, partIdentifiers); } else { changeProcessed(); } } void AgentBasePrivate::itemMoved(const Akonadi::Item &item, const Akonadi::Collection &source, const Akonadi::Collection &dest) { AgentBase::ObserverV2 *observer2 = dynamic_cast(mObserver); if (mObserver) { // inter-resource moves, requires we know which resources the source and destination are in though if (!source.resource().isEmpty() && !dest.resource().isEmpty()) { if (source.resource() != dest.resource()) { if (source.resource() == q_ptr->identifier()) { // moved away from us Akonadi::Item i(item); i.setParentCollection(source); mObserver->itemRemoved(i); } else if (dest.resource() == q_ptr->identifier()) { // moved to us mObserver->itemAdded(item, dest); } else if (observer2) { observer2->itemMoved(item, source, dest); } else { // not for us, not sure if we should get here at all changeProcessed(); } return; } } // intra-resource move if (observer2) { observer2->itemMoved(item, source, dest); } else { // ### we cannot just call itemRemoved here as this will already trigger changeProcessed() // so, just itemAdded() is good enough as no resource can have implemented intra-resource moves anyway // without using ObserverV2 mObserver->itemAdded(item, dest); // mObserver->itemRemoved( item ); } } } void AgentBasePrivate::itemRemoved(const Akonadi::Item &item) { if (mObserver) { mObserver->itemRemoved(item); } else { changeProcessed(); } } void AgentBasePrivate::itemLinked(const Akonadi::Item &item, const Akonadi::Collection &collection) { AgentBase::ObserverV2 *observer2 = dynamic_cast(mObserver); if (observer2) { observer2->itemLinked(item, collection); } else { changeProcessed(); } } void AgentBasePrivate::itemUnlinked(const Akonadi::Item &item, const Akonadi::Collection &collection) { AgentBase::ObserverV2 *observer2 = dynamic_cast(mObserver); if (observer2) { observer2->itemUnlinked(item, collection); } else { changeProcessed(); } } void AgentBasePrivate::itemsFlagsChanged(const Akonadi::Item::List &items, const QSet &addedFlags, const QSet &removedFlags) { AgentBase::ObserverV3 *observer3 = dynamic_cast(mObserver); if (observer3) { observer3->itemsFlagsChanged(items, addedFlags, removedFlags); } else { Q_ASSERT_X(false, Q_FUNC_INFO, "Batch slots must never be called when ObserverV3 is not available"); } } void AgentBasePrivate::itemsMoved(const Akonadi::Item::List &items, const Akonadi::Collection &source, const Akonadi::Collection &destination) { AgentBase::ObserverV3 *observer3 = dynamic_cast(mObserver); if (observer3) { observer3->itemsMoved(items, source, destination); } else { Q_ASSERT_X(false, Q_FUNC_INFO, "Batch slots must never be called when ObserverV3 is not available"); } } void AgentBasePrivate::itemsRemoved(const Akonadi::Item::List &items) { AgentBase::ObserverV3 *observer3 = dynamic_cast(mObserver); if (observer3) { observer3->itemsRemoved(items); } else { Q_ASSERT_X(false, Q_FUNC_INFO, "Batch slots must never be called when ObserverV3 is not available"); } } void AgentBasePrivate::itemsLinked(const Akonadi::Item::List &items, const Akonadi::Collection &collection) { if (!mObserver) { changeProcessed(); return; } AgentBase::ObserverV3 *observer3 = dynamic_cast(mObserver); if (observer3) { observer3->itemsLinked(items, collection); } else { Q_ASSERT_X(false, Q_FUNC_INFO, "Batch slots must never be called when ObserverV3 is not available"); } } void AgentBasePrivate::itemsUnlinked(const Akonadi::Item::List &items, const Akonadi::Collection &collection) { if (!mObserver) { changeProcessed(); return; } AgentBase::ObserverV3 *observer3 = dynamic_cast(mObserver); if (observer3) { observer3->itemsUnlinked(items, collection); } else { Q_ASSERT_X(false, Q_FUNC_INFO, "Batch slots must never be called when ObserverV3 is not available"); } } void AgentBasePrivate::tagAdded(const Akonadi::Tag &tag) { AgentBase::ObserverV4 *observer4 = dynamic_cast(mObserver); if (observer4) { observer4->tagAdded(tag); } else { changeProcessed(); } } void AgentBasePrivate::tagChanged(const Akonadi::Tag &tag) { AgentBase::ObserverV4 *observer4 = dynamic_cast(mObserver); if (observer4) { observer4->tagChanged(tag); } else { changeProcessed(); } } void AgentBasePrivate::tagRemoved(const Akonadi::Tag &tag) { AgentBase::ObserverV4 *observer4 = dynamic_cast(mObserver); if (observer4) { observer4->tagRemoved(tag); } else { changeProcessed(); } } void AgentBasePrivate::itemsTagsChanged(const Akonadi::Item::List &items, const QSet &addedTags, const QSet &removedTags) { AgentBase::ObserverV4 *observer4 = dynamic_cast(mObserver); if (observer4) { observer4->itemsTagsChanged(items, addedTags, removedTags); } else { changeProcessed(); } } void AgentBasePrivate::relationAdded(const Akonadi::Relation &relation) { AgentBase::ObserverV4 *observer4 = dynamic_cast(mObserver); if (observer4) { observer4->relationAdded(relation); } else { changeProcessed(); } } void AgentBasePrivate::relationRemoved(const Akonadi::Relation &relation) { AgentBase::ObserverV4 *observer4 = dynamic_cast(mObserver); if (observer4) { observer4->relationRemoved(relation); } else { changeProcessed(); } } void AgentBasePrivate::itemsRelationsChanged(const Akonadi::Item::List &items, const Akonadi::Relation::List &addedRelations, const Akonadi::Relation::List &removedRelations) { AgentBase::ObserverV4 *observer4 = dynamic_cast(mObserver); if (observer4) { observer4->itemsRelationsChanged(items, addedRelations, removedRelations); } else { changeProcessed(); } } void AgentBasePrivate::collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent) { if (mObserver) { mObserver->collectionAdded(collection, parent); } else { changeProcessed(); } } void AgentBasePrivate::collectionChanged(const Akonadi::Collection &collection) { AgentBase::ObserverV2 *observer2 = dynamic_cast(mObserver); if (mObserver && observer2 == nullptr) { // For ObserverV2 we use the variant with the part identifiers mObserver->collectionChanged(collection); } else { changeProcessed(); } } void AgentBasePrivate::collectionChanged(const Akonadi::Collection &collection, const QSet &changedAttributes) { AgentBase::ObserverV2 *observer2 = dynamic_cast(mObserver); if (observer2) { observer2->collectionChanged(collection, changedAttributes); } else { changeProcessed(); } } void AgentBasePrivate::collectionMoved(const Akonadi::Collection &collection, const Akonadi::Collection &source, const Akonadi::Collection &dest) { AgentBase::ObserverV2 *observer2 = dynamic_cast(mObserver); if (observer2) { observer2->collectionMoved(collection, source, dest); } else if (mObserver) { // ### we cannot just call collectionRemoved here as this will already trigger changeProcessed() // so, just collectionAdded() is good enough as no resource can have implemented intra-resource moves anyway // without using ObserverV2 mObserver->collectionAdded(collection, dest); } else { changeProcessed(); } } void AgentBasePrivate::collectionRemoved(const Akonadi::Collection &collection) { if (mObserver) { mObserver->collectionRemoved(collection); } else { changeProcessed(); } } void AgentBasePrivate::collectionSubscribed(const Akonadi::Collection &collection, const Akonadi::Collection &parent) { Q_UNUSED(collection); Q_UNUSED(parent); changeProcessed(); } void AgentBasePrivate::collectionUnsubscribed(const Akonadi::Collection &collection) { Q_UNUSED(collection); changeProcessed(); } void AgentBasePrivate::changeProcessed() { mChangeRecorder->changeProcessed(); QTimer::singleShot(0, mChangeRecorder, &ChangeRecorder::replayNext); } void AgentBasePrivate::slotStatus(int status, const QString &message) { mStatusMessage = message; mStatusCode = 0; switch (status) { case AgentBase::Idle: if (mStatusMessage.isEmpty()) { mStatusMessage = defaultReadyMessage(); } mStatusCode = 0; break; case AgentBase::Running: if (mStatusMessage.isEmpty()) { mStatusMessage = defaultSyncingMessage(); } mStatusCode = 1; break; case AgentBase::Broken: if (mStatusMessage.isEmpty()) { mStatusMessage = defaultErrorMessage(); } mStatusCode = 2; break; case AgentBase::NotConfigured: if (mStatusMessage.isEmpty()) { mStatusMessage = defaultUnconfiguredMessage(); } mStatusCode = 3; break; default: Q_ASSERT(!"Unknown status passed"); break; } } void AgentBasePrivate::slotPercent(int progress) { mProgress = progress; } void AgentBasePrivate::slotWarning(const QString &message) { mTracer->warning(QStringLiteral("AgentBase(%1)").arg(mId), message); } void AgentBasePrivate::slotError(const QString &message) { mTracer->error(QStringLiteral("AgentBase(%1)").arg(mId), message); } void AgentBasePrivate::slotNetworkStatusChange(bool isOnline) { Q_UNUSED(isOnline); Q_Q(AgentBase); q->setOnlineInternal(mDesiredOnlineState); } void AgentBasePrivate::slotResumedFromSuspend() { if (mNeedsNetwork) { slotNetworkStatusChange(mNetworkManager->isOnline()); } } void AgentBasePrivate::slotTemporaryOfflineTimeout() { Q_Q(AgentBase); q->setOnlineInternal(true); } QString AgentBasePrivate::dumpNotificationListToString() const { return mChangeRecorder->dumpNotificationListToString(); } void AgentBasePrivate::dumpMemoryInfo() const { // Send it to stdout, so we can debug user problems. // since you have to explicitly call this // it won't flood users with release builds. QTextStream stream(stdout); stream << dumpMemoryInfoToString(); } QString AgentBasePrivate::dumpMemoryInfoToString() const { // man mallinfo for more info QString str; #if defined __GLIBC__ struct mallinfo mi; mi = mallinfo(); QTextStream stream(&str); stream << "Total non-mmapped bytes (arena): " << mi.arena << '\n' << "# of free chunks (ordblks): " << mi.ordblks << '\n' << "# of free fastbin blocks (smblks>: " << mi.smblks << '\n' << "# of mapped regions (hblks): " << mi.hblks << '\n' << "Bytes in mapped regions (hblkhd): " << mi.hblkhd << '\n' << "Max. total allocated space (usmblks): " << mi.usmblks << '\n' << "Free bytes held in fastbins (fsmblks):" << mi.fsmblks << '\n' << "Total allocated space (uordblks): " << mi.uordblks << '\n' << "Total free space (fordblks): " << mi.fordblks << '\n' << "Topmost releasable block (keepcost): " << mi.keepcost << '\n'; #else str = QLatin1String("mallinfo() not supported"); #endif return str; } AgentBase::AgentBase(const QString &id) : d_ptr(new AgentBasePrivate(this)) { sAgentBase = this; d_ptr->mId = id; d_ptr->init(); } AgentBase::AgentBase(AgentBasePrivate *d, const QString &id) : d_ptr(d) { sAgentBase = this; d_ptr->mId = id; d_ptr->init(); } AgentBase::~AgentBase() { delete d_ptr; } void AgentBase::debugAgent(int argc, char **argv) { Q_UNUSED(argc); #ifdef Q_OS_WIN if (qEnvironmentVariableIsSet("AKONADI_DEBUG_WAIT")) { if (QByteArray(argv[0]).endsWith(qgetenv("AKONADI_DEBUG_WAIT") + ".exe")) { while (!IsDebuggerPresent()) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } DebugBreak(); } } #else Q_UNUSED(argv); #endif } QString AgentBase::parseArguments(int argc, char **argv) { Q_UNUSED(argc); QCommandLineOption identifierOption(QStringLiteral("identifier"), i18n("Agent identifier"), QStringLiteral("argument")); QCommandLineParser parser; parser.addOption(identifierOption); parser.addHelpOption(); parser.addVersionOption(); parser.process(*qApp); parser.setApplicationDescription(i18n("Akonadi Agent")); if (!parser.isSet(identifierOption)) { qCDebug(AKONADIAGENTBASE_LOG) << "Identifier argument missing"; exit(1); } const QString identifier = parser.value(identifierOption); if (identifier.isEmpty()) { qCDebug(AKONADIAGENTBASE_LOG) << "Identifier argument is empty"; exit(1); } QCoreApplication::setApplicationName(ServerManager::addNamespace(identifier)); QCoreApplication::setApplicationVersion(QStringLiteral(AKONADI_VERSION_STRING)); const QFileInfo fi(QString::fromLocal8Bit(argv[0])); // strip off full path and possible .exe suffix const QString catalog = fi.baseName(); QTranslator *translator = new QTranslator(); translator->load(catalog); QCoreApplication::installTranslator(translator); return identifier; } // @endcond int AgentBase::init(AgentBase *r) { KLocalizedString::setApplicationDomain("libakonadi5"); int rv = qApp->exec(); delete r; return rv; } int AgentBase::status() const { Q_D(const AgentBase); return d->mStatusCode; } QString AgentBase::statusMessage() const { Q_D(const AgentBase); return d->mStatusMessage; } int AgentBase::progress() const { Q_D(const AgentBase); return d->mProgress; } QString AgentBase::progressMessage() const { Q_D(const AgentBase); return d->mProgressMessage; } bool AgentBase::isOnline() const { Q_D(const AgentBase); return d->mOnline; } void AgentBase::setNeedsNetwork(bool needsNetwork) { Q_D(AgentBase); if (d->mNeedsNetwork == needsNetwork) { return; } d->mNeedsNetwork = needsNetwork; if (d->mNeedsNetwork) { d->mNetworkManager = new QNetworkConfigurationManager(this); connect(d->mNetworkManager, SIGNAL(onlineStateChanged(bool)), this, SLOT(slotNetworkStatusChange(bool)), Qt::UniqueConnection); } else { delete d->mNetworkManager; d->mNetworkManager = nullptr; setOnlineInternal(d->mDesiredOnlineState); } } void AgentBase::setOnline(bool state) { Q_D(AgentBase); d->mDesiredOnlineState = state; d->mSettings->setValue(QStringLiteral("Agent/DesiredOnlineState"), state); setOnlineInternal(state); } void AgentBase::setTemporaryOffline(int makeOnlineInSeconds) { Q_D(AgentBase); // if not currently online, avoid bringing it online after the timeout if (!d->mOnline) { return; } setOnlineInternal(false); if (!d->mTemporaryOfflineTimer) { d->mTemporaryOfflineTimer = new QTimer(d); d->mTemporaryOfflineTimer->setSingleShot(true); connect(d->mTemporaryOfflineTimer, &QTimer::timeout, this, [this]() { d_ptr->slotTemporaryOfflineTimeout(); }); } d->mTemporaryOfflineTimer->setInterval(makeOnlineInSeconds * 1000); d->mTemporaryOfflineTimer->start(); } void AgentBase::setOnlineInternal(bool state) { Q_D(AgentBase); if (state && d->mNeedsNetwork) { if (!d->mNetworkManager->isOnline()) { //Don't go online if the resource needs network but there is none state = false; } } d->mOnline = state; if (d->mTemporaryOfflineTimer) { d->mTemporaryOfflineTimer->stop(); } const QString newMessage = d->defaultReadyMessage(); if (d->mStatusMessage != newMessage && d->mStatusCode != AgentBase::Broken) { - emit status(d->mStatusCode, newMessage); + Q_EMIT status(d->mStatusCode, newMessage); } doSetOnline(state); - emit onlineChanged(state); + Q_EMIT onlineChanged(state); } void AgentBase::doSetOnline(bool online) { Q_UNUSED(online); } void AgentBase::configure(WId windowId) { Q_UNUSED(windowId); // Fallback if the agent implements the new plugin-based configuration, // but someone calls the deprecated configure() method auto instance = Akonadi::AgentManager::self()->instance(identifier()); QPointer dialog = new AgentConfigurationDialog(instance, nullptr); if (dialog->exec()) { Q_EMIT configurationDialogAccepted(); } else { Q_EMIT configurationDialogRejected(); } delete dialog; } #ifdef Q_OS_WIN //krazy:exclude=cpp void AgentBase::configure(qlonglong windowId) { configure(static_cast(windowId)); } #endif WId AgentBase::winIdForDialogs() const { const bool registered = KDBusConnectionPool::threadConnection().interface()->isServiceRegistered(QStringLiteral("org.freedesktop.akonaditray")); if (!registered) { return 0; } QDBusInterface dbus(QStringLiteral("org.freedesktop.akonaditray"), QStringLiteral("/Actions"), QStringLiteral("org.freedesktop.Akonadi.Tray")); const QDBusMessage reply = dbus.call(QStringLiteral("getWinId")); if (reply.type() == QDBusMessage::ErrorMessage) { return 0; } const WId winid = (WId)reply.arguments().at(0).toLongLong(); return winid; } void AgentBase::quit() { Q_D(AgentBase); aboutToQuit(); if (d->mSettings) { d->mChangeRecorder->setConfig(nullptr); d->mSettings->sync(); delete d->mSettings; d->mSettings = nullptr; } delete d->mEventLoopLocker; d->mEventLoopLocker = nullptr; } void AgentBase::aboutToQuit() { } void AgentBase::cleanup() { Q_D(AgentBase); // prevent the monitor from picking up deletion signals for our own data if we are a resource // and thus avoid that we kill our own data as last act before our own death d->mChangeRecorder->blockSignals(true); aboutToQuit(); const QString fileName = d->mSettings->fileName(); /* * First destroy the settings object... */ d->mChangeRecorder->setConfig(nullptr); delete d->mSettings; d->mSettings = nullptr; /* * ... then remove the file from hd. */ if (!QFile::remove(fileName)) { qCWarning(AKONADIAGENTBASE_LOG) << "Impossible to remove " << fileName; } /* * ... and remove the changes file from hd. */ const QString changeDataFileName = fileName + QStringLiteral("_changes.dat"); if (!QFile::remove(changeDataFileName)) { qCWarning(AKONADIAGENTBASE_LOG) << "Impossible to remove " << changeDataFileName; } /* * ... and also remove the agent configuration file if there is one. */ const QString configFile = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QLatin1Char('/') + config()->name(); if (!QFile::remove(configFile)) { qCWarning(AKONADIAGENTBASE_LOG) << "Impossible to remove config file " << configFile; } delete d->mEventLoopLocker; d->mEventLoopLocker = nullptr; } void AgentBase::registerObserver(Observer *observer) { // TODO in theory we should re-connect change recorder signals here that we disconnected previously d_ptr->mObserver = observer; const bool hasObserverV3 = (dynamic_cast(d_ptr->mObserver) != nullptr); const bool hasObserverV4 = (dynamic_cast(d_ptr->mObserver) != nullptr); disconnect(d_ptr->mChangeRecorder, &Monitor::tagAdded, d_ptr, &AgentBasePrivate::tagAdded); disconnect(d_ptr->mChangeRecorder, &Monitor::tagChanged, d_ptr, &AgentBasePrivate::tagChanged); disconnect(d_ptr->mChangeRecorder, &Monitor::tagRemoved, d_ptr, &AgentBasePrivate::tagRemoved); disconnect(d_ptr->mChangeRecorder, &Monitor::itemsTagsChanged, d_ptr, &AgentBasePrivate::itemsTagsChanged); disconnect(d_ptr->mChangeRecorder, &Monitor::itemsFlagsChanged, d_ptr, &AgentBasePrivate::itemsFlagsChanged); disconnect(d_ptr->mChangeRecorder, &Monitor::itemsMoved, d_ptr, &AgentBasePrivate::itemsMoved); disconnect(d_ptr->mChangeRecorder, &Monitor::itemsRemoved, d_ptr, &AgentBasePrivate::itemsRemoved); disconnect(d_ptr->mChangeRecorder, &Monitor::itemsLinked, d_ptr, &AgentBasePrivate::itemsLinked); disconnect(d_ptr->mChangeRecorder, &Monitor::itemsUnlinked, d_ptr, &AgentBasePrivate::itemsUnlinked); disconnect(d_ptr->mChangeRecorder, &Monitor::itemMoved, d_ptr, &AgentBasePrivate::itemMoved); disconnect(d_ptr->mChangeRecorder, &Monitor::itemRemoved, d_ptr, &AgentBasePrivate::itemRemoved); disconnect(d_ptr->mChangeRecorder, &Monitor::itemLinked, d_ptr, &AgentBasePrivate::itemLinked); disconnect(d_ptr->mChangeRecorder, &Monitor::itemUnlinked, d_ptr, &AgentBasePrivate::itemUnlinked); if (hasObserverV4) { connect(d_ptr->mChangeRecorder, &Monitor::tagAdded, d_ptr, &AgentBasePrivate::tagAdded); connect(d_ptr->mChangeRecorder, &Monitor::tagChanged, d_ptr, &AgentBasePrivate::tagChanged); connect(d_ptr->mChangeRecorder, &Monitor::tagRemoved, d_ptr, &AgentBasePrivate::tagRemoved); connect(d_ptr->mChangeRecorder, &Monitor::itemsTagsChanged, d_ptr, &AgentBasePrivate::itemsTagsChanged); } if (hasObserverV3) { connect(d_ptr->mChangeRecorder, &Monitor::itemsFlagsChanged, d_ptr, &AgentBasePrivate::itemsFlagsChanged); connect(d_ptr->mChangeRecorder, &Monitor::itemsMoved, d_ptr, &AgentBasePrivate::itemsMoved); connect(d_ptr->mChangeRecorder, &Monitor::itemsRemoved, d_ptr, &AgentBasePrivate::itemsRemoved); connect(d_ptr->mChangeRecorder, &Monitor::itemsLinked, d_ptr, &AgentBasePrivate::itemsLinked); connect(d_ptr->mChangeRecorder, &Monitor::itemsUnlinked, d_ptr, &AgentBasePrivate::itemsUnlinked); } else { // V2 - don't connect these if we have V3 connect(d_ptr->mChangeRecorder, &Monitor::itemMoved, d_ptr, &AgentBasePrivate::itemMoved); connect(d_ptr->mChangeRecorder, &Monitor::itemRemoved, d_ptr, &AgentBasePrivate::itemRemoved); connect(d_ptr->mChangeRecorder, &Monitor::itemLinked, d_ptr, &AgentBasePrivate::itemLinked); connect(d_ptr->mChangeRecorder, &Monitor::itemUnlinked, d_ptr, &AgentBasePrivate::itemUnlinked); } } QString AgentBase::identifier() const { return d_ptr->mId; } void AgentBase::setAgentName(const QString &name) { Q_D(AgentBase); if (name == d->mName) { return; } // TODO: rename collection d->mName = name; if (d->mName.isEmpty() || d->mName == d->mId) { d->mSettings->remove(QStringLiteral("Resource/Name")); d->mSettings->remove(QStringLiteral("Agent/Name")); } else { d->mSettings->setValue(QStringLiteral("Agent/Name"), d->mName); } d->mSettings->sync(); d->setProgramName(); - emit agentNameChanged(d->mName); + Q_EMIT agentNameChanged(d->mName); } QString AgentBase::agentName() const { Q_D(const AgentBase); if (d->mName.isEmpty()) { return d->mId; } else { return d->mName; } } void AgentBase::changeProcessed() { Q_D(AgentBase); d->changeProcessed(); } ChangeRecorder *AgentBase::changeRecorder() const { return d_ptr->mChangeRecorder; } KSharedConfigPtr AgentBase::config() { return KSharedConfig::openConfig(); } void AgentBase::abort() { - emit abortRequested(); + Q_EMIT abortRequested(); } void AgentBase::reconfigure() { - emit reloadConfiguration(); + Q_EMIT reloadConfiguration(); } #include "moc_agentbase.cpp" #include "moc_agentbase_p.cpp" diff --git a/src/agentbase/agentbase.h b/src/agentbase/agentbase.h index 2ae2b7410..45c8036f3 100644 --- a/src/agentbase/agentbase.h +++ b/src/agentbase/agentbase.h @@ -1,814 +1,814 @@ /* This file is part of akonadiresources. Copyright (c) 2006 Till Adam Copyright (c) 2007 Volker Krause Copyright (c) 2008 Kevin Krammer 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. */ #ifndef AKONADI_AGENTBASE_H #define AKONADI_AGENTBASE_H #include "akonadiagentbase_export.h" #include "item.h" #include #include #include #include class Akonadi__ControlAdaptor; class Akonadi__StatusAdaptor; namespace Akonadi { class AgentBasePrivate; class ChangeRecorder; class Collection; class Item; /** * @short The base class for all Akonadi agents and resources. * * This class is a base class for all Akonadi agents, which covers the real * agent processes and all resources. * * It provides: * - lifetime management * - change monitoring and recording * - configuration interface * - problem reporting * * Akonadi Server supports several ways to launch agents and resources: * - As a separate application (@see AKONADI_AGENT_MAIN) * - As a thread in the AgentServer * - As a separate process, using the akonadi_agent_launcher * * The idea is this, the agent or resource is written as a plugin instead of an * executable (@see AgentFactory). In the AgentServer case, the AgentServer * looks up the plugin and launches the agent in a separate thread. In the * launcher case, a new akonadi_agent_launcher process is started for each * agent or resource instance. * * When making an Agent or Resource suitable for running in the AgentServer some * extra caution is needed. Because multiple instances of several kinds of agents * run in the same process, one cannot blindly use global objects like KGlobal. * For this reasons several methods where added to avoid problems in this context, * most notably AgentBase::config(). Additionally, * one cannot use QDBusConnection::sessionBus() with dbus < 1.4, because of a * multithreading bug in libdbus. Instead one should use * KDBusConnectionPool::threadConnection() which works around this problem. * * @author Till Adam , Volker Krause */ class AKONADIAGENTBASE_EXPORT AgentBase : public QObject, protected QDBusContext { Q_OBJECT public: /** * @short The interface for reacting on monitored or replayed changes. * * The Observer provides an interface to react on monitored or replayed changes. * * Since the this base class does only tell the change recorder that the change * has been processed, an AgentBase subclass which wants to actually process * the change needs to subclass Observer and reimplement the methods it is * interested in. * * Such an agent specific Observer implementation can either be done * stand-alone, i.e. as a separate object, or by inheriting both AgentBase * and AgentBase::Observer. * * The observer implementation then has registered with the agent, so it * can forward the incoming changes to the observer. * * @note In the multiple inheritance approach the init() method automatically * registers itself as the observer. * * @note Do not call the base implementation of reimplemented virtual methods! * The default implementation disconnected themselves from the Akonadi::ChangeRecorder * to enable internal optimizations for unused notifications. * * Example for stand-alone observer: * @code * class ExampleAgent : public AgentBase * { * public: * ExampleAgent( const QString &id ); * * ~ExampleAgent(); * * private: * AgentBase::Observer *mObserver; * }; * * class ExampleObserver : public AgentBase::Observer * { * protected: * void itemChanged( const Item &item ); * }; * * ExampleAgent::ExampleAgent( const QString &id ) : AgentBase( id ) , mObserver( 0 ) * { * mObserver = new ExampleObserver(); * registerObserver( mObserver ); * } * * ExampleAgent::~ExampleAgent() * { * delete mObserver; * } * * void ExampleObserver::itemChanged( const Item &item ) * { * // do something with item * qCDebug(AKONADIAGENTBASE_LOG) << "Item id=" << item.id(); * * // let base implementation tell the change recorder that we * // have processed the change * AgentBase::Observer::itemChanged( item ); * } * @endcode * * Example for observer through multiple inheritance: * @code * class ExampleAgent : public AgentBase, public AgentBase::Observer * { * public: * ExampleAgent( const QString &id ); * * protected: * void itemChanged( const Item &item ); * }; * * ExampleAgent::ExampleAgent( const QString &id ) : AgentBase( id ) * { * // no need to create or register observer since * // we are the observer and registration happens automatically * // in init() * } * * void ExampleAgent::itemChanged( const Item &item ) * { * // do something with item * qCDebug(AKONADIAGENTBASE_LOG) << "Item id=" << item.id(); * * // let base implementation tell the change recorder that we * // have processed the change * AgentBase::Observer::itemChanged( item ); * } * @endcode * * @author Kevin Krammer * * @deprecated Use ObserverV2 instead */ class AKONADIAGENTBASE_DEPRECATED AKONADIAGENTBASE_EXPORT Observer // krazy:exclude=dpointer { public: /** * Creates an observer instance. */ Observer(); /** * Destroys the observer instance. */ virtual ~Observer(); /** * Reimplement to handle adding of new items. * @param item The newly added item. * @param collection The collection @p item got added to. */ virtual void itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection); /** * Reimplement to handle changes to existing items. * @param item The changed item. * @param partIdentifiers The identifiers of the item parts that has been changed. */ virtual void itemChanged(const Akonadi::Item &item, const QSet &partIdentifiers); /** * Reimplement to handle deletion of items. * @param item The deleted item. */ virtual void itemRemoved(const Akonadi::Item &item); /** * Reimplement to handle adding of new collections. * @param collection The newly added collection. * @param parent The parent collection. */ virtual void collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent); /** * Reimplement to handle changes to existing collections. * @param collection The changed collection. */ virtual void collectionChanged(const Akonadi::Collection &collection); /** * Reimplement to handle deletion of collections. * @param collection The deleted collection. */ virtual void collectionRemoved(const Akonadi::Collection &collection); }; /** * BC extension of Observer with support for monitoring item and collection moves. * Use this one instead of Observer. * * @since 4.4 */ class AKONADIAGENTBASE_EXPORT ObserverV2 : public Observer // krazy:exclude=dpointer { public: using Observer::collectionChanged; /** * Reimplement to handle item moves. * When using this class in combination with Akonadi::ResourceBase, inter-resource * moves are handled internally already and the corresponding add or delete method * is called instead. * * @param item The moved item. * @param collectionSource The collection the item has been moved from. * @param collectionDestination The collection the item has been moved to. */ virtual void itemMoved(const Akonadi::Item &item, const Akonadi::Collection &collectionSource, const Akonadi::Collection &collectionDestination); /** * Reimplement to handle item linking. * This is only relevant for virtual resources. * @param item The linked item. * @param collection The collection the item is linked to. */ virtual void itemLinked(const Akonadi::Item &item, const Akonadi::Collection &collection); /** * Reimplement to handle item unlinking. * This is only relevant for virtual resources. * @param item The unlinked item. * @param collection The collection the item is unlinked from. */ virtual void itemUnlinked(const Akonadi::Item &item, const Akonadi::Collection &collection); /** * Reimplement to handle collection moves. * When using this class in combination with Akonadi::ResourceBase, inter-resource * moves are handled internally already and the corresponding add or delete method * is called instead. * * @param collection The moved collection. * @param collectionSource The previous parent collection. * @param collectionDestination The new parent collection. */ virtual void collectionMoved(const Akonadi::Collection &collection, const Akonadi::Collection &collectionSource, const Akonadi::Collection &collectionDestination); /** * Reimplement to handle changes to existing collections. * @param collection The changed collection. * @param changedAttributes The identifiers of the collection parts/attributes that has been changed. */ virtual void collectionChanged(const Akonadi::Collection &collection, const QSet &changedAttributes); }; /** * BC extension of ObserverV2 with support for batch operations * * @warning When using ObserverV3, you will never get single-item notifications * from AgentBase::Observer, even when you don't reimplement corresponding batch * method from ObserverV3. For instance, when you don't reimplement itemsRemoved() * here, you will not get any notifications about item removal whatsoever! * * @since 4.11 */ class AKONADIAGENTBASE_EXPORT ObserverV3 : public ObserverV2 // krazy:exclude=dpointer { public: /** * Reimplement to handle changes in flags of existing items * * @warning When using ObserverV3, you will never get notifications about * flag changes via Observer::itemChanged(), even when you don't reimplement * itemsFlagsChanged()! * * @param items The changed items * @param addedFlags Flags that have been added to the item * @param removedFlags Flags that have been removed from the item */ virtual void itemsFlagsChanged(const Akonadi::Item::List &items, const QSet &addedFlags, const QSet &removedFlags); /** * Reimplement to handle batch notification about items deletion. * * @param items List of deleted items */ virtual void itemsRemoved(const Akonadi::Item::List &items); /** * Reimplement to handle batch notification about items move * * @param items List of moved items * @param sourceCollection Collection from where the items were moved * @param destinationCollection Collection to which the items were moved */ virtual void itemsMoved(const Akonadi::Item::List &items, const Akonadi::Collection &sourceCollection, const Akonadi::Collection &destinationCollection); /** * Reimplement to handle batch notifications about items linking. * * @param items Linked items * @param collection Collection to which the items have been linked */ virtual void itemsLinked(const Akonadi::Item::List &items, const Akonadi::Collection &collection); /** * Reimplement to handle batch notifications about items unlinking. * * @param items Unlinked items * @param collection Collection from which the items have been unlinked */ virtual void itemsUnlinked(const Akonadi::Item::List &items, const Akonadi::Collection &collection); }; /** * Observer that adds support for item tagging * * @warning ObserverV4 subclasses ObserverV3 which changes behavior of some of the * virtual methods from Observer and ObserverV2. Please make sure you read * documentation of ObserverV3 and adapt your agent accordingly. * * @since 4.13 */ class AKONADIAGENTBASE_EXPORT ObserverV4 : public ObserverV3 // krazy:exclude=dpointer { public: /** * Reimplement to handle tags additions * * @param tag Newly added tag */ virtual void tagAdded(const Akonadi::Tag &tag); /** * Reimplement to handle tags changes * * @param tag Tag that has been changed */ virtual void tagChanged(const Akonadi::Tag &tag); /** * Reimplement to handle tags removal. * * @note All items that were tagged by @p tag will get a separate notification * about untagging via itemsTagsChanged(). It is guaranteed that the itemsTagsChanged() * notification will be delivered before this one. * * @param tag Tag that has been removed. */ virtual void tagRemoved(const Akonadi::Tag &tag); /** * Reimplement to handle items tagging * * @param items Items that were tagged or untagged * @param addedTags Set of tags that were added to all @p items * @param removedTags Set of tags that were removed from all @p items */ virtual void itemsTagsChanged(const Akonadi::Item::List &items, const QSet &addedTags, const QSet &removedTags); /** * Reimplement to handle relations being added */ virtual void relationAdded(const Akonadi::Relation &relation); /** * Reimplement to handle relations being removed */ virtual void relationRemoved(const Akonadi::Relation &relation); /** * Reimplement to handled relations changing on items * @param items Items that had relations added/removed from them * @param addedRelations the list of relations that were added to all @p items * @param removedRelations the list of relations that were removed from all @p items */ virtual void itemsRelationsChanged(const Akonadi::Item::List &items, const Akonadi::Relation::List &addedRelations, const Akonadi::Relation::List &removedRelations); }; /** * This enum describes the different states the * agent can be in. */ enum Status { Idle = 0, ///< The agent does currently nothing. Running, ///< The agent is working on something. Broken, ///< The agent encountered an error state. NotConfigured ///< The agent is lacking required configuration }; /** * Use this method in the main function of your agent * application to initialize your agent subclass. * This method also takes care of creating a KApplication * object and parsing command line arguments. * * @note In case the given class is also derived from AgentBase::Observer * it gets registered as its own observer (see AgentBase::Observer), e.g. * agentInstance->registerObserver( agentInstance ); * * @code * * class MyAgent : public AgentBase * { * ... * }; * * AKONADI_AGENT_MAIN( MyAgent ) * * @endcode * * @param argc number of arguments * @param argv arguments for the function */ template static int init(int argc, char **argv) { // Disable session management qunsetenv("SESSION_MANAGER"); QApplication app(argc, argv); debugAgent(argc, argv); const QString id = parseArguments(argc, argv); T *r = new T(id); // check if T also inherits AgentBase::Observer and // if it does, automatically register it on itself Observer *observer = dynamic_cast(r); if (observer != nullptr) { r->registerObserver(observer); } return init(r); } /** * This method returns the current status code of the agent. * * The following return values are possible: * * - 0 - Idle * - 1 - Running * - 2 - Broken * - 3 - NotConfigured */ Q_REQUIRED_RESULT virtual int status() const; /** * This method returns an i18n'ed description of the current status code. */ Q_REQUIRED_RESULT virtual QString statusMessage() const; /** * This method returns the current progress of the agent in percentage. */ Q_REQUIRED_RESULT virtual int progress() const; /** * This method returns an i18n'ed description of the current progress. */ Q_REQUIRED_RESULT virtual QString progressMessage() const; public Q_SLOTS: /** * This method is called whenever the agent shall show its configuration dialog * to the user. It will be automatically called when the agent is started for * the first time. * * @param windowId The parent window id. * * @note If the method is reimplemented it has to emit the configurationDialogAccepted() * or configurationDialogRejected() signals depending on the users choice. */ virtual void configure(WId windowId); public: /** * This method returns the windows id, which should be used for dialogs. */ Q_REQUIRED_RESULT WId winIdForDialogs() const; #ifdef Q_OS_WIN /** * Overload of @ref configure needed because WId cannot be automatically casted * to qlonglong on Windows. */ void configure(qlonglong windowId); #endif /** * Returns the instance identifier of this agent. */ Q_REQUIRED_RESULT QString identifier() const; /** * This method is called when the agent is removed from * the system, so it can do some cleanup stuff. * * @note If you reimplement this in a subclass make sure * to call this base implementation at the end. */ virtual void cleanup(); /** * Registers the given observer for reacting on monitored or recorded changes. * * @param observer The change handler to register. No ownership transfer, i.e. * the caller stays owner of the pointer and can reset * the registration by calling this method with @c 0 */ void registerObserver(Observer *observer); /** * This method is used to set the name of the agent. * * @since 4.3 * @param name name of the agent */ //FIXME_API: make sure location is renamed to this by agentbase void setAgentName(const QString &name); /** * Returns the name of the agent. * * @since 4.3 */ Q_REQUIRED_RESULT QString agentName() const; Q_SIGNALS: /** * This signal is emitted whenever the name of the agent has changed. * * @param name The new name of the agent. * * @since 4.3 */ void agentNameChanged(const QString &name); /** * This signal should be emitted whenever the status of the agent has been changed. * @param status The new Status code. * @param message A i18n'ed description of the new status. */ void status(int status, const QString &message = QString()); /** * This signal should be emitted whenever the progress of an action in the agent * (e.g. data transfer, connection establishment to remote server etc.) has changed. * * @param progress The progress of the action in percent. */ void percent(int progress); /** * This signal shall be used to report warnings. * * @param message The i18n'ed warning message. */ void warning(const QString &message); /** * This signal shall be used to report errors. * * @param message The i18n'ed error message. */ void error(const QString &message); /** * This signal should be emitted whenever the status of the agent has been changed. * @param status The object that describes the status change. * * @since 4.6 */ void advancedStatus(const QVariantMap &status); /** * Emitted when another application has remotely asked the agent to abort * its current operation. * Connect to this signal if your agent supports abortion. After aborting * and cleaning up, agents should return to Idle status. * * @since 4.4 */ void abortRequested(); /** * Emitted if another application has changed the agent's configuration remotely * and called AgentInstance::reconfigure(). * * @since 4.2 */ void reloadConfiguration(); /** * Emitted when the online state changed. * @param online The online state. * @since 4.2 */ void onlineChanged(bool online); /** * This signal is emitted whenever the user has accepted the configuration dialog. * * @note Implementors of agents/resources are responsible to emit this signal if * the agent/resource reimplements configure(). * * @since 4.4 */ void configurationDialogAccepted(); /** * This signal is emitted whenever the user has rejected the configuration dialog. * * @note Implementors of agents/resources are responsible to emit this signal if * the agent/resource reimplements configure(). * * @since 4.4 */ void configurationDialogRejected(); protected: /** * Creates an agent base. * * @param id The instance id of the agent. */ AgentBase(const QString &id); /** * Destroys the agent base. */ ~AgentBase(); /** * This method is called whenever the agent application is about to * quit. * * Reimplement this method to do session cleanup (e.g. disconnecting * from groupware server). */ virtual void aboutToQuit(); /** * Returns the Akonadi::ChangeRecorder object used for monitoring. * Use this to configure which parts you want to monitor. */ ChangeRecorder *changeRecorder() const; /** * Returns the config object for this Agent. */ KSharedConfigPtr config(); /** * Marks the current change as processes and replays the next change if change * recording is enabled (noop otherwise). This method is called * from the default implementation of the change notification slots. While not * required when not using change recording, it is nevertheless recommended * to call this method when done with processing a change notification. */ void changeProcessed(); /** * Returns whether the agent is currently online. */ bool isOnline() const; /** * Sets whether the agent needs network or not. * * @since 4.2 * @todo use this in combination with QNetworkConfiguration to change * the onLine status of the agent. * @param needsNetwork @c true if the agents needs network. Defaults to @c false */ void setNeedsNetwork(bool needsNetwork); /** * Sets whether the agent shall be online or not. */ void setOnline(bool state); protected: /** * Sets the agent offline but will make it online again after a given time * * Use this method when the agent detects some problem with its backend but it wants * to retry all pending operations after some time - e.g. a server can not be reached currently * * Example usage: * @code * void ExampleResource::onItemRemovedFinished(KJob *job) * { * if (job->error()) { - * emit status(Broken, job->errorString()); + * Q_EMIT status(Broken, job->errorString()); * deferTask(); * setTemporaryOffline(300); * return; * } * ... * } * @endcode * * @since 4.13 * @param makeOnlineInSeconds timeout in seconds after which the agent changes to online */ void setTemporaryOffline(int makeOnlineInSeconds = 300); //@cond PRIVATE static void debugAgent(int argc, char **argv); AgentBasePrivate *d_ptr; explicit AgentBase(AgentBasePrivate *d, const QString &id); friend class ObserverV2; //@endcond /** * This method is called whenever the @p online status has changed. * Reimplement this method to react on online status changes. * @param online online status */ virtual void doSetOnline(bool online); private: //@cond PRIVATE static QString parseArguments(int argc, char **argv); static int init(AgentBase *r); void setOnlineInternal(bool state); // D-Bus interface stuff void abort(); void reconfigure(); void quit(); // dbus agent interface friend class ::Akonadi__StatusAdaptor; friend class ::Akonadi__ControlAdaptor; Q_DECLARE_PRIVATE(AgentBase) Q_PRIVATE_SLOT(d_func(), void delayedInit()) Q_PRIVATE_SLOT(d_func(), void slotStatus(int, const QString &)) Q_PRIVATE_SLOT(d_func(), void slotPercent(int)) Q_PRIVATE_SLOT(d_func(), void slotWarning(const QString &)) Q_PRIVATE_SLOT(d_func(), void slotError(const QString &)) Q_PRIVATE_SLOT(d_func(), void slotNetworkStatusChange(bool)) Q_PRIVATE_SLOT(d_func(), void slotResumedFromSuspend()) Q_PRIVATE_SLOT(d_func(), void slotTemporaryOfflineTimeout()) //@endcond }; } #ifndef AKONADI_AGENT_MAIN /** * Convenience Macro for the most common main() function for Akonadi agents. */ #define AKONADI_AGENT_MAIN( agentClass ) \ int main( int argc, char **argv ) \ { \ return Akonadi::AgentBase::init( argc, argv ); \ } #endif #endif diff --git a/src/agentbase/preprocessorbase.cpp b/src/agentbase/preprocessorbase.cpp index 3d4589e15..73b273ed1 100644 --- a/src/agentbase/preprocessorbase.cpp +++ b/src/agentbase/preprocessorbase.cpp @@ -1,63 +1,63 @@ /****************************************************************************** * * Copyright (c) 2009 Szymon Stefanek * * 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 "preprocessorbase.h" #include "preprocessorbase_p.h" #include "akonadiagentbase_debug.h" using namespace Akonadi; PreprocessorBase::PreprocessorBase(const QString &id) : AgentBase(new PreprocessorBasePrivate(this), id) { } PreprocessorBase::~PreprocessorBase() { } void PreprocessorBase::finishProcessing(ProcessingResult result) { Q_D(PreprocessorBase); Q_ASSERT_X(result != ProcessingDelayed, "PreprocessorBase::terminateProcessing", "You should never pass ProcessingDelayed to this function"); Q_ASSERT_X(d->mInDelayedProcessing, "PreprocessorBase::terminateProcessing", "terminateProcessing() called while not in delayed processing mode"); Q_UNUSED(result); d->mInDelayedProcessing = false; - emit d->itemProcessed(d->mDelayedProcessingItemId); + Q_EMIT d->itemProcessed(d->mDelayedProcessingItemId); } void PreprocessorBase::setFetchScope(const ItemFetchScope &fetchScope) { Q_D(PreprocessorBase); d->mFetchScope = fetchScope; } ItemFetchScope &PreprocessorBase::fetchScope() { Q_D(PreprocessorBase); return d->mFetchScope; } diff --git a/src/agentbase/preprocessorbase_p.cpp b/src/agentbase/preprocessorbase_p.cpp index 5950ecb7b..68a75734a 100644 --- a/src/agentbase/preprocessorbase_p.cpp +++ b/src/agentbase/preprocessorbase_p.cpp @@ -1,102 +1,102 @@ /* Copyright (c) 2009 Tobias Koenig 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 "preprocessorbase_p.h" #include "preprocessorbase.h" #include "KDBusConnectionPool" #include "preprocessoradaptor.h" #include "servermanager.h" #include "itemfetchjob.h" #include "akonadiagentbase_debug.h" using namespace Akonadi; PreprocessorBasePrivate::PreprocessorBasePrivate(PreprocessorBase *parent) : AgentBasePrivate(parent) , mInDelayedProcessing(false) , mDelayedProcessingItemId(0) { Q_Q(PreprocessorBase); new Akonadi__PreprocessorAdaptor(this); if (!KDBusConnectionPool::threadConnection().registerObject(QStringLiteral("/Preprocessor"), this, QDBusConnection::ExportAdaptors)) { q->error(i18n("Unable to register object at dbus: %1", KDBusConnectionPool::threadConnection().lastError().message())); } } void PreprocessorBasePrivate::delayedInit() { if (!KDBusConnectionPool::threadConnection().registerService(ServerManager::agentServiceName(ServerManager::Preprocessor, mId))) { qCCritical(AKONADIAGENTBASE_LOG) << "Unable to register service at D-Bus: " << KDBusConnectionPool::threadConnection().lastError().message(); } AgentBasePrivate::delayedInit(); } void PreprocessorBasePrivate::beginProcessItem(qlonglong itemId, qlonglong collectionId, const QString &mimeType) { qCDebug(AKONADIAGENTBASE_LOG) << "PreprocessorBase: about to process item " << itemId << " in collection " << collectionId << " with mimeType " << mimeType; ItemFetchJob *fetchJob = new ItemFetchJob(Item(itemId), this); fetchJob->setFetchScope(mFetchScope); connect(fetchJob, &ItemFetchJob::result, this, &PreprocessorBasePrivate::itemFetched); } void PreprocessorBasePrivate::itemFetched(KJob *job) { Q_Q(PreprocessorBase); if (job->error()) { - emit itemProcessed(PreprocessorBase::ProcessingFailed); + Q_EMIT itemProcessed(PreprocessorBase::ProcessingFailed); return; } ItemFetchJob *fetchJob = qobject_cast(job); if (fetchJob->items().isEmpty()) { - emit itemProcessed(PreprocessorBase::ProcessingFailed); + Q_EMIT itemProcessed(PreprocessorBase::ProcessingFailed); return; } const Item item = fetchJob->items().at(0); switch (q->processItem(item)) { case PreprocessorBase::ProcessingFailed: case PreprocessorBase::ProcessingRefused: case PreprocessorBase::ProcessingCompleted: qCDebug(AKONADIAGENTBASE_LOG) << "PreprocessorBase: item processed, emitting signal (" << item.id() << ")"; // TODO: Handle the different status codes appropriately - emit itemProcessed(item.id()); + Q_EMIT itemProcessed(item.id()); qCDebug(AKONADIAGENTBASE_LOG) << "PreprocessorBase: item processed, signal emitted (" << item.id() << ")"; break; case PreprocessorBase::ProcessingDelayed: qCDebug(AKONADIAGENTBASE_LOG) << "PreprocessorBase: item processing delayed (" << item.id() << ")"; mInDelayedProcessing = true; mDelayedProcessingItemId = item.id(); break; } } diff --git a/src/agentbase/resourcebase.cpp b/src/agentbase/resourcebase.cpp index be2cc776a..03fd29f64 100644 --- a/src/agentbase/resourcebase.cpp +++ b/src/agentbase/resourcebase.cpp @@ -1,1652 +1,1652 @@ /* Copyright (c) 2006 Till Adam Copyright (c) 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 "resourcebase.h" #include "agentbase_p.h" #include "resourceadaptor.h" #include "collectiondeletejob.h" #include "collectionsync_p.h" #include "KDBusConnectionPool" #include "itemsync.h" #include "akonadi_version.h" #include "tagsync.h" #include "relationsync.h" #include "resourcescheduler_p.h" #include "tracerinterface.h" #include "changerecorder.h" #include "collectionfetchjob.h" #include "collectionfetchscope.h" #include "collectionmodifyjob.h" #include "invalidatecachejob_p.h" #include "itemfetchjob.h" #include "itemfetchscope.h" #include "itemmodifyjob.h" #include "itemmodifyjob_p.h" #include "itemcreatejob.h" #include "session.h" #include "resourceselectjob_p.h" #include "monitor_p.h" #include "servermanager_p.h" #include "recursivemover_p.h" #include "tagmodifyjob.h" #include "specialcollectionattribute.h" #include "favoritecollectionattribute.h" #include "akonadiagentbase_debug.h" #include #include #include #include using namespace Akonadi; class Akonadi::ResourceBasePrivate : public AgentBasePrivate { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.dfaure") public: ResourceBasePrivate(ResourceBase *parent) : AgentBasePrivate(parent) , scheduler(nullptr) , mItemSyncer(nullptr) , mItemSyncFetchScope(nullptr) , mItemTransactionMode(ItemSync::SingleTransaction) , mItemMergeMode(ItemSync::RIDMerge) , mCollectionSyncer(nullptr) , mTagSyncer(nullptr) , mRelationSyncer(nullptr) , mHierarchicalRid(false) , mUnemittedProgress(0) , mAutomaticProgressReporting(true) , mDisableAutomaticItemDeliveryDone(false) , mItemSyncBatchSize(10) , mCurrentCollectionFetchJob(nullptr) , mScheduleAttributeSyncBeforeCollectionSync(false) { Internal::setClientType(Internal::Resource); mStatusMessage = defaultReadyMessage(); mProgressEmissionCompressor.setInterval(1000); mProgressEmissionCompressor.setSingleShot(true); // HACK: skip local changes of the EntityDisplayAttribute by default. Remove this for KDE5 and adjust resource implementations accordingly. mKeepLocalCollectionChanges << "ENTITYDISPLAY"; } ~ResourceBasePrivate() override { delete mItemSyncFetchScope; } Q_DECLARE_PUBLIC(ResourceBase) void delayedInit() override { const QString serviceId = ServerManager::agentServiceName(ServerManager::Resource, mId); if (!KDBusConnectionPool::threadConnection().registerService(serviceId)) { QString reason = KDBusConnectionPool::threadConnection().lastError().message(); if (reason.isEmpty()) { reason = QStringLiteral("this service is probably running already."); } qCCritical(AKONADIAGENTBASE_LOG) << "Unable to register service" << serviceId << "at D-Bus:" << reason; if (QThread::currentThread() == QCoreApplication::instance()->thread()) { QCoreApplication::instance()->exit(1); } } else { AgentBasePrivate::delayedInit(); } } void changeProcessed() override { if (m_recursiveMover) { m_recursiveMover->changeProcessed(); QTimer::singleShot(0, m_recursiveMover.data(), &RecursiveMover::replayNext); return; } mChangeRecorder->changeProcessed(); if (!mChangeRecorder->isEmpty()) { scheduler->scheduleChangeReplay(); } scheduler->taskDone(); } void slotAbortRequested(); void slotDeliveryDone(KJob *job); void slotCollectionSyncDone(KJob *job); void slotLocalListDone(KJob *job); void slotSynchronizeCollection(const Collection &col); void slotItemRetrievalCollectionFetchDone(KJob *job); void slotCollectionListDone(KJob *job); void slotSynchronizeCollectionAttributes(const Collection &col); void slotCollectionListForAttributesDone(KJob *job); void slotCollectionAttributesSyncDone(KJob *job); void slotSynchronizeTags(); void slotSynchronizeRelations(); void slotAttributeRetrievalCollectionFetchDone(KJob *job); void slotItemSyncDone(KJob *job); void slotPercent(KJob *job, unsigned long percent); void slotDelayedEmitProgress(); void slotDeleteResourceCollection(); void slotDeleteResourceCollectionDone(KJob *job); void slotCollectionDeletionDone(KJob *job); void slotInvalidateCache(const Akonadi::Collection &collection); void slotPrepareItemRetrieval(const Akonadi::Item &item); void slotPrepareItemRetrievalResult(KJob *job); void slotPrepareItemsRetrieval(const QVector &item); void slotPrepareItemsRetrievalResult(KJob *job); void changeCommittedResult(KJob *job); void slotRecursiveMoveReplay(RecursiveMover *mover); void slotRecursiveMoveReplayResult(KJob *job); void slotTagSyncDone(KJob *job); void slotRelationSyncDone(KJob *job); void slotSessionReconnected() { Q_Q(ResourceBase); new ResourceSelectJob(q->identifier()); } void createItemSyncInstanceIfMissing() { Q_Q(ResourceBase); Q_ASSERT_X(scheduler->currentTask().type == ResourceScheduler::SyncCollection, "createItemSyncInstance", "Calling items retrieval methods although no item retrieval is in progress"); if (!mItemSyncer) { mItemSyncer = new ItemSync(q->currentCollection()); mItemSyncer->setTransactionMode(mItemTransactionMode); mItemSyncer->setBatchSize(mItemSyncBatchSize); mItemSyncer->setMergeMode(mItemMergeMode); if (mItemSyncFetchScope) { mItemSyncer->setFetchScope(*mItemSyncFetchScope); } mItemSyncer->setDisableAutomaticDeliveryDone(mDisableAutomaticItemDeliveryDone); mItemSyncer->setProperty("collection", QVariant::fromValue(q->currentCollection())); connect(mItemSyncer, SIGNAL(percent(KJob*,ulong)), q, SLOT(slotPercent(KJob*,ulong))); connect(mItemSyncer, SIGNAL(result(KJob*)), q, SLOT(slotItemSyncDone(KJob*))); connect(mItemSyncer, &ItemSync::readyForNextBatch, q, &ResourceBase::retrieveNextItemSyncBatch); } Q_ASSERT(mItemSyncer); } public Q_SLOTS: // Dump the state of the scheduler Q_SCRIPTABLE QString dumpToString() const { Q_Q(const ResourceBase); return scheduler->dumpToString() + QLatin1Char('\n') + q->dumpResourceToString(); } Q_SCRIPTABLE void dump() { scheduler->dump(); } Q_SCRIPTABLE void clear() { scheduler->clear(); } protected Q_SLOTS: // reimplementations from AgentbBasePrivate, containing sanity checks that only apply to resources // such as making sure that RIDs are present as well as translations of cross-resource moves // TODO: we could possibly add recovery code for no-RID notifications by re-enquing those to the change recorder // as the corresponding Add notifications, although that contains a risk of endless fail/retry loops void itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection) override { if (collection.remoteId().isEmpty()) { changeProcessed(); return; } AgentBasePrivate::itemAdded(item, collection); } void itemChanged(const Akonadi::Item &item, const QSet &partIdentifiers) override { if (item.remoteId().isEmpty()) { changeProcessed(); return; } AgentBasePrivate::itemChanged(item, partIdentifiers); } void itemsFlagsChanged(const Item::List &items, const QSet &addedFlags, const QSet &removedFlags) override { if (addedFlags.isEmpty() && removedFlags.isEmpty()) { changeProcessed(); return; } Item::List validItems; for (const Akonadi::Item &item : items) { if (!item.remoteId().isEmpty()) { validItems << item; } } if (validItems.isEmpty()) { changeProcessed(); return; } AgentBasePrivate::itemsFlagsChanged(validItems, addedFlags, removedFlags); } void itemsTagsChanged(const Item::List &items, const QSet &addedTags, const QSet &removedTags) override { if (addedTags.isEmpty() && removedTags.isEmpty()) { changeProcessed(); return; } Item::List validItems; for (const Akonadi::Item &item : items) { if (!item.remoteId().isEmpty()) { validItems << item; } } if (validItems.isEmpty()) { changeProcessed(); return; } AgentBasePrivate::itemsTagsChanged(validItems, addedTags, removedTags); } // TODO move the move translation code from AgentBasePrivate here, it's wrong for agents void itemMoved(const Akonadi::Item &item, const Akonadi::Collection &source, const Akonadi::Collection &destination) override { if (item.remoteId().isEmpty() || destination.remoteId().isEmpty() || destination == source) { changeProcessed(); return; } AgentBasePrivate::itemMoved(item, source, destination); } void itemsMoved(const Item::List &items, const Collection &source, const Collection &destination) override { if (destination.remoteId().isEmpty() || destination == source) { changeProcessed(); return; } Item::List validItems; for (const Akonadi::Item &item : items) { if (!item.remoteId().isEmpty()) { validItems << item; } } if (validItems.isEmpty()) { changeProcessed(); return; } AgentBasePrivate::itemsMoved(validItems, source, destination); } void itemRemoved(const Akonadi::Item &item) override { if (item.remoteId().isEmpty()) { changeProcessed(); return; } AgentBasePrivate::itemRemoved(item); } void itemsRemoved(const Item::List &items) override { Item::List validItems; for (const Akonadi::Item &item : items) { if (!item.remoteId().isEmpty()) { validItems << item; } } if (validItems.isEmpty()) { changeProcessed(); return; } AgentBasePrivate::itemsRemoved(validItems); } void collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent) override { if (parent.remoteId().isEmpty()) { changeProcessed(); return; } AgentBasePrivate::collectionAdded(collection, parent); } void collectionChanged(const Akonadi::Collection &collection) override { if (collection.remoteId().isEmpty()) { changeProcessed(); return; } AgentBasePrivate::collectionChanged(collection); } void collectionChanged(const Akonadi::Collection &collection, const QSet &partIdentifiers) override { if (collection.remoteId().isEmpty()) { changeProcessed(); return; } AgentBasePrivate::collectionChanged(collection, partIdentifiers); } void collectionMoved(const Akonadi::Collection &collection, const Akonadi::Collection &source, const Akonadi::Collection &destination) override { // unknown destination or source == destination means we can't do/don't have to do anything if (destination.remoteId().isEmpty() || source == destination) { changeProcessed(); return; } // inter-resource moves, requires we know which resources the source and destination are in though if (!source.resource().isEmpty() && !destination.resource().isEmpty() && source.resource() != destination.resource()) { if (source.resource() == q_ptr->identifier()) { // moved away from us AgentBasePrivate::collectionRemoved(collection); } else if (destination.resource() == q_ptr->identifier()) { // moved to us scheduler->taskDone(); // stop change replay for now RecursiveMover *mover = new RecursiveMover(this); mover->setCollection(collection, destination); scheduler->scheduleMoveReplay(collection, mover); } return; } // intra-resource move, requires the moved collection to have a valid id though if (collection.remoteId().isEmpty()) { changeProcessed(); return; } // intra-resource move, ie. something we can handle internally AgentBasePrivate::collectionMoved(collection, source, destination); } void collectionRemoved(const Akonadi::Collection &collection) override { if (collection.remoteId().isEmpty()) { changeProcessed(); return; } AgentBasePrivate::collectionRemoved(collection); } void tagAdded(const Akonadi::Tag &tag) override { if (!tag.isValid()) { changeProcessed(); return; } AgentBasePrivate::tagAdded(tag); } void tagChanged(const Akonadi::Tag &tag) override { if (tag.remoteId().isEmpty()) { changeProcessed(); return; } AgentBasePrivate::tagChanged(tag); } void tagRemoved(const Akonadi::Tag &tag) override { if (tag.remoteId().isEmpty()) { changeProcessed(); return; } AgentBasePrivate::tagRemoved(tag); } public: // synchronize states Collection currentCollection; ResourceScheduler *scheduler = nullptr; ItemSync *mItemSyncer = nullptr; ItemFetchScope *mItemSyncFetchScope = nullptr; ItemSync::TransactionMode mItemTransactionMode; ItemSync::MergeMode mItemMergeMode; CollectionSync *mCollectionSyncer = nullptr; TagSync *mTagSyncer = nullptr; RelationSync *mRelationSyncer = nullptr; bool mHierarchicalRid; QTimer mProgressEmissionCompressor; int mUnemittedProgress; QMap mUnemittedAdvancedStatus; bool mAutomaticProgressReporting; bool mDisableAutomaticItemDeliveryDone; QPointer m_recursiveMover; int mItemSyncBatchSize; QSet mKeepLocalCollectionChanges; KJob *mCurrentCollectionFetchJob = nullptr; bool mScheduleAttributeSyncBeforeCollectionSync; }; ResourceBase::ResourceBase(const QString &id) : AgentBase(new ResourceBasePrivate(this), id) { Q_D(ResourceBase); qDBusRegisterMetaType(); new Akonadi__ResourceAdaptor(this); d->scheduler = new ResourceScheduler(this); d->mChangeRecorder->setChangeRecordingEnabled(true); d->mChangeRecorder->setCollectionMoveTranslationEnabled(false); // we deal with this ourselves connect(d->mChangeRecorder, &ChangeRecorder::changesAdded, d->scheduler, &ResourceScheduler::scheduleChangeReplay); d->mChangeRecorder->setResourceMonitored(d->mId.toLatin1()); d->mChangeRecorder->fetchCollection(true); connect(d->scheduler, &ResourceScheduler::executeFullSync, this, &ResourceBase::retrieveCollections); connect(d->scheduler, &ResourceScheduler::executeCollectionTreeSync, this, &ResourceBase::retrieveCollections); connect(d->scheduler, SIGNAL(executeCollectionSync(Akonadi::Collection)), SLOT(slotSynchronizeCollection(Akonadi::Collection))); connect(d->scheduler, SIGNAL(executeCollectionAttributesSync(Akonadi::Collection)), SLOT(slotSynchronizeCollectionAttributes(Akonadi::Collection))); connect(d->scheduler, SIGNAL(executeTagSync()), SLOT(slotSynchronizeTags())); connect(d->scheduler, SIGNAL(executeRelationSync()), SLOT(slotSynchronizeRelations())); connect(d->scheduler, SIGNAL(executeItemFetch(Akonadi::Item,QSet)), SLOT(slotPrepareItemRetrieval(Akonadi::Item))); connect(d->scheduler, SIGNAL(executeItemsFetch(QVector,QSet)), SLOT(slotPrepareItemsRetrieval(QVector))); connect(d->scheduler, SIGNAL(executeResourceCollectionDeletion()), SLOT(slotDeleteResourceCollection())); connect(d->scheduler, SIGNAL(executeCacheInvalidation(Akonadi::Collection)), SLOT(slotInvalidateCache(Akonadi::Collection))); connect(d->scheduler, SIGNAL(status(int,QString)), SIGNAL(status(int,QString))); connect(d->scheduler, &ResourceScheduler::executeChangeReplay, d->mChangeRecorder, &ChangeRecorder::replayNext); connect(d->scheduler, SIGNAL(executeRecursiveMoveReplay(RecursiveMover*)), SLOT(slotRecursiveMoveReplay(RecursiveMover*))); connect(d->scheduler, &ResourceScheduler::fullSyncComplete, this, &ResourceBase::synchronized); connect(d->scheduler, &ResourceScheduler::collectionTreeSyncComplete, this, &ResourceBase::collectionTreeSynchronized); connect(d->mChangeRecorder, &ChangeRecorder::nothingToReplay, d->scheduler, &ResourceScheduler::taskDone); connect(d->mChangeRecorder, &Monitor::collectionRemoved, d->scheduler, &ResourceScheduler::collectionRemoved); connect(this, SIGNAL(abortRequested()), this, SLOT(slotAbortRequested())); connect(this, &ResourceBase::synchronized, d->scheduler, &ResourceScheduler::taskDone); connect(this, &ResourceBase::collectionTreeSynchronized, d->scheduler, &ResourceScheduler::taskDone); connect(this, &AgentBase::agentNameChanged, this, &ResourceBase::nameChanged); connect(&d->mProgressEmissionCompressor, SIGNAL(timeout()), this, SLOT(slotDelayedEmitProgress())); d->scheduler->setOnline(d->mOnline); if (!d->mChangeRecorder->isEmpty()) { d->scheduler->scheduleChangeReplay(); } new ResourceSelectJob(identifier()); connect(d->mChangeRecorder->session(), SIGNAL(reconnected()), SLOT(slotSessionReconnected())); } ResourceBase::~ResourceBase() { } void ResourceBase::synchronize() { d_func()->scheduler->scheduleFullSync(); } void ResourceBase::setName(const QString &name) { AgentBase::setAgentName(name); } QString ResourceBase::name() const { return AgentBase::agentName(); } QString ResourceBase::parseArguments(int argc, char **argv) { Q_UNUSED(argc); QCommandLineOption identifierOption(QStringLiteral("identifier"), i18nc("@label command line option", "Resource identifier"), QStringLiteral("argument")); QCommandLineParser parser; parser.addOption(identifierOption); parser.addHelpOption(); parser.addVersionOption(); parser.process(*qApp); parser.setApplicationDescription(i18n("Akonadi Resource")); if (!parser.isSet(identifierOption)) { qCDebug(AKONADIAGENTBASE_LOG) << "Identifier argument missing"; exit(1); } const QString identifier = parser.value(identifierOption); if (identifier.isEmpty()) { qCDebug(AKONADIAGENTBASE_LOG) << "Identifier is empty"; exit(1); } QCoreApplication::setApplicationName(ServerManager::addNamespace(identifier)); QCoreApplication::setApplicationVersion(QStringLiteral(AKONADI_VERSION_STRING)); const QFileInfo fi(QString::fromLocal8Bit(argv[0])); // strip off full path and possible .exe suffix const QString catalog = fi.baseName(); QTranslator *translator = new QTranslator(); translator->load(catalog); QCoreApplication::installTranslator(translator); return identifier; } int ResourceBase::init(ResourceBase *r) { int rv = qApp->exec(); delete r; return rv; } void ResourceBasePrivate::slotAbortRequested() { Q_Q(ResourceBase); scheduler->cancelQueues(); q->abortActivity(); } void ResourceBase::itemRetrieved(const Item &item) { Q_D(ResourceBase); Q_ASSERT(d->scheduler->currentTask().type == ResourceScheduler::FetchItem); if (!item.isValid()) { d->scheduler->itemFetchDone(i18nc("@info", "Invalid item retrieved")); return; } Item i(item); const QSet requestedParts = d->scheduler->currentTask().itemParts; for (const QByteArray &part : requestedParts) { if (!item.loadedPayloadParts().contains(part)) { qCWarning(AKONADIAGENTBASE_LOG) << "Item does not provide part" << part; } } ItemModifyJob *job = new ItemModifyJob(i); job->d_func()->setSilent(true); // FIXME: remove once the item with which we call retrieveItem() has a revision number job->disableRevisionCheck(); connect(job, SIGNAL(result(KJob*)), SLOT(slotDeliveryDone(KJob*))); } void ResourceBasePrivate::slotDeliveryDone(KJob *job) { Q_Q(ResourceBase); Q_ASSERT(scheduler->currentTask().type == ResourceScheduler::FetchItem); if (job->error()) { - emit q->error(i18nc("@info", "Error while creating item: %1", job->errorString())); + Q_EMIT q->error(i18nc("@info", "Error while creating item: %1", job->errorString())); } scheduler->itemFetchDone(QString()); } void ResourceBase::collectionAttributesRetrieved(const Collection &collection) { Q_D(ResourceBase); Q_ASSERT(d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionAttributes); if (!collection.isValid()) { - emit attributesSynchronized(d->scheduler->currentTask().collection.id()); + Q_EMIT attributesSynchronized(d->scheduler->currentTask().collection.id()); d->scheduler->taskDone(); return; } CollectionModifyJob *job = new CollectionModifyJob(collection); connect(job, SIGNAL(result(KJob*)), SLOT(slotCollectionAttributesSyncDone(KJob*))); } void ResourceBasePrivate::slotCollectionAttributesSyncDone(KJob *job) { Q_Q(ResourceBase); Q_ASSERT(scheduler->currentTask().type == ResourceScheduler::SyncCollectionAttributes); if (job->error()) { - emit q->error(i18nc("@info", "Error while updating collection: %1", job->errorString())); + Q_EMIT q->error(i18nc("@info", "Error while updating collection: %1", job->errorString())); } - emit q->attributesSynchronized(scheduler->currentTask().collection.id()); + Q_EMIT q->attributesSynchronized(scheduler->currentTask().collection.id()); scheduler->taskDone(); } void ResourceBasePrivate::slotDeleteResourceCollection() { Q_Q(ResourceBase); CollectionFetchJob *job = new CollectionFetchJob(Collection::root(), CollectionFetchJob::FirstLevel); job->fetchScope().setResource(q->identifier()); connect(job, SIGNAL(result(KJob*)), q, SLOT(slotDeleteResourceCollectionDone(KJob*))); } void ResourceBasePrivate::slotDeleteResourceCollectionDone(KJob *job) { Q_Q(ResourceBase); if (job->error()) { - emit q->error(job->errorString()); + Q_EMIT q->error(job->errorString()); scheduler->taskDone(); } else { const CollectionFetchJob *fetchJob = static_cast(job); if (!fetchJob->collections().isEmpty()) { CollectionDeleteJob *job = new CollectionDeleteJob(fetchJob->collections().at(0)); connect(job, SIGNAL(result(KJob*)), q, SLOT(slotCollectionDeletionDone(KJob*))); } else { // there is no resource collection, so just ignore the request scheduler->taskDone(); } } } void ResourceBasePrivate::slotCollectionDeletionDone(KJob *job) { Q_Q(ResourceBase); if (job->error()) { - emit q->error(job->errorString()); + Q_EMIT q->error(job->errorString()); } scheduler->taskDone(); } void ResourceBasePrivate::slotInvalidateCache(const Akonadi::Collection &collection) { Q_Q(ResourceBase); InvalidateCacheJob *job = new InvalidateCacheJob(collection, q); connect(job, &KJob::result, scheduler, &ResourceScheduler::taskDone); } void ResourceBase::changeCommitted(const Item &item) { changesCommitted(Item::List() << item); } void ResourceBase::changesCommitted(const Item::List &items) { TransactionSequence *transaction = new TransactionSequence(this); connect(transaction, SIGNAL(finished(KJob*)), this, SLOT(changeCommittedResult(KJob*))); // Modify the items one-by-one, because STORE does not support mass RID change for (const Item &item : items) { ItemModifyJob *job = new ItemModifyJob(item, transaction); job->d_func()->setClean(); job->disableRevisionCheck(); // TODO: remove, but where/how do we handle the error? job->setIgnorePayload(true); // we only want to reset the dirty flag and update the remote id } } void ResourceBase::changeCommitted(const Collection &collection) { CollectionModifyJob *job = new CollectionModifyJob(collection); connect(job, SIGNAL(result(KJob*)), SLOT(changeCommittedResult(KJob*))); } void ResourceBasePrivate::changeCommittedResult(KJob *job) { if (job->error()) { qCWarning(AKONADIAGENTBASE_LOG) << job->errorText(); } Q_Q(ResourceBase); if (qobject_cast(job)) { if (job->error()) { - emit q->error(i18nc("@info", "Updating local collection failed: %1.", job->errorText())); + Q_EMIT q->error(i18nc("@info", "Updating local collection failed: %1.", job->errorText())); } mChangeRecorder->d_ptr->invalidateCache(static_cast(job)->collection()); } else { if (job->error()) { - emit q->error(i18nc("@info", "Updating local items failed: %1.", job->errorText())); + Q_EMIT q->error(i18nc("@info", "Updating local items failed: %1.", job->errorText())); } // Item and tag cache is invalidated by modify job } changeProcessed(); } void ResourceBase::changeCommitted(const Tag &tag) { TagModifyJob *job = new TagModifyJob(tag); connect(job, SIGNAL(result(KJob*)), SLOT(changeCommittedResult(KJob*))); } QString ResourceBase::requestItemDelivery(const QList &uids, const QByteArrayList &parts) { Q_D(ResourceBase); if (!isOnline()) { const QString errorMsg = i18nc("@info", "Cannot fetch item in offline mode."); - emit error(errorMsg); + Q_EMIT error(errorMsg); return errorMsg; } setDelayedReply(true); Item::List items; items.reserve(uids.size()); for (auto uid : uids) { items.push_back(Item(uid)); } d->scheduler->scheduleItemsFetch(items, QSet::fromList(parts), message()); return QString(); } void ResourceBase::collectionsRetrieved(const Collection::List &collections) { Q_D(ResourceBase); Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || d->scheduler->currentTask().type == ResourceScheduler::SyncAll, "ResourceBase::collectionsRetrieved()", "Calling collectionsRetrieved() although no collection retrieval is in progress"); if (!d->mCollectionSyncer) { d->mCollectionSyncer = new CollectionSync(identifier()); d->mCollectionSyncer->setHierarchicalRemoteIds(d->mHierarchicalRid); d->mCollectionSyncer->setKeepLocalChanges(d->mKeepLocalCollectionChanges); connect(d->mCollectionSyncer, SIGNAL(percent(KJob*,ulong)), SLOT(slotPercent(KJob*,ulong))); connect(d->mCollectionSyncer, SIGNAL(result(KJob*)), SLOT(slotCollectionSyncDone(KJob*))); } d->mCollectionSyncer->setRemoteCollections(collections); } void ResourceBase::collectionsRetrievedIncremental(const Collection::List &changedCollections, const Collection::List &removedCollections) { Q_D(ResourceBase); Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || d->scheduler->currentTask().type == ResourceScheduler::SyncAll, "ResourceBase::collectionsRetrievedIncremental()", "Calling collectionsRetrievedIncremental() although no collection retrieval is in progress"); if (!d->mCollectionSyncer) { d->mCollectionSyncer = new CollectionSync(identifier()); d->mCollectionSyncer->setHierarchicalRemoteIds(d->mHierarchicalRid); d->mCollectionSyncer->setKeepLocalChanges(d->mKeepLocalCollectionChanges); connect(d->mCollectionSyncer, SIGNAL(percent(KJob*,ulong)), SLOT(slotPercent(KJob*,ulong))); connect(d->mCollectionSyncer, SIGNAL(result(KJob*)), SLOT(slotCollectionSyncDone(KJob*))); } d->mCollectionSyncer->setRemoteCollections(changedCollections, removedCollections); } void ResourceBase::setCollectionStreamingEnabled(bool enable) { Q_D(ResourceBase); Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || d->scheduler->currentTask().type == ResourceScheduler::SyncAll, "ResourceBase::setCollectionStreamingEnabled()", "Calling setCollectionStreamingEnabled() although no collection retrieval is in progress"); if (!d->mCollectionSyncer) { d->mCollectionSyncer = new CollectionSync(identifier()); d->mCollectionSyncer->setHierarchicalRemoteIds(d->mHierarchicalRid); connect(d->mCollectionSyncer, SIGNAL(percent(KJob*,ulong)), SLOT(slotPercent(KJob*,ulong))); connect(d->mCollectionSyncer, SIGNAL(result(KJob*)), SLOT(slotCollectionSyncDone(KJob*))); } d->mCollectionSyncer->setStreamingEnabled(enable); } void ResourceBase::collectionsRetrievalDone() { Q_D(ResourceBase); Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree || d->scheduler->currentTask().type == ResourceScheduler::SyncAll, "ResourceBase::collectionsRetrievalDone()", "Calling collectionsRetrievalDone() although no collection retrieval is in progress"); // streaming enabled, so finalize the sync if (d->mCollectionSyncer) { d->mCollectionSyncer->retrievalDone(); } else { // user did the sync himself, we are done now // FIXME: we need the same special case for SyncAll as in slotCollectionSyncDone here! d->scheduler->taskDone(); } } void ResourceBase::setKeepLocalCollectionChanges(const QSet &parts) { Q_D(ResourceBase); d->mKeepLocalCollectionChanges = parts; } void ResourceBasePrivate::slotCollectionSyncDone(KJob *job) { Q_Q(ResourceBase); mCollectionSyncer = nullptr; if (job->error()) { if (job->error() != Job::UserCanceled) { - emit q->error(job->errorString()); + Q_EMIT q->error(job->errorString()); } } else { if (scheduler->currentTask().type == ResourceScheduler::SyncAll) { CollectionFetchJob *list = new CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive); list->setFetchScope(q->changeRecorder()->collectionFetchScope()); list->fetchScope().fetchAttribute(); list->fetchScope().fetchAttribute(); list->fetchScope().setResource(mId); list->fetchScope().setListFilter(CollectionFetchScope::Sync); q->connect(list, SIGNAL(result(KJob*)), q, SLOT(slotLocalListDone(KJob*))); return; } else if (scheduler->currentTask().type == ResourceScheduler::SyncCollectionTree) { scheduler->scheduleCollectionTreeSyncCompletion(); } } scheduler->taskDone(); } namespace { bool sortCollectionsForSync(const Collection &l, const Collection &r) { const auto lType = l.hasAttribute() ? l.attribute()->collectionType() : QByteArray(); const bool lInbox = (lType == "inbox") || (l.remoteId().midRef(1).compare(QLatin1String("inbox"), Qt::CaseInsensitive) == 0); const bool lFav = l.hasAttribute(); const auto rType = r.hasAttribute() ? r.attribute()->collectionType() : QByteArray(); const bool rInbox = (rType == "inbox") || (r.remoteId().midRef(1).compare(QLatin1String("inbox"), Qt::CaseInsensitive) == 0); const bool rFav = r.hasAttribute(); // inbox is always first if (lInbox) { return true; } else if (rInbox) { return false; } // favorites right after inbox if (lFav) { return !rInbox; } else if (rFav) { return lInbox; } // trash is always last (unless it's favorite) if (lType == "trash") { return false; } else if (rType == "trash") { return true; } // Fallback to sorting by id return l.id() < r.id(); } } void ResourceBasePrivate::slotLocalListDone(KJob *job) { Q_Q(ResourceBase); if (job->error()) { - emit q->error(job->errorString()); + Q_EMIT q->error(job->errorString()); } else { Collection::List cols = static_cast(job)->collections(); std::sort(cols.begin(), cols.end(), sortCollectionsForSync); for (const Collection &col : qAsConst(cols)) { scheduler->scheduleSync(col); } scheduler->scheduleFullSyncCompletion(); } scheduler->taskDone(); } void ResourceBasePrivate::slotSynchronizeCollection(const Collection &col) { Q_Q(ResourceBase); currentCollection = col; // This can happen due to FetchHelper::triggerOnDemandFetch() in the akonadi server (not an error). if (!col.remoteId().isEmpty()) { // check if this collection actually can contain anything QStringList contentTypes = currentCollection.contentMimeTypes(); contentTypes.removeAll(Collection::mimeType()); contentTypes.removeAll(Collection::virtualMimeType()); if (!contentTypes.isEmpty() || col.isVirtual()) { if (mAutomaticProgressReporting) { - emit q->status(AgentBase::Running, i18nc("@info:status", "Syncing folder '%1'", currentCollection.displayName())); + Q_EMIT q->status(AgentBase::Running, i18nc("@info:status", "Syncing folder '%1'", currentCollection.displayName())); } qCDebug(AKONADIAGENTBASE_LOG) << "Preparing collection sync of collection" << currentCollection.id() << currentCollection.displayName(); Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(col, CollectionFetchJob::Base, this); fetchJob->setFetchScope(q->changeRecorder()->collectionFetchScope()); connect(fetchJob, SIGNAL(result(KJob*)), q, SLOT(slotItemRetrievalCollectionFetchDone(KJob*))); mCurrentCollectionFetchJob = fetchJob; return; } } scheduler->taskDone(); } void ResourceBasePrivate::slotItemRetrievalCollectionFetchDone(KJob *job) { Q_Q(ResourceBase); mCurrentCollectionFetchJob = nullptr; if (job->error()) { qCWarning(AKONADIAGENTBASE_LOG) << "Failed to retrieve collection for sync: " << job->errorString(); q->cancelTask(i18n("Failed to retrieve collection for sync.")); return; } Akonadi::CollectionFetchJob *fetchJob = static_cast(job); const Collection::List collections = fetchJob->collections(); if (collections.isEmpty()) { qCWarning(AKONADIAGENTBASE_LOG) << "The fetch job returned empty collection set. This is unexpected."; q->cancelTask(i18n("Failed to retrieve collection for sync.")); return; } q->retrieveItems(collections.at(0)); } int ResourceBase::itemSyncBatchSize() const { Q_D(const ResourceBase); return d->mItemSyncBatchSize; } void ResourceBase::setItemSyncBatchSize(int batchSize) { Q_D(ResourceBase); d->mItemSyncBatchSize = batchSize; } void ResourceBase::setScheduleAttributeSyncBeforeItemSync(bool enable) { Q_D(ResourceBase); d->mScheduleAttributeSyncBeforeCollectionSync = enable; } void ResourceBasePrivate::slotSynchronizeCollectionAttributes(const Collection &col) { Q_Q(ResourceBase); Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(col, CollectionFetchJob::Base, this); fetchJob->setFetchScope(q->changeRecorder()->collectionFetchScope()); connect(fetchJob, SIGNAL(result(KJob*)), q, SLOT(slotAttributeRetrievalCollectionFetchDone(KJob*))); Q_ASSERT(!mCurrentCollectionFetchJob); mCurrentCollectionFetchJob = fetchJob; } void ResourceBasePrivate::slotAttributeRetrievalCollectionFetchDone(KJob *job) { mCurrentCollectionFetchJob = nullptr; Q_Q(ResourceBase); if (job->error()) { qCWarning(AKONADIAGENTBASE_LOG) << "Failed to retrieve collection for attribute sync: " << job->errorString(); q->cancelTask(i18n("Failed to retrieve collection for attribute sync.")); return; } Akonadi::CollectionFetchJob *fetchJob = static_cast(job); QMetaObject::invokeMethod(q, "retrieveCollectionAttributes", Q_ARG(Akonadi::Collection, fetchJob->collections().at(0))); } void ResourceBasePrivate::slotSynchronizeTags() { Q_Q(ResourceBase); QMetaObject::invokeMethod(this, [q] { q->retrieveTags(); }); } void ResourceBasePrivate::slotSynchronizeRelations() { Q_Q(ResourceBase); QMetaObject::invokeMethod(this, [q] { q->retrieveRelations(); }); } void ResourceBasePrivate::slotPrepareItemRetrieval(const Item &item) { Q_Q(ResourceBase); auto fetch = new ItemFetchJob(item, this); // we always need at least parent so we can use ItemCreateJob to merge fetch->fetchScope().setAncestorRetrieval(qMax(ItemFetchScope::Parent, q->changeRecorder()->itemFetchScope().ancestorRetrieval())); fetch->fetchScope().setCacheOnly(true); fetch->fetchScope().setFetchRemoteIdentification(true); // copy list of attributes to fetch const QSet attributes = q->changeRecorder()->itemFetchScope().attributes(); for (const auto &attribute : attributes) { fetch->fetchScope().fetchAttribute(attribute); } q->connect(fetch, SIGNAL(result(KJob*)), SLOT(slotPrepareItemRetrievalResult(KJob*))); } void ResourceBasePrivate::slotPrepareItemRetrievalResult(KJob *job) { Q_Q(ResourceBase); Q_ASSERT_X(scheduler->currentTask().type == ResourceScheduler::FetchItem, "ResourceBasePrivate::slotPrepareItemRetrievalResult()", "Preparing item retrieval although no item retrieval is in progress"); if (job->error()) { q->cancelTask(job->errorText()); return; } ItemFetchJob *fetch = qobject_cast(job); if (fetch->items().count() != 1) { q->cancelTask(i18n("The requested item no longer exists")); return; } const QSet parts = scheduler->currentTask().itemParts; if (!q->retrieveItem(fetch->items().at(0), parts)) { q->cancelTask(); } } void ResourceBasePrivate::slotPrepareItemsRetrieval(const QVector &items) { Q_Q(ResourceBase); ItemFetchJob *fetch = new ItemFetchJob(items, this); // we always need at least parent so we can use ItemCreateJob to merge fetch->fetchScope().setAncestorRetrieval(qMax(ItemFetchScope::Parent, q->changeRecorder()->itemFetchScope().ancestorRetrieval())); fetch->fetchScope().setCacheOnly(true); fetch->fetchScope().setFetchRemoteIdentification(true); // It's possible that one or more items were removed before this task was // executed, so ignore it and just handle the rest. fetch->fetchScope().setIgnoreRetrievalErrors(true); // copy list of attributes to fetch const QSet attributes = q->changeRecorder()->itemFetchScope().attributes(); for (const auto &attribute : attributes) { fetch->fetchScope().fetchAttribute(attribute); } q->connect(fetch, SIGNAL(result(KJob*)), SLOT(slotPrepareItemsRetrievalResult(KJob*))); } void ResourceBasePrivate::slotPrepareItemsRetrievalResult(KJob *job) { Q_Q(ResourceBase); Q_ASSERT_X(scheduler->currentTask().type == ResourceScheduler::FetchItems, "ResourceBasePrivate::slotPrepareItemsRetrievalResult()", "Preparing items retrieval although no items retrieval is in progress"); if (job->error()) { q->cancelTask(job->errorText()); return; } ItemFetchJob *fetch = qobject_cast(job); const auto items = fetch->items(); if (items.isEmpty()) { q->cancelTask(); return; } const QSet parts = scheduler->currentTask().itemParts; Q_ASSERT(items.first().parentCollection().isValid()); if (!q->retrieveItems(items, parts)) { q->cancelTask(); } } void ResourceBasePrivate::slotRecursiveMoveReplay(RecursiveMover *mover) { Q_Q(ResourceBase); Q_ASSERT(mover); Q_ASSERT(!m_recursiveMover); m_recursiveMover = mover; connect(mover, SIGNAL(result(KJob*)), q, SLOT(slotRecursiveMoveReplayResult(KJob*))); mover->start(); } void ResourceBasePrivate::slotRecursiveMoveReplayResult(KJob *job) { Q_Q(ResourceBase); m_recursiveMover = nullptr; if (job->error()) { q->deferTask(); return; } changeProcessed(); } void ResourceBase::itemsRetrievalDone() { Q_D(ResourceBase); // streaming enabled, so finalize the sync if (d->mItemSyncer) { d->mItemSyncer->deliveryDone(); } else { if (d->scheduler->currentTask().type == ResourceScheduler::FetchItems) { d->scheduler->currentTask().sendDBusReplies(QString()); } // user did the sync himself, we are done now d->scheduler->taskDone(); } } void ResourceBase::clearCache() { Q_D(ResourceBase); d->scheduler->scheduleResourceCollectionDeletion(); } void ResourceBase::invalidateCache(const Collection &collection) { Q_D(ResourceBase); d->scheduler->scheduleCacheInvalidation(collection); } Collection ResourceBase::currentCollection() const { Q_D(const ResourceBase); Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::SyncCollection, "ResourceBase::currentCollection()", "Trying to access current collection although no item retrieval is in progress"); return d->currentCollection; } Item ResourceBase::currentItem() const { Q_D(const ResourceBase); Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::FetchItem, "ResourceBase::currentItem()", "Trying to access current item although no item retrieval is in progress"); return d->scheduler->currentTask().items[0]; } Item::List ResourceBase::currentItems() const { Q_D(const ResourceBase); Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::FetchItems, "ResourceBase::currentItems()", "Trying to access current items although no items retrieval is in progress"); return d->scheduler->currentTask().items; } void ResourceBase::synchronizeCollectionTree() { d_func()->scheduler->scheduleCollectionTreeSync(); } void ResourceBase::synchronizeTags() { d_func()->scheduler->scheduleTagSync(); } void ResourceBase::synchronizeRelations() { d_func()->scheduler->scheduleRelationSync(); } void ResourceBase::cancelTask() { Q_D(ResourceBase); if (d->mCurrentCollectionFetchJob) { d->mCurrentCollectionFetchJob->kill(); d->mCurrentCollectionFetchJob = nullptr; } switch (d->scheduler->currentTask().type) { case ResourceScheduler::FetchItem: itemRetrieved(Item()); // sends the error reply and break; case ResourceScheduler::FetchItems: itemsRetrieved(Item::List()); break; case ResourceScheduler::ChangeReplay: d->changeProcessed(); break; case ResourceScheduler::SyncCollectionTree: case ResourceScheduler::SyncAll: if (d->mCollectionSyncer) { d->mCollectionSyncer->rollback(); } else { d->scheduler->taskDone(); } break; case ResourceScheduler::SyncCollection: if (d->mItemSyncer) { d->mItemSyncer->rollback(); } else { d->scheduler->taskDone(); } break; default: d->scheduler->taskDone(); } } void ResourceBase::cancelTask(const QString &msg) { cancelTask(); - emit error(msg); + Q_EMIT error(msg); } void ResourceBase::deferTask() { Q_D(ResourceBase); qCDebug(AKONADIAGENTBASE_LOG) << "Deferring task" << d->scheduler->currentTask(); // Deferring a CollectionSync is just not implemented. // We'd need to d->mItemSyncer->rollback() but also to NOT call taskDone in slotItemSyncDone() here... Q_ASSERT(!d->mItemSyncer); d->scheduler->deferTask(); } void ResourceBase::doSetOnline(bool state) { d_func()->scheduler->setOnline(state); } void ResourceBase::synchronizeCollection(qint64 collectionId) { synchronizeCollection(collectionId, false); } void ResourceBase::synchronizeCollection(qint64 collectionId, bool recursive) { CollectionFetchJob *job = new CollectionFetchJob(Collection(collectionId), recursive ? CollectionFetchJob::Recursive : CollectionFetchJob::Base); job->setFetchScope(changeRecorder()->collectionFetchScope()); job->fetchScope().setResource(identifier()); job->fetchScope().setListFilter(CollectionFetchScope::Sync); connect(job, SIGNAL(result(KJob*)), SLOT(slotCollectionListDone(KJob*))); } void ResourceBasePrivate::slotCollectionListDone(KJob *job) { if (!job->error()) { const Collection::List list = static_cast(job)->collections(); for (const Collection &collection : list) { //We also get collections that should not be synced but are part of the tree. if (collection.shouldList(Collection::ListSync) || collection.referenced()) { if (mScheduleAttributeSyncBeforeCollectionSync) { scheduler->scheduleAttributesSync(collection); } scheduler->scheduleSync(collection); } } } else { qCWarning(AKONADIAGENTBASE_LOG) << "Failed to fetch collection for collection sync: " << job->errorString(); } } void ResourceBase::synchronizeCollectionAttributes(const Akonadi::Collection &col) { Q_D(ResourceBase); d->scheduler->scheduleAttributesSync(col); } void ResourceBase::synchronizeCollectionAttributes(qint64 collectionId) { CollectionFetchJob *job = new CollectionFetchJob(Collection(collectionId), CollectionFetchJob::Base); job->setFetchScope(changeRecorder()->collectionFetchScope()); job->fetchScope().setResource(identifier()); connect(job, SIGNAL(result(KJob*)), SLOT(slotCollectionListForAttributesDone(KJob*))); } void ResourceBasePrivate::slotCollectionListForAttributesDone(KJob *job) { if (!job->error()) { const Collection::List list = static_cast(job)->collections(); if (!list.isEmpty()) { const Collection col = list.first(); scheduler->scheduleAttributesSync(col); } } // TODO: error handling } void ResourceBase::setTotalItems(int amount) { qCDebug(AKONADIAGENTBASE_LOG) << amount; Q_D(ResourceBase); setItemStreamingEnabled(true); if (d->mItemSyncer) { d->mItemSyncer->setTotalItems(amount); } } void ResourceBase::setDisableAutomaticItemDeliveryDone(bool disable) { Q_D(ResourceBase); if (d->mItemSyncer) { d->mItemSyncer->setDisableAutomaticDeliveryDone(disable); } d->mDisableAutomaticItemDeliveryDone = disable; } void ResourceBase::setItemStreamingEnabled(bool enable) { Q_D(ResourceBase); d->createItemSyncInstanceIfMissing(); if (d->mItemSyncer) { d->mItemSyncer->setStreamingEnabled(enable); } } void ResourceBase::itemsRetrieved(const Item::List &items) { Q_D(ResourceBase); if (d->scheduler->currentTask().type == ResourceScheduler::FetchItems) { auto trx = new TransactionSequence(this); connect(trx, SIGNAL(result(KJob*)), this, SLOT(slotItemSyncDone(KJob*))); for (const Item &item : items) { Q_ASSERT(item.parentCollection().isValid()); if (item.isValid()) { new ItemModifyJob(item, trx); } else if (!item.remoteId().isEmpty()) { auto job = new ItemCreateJob(item, item.parentCollection(), trx); job->setMerge(ItemCreateJob::RID); } else { // This should not happen, but just to be sure... new ItemModifyJob(item, trx); } } trx->commit(); } else { d->createItemSyncInstanceIfMissing(); if (d->mItemSyncer) { d->mItemSyncer->setFullSyncItems(items); } } } void ResourceBase::itemsRetrievedIncremental(const Item::List &changedItems, const Item::List &removedItems) { Q_D(ResourceBase); d->createItemSyncInstanceIfMissing(); if (d->mItemSyncer) { d->mItemSyncer->setIncrementalSyncItems(changedItems, removedItems); } } void ResourceBasePrivate::slotItemSyncDone(KJob *job) { mItemSyncer = nullptr; Q_Q(ResourceBase); if (job->error() && job->error() != Job::UserCanceled) { - emit q->error(job->errorString()); + Q_EMIT q->error(job->errorString()); } if (scheduler->currentTask().type == ResourceScheduler::FetchItems) { scheduler->currentTask().sendDBusReplies((job->error() && job->error() != Job::UserCanceled) ? job->errorString() : QString()); } scheduler->taskDone(); } void ResourceBasePrivate::slotDelayedEmitProgress() { Q_Q(ResourceBase); if (mAutomaticProgressReporting) { - emit q->percent(mUnemittedProgress); + Q_EMIT q->percent(mUnemittedProgress); for (const QVariantMap &statusMap : qAsConst(mUnemittedAdvancedStatus)) { - emit q->advancedStatus(statusMap); + Q_EMIT q->advancedStatus(statusMap); } } mUnemittedProgress = 0; mUnemittedAdvancedStatus.clear(); } void ResourceBasePrivate::slotPercent(KJob *job, unsigned long percent) { mUnemittedProgress = percent; const Collection collection = job->property("collection").value(); if (collection.isValid()) { QVariantMap statusMap; statusMap.insert(QStringLiteral("key"), QStringLiteral("collectionSyncProgress")); statusMap.insert(QStringLiteral("collectionId"), collection.id()); statusMap.insert(QStringLiteral("percent"), static_cast(percent)); mUnemittedAdvancedStatus[collection.id()] = statusMap; } // deliver completion right away, intermediate progress at 1s intervals if (percent == 100) { mProgressEmissionCompressor.stop(); slotDelayedEmitProgress(); } else if (!mProgressEmissionCompressor.isActive()) { mProgressEmissionCompressor.start(); } } void ResourceBase::setHierarchicalRemoteIdentifiersEnabled(bool enable) { Q_D(ResourceBase); d->mHierarchicalRid = enable; } void ResourceBase::scheduleCustomTask(QObject *receiver, const char *method, const QVariant &argument, SchedulePriority priority) { Q_D(ResourceBase); d->scheduler->scheduleCustomTask(receiver, method, argument, priority); } void ResourceBase::taskDone() { Q_D(ResourceBase); d->scheduler->taskDone(); } void ResourceBase::retrieveCollectionAttributes(const Collection &collection) { collectionAttributesRetrieved(collection); } void ResourceBase::retrieveTags() { Q_D(ResourceBase); d->scheduler->taskDone(); } void ResourceBase::retrieveRelations() { Q_D(ResourceBase); d->scheduler->taskDone(); } bool ResourceBase::retrieveItem(const Akonadi::Item &item, const QSet &parts) { Q_UNUSED(item); Q_UNUSED(parts); // retrieveItem() can no longer be pure virtual, because then we could not mark // it as deprecated (i.e. implementations would still be forced to implement it), // so instead we assert here. // NOTE: Don't change to Q_ASSERT_X here: while the macro can be disabled at // compile time, we want to hit this assert *ALWAYS*. qt_assert_x("Akonadi::ResourceBase::retrieveItem()", "The base implementation of retrieveItem() must never be reached. " "You must implement either retrieveItem() or retrieveItems(Akonadi::Item::List, QSet) overload " "to handle item retrieval requests.", __FILE__, __LINE__); return false; } bool ResourceBase::retrieveItems(const Akonadi::Item::List &items, const QSet &parts) { Q_D(ResourceBase); // If we reach this implementation of retrieveItems() then it means that the // resource is still using the deprecated retrieveItem() method, so we explode // this to a myriad of tasks in scheduler and let them be processed one by one const qint64 id = d->scheduler->currentTask().serial; for (const auto &item : items) { d->scheduler->scheduleItemFetch(item, parts, d->scheduler->currentTask().dbusMsgs, id); } taskDone(); return true; } void Akonadi::ResourceBase::abortActivity() { } void ResourceBase::setItemTransactionMode(ItemSync::TransactionMode mode) { Q_D(ResourceBase); d->mItemTransactionMode = mode; } void ResourceBase::setItemSynchronizationFetchScope(const ItemFetchScope &fetchScope) { Q_D(ResourceBase); if (!d->mItemSyncFetchScope) { d->mItemSyncFetchScope = new ItemFetchScope; } *(d->mItemSyncFetchScope) = fetchScope; } void ResourceBase::setItemMergingMode(ItemSync::MergeMode mode) { Q_D(ResourceBase); d->mItemMergeMode = mode; } void ResourceBase::setAutomaticProgressReporting(bool enabled) { Q_D(ResourceBase); d->mAutomaticProgressReporting = enabled; } QString ResourceBase::dumpNotificationListToString() const { Q_D(const ResourceBase); return d->dumpNotificationListToString(); } QString ResourceBase::dumpSchedulerToString() const { Q_D(const ResourceBase); return d->dumpToString(); } void ResourceBase::dumpMemoryInfo() const { Q_D(const ResourceBase); return d->dumpMemoryInfo(); } QString ResourceBase::dumpMemoryInfoToString() const { Q_D(const ResourceBase); return d->dumpMemoryInfoToString(); } void ResourceBase::tagsRetrieved(const Tag::List &tags, const QHash &tagMembers) { Q_D(ResourceBase); Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::SyncTags || d->scheduler->currentTask().type == ResourceScheduler::SyncAll || d->scheduler->currentTask().type == ResourceScheduler::Custom, "ResourceBase::tagsRetrieved()", "Calling tagsRetrieved() although no tag retrieval is in progress"); if (!d->mTagSyncer) { d->mTagSyncer = new TagSync(this); connect(d->mTagSyncer, SIGNAL(percent(KJob*,ulong)), SLOT(slotPercent(KJob*,ulong))); connect(d->mTagSyncer, SIGNAL(result(KJob*)), SLOT(slotTagSyncDone(KJob*))); } d->mTagSyncer->setFullTagList(tags); d->mTagSyncer->setTagMembers(tagMembers); } void ResourceBasePrivate::slotTagSyncDone(KJob *job) { Q_Q(ResourceBase); mTagSyncer = nullptr; if (job->error()) { if (job->error() != Job::UserCanceled) { qCWarning(AKONADIAGENTBASE_LOG) << "TagSync failed: " << job->errorString(); - emit q->error(job->errorString()); + Q_EMIT q->error(job->errorString()); } } scheduler->taskDone(); } void ResourceBase::relationsRetrieved(const Relation::List &relations) { Q_D(ResourceBase); Q_ASSERT_X(d->scheduler->currentTask().type == ResourceScheduler::SyncRelations || d->scheduler->currentTask().type == ResourceScheduler::SyncAll || d->scheduler->currentTask().type == ResourceScheduler::Custom, "ResourceBase::relationsRetrieved()", "Calling relationsRetrieved() although no relation retrieval is in progress"); if (!d->mRelationSyncer) { d->mRelationSyncer = new RelationSync(this); connect(d->mRelationSyncer, SIGNAL(percent(KJob*,ulong)), SLOT(slotPercent(KJob*,ulong))); connect(d->mRelationSyncer, SIGNAL(result(KJob*)), SLOT(slotRelationSyncDone(KJob*))); } d->mRelationSyncer->setRemoteRelations(relations); } void ResourceBasePrivate::slotRelationSyncDone(KJob *job) { Q_Q(ResourceBase); mRelationSyncer = nullptr; if (job->error()) { if (job->error() != Job::UserCanceled) { qCWarning(AKONADIAGENTBASE_LOG) << "RelationSync failed: " << job->errorString(); - emit q->error(job->errorString()); + Q_EMIT q->error(job->errorString()); } } scheduler->taskDone(); } #include "resourcebase.moc" #include "moc_resourcebase.cpp" diff --git a/src/agentbase/resourcescheduler.cpp b/src/agentbase/resourcescheduler.cpp index 83a6955cf..0638194de 100644 --- a/src/agentbase/resourcescheduler.cpp +++ b/src/agentbase/resourcescheduler.cpp @@ -1,708 +1,708 @@ /* Copyright (c) 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 "resourcescheduler_p.h" #include "KDBusConnectionPool" #include "recursivemover_p.h" #include "akonadiagentbase_debug.h" #include "private/instance_p.h" #include #include #include using namespace Akonadi; qint64 ResourceScheduler::Task::latestSerial = 0; static QDBusAbstractInterface *s_resourcetracker = nullptr; //@cond PRIVATE ResourceScheduler::ResourceScheduler(QObject *parent) : QObject(parent) , mCurrentTasksQueue(-1) , mOnline(false) { } void ResourceScheduler::scheduleFullSync() { Task t; t.type = SyncAll; TaskList &queue = queueForTaskType(t.type); if (queue.contains(t) || mCurrentTask == t) { return; } queue << t; signalTaskToTracker(t, "SyncAll"); scheduleNext(); } void ResourceScheduler::scheduleCollectionTreeSync() { Task t; t.type = SyncCollectionTree; TaskList &queue = queueForTaskType(t.type); if (queue.contains(t) || mCurrentTask == t) { return; } queue << t; signalTaskToTracker(t, "SyncCollectionTree"); scheduleNext(); } void ResourceScheduler::scheduleTagSync() { Task t; t.type = SyncTags; TaskList &queue = queueForTaskType(t.type); if (queue.contains(t) || mCurrentTask == t) { return; } queue << t; signalTaskToTracker(t, "SyncTags"); scheduleNext(); } void ResourceScheduler::scheduleRelationSync() { Task t; t.type = SyncRelations; TaskList &queue = queueForTaskType(t.type); if (queue.contains(t) || mCurrentTask == t) { return; } queue << t; signalTaskToTracker(t, "SyncRelations"); scheduleNext(); } void ResourceScheduler::scheduleSync(const Collection &col) { Task t; t.type = SyncCollection; t.collection = col; TaskList &queue = queueForTaskType(t.type); if (queue.contains(t) || mCurrentTask == t) { return; } queue << t; signalTaskToTracker(t, "SyncCollection", QString::number(col.id())); scheduleNext(); } void ResourceScheduler::scheduleAttributesSync(const Collection &collection) { Task t; t.type = SyncCollectionAttributes; t.collection = collection; TaskList &queue = queueForTaskType(t.type); if (queue.contains(t) || mCurrentTask == t) { return; } queue << t; signalTaskToTracker(t, "SyncCollectionAttributes", QString::number(collection.id())); scheduleNext(); } void ResourceScheduler::scheduleItemFetch(const Akonadi::Item &item, const QSet &parts, const QList &msgs, qint64 parentId) { Task t; t.type = FetchItem; t.items << item; t.itemParts = parts; t.dbusMsgs = msgs; t.argument = parentId; TaskList &queue = queueForTaskType(t.type); queue << t; signalTaskToTracker(t, "FetchItem", QString::number(item.id())); scheduleNext(); } void ResourceScheduler::scheduleItemsFetch(const Item::List &items, const QSet &parts, const QDBusMessage &msg) { Task t; t.type = FetchItems; t.items = items; t.itemParts = parts; // if the current task does already fetch the requested item, break here but // keep the dbus message, so we can send the reply later on if (mCurrentTask == t) { mCurrentTask.dbusMsgs << msg; return; } // If this task is already in the queue, merge with it. TaskList &queue = queueForTaskType(t.type); const int idx = queue.indexOf(t); if (idx != -1) { queue[ idx ].dbusMsgs << msg; return; } t.dbusMsgs << msg; queue << t; QStringList ids; ids.reserve(items.size()); for (const auto &item : items) { ids.push_back(QString::number(item.id())); } signalTaskToTracker(t, "FetchItems", ids.join(QStringLiteral(", "))); scheduleNext(); } void ResourceScheduler::scheduleResourceCollectionDeletion() { Task t; t.type = DeleteResourceCollection; TaskList &queue = queueForTaskType(t.type); if (queue.contains(t) || mCurrentTask == t) { return; } queue << t; signalTaskToTracker(t, "DeleteResourceCollection"); scheduleNext(); } void ResourceScheduler::scheduleCacheInvalidation(const Collection &collection) { Task t; t.type = InvalideCacheForCollection; t.collection = collection; TaskList &queue = queueForTaskType(t.type); if (queue.contains(t) || mCurrentTask == t) { return; } queue << t; signalTaskToTracker(t, "InvalideCacheForCollection", QString::number(collection.id())); scheduleNext(); } void ResourceScheduler::scheduleChangeReplay() { Task t; t.type = ChangeReplay; TaskList &queue = queueForTaskType(t.type); // see ResourceBase::changeProcessed() for why we do not check for mCurrentTask == t here like in the other tasks if (queue.contains(t)) { return; } queue << t; signalTaskToTracker(t, "ChangeReplay"); scheduleNext(); } void ResourceScheduler::scheduleMoveReplay(const Collection &movedCollection, RecursiveMover *mover) { Task t; t.type = RecursiveMoveReplay; t.collection = movedCollection; t.argument = QVariant::fromValue(mover); TaskList &queue = queueForTaskType(t.type); if (queue.contains(t) || mCurrentTask == t) { return; } queue << t; signalTaskToTracker(t, "RecursiveMoveReplay", QString::number(t.collection.id())); scheduleNext(); } void Akonadi::ResourceScheduler::scheduleFullSyncCompletion() { Task t; t.type = SyncAllDone; TaskList &queue = queueForTaskType(t.type); // no compression here, all this does is emitting a D-Bus signal anyway, and compression can trigger races on the receiver side with the signal being lost queue << t; signalTaskToTracker(t, "SyncAllDone"); scheduleNext(); } void Akonadi::ResourceScheduler::scheduleCollectionTreeSyncCompletion() { Task t; t.type = SyncCollectionTreeDone; TaskList &queue = queueForTaskType(t.type); // no compression here, all this does is emitting a D-Bus signal anyway, and compression can trigger races on the receiver side with the signal being lost queue << t; signalTaskToTracker(t, "SyncCollectionTreeDone"); scheduleNext(); } void Akonadi::ResourceScheduler::scheduleCustomTask(QObject *receiver, const char *methodName, const QVariant &argument, ResourceBase::SchedulePriority priority) { Task t; t.type = Custom; t.receiver = receiver; t.methodName = methodName; t.argument = argument; QueueType queueType = GenericTaskQueue; if (priority == ResourceBase::AfterChangeReplay) { queueType = AfterChangeReplayQueue; } else if (priority == ResourceBase::Prepend) { queueType = PrependTaskQueue; } TaskList &queue = mTaskList[queueType]; if (queue.contains(t)) { return; } switch (priority) { case ResourceBase::Prepend: queue.prepend(t); break; default: queue.append(t); break; } signalTaskToTracker(t, "Custom-" + t.methodName); scheduleNext(); } void ResourceScheduler::taskDone() { if (isEmpty()) { - emit status(AgentBase::Idle, i18nc("@info:status Application ready for work", "Ready")); + Q_EMIT status(AgentBase::Idle, i18nc("@info:status Application ready for work", "Ready")); } if (s_resourcetracker) { const QList argumentList = { QString::number(mCurrentTask.serial), QString() }; s_resourcetracker->asyncCallWithArgumentList(QStringLiteral("jobEnded"), argumentList); } mCurrentTask = Task(); mCurrentTasksQueue = -1; scheduleNext(); } void ResourceScheduler::itemFetchDone(const QString &msg) { Q_ASSERT(mCurrentTask.type == FetchItem); TaskList &queue = queueForTaskType(mCurrentTask.type); const qint64 parentId = mCurrentTask.argument.toLongLong(); // msg is empty, there was no error if (msg.isEmpty() && !queue.isEmpty()) { Task &nextTask = queue[0]; // If the next task is FetchItem too... if (nextTask.type != mCurrentTask.type || nextTask.argument.toLongLong() != parentId) { // If the next task is not FetchItem or the next FetchItem task has // different parentId then this was the last task in the series, so // send the DBus replies. mCurrentTask.sendDBusReplies(msg); } } else { // msg was not empty, there was an error. // remove all subsequent FetchItem tasks with the same parentId auto iter = queue.begin(); while (iter != queue.end()) { if (iter->type != mCurrentTask.type || iter->argument.toLongLong() == parentId) { iter = queue.erase(iter); continue; } else { break; } } // ... and send DBus reply with the error message mCurrentTask.sendDBusReplies(msg); } taskDone(); } void ResourceScheduler::deferTask() { if (mCurrentTask.type == Invalid) { return; } if (s_resourcetracker) { const QList argumentList = {QString::number(mCurrentTask.serial), QString()}; s_resourcetracker->asyncCallWithArgumentList(QStringLiteral("jobEnded"), argumentList); } Task t = mCurrentTask; mCurrentTask = Task(); Q_ASSERT(mCurrentTasksQueue >= 0 && mCurrentTasksQueue < NQueueCount); mTaskList[mCurrentTasksQueue].prepend(t); mCurrentTasksQueue = -1; signalTaskToTracker(t, "DeferedTask"); scheduleNext(); } bool ResourceScheduler::isEmpty() { for (int i = 0; i < NQueueCount; ++i) { if (!mTaskList[i].isEmpty()) { return false; } } return true; } void ResourceScheduler::scheduleNext() { if (mCurrentTask.type != Invalid || isEmpty() || !mOnline) { return; } QTimer::singleShot(0, this, &ResourceScheduler::executeNext); } void ResourceScheduler::executeNext() { if (mCurrentTask.type != Invalid || isEmpty()) { return; } for (int i = 0; i < NQueueCount; ++i) { if (!mTaskList[i].isEmpty()) { mCurrentTask = mTaskList[i].takeFirst(); mCurrentTasksQueue = i; break; } } if (s_resourcetracker) { const QList argumentList = { QString::number(mCurrentTask.serial) }; s_resourcetracker->asyncCallWithArgumentList(QStringLiteral("jobStarted"), argumentList); } switch (mCurrentTask.type) { case SyncAll: - emit executeFullSync(); + Q_EMIT executeFullSync(); break; case SyncCollectionTree: - emit executeCollectionTreeSync(); + Q_EMIT executeCollectionTreeSync(); break; case SyncCollection: - emit executeCollectionSync(mCurrentTask.collection); + Q_EMIT executeCollectionSync(mCurrentTask.collection); break; case SyncCollectionAttributes: - emit executeCollectionAttributesSync(mCurrentTask.collection); + Q_EMIT executeCollectionAttributesSync(mCurrentTask.collection); break; case SyncTags: - emit executeTagSync(); + Q_EMIT executeTagSync(); break; case FetchItem: - emit executeItemFetch(mCurrentTask.items.at(0), mCurrentTask.itemParts); + Q_EMIT executeItemFetch(mCurrentTask.items.at(0), mCurrentTask.itemParts); break; case FetchItems: - emit executeItemsFetch(mCurrentTask.items, mCurrentTask.itemParts); + Q_EMIT executeItemsFetch(mCurrentTask.items, mCurrentTask.itemParts); break; case DeleteResourceCollection: - emit executeResourceCollectionDeletion(); + Q_EMIT executeResourceCollectionDeletion(); break; case InvalideCacheForCollection: - emit executeCacheInvalidation(mCurrentTask.collection); + Q_EMIT executeCacheInvalidation(mCurrentTask.collection); break; case ChangeReplay: - emit executeChangeReplay(); + Q_EMIT executeChangeReplay(); break; case RecursiveMoveReplay: - emit executeRecursiveMoveReplay(mCurrentTask.argument.value()); + Q_EMIT executeRecursiveMoveReplay(mCurrentTask.argument.value()); break; case SyncAllDone: - emit fullSyncComplete(); + Q_EMIT fullSyncComplete(); break; case SyncCollectionTreeDone: - emit collectionTreeSyncComplete(); + Q_EMIT collectionTreeSyncComplete(); break; case SyncRelations: - emit executeRelationSync(); + Q_EMIT executeRelationSync(); break; case Custom: { const QByteArray methodSig = mCurrentTask.methodName + QByteArray("(QVariant)"); const bool hasSlotWithVariant = mCurrentTask.receiver->metaObject()->indexOfMethod(methodSig.constData()) != -1; bool success = false; if (hasSlotWithVariant) { success = QMetaObject::invokeMethod(mCurrentTask.receiver, mCurrentTask.methodName.constData(), Q_ARG(QVariant, mCurrentTask.argument)); Q_ASSERT_X(success || !mCurrentTask.argument.isValid(), "ResourceScheduler::executeNext", "Valid argument was provided but the method wasn't found"); } if (!success) { success = QMetaObject::invokeMethod(mCurrentTask.receiver, mCurrentTask.methodName.constData()); } if (!success) { qCCritical(AKONADIAGENTBASE_LOG) << "Could not invoke slot" << mCurrentTask.methodName << "on" << mCurrentTask.receiver << "with argument" << mCurrentTask.argument; } break; } default: { qCCritical(AKONADIAGENTBASE_LOG) << "Unhandled task type" << mCurrentTask.type; dump(); Q_ASSERT(false); } } } ResourceScheduler::Task ResourceScheduler::currentTask() const { return mCurrentTask; } ResourceScheduler::Task &ResourceScheduler::currentTask() { return mCurrentTask; } void ResourceScheduler::setOnline(bool state) { if (mOnline == state) { return; } mOnline = state; if (mOnline) { scheduleNext(); } else { if (mCurrentTask.type != Invalid) { // abort running task queueForTaskType(mCurrentTask.type).prepend(mCurrentTask); mCurrentTask = Task(); mCurrentTasksQueue = -1; } // abort pending synchronous tasks, might take longer until the resource goes online again TaskList &itemFetchQueue = queueForTaskType(FetchItem); qint64 parentId = -1; Task lastTask; for (QList< Task >::iterator it = itemFetchQueue.begin(); it != itemFetchQueue.end();) { if ((*it).type == FetchItem) { qint64 idx = it->argument.toLongLong(); if (parentId == -1) { parentId = idx; } if (idx != parentId) { // Only emit the DBus reply once we reach the last taskwith the // same "idx" lastTask.sendDBusReplies(i18nc("@info", "Job canceled.")); parentId = idx; } lastTask = (*it); it = itemFetchQueue.erase(it); if (s_resourcetracker) { const QList argumentList = { QString::number(mCurrentTask.serial), i18nc("@info", "Job canceled.")}; s_resourcetracker->asyncCallWithArgumentList(QStringLiteral("jobEnded"), argumentList); } } else { ++it; } } } } void ResourceScheduler::signalTaskToTracker(const Task &task, const QByteArray &taskType, const QString &debugString) { // if there's a job tracer running, tell it about the new job if (!s_resourcetracker) { const QString suffix = Akonadi::Instance::identifier().isEmpty() ? QString() : QLatin1Char('-') + Akonadi::Instance::identifier(); if (KDBusConnectionPool::threadConnection().interface()->isServiceRegistered(QStringLiteral("org.kde.akonadiconsole") + suffix)) { s_resourcetracker = new QDBusInterface(QStringLiteral("org.kde.akonadiconsole") + suffix, QStringLiteral("/resourcesJobtracker"), QStringLiteral("org.freedesktop.Akonadi.JobTracker"), KDBusConnectionPool::threadConnection(), nullptr); } } if (s_resourcetracker) { const QList argumentList = QList() << static_cast(parent())->identifier() // "session" (in our case resource) << QString::number(task.serial) // "job" << QString() // "parent job" << QString::fromLatin1(taskType) // "job type" << debugString // "job debugging string" ; s_resourcetracker->asyncCallWithArgumentList(QStringLiteral("jobCreated"), argumentList); } } void ResourceScheduler::collectionRemoved(const Akonadi::Collection &collection) { if (!collection.isValid()) { // should not happen, but you never know... return; } TaskList &queue = queueForTaskType(SyncCollection); for (QList::iterator it = queue.begin(); it != queue.end();) { if ((*it).type == SyncCollection && (*it).collection == collection) { it = queue.erase(it); qCDebug(AKONADIAGENTBASE_LOG) << " erasing"; } else { ++it; } } } void ResourceScheduler::Task::sendDBusReplies(const QString &errorMsg) { for (const QDBusMessage &msg : qAsConst(dbusMsgs)) { QDBusMessage reply(msg.createReply()); const QString methodName = msg.member(); if (methodName == QLatin1String("requestItemDelivery")) { reply << errorMsg; } else if (methodName.isEmpty()) { continue; // unittest calls scheduleItemFetch with empty QDBusMessage } else { qCCritical(AKONADIAGENTBASE_LOG) << "Got unexpected member:" << methodName; } KDBusConnectionPool::threadConnection().send(reply); } } ResourceScheduler::QueueType ResourceScheduler::queueTypeForTaskType(TaskType type) { switch (type) { case ChangeReplay: case RecursiveMoveReplay: return ChangeReplayQueue; case FetchItem: case FetchItems: case SyncCollectionAttributes: return UserActionQueue; default: return GenericTaskQueue; } } ResourceScheduler::TaskList &ResourceScheduler::queueForTaskType(TaskType type) { const QueueType qt = queueTypeForTaskType(type); return mTaskList[qt]; } void ResourceScheduler::dump() { qCDebug(AKONADIAGENTBASE_LOG) << dumpToString(); } QString ResourceScheduler::dumpToString() const { QString ret; QTextStream str(&ret); str << "ResourceScheduler: " << (mOnline ? "Online" : "Offline") << endl; str << " current task: " << mCurrentTask << endl; for (int i = 0; i < NQueueCount; ++i) { const TaskList &queue = mTaskList[i]; if (queue.isEmpty()) { str << " queue " << i << " is empty" << endl; } else { str << " queue " << i << " " << queue.size() << " tasks:" << endl; const QList::const_iterator queueEnd(queue.constEnd()); for (QList::const_iterator it = queue.constBegin(); it != queueEnd; ++it) { str << " " << (*it) << endl; } } } return ret; } void ResourceScheduler::clear() { qCDebug(AKONADIAGENTBASE_LOG) << "Clearing ResourceScheduler queues:"; for (int i = 0; i < NQueueCount; ++i) { TaskList &queue = mTaskList[i]; queue.clear(); } mCurrentTask = Task(); mCurrentTasksQueue = -1; } void Akonadi::ResourceScheduler::cancelQueues() { for (int i = 0; i < NQueueCount; ++i) { TaskList &queue = mTaskList[i]; if (s_resourcetracker) { foreach (const Task &t, queue) { QList argumentList; argumentList << QString::number(t.serial) << QString(); s_resourcetracker->asyncCallWithArgumentList(QStringLiteral("jobEnded"), argumentList); } } queue.clear(); } } static const char s_taskTypes[][27] = { "Invalid (no task)", "SyncAll", "SyncCollectionTree", "SyncCollection", "SyncCollectionAttributes", "SyncTags", "FetchItem", "FetchItems", "ChangeReplay", "RecursiveMoveReplay", "DeleteResourceCollection", "InvalideCacheForCollection", "SyncAllDone", "SyncCollectionTreeDone", "SyncRelations", "Custom" }; QTextStream &Akonadi::operator<<(QTextStream &d, const ResourceScheduler::Task &task) { d << task.serial << " " << s_taskTypes[task.type] << " "; if (task.type != ResourceScheduler::Invalid) { if (task.collection.isValid()) { d << "collection " << task.collection.id() << " "; } if (!task.items.isEmpty()) { QStringList ids; ids.reserve(task.items.size()); for (const auto &item : qAsConst(task.items)) { ids.push_back(QString::number(item.id())); } d << "items " << ids.join(QStringLiteral(", ")) << " "; } if (!task.methodName.isEmpty()) { d << task.methodName << " " << task.argument.toString(); } } return d; } QDebug Akonadi::operator<<(QDebug d, const ResourceScheduler::Task &task) { QString s; QTextStream str(&s); str << task; d << s; return d; } //@endcond #include "moc_resourcescheduler_p.cpp" diff --git a/src/agentbase/transportresourcebase.cpp b/src/agentbase/transportresourcebase.cpp index 491c28977..5dea4bcb8 100644 --- a/src/agentbase/transportresourcebase.cpp +++ b/src/agentbase/transportresourcebase.cpp @@ -1,82 +1,82 @@ /* Copyright (c) 2009 Constantin Berzan 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 "transportresourcebase.h" #include "transportresourcebase_p.h" #include "KDBusConnectionPool" #include "transportadaptor.h" #include "itemfetchjob.h" #include "itemfetchscope.h" #include using namespace Akonadi; TransportResourceBasePrivate::TransportResourceBasePrivate(TransportResourceBase *qq) : QObject() , q(qq) { new Akonadi__TransportAdaptor(this); KDBusConnectionPool::threadConnection().registerObject(QStringLiteral("/Transport"), this, QDBusConnection::ExportAdaptors); } void TransportResourceBasePrivate::send(Item::Id id) { ItemFetchJob *job = new ItemFetchJob(Item(id)); job->fetchScope().fetchFullPayload(); job->setProperty("id", QVariant(id)); connect(job, &KJob::result, this, &TransportResourceBasePrivate::fetchResult); } void TransportResourceBasePrivate::fetchResult(KJob *job) { if (job->error()) { const Item::Id id = job->property("id").toLongLong(); - emit transportResult(id, static_cast(TransportResourceBase::TransportFailed), job->errorText()); + Q_EMIT transportResult(id, static_cast(TransportResourceBase::TransportFailed), job->errorText()); return; } ItemFetchJob *fetchJob = qobject_cast(job); Q_ASSERT(fetchJob); const Item item = fetchJob->items().at(0); q->sendItem(item); } TransportResourceBase::TransportResourceBase() : d(new TransportResourceBasePrivate(this)) { } TransportResourceBase::~TransportResourceBase() { delete d; } void TransportResourceBase::itemSent(const Item &item, TransportResult result, const QString &message) { - emit d->transportResult(item.id(), static_cast(result), message); + Q_EMIT d->transportResult(item.id(), static_cast(result), message); } #include "moc_transportresourcebase_p.cpp" diff --git a/src/core/agentmanager.cpp b/src/core/agentmanager.cpp index 6f3dc447a..2fc89e17e 100644 --- a/src/core/agentmanager.cpp +++ b/src/core/agentmanager.cpp @@ -1,437 +1,437 @@ /* Copyright (c) 2006-2008 Tobias Koenig 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 "agentmanager.h" #include "agentmanager_p.h" #include "vectorhelper.h" #include "agenttype_p.h" #include "agentinstance_p.h" #include "KDBusConnectionPool" #include "servermanager.h" #include "collection.h" #include #include using namespace Akonadi; // @cond PRIVATE AgentInstance AgentManagerPrivate::createInstance(const AgentType &type) { const QString &identifier = mManager->createAgentInstance(type.identifier()); if (identifier.isEmpty()) { return AgentInstance(); } return fillAgentInstanceLight(identifier); } void AgentManagerPrivate::agentTypeAdded(const QString &identifier) { // Ignore agent types we already know about, for example because we called // readAgentTypes before. if (mTypes.contains(identifier)) { return; } if (mTypes.isEmpty()) { // The Akonadi ServerManager assumes that the server is up and running as soon // as it knows about at least one agent type. - // If we emit the typeAdded() signal here, it therefore thinks the server is + // If we Q_EMIT the typeAdded() signal here, it therefore thinks the server is // running. However, the AgentManager does not know about all agent types yet, // as the server might still have pending agentTypeAdded() signals, even though // it internally knows all agent types already. // This can cause situations where the client gets told by the ServerManager that // the server is running, yet the client will encounter an error because the // AgentManager doesn't know all types yet. // // Therefore, we read all agent types from the server here so they are known. readAgentTypes(); } const AgentType type = fillAgentType(identifier); if (type.isValid()) { mTypes.insert(identifier, type); - emit mParent->typeAdded(type); + Q_EMIT mParent->typeAdded(type); } } void AgentManagerPrivate::agentTypeRemoved(const QString &identifier) { if (!mTypes.contains(identifier)) { return; } const AgentType type = mTypes.take(identifier); - emit mParent->typeRemoved(type); + Q_EMIT mParent->typeRemoved(type); } void AgentManagerPrivate::agentInstanceAdded(const QString &identifier) { const AgentInstance instance = fillAgentInstance(identifier); if (instance.isValid()) { // It is possible that this function is called when the instance is already // in our list we filled initially in the constructor. // This happens when the constructor is called during Akonadi startup, when // the agent processes are not fully loaded and have no D-Bus interface yet. // The server-side agent manager then emits the instance added signal when // the D-Bus interface for the agent comes up. // In this case, we simply notify that the instance status has changed. const bool newAgentInstance = !mInstances.contains(identifier); if (newAgentInstance) { mInstances.insert(identifier, instance); - emit mParent->instanceAdded(instance); + Q_EMIT mParent->instanceAdded(instance); } else { mInstances.remove(identifier); mInstances.insert(identifier, instance); - emit mParent->instanceStatusChanged(instance); + Q_EMIT mParent->instanceStatusChanged(instance); } } } void AgentManagerPrivate::agentInstanceRemoved(const QString &identifier) { if (!mInstances.contains(identifier)) { return; } const AgentInstance instance = mInstances.take(identifier); - emit mParent->instanceRemoved(instance); + Q_EMIT mParent->instanceRemoved(instance); } void AgentManagerPrivate::agentInstanceStatusChanged(const QString &identifier, int status, const QString &msg) { if (!mInstances.contains(identifier)) { return; } AgentInstance &instance = mInstances[identifier]; instance.d->mStatus = status; instance.d->mStatusMessage = msg; - emit mParent->instanceStatusChanged(instance); + Q_EMIT mParent->instanceStatusChanged(instance); } void AgentManagerPrivate::agentInstanceProgressChanged(const QString &identifier, uint progress, const QString &msg) { if (!mInstances.contains(identifier)) { return; } AgentInstance &instance = mInstances[identifier]; instance.d->mProgress = progress; if (!msg.isEmpty()) { instance.d->mStatusMessage = msg; } - emit mParent->instanceProgressChanged(instance); + Q_EMIT mParent->instanceProgressChanged(instance); } void AgentManagerPrivate::agentInstanceWarning(const QString &identifier, const QString &msg) { if (!mInstances.contains(identifier)) { return; } AgentInstance &instance = mInstances[identifier]; - emit mParent->instanceWarning(instance, msg); + Q_EMIT mParent->instanceWarning(instance, msg); } void AgentManagerPrivate::agentInstanceError(const QString &identifier, const QString &msg) { if (!mInstances.contains(identifier)) { return; } AgentInstance &instance = mInstances[identifier]; - emit mParent->instanceError(instance, msg); + Q_EMIT mParent->instanceError(instance, msg); } void AgentManagerPrivate::agentInstanceOnlineChanged(const QString &identifier, bool state) { if (!mInstances.contains(identifier)) { return; } AgentInstance &instance = mInstances[identifier]; instance.d->mIsOnline = state; - emit mParent->instanceOnline(instance, state); + Q_EMIT mParent->instanceOnline(instance, state); } void AgentManagerPrivate::agentInstanceNameChanged(const QString &identifier, const QString &name) { if (!mInstances.contains(identifier)) { return; } AgentInstance &instance = mInstances[identifier]; instance.d->mName = name; - emit mParent->instanceNameChanged(instance); + Q_EMIT mParent->instanceNameChanged(instance); } void AgentManagerPrivate::readAgentTypes() { const QDBusReply types = mManager->agentTypes(); if (types.isValid()) { const QStringList lst = types.value(); for (const QString &type : lst) { const AgentType agentType = fillAgentType(type); if (agentType.isValid()) { mTypes.insert(type, agentType); - emit mParent->typeAdded(agentType); + Q_EMIT mParent->typeAdded(agentType); } } } } void AgentManagerPrivate::readAgentInstances() { const QDBusReply instances = mManager->agentInstances(); if (instances.isValid()) { const QStringList lst = instances.value(); for (const QString &instance : lst) { const AgentInstance agentInstance = fillAgentInstance(instance); if (agentInstance.isValid()) { mInstances.insert(instance, agentInstance); - emit mParent->instanceAdded(agentInstance); + Q_EMIT mParent->instanceAdded(agentInstance); } } } } AgentType AgentManagerPrivate::fillAgentType(const QString &identifier) const { AgentType type; type.d->mIdentifier = identifier; type.d->mName = mManager->agentName(identifier); type.d->mDescription = mManager->agentComment(identifier); type.d->mIconName = mManager->agentIcon(identifier); type.d->mMimeTypes = mManager->agentMimeTypes(identifier); type.d->mCapabilities = mManager->agentCapabilities(identifier); type.d->mCustomProperties = mManager->agentCustomProperties(identifier); return type; } void AgentManagerPrivate::setName(const AgentInstance &instance, const QString &name) { mManager->setAgentInstanceName(instance.identifier(), name); } void AgentManagerPrivate::setOnline(const AgentInstance &instance, bool state) { mManager->setAgentInstanceOnline(instance.identifier(), state); } void AgentManagerPrivate::configure(const AgentInstance &instance, QWidget *parent) { qlonglong winId = 0; if (parent) { winId = static_cast(parent->window()->winId()); } mManager->agentInstanceConfigure(instance.identifier(), winId); } void AgentManagerPrivate::synchronize(const AgentInstance &instance) { mManager->agentInstanceSynchronize(instance.identifier()); } void AgentManagerPrivate::synchronizeCollectionTree(const AgentInstance &instance) { mManager->agentInstanceSynchronizeCollectionTree(instance.identifier()); } void AgentManagerPrivate::synchronizeTags(const AgentInstance &instance) { mManager->agentInstanceSynchronizeTags(instance.identifier()); } void AgentManagerPrivate::synchronizeRelations(const AgentInstance &instance) { mManager->agentInstanceSynchronizeRelations(instance.identifier()); } AgentInstance AgentManagerPrivate::fillAgentInstance(const QString &identifier) const { AgentInstance instance; const QString agentTypeIdentifier = mManager->agentInstanceType(identifier); if (!mTypes.contains(agentTypeIdentifier)) { return instance; } instance.d->mType = mTypes.value(agentTypeIdentifier); instance.d->mIdentifier = identifier; instance.d->mName = mManager->agentInstanceName(identifier); instance.d->mStatus = mManager->agentInstanceStatus(identifier); instance.d->mStatusMessage = mManager->agentInstanceStatusMessage(identifier); instance.d->mProgress = mManager->agentInstanceProgress(identifier); instance.d->mIsOnline = mManager->agentInstanceOnline(identifier); return instance; } AgentInstance AgentManagerPrivate::fillAgentInstanceLight(const QString &identifier) const { AgentInstance instance; const QString agentTypeIdentifier = mManager->agentInstanceType(identifier); Q_ASSERT_X(mTypes.contains(agentTypeIdentifier), "fillAgentInstanceLight", "Requests non-existing agent type"); instance.d->mType = mTypes.value(agentTypeIdentifier); instance.d->mIdentifier = identifier; return instance; } void AgentManagerPrivate::serviceOwnerChanged(const QString &, const QString &oldOwner, const QString &) { if (oldOwner.isEmpty()) { if (mTypes.isEmpty()) { // just to be safe readAgentTypes(); } if (mInstances.isEmpty()) { readAgentInstances(); } } } void AgentManagerPrivate::createDBusInterface() { mTypes.clear(); mInstances.clear(); delete mManager; mManager = new org::freedesktop::Akonadi::AgentManager(ServerManager::serviceName(ServerManager::Control), QStringLiteral("/AgentManager"), KDBusConnectionPool::threadConnection(), mParent); QObject::connect(mManager, SIGNAL(agentTypeAdded(QString)), mParent, SLOT(agentTypeAdded(QString))); QObject::connect(mManager, SIGNAL(agentTypeRemoved(QString)), mParent, SLOT(agentTypeRemoved(QString))); QObject::connect(mManager, SIGNAL(agentInstanceAdded(QString)), mParent, SLOT(agentInstanceAdded(QString))); QObject::connect(mManager, SIGNAL(agentInstanceRemoved(QString)), mParent, SLOT(agentInstanceRemoved(QString))); QObject::connect(mManager, SIGNAL(agentInstanceStatusChanged(QString,int,QString)), mParent, SLOT(agentInstanceStatusChanged(QString,int,QString))); QObject::connect(mManager, SIGNAL(agentInstanceProgressChanged(QString,uint,QString)), mParent, SLOT(agentInstanceProgressChanged(QString,uint,QString))); QObject::connect(mManager, SIGNAL(agentInstanceNameChanged(QString,QString)), mParent, SLOT(agentInstanceNameChanged(QString,QString))); QObject::connect(mManager, SIGNAL(agentInstanceWarning(QString,QString)), mParent, SLOT(agentInstanceWarning(QString,QString))); QObject::connect(mManager, SIGNAL(agentInstanceError(QString,QString)), mParent, SLOT(agentInstanceError(QString,QString))); QObject::connect(mManager, SIGNAL(agentInstanceOnlineChanged(QString,bool)), mParent, SLOT(agentInstanceOnlineChanged(QString,bool))); if (mManager->isValid()) { readAgentTypes(); readAgentInstances(); } } AgentManager *AgentManagerPrivate::mSelf = nullptr; AgentManager::AgentManager() : QObject(nullptr) , d(new AgentManagerPrivate(this)) { // needed for queued connections on our signals qRegisterMetaType(); qRegisterMetaType(); d->createDBusInterface(); QDBusServiceWatcher *watcher = new QDBusServiceWatcher(ServerManager::serviceName(ServerManager::Control), KDBusConnectionPool::threadConnection(), QDBusServiceWatcher::WatchForOwnerChange, this); connect(watcher, &QDBusServiceWatcher::serviceOwnerChanged, this, [this](const QString &arg1, const QString &arg2 , const QString &arg3) { d->serviceOwnerChanged(arg1, arg2, arg3); }); } // @endcond AgentManager::~AgentManager() { delete d; } AgentManager *AgentManager::self() { if (!AgentManagerPrivate::mSelf) { AgentManagerPrivate::mSelf = new AgentManager(); } return AgentManagerPrivate::mSelf; } AgentType::List AgentManager::types() const { // Maybe the Control process is up and ready but we haven't been to the event loop yet so serviceOwnerChanged wasn't called yet. // In that case make sure to do it here, to avoid going into Broken state. if (d->mTypes.isEmpty()) { d->readAgentTypes(); } return Akonadi::valuesToVector(d->mTypes); } AgentType AgentManager::type(const QString &identifier) const { return d->mTypes.value(identifier); } AgentInstance::List AgentManager::instances() const { return Akonadi::valuesToVector(d->mInstances); } AgentInstance AgentManager::instance(const QString &identifier) const { return d->mInstances.value(identifier); } void AgentManager::removeInstance(const AgentInstance &instance) { d->mManager->removeAgentInstance(instance.identifier()); } void AgentManager::synchronizeCollection(const Collection &collection) { synchronizeCollection(collection, false); } void AgentManager::synchronizeCollection(const Collection &collection, bool recursive) { const QString resId = collection.resource(); Q_ASSERT(!resId.isEmpty()); d->mManager->agentInstanceSynchronizeCollection(resId, collection.id(), recursive); } #include "moc_agentmanager.cpp" diff --git a/src/core/asyncselectionhandler.cpp b/src/core/asyncselectionhandler.cpp index 528a1c77c..a96813680 100644 --- a/src/core/asyncselectionhandler.cpp +++ b/src/core/asyncselectionhandler.cpp @@ -1,95 +1,95 @@ /* Copyright (c) 2009 Tobias Koenig 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 "asyncselectionhandler_p.h" #include "models/entitytreemodel.h" #include "akonadicore_debug.h" using namespace Akonadi; AsyncSelectionHandler::AsyncSelectionHandler(QAbstractItemModel *model, QObject *parent) : QObject(parent) , mModel(model) { Q_ASSERT(mModel); connect(mModel, &QAbstractItemModel::rowsInserted, this, &AsyncSelectionHandler::rowsInserted); } AsyncSelectionHandler::~AsyncSelectionHandler() { } bool AsyncSelectionHandler::scanSubTree(const QModelIndex &index, bool searchForItem) { if (searchForItem) { const Item::Id id = index.data(EntityTreeModel::ItemIdRole).toLongLong(); if (mItem.id() == id) { - emit itemAvailable(index); + Q_EMIT itemAvailable(index); return true; } } else { const Collection::Id id = index.data(EntityTreeModel::CollectionIdRole).toLongLong(); if (mCollection.id() == id) { - emit collectionAvailable(index); + Q_EMIT collectionAvailable(index); return true; } } for (int row = 0; row < mModel->rowCount(index); ++row) { const QModelIndex childIndex = mModel->index(row, 0, index); //This should not normally happen, but if it does we end up in an endless loop if (!childIndex.isValid()) { qCWarning(AKONADICORE_LOG) << "Invalid child detected: " << index.data().toString(); Q_ASSERT(false); return false; } if (scanSubTree(childIndex, searchForItem)) { return true; } } return false; } void AsyncSelectionHandler::waitForCollection(const Collection &collection) { mCollection = collection; scanSubTree(QModelIndex(), false); } void AsyncSelectionHandler::waitForItem(const Item &item) { mItem = item; scanSubTree(QModelIndex(), true); } void AsyncSelectionHandler::rowsInserted(const QModelIndex &parent, int start, int end) { for (int i = start; i <= end; ++i) { scanSubTree(mModel->index(i, 0, parent), false); scanSubTree(mModel->index(i, 0, parent), true); } } #include "moc_asyncselectionhandler_p.cpp" diff --git a/src/core/changerecorder.cpp b/src/core/changerecorder.cpp index 0f0d2f4b6..39ed047e1 100644 --- a/src/core/changerecorder.cpp +++ b/src/core/changerecorder.cpp @@ -1,128 +1,128 @@ /* Copyright (c) 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 "changerecorder.h" #include "changerecorder_p.h" #include #include using namespace Akonadi; ChangeRecorder::ChangeRecorder(QObject *parent) : Monitor(new ChangeRecorderPrivate(nullptr, this), parent) { } ChangeRecorder::ChangeRecorder(ChangeRecorderPrivate *privateclass, QObject *parent) : Monitor(privateclass, parent) { } ChangeRecorder::~ChangeRecorder() { } void ChangeRecorder::setConfig(QSettings *settings) { Q_D(ChangeRecorder); if (settings) { d->settings = settings; Q_ASSERT(d->pendingNotifications.isEmpty()); d->loadNotifications(); } else if (d->settings) { if (d->enableChangeRecording) { d->saveNotifications(); } d->settings = settings; } } void ChangeRecorder::replayNext() { Q_D(ChangeRecorder); if (!d->enableChangeRecording) { return; } if (!d->pendingNotifications.isEmpty()) { const auto msg = d->pendingNotifications.head(); if (d->ensureDataAvailable(msg)) { d->emitNotification(msg); } else if (d->translateAndCompress(d->pipeline, msg)) { // The msg is now in both pipeline and pendingNotifications. // When data is available, MonitorPrivate::flushPipeline will emitNotification. // When changeProcessed is called, we'll finally remove it from pendingNotifications. } else { // In the case of a move where both source and destination are // ignored, we ignore the message and process the next one. d->dequeueNotification(); return replayNext(); } } else { // This is necessary when none of the notifications were accepted / processed // above, and so there is no one to call changeProcessed() and the ChangeReplay task // will be stuck forever in the ResourceScheduler. - emit nothingToReplay(); + Q_EMIT nothingToReplay(); } } bool ChangeRecorder::isEmpty() const { Q_D(const ChangeRecorder); return d->pendingNotifications.isEmpty(); } void ChangeRecorder::changeProcessed() { Q_D(ChangeRecorder); if (!d->enableChangeRecording) { return; } // changerecordertest.cpp calls changeProcessed after receiving nothingToReplay, // so test for emptiness. Not sure real code does this though. // Q_ASSERT( !d->pendingNotifications.isEmpty() ) if (!d->pendingNotifications.isEmpty()) { d->dequeueNotification(); } } void ChangeRecorder::setChangeRecordingEnabled(bool enable) { Q_D(ChangeRecorder); if (d->enableChangeRecording == enable) { return; } d->enableChangeRecording = enable; if (enable) { d->m_needFullSave = true; d->notificationsLoaded(); } else { d->dispatchNotifications(); } } QString Akonadi::ChangeRecorder::dumpNotificationListToString() const { Q_D(const ChangeRecorder); return d->dumpNotificationListToString(); } diff --git a/src/core/changerecorder_p.cpp b/src/core/changerecorder_p.cpp index ad5eb0dfe..bf84f8bdb 100644 --- a/src/core/changerecorder_p.cpp +++ b/src/core/changerecorder_p.cpp @@ -1,245 +1,245 @@ /* Copyright (c) 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 "changerecorder_p.h" #include "akonadicore_debug.h" #include "changerecorderjournal_p.h" #include #include #include #include #include using namespace Akonadi; ChangeRecorderPrivate::ChangeRecorderPrivate(ChangeNotificationDependenciesFactory *dependenciesFactory_, ChangeRecorder *parent) : MonitorPrivate(dependenciesFactory_, parent) , settings(nullptr) , enableChangeRecording(true) , m_lastKnownNotificationsCount(0) , m_startOffset(0) , m_needFullSave(true) { } int ChangeRecorderPrivate::pipelineSize() const { if (enableChangeRecording) { return 0; // we fill the pipeline ourselves when using change recording } return MonitorPrivate::pipelineSize(); } void ChangeRecorderPrivate::slotNotify(const Protocol::ChangeNotificationPtr &msg) { Q_Q(ChangeRecorder); const int oldChanges = pendingNotifications.size(); // with change recording disabled this will automatically take care of dispatching notification messages and saving MonitorPrivate::slotNotify(msg); if (enableChangeRecording && pendingNotifications.size() != oldChanges) { - emit q->changesAdded(); + Q_EMIT q->changesAdded(); } } // The QSettings object isn't actually used anymore, except for migrating old data // and it gives us the base of the filename to use. This is all historical. QString ChangeRecorderPrivate::notificationsFileName() const { return settings->fileName() + QStringLiteral("_changes.dat"); } void ChangeRecorderPrivate::loadNotifications() { pendingNotifications.clear(); Q_ASSERT(pipeline.isEmpty()); pipeline.clear(); const QString changesFileName = notificationsFileName(); /** * In an older version we recorded changes inside the settings object, however * for performance reasons we changed that to store them in a separated file. * If this file doesn't exists, it means we run the new version the first time, * so we have to read in the legacy list of changes first. */ if (!QFile::exists(changesFileName)) { settings->beginGroup(QStringLiteral("ChangeRecorder")); const int size = settings->beginReadArray(QStringLiteral("change")); for (int i = 0; i < size; ++i) { settings->setArrayIndex(i); auto msg = ChangeRecorderJournalReader::loadQSettingsNotification(settings); if (msg->isValid()) { pendingNotifications << msg; } } settings->endArray(); // save notifications to the new file... saveNotifications(); // ...delete the legacy list... settings->remove(QString()); settings->endGroup(); // ...and continue as usually } QFile file(changesFileName); if (file.open(QIODevice::ReadOnly)) { m_needFullSave = false; pendingNotifications = ChangeRecorderJournalReader::loadFrom(&file, m_needFullSave); } else { m_needFullSave = true; } notificationsLoaded(); } QString ChangeRecorderPrivate::dumpNotificationListToString() const { if (!settings) { return QStringLiteral("No settings set in ChangeRecorder yet."); } const QString changesFileName = notificationsFileName(); QFile file(changesFileName); if (!file.open(QIODevice::ReadOnly)) { return QLatin1String("Error reading ") + changesFileName; } QString result; bool dummy; const auto notifications = ChangeRecorderJournalReader::loadFrom(&file, dummy); for (const auto &n : notifications) { result += Protocol::debugString(n) + QLatin1Char('\n'); } return result; } void ChangeRecorderPrivate::writeStartOffset() { if (!settings) { return; } QFile file(notificationsFileName()); if (!file.open(QIODevice::ReadWrite)) { qCWarning(AKONADICORE_LOG) << "Could not update notifications in file" << file.fileName(); return; } // Skip "countAndVersion" file.seek(8); //qCDebug(AKONADICORE_LOG) << "Writing start offset=" << m_startOffset; QDataStream stream(&file); stream.setVersion(QDataStream::Qt_4_6); stream << static_cast(m_startOffset); // Everything else stays unchanged } void ChangeRecorderPrivate::saveNotifications() { if (!settings) { return; } QFile file(notificationsFileName()); QFileInfo info(file); if (!QFile::exists(info.absolutePath())) { QDir dir; dir.mkpath(info.absolutePath()); } if (!file.open(QIODevice::WriteOnly)) { qCWarning(AKONADICORE_LOG) << "Could not save notifications to file" << file.fileName(); return; } ChangeRecorderJournalWriter::saveTo(pendingNotifications, &file); m_needFullSave = false; m_startOffset = 0; } void ChangeRecorderPrivate::notificationsEnqueued(int count) { // Just to ensure the contract is kept, and these two methods are always properly called. if (enableChangeRecording) { m_lastKnownNotificationsCount += count; if (m_lastKnownNotificationsCount != pendingNotifications.count()) { qCWarning(AKONADICORE_LOG) << this << "The number of pending notifications changed without telling us! Expected" << m_lastKnownNotificationsCount << "but got" << pendingNotifications.count() << "Caller just added" << count; Q_ASSERT(pendingNotifications.count() == m_lastKnownNotificationsCount); } saveNotifications(); } } void ChangeRecorderPrivate::dequeueNotification() { if (pendingNotifications.isEmpty()) { return; } pendingNotifications.dequeue(); if (enableChangeRecording) { Q_ASSERT(pendingNotifications.count() == m_lastKnownNotificationsCount - 1); --m_lastKnownNotificationsCount; if (m_needFullSave || pendingNotifications.isEmpty()) { saveNotifications(); } else { ++m_startOffset; writeStartOffset(); } } } void ChangeRecorderPrivate::notificationsErased() { if (enableChangeRecording) { m_lastKnownNotificationsCount = pendingNotifications.count(); m_needFullSave = true; saveNotifications(); } } void ChangeRecorderPrivate::notificationsLoaded() { m_lastKnownNotificationsCount = pendingNotifications.count(); m_startOffset = 0; } bool ChangeRecorderPrivate::emitNotification(const Protocol::ChangeNotificationPtr &msg) { const bool someoneWasListening = MonitorPrivate::emitNotification(msg); if (!someoneWasListening && enableChangeRecording) { //If no signal was emitted (e.g. because no one was connected to it), no one is going to call changeProcessed, so we help ourselves. dequeueNotification(); QMetaObject::invokeMethod(q_ptr, "replayNext", Qt::QueuedConnection); } return someoneWasListening; } diff --git a/src/core/conflicthandler.cpp b/src/core/conflicthandler.cpp index 82dd822fa..8f96ecfb7 100644 --- a/src/core/conflicthandler.cpp +++ b/src/core/conflicthandler.cpp @@ -1,142 +1,142 @@ /* Copyright (c) 2010 KDAB Author: Tobias Koenig 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 "conflicthandler_p.h" #include "itemcreatejob.h" #include "itemfetchjob.h" #include "itemfetchscope.h" #include "itemmodifyjob.h" #include "session.h" #include using namespace Akonadi; ConflictHandler::ConflictHandler(ConflictType type, QObject *parent) : QObject(parent) , mConflictType(type) , mSession(new Session("conflict handling session", this)) { } void ConflictHandler::setConflictingItems(const Akonadi::Item &changedItem, const Akonadi::Item &conflictingItem) { mChangedItem = changedItem; mConflictingItem = conflictingItem; } void ConflictHandler::start() { if (mConflictType == LocalLocalConflict || mConflictType == LocalRemoteConflict) { ItemFetchJob *job = new ItemFetchJob(mConflictingItem, mSession); job->fetchScope().fetchFullPayload(); job->fetchScope().setAncestorRetrieval(ItemFetchScope::Parent); connect(job, &ItemFetchJob::result, this, &ConflictHandler::slotOtherItemFetched); } else { resolve(); } } void ConflictHandler::slotOtherItemFetched(KJob *job) { if (job->error()) { - emit error(job->errorText()); //TODO: extend error message + Q_EMIT error(job->errorText()); //TODO: extend error message return; } ItemFetchJob *fetchJob = qobject_cast(job); if (fetchJob->items().isEmpty()) { - emit error(i18n("Did not find other item for conflict handling")); + Q_EMIT error(i18n("Did not find other item for conflict handling")); return; } mConflictingItem = fetchJob->items().at(0); QMetaObject::invokeMethod(this, &ConflictHandler::resolve, Qt::QueuedConnection); } void ConflictHandler::resolve() { #pragma message ("warning KF5 Port me!") #if 0 ConflictResolveDialog dlg; dlg.setConflictingItems(mChangedItem, mConflictingItem); dlg.exec(); const ResolveStrategy strategy = dlg.resolveStrategy(); switch (strategy) { case UseLocalItem: useLocalItem(); break; case UseOtherItem: useOtherItem(); break; case UseBothItems: useBothItems(); break; } #endif } void ConflictHandler::useLocalItem() { // We have to overwrite the other item inside the Akonadi storage with the local // item. To make this happen, we have to set the revision of the local item to // the one of the other item to let the Akonadi server accept it. Item newItem(mChangedItem); newItem.setRevision(mConflictingItem.revision()); ItemModifyJob *job = new ItemModifyJob(newItem, mSession); connect(job, &ItemModifyJob::result, this, &ConflictHandler::slotUseLocalItemFinished); } void ConflictHandler::slotUseLocalItemFinished(KJob *job) { if (job->error()) { - emit error(job->errorText()); //TODO: extend error message + Q_EMIT error(job->errorText()); //TODO: extend error message } else { - emit conflictResolved(); + Q_EMIT conflictResolved(); } } void ConflictHandler::useOtherItem() { // We can just ignore the local item here and leave everything as it is. - emit conflictResolved(); + Q_EMIT conflictResolved(); } void ConflictHandler::useBothItems() { // We have to create a new item for the local item under the collection that has // been retrieved when we fetched the other item. ItemCreateJob *job = new ItemCreateJob(mChangedItem, mConflictingItem.parentCollection(), mSession); connect(job, &ItemCreateJob::result, this, &ConflictHandler::slotUseBothItemsFinished); } void ConflictHandler::slotUseBothItemsFinished(KJob *job) { if (job->error()) { - emit error(job->errorText()); //TODO: extend error message + Q_EMIT error(job->errorText()); //TODO: extend error message } else { - emit conflictResolved(); + Q_EMIT conflictResolved(); } } #include "moc_conflicthandler_p.cpp" diff --git a/src/core/entitycache_p.h b/src/core/entitycache_p.h index 32b2241a4..71c1cd12e 100644 --- a/src/core/entitycache_p.h +++ b/src/core/entitycache_p.h @@ -1,544 +1,544 @@ /* Copyright (c) 2009 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. */ #ifndef AKONADI_ENTITYCACHE_P_H #define AKONADI_ENTITYCACHE_P_H #include "item.h" #include "itemfetchjob.h" #include "itemfetchscope.h" #include "collection.h" #include "collectionfetchjob.h" #include "collectionfetchscope.h" #include "tag.h" #include "tagfetchjob.h" #include "tagfetchscope.h" #include "session.h" #include "akonaditests_export.h" #include #include #include #include #include class KJob; Q_DECLARE_METATYPE(QList) namespace Akonadi { /** @internal QObject part of EntityCache. */ class AKONADI_TESTS_EXPORT EntityCacheBase : public QObject { Q_OBJECT public: explicit EntityCacheBase(Session *session, QObject *parent = nullptr); void setSession(Session *session); protected: Session *session = nullptr; Q_SIGNALS: void dataAvailable(); private Q_SLOTS: virtual void processResult(KJob *job) = 0; }; template struct EntityCacheNode { EntityCacheNode() : pending(false) , invalid(false) { } EntityCacheNode(typename T::Id id) : entity(T(id)) , pending(true) , invalid(false) { } T entity; bool pending; bool invalid; }; /** * @internal * A in-memory FIFO cache for a small amount of Item or Collection objects. */ template class EntityCache : public EntityCacheBase { public: typedef FetchScope_ FetchScope; explicit EntityCache(int maxCapacity, Session *session = nullptr, QObject *parent = nullptr) : EntityCacheBase(session, parent) , mCapacity(maxCapacity) { } ~EntityCache() override { qDeleteAll(mCache); } /** Object is available in the cache and can be retrieved. */ bool isCached(typename T::Id id) const { EntityCacheNode *node = cacheNodeForId(id); return node && !node->pending; } /** Object has been requested but is not yet loaded into the cache or is already available. */ bool isRequested(typename T::Id id) const { return cacheNodeForId(id); } /** Returns the cached object if available, an empty instance otherwise. */ virtual T retrieve(typename T::Id id) const { EntityCacheNode *node = cacheNodeForId(id); if (node && !node->pending && !node->invalid) { return node->entity; } return T(); } /** Marks the cache entry as invalid, use in case the object has been deleted on the server. */ void invalidate(typename T::Id id) { EntityCacheNode *node = cacheNodeForId(id); if (node) { node->invalid = true; } } /** Triggers a re-fetching of a cache entry, use if it has changed on the server. */ void update(typename T::Id id, const FetchScope &scope) { EntityCacheNode *node = cacheNodeForId(id); if (node) { mCache.removeAll(node); if (node->pending) { request(id, scope); } delete node; } } /** Requests the object to be cached if it is not yet in the cache. @returns @c true if it was in the cache already. */ virtual bool ensureCached(typename T::Id id, const FetchScope &scope) { EntityCacheNode *node = cacheNodeForId(id); if (!node) { request(id, scope); return false; } return !node->pending; } /** Asks the cache to retrieve @p id. @p request is used as a token to indicate which request has been finished in the dataAvailable() signal. */ virtual void request(typename T::Id id, const FetchScope &scope) { Q_ASSERT(!isRequested(id)); shrinkCache(); EntityCacheNode *node = new EntityCacheNode(id); FetchJob *job = createFetchJob(id, scope); job->setProperty("EntityCacheNode", QVariant::fromValue(id)); connect(job, SIGNAL(result(KJob *)), SLOT(processResult(KJob *))); mCache.enqueue(node); } private: EntityCacheNode *cacheNodeForId(typename T::Id id) const { for (typename QQueue *>::const_iterator it = mCache.constBegin(), endIt = mCache.constEnd(); it != endIt; ++it) { if ((*it)->entity.id() == id) { return *it; } } return nullptr; } void processResult(KJob *job) override { if (job->error()) { //This can happen if we have stale notifications for items that have already been removed } typename T::Id id = job->property("EntityCacheNode").template value(); EntityCacheNode *node = cacheNodeForId(id); if (!node) { return; // got replaced in the meantime } node->pending = false; extractResult(node, job); // make sure we find this node again if something went wrong here, // most likely the object got deleted from the server in the meantime if (node->entity.id() != id) { // TODO: Recursion guard? If this is called with non-existing ids, the if will never be true! node->entity.setId(id); node->invalid = true; } - emit dataAvailable(); + Q_EMIT dataAvailable(); } void extractResult(EntityCacheNode *node, KJob *job) const; inline FetchJob *createFetchJob(typename T::Id id, const FetchScope &scope) { FetchJob *fetch = new FetchJob(T(id), session); fetch->setFetchScope(scope); return fetch; } /** Tries to reduce the cache size until at least one more object fits in. */ void shrinkCache() { while (mCache.size() >= mCapacity && !mCache.first()->pending) { delete mCache.dequeue(); } } private: QQueue *> mCache; int mCapacity; }; template<> inline void EntityCache::extractResult(EntityCacheNode *node, KJob *job) const { CollectionFetchJob *fetch = qobject_cast(job); Q_ASSERT(fetch); if (fetch->collections().isEmpty()) { node->entity = Collection(); } else { node->entity = fetch->collections().at(0); } } template<> inline void EntityCache::extractResult(EntityCacheNode *node, KJob *job) const { ItemFetchJob *fetch = qobject_cast(job); Q_ASSERT(fetch); if (fetch->items().isEmpty()) { node->entity = Item(); } else { node->entity = fetch->items().at(0); } } template<> inline void EntityCache::extractResult(EntityCacheNode *node, KJob *job) const { TagFetchJob *fetch = qobject_cast(job); Q_ASSERT(fetch); if (fetch->tags().isEmpty()) { node->entity = Tag(); } else { node->entity = fetch->tags().at(0); } } template<> inline CollectionFetchJob *EntityCache::createFetchJob(Collection::Id id, const CollectionFetchScope &scope) { CollectionFetchJob *fetch = new CollectionFetchJob(Collection(id), CollectionFetchJob::Base, session); fetch->setFetchScope(scope); return fetch; } typedef EntityCache CollectionCache; typedef EntityCache ItemCache; typedef EntityCache TagCache; template struct EntityListCacheNode { EntityListCacheNode() : pending(false) , invalid(false) { } EntityListCacheNode(typename T::Id id) : entity(id) , pending(true) , invalid(false) { } T entity; bool pending; bool invalid; }; template class EntityListCache : public EntityCacheBase { public: typedef FetchScope_ FetchScope; explicit EntityListCache(int maxCapacity, Session *session = nullptr, QObject *parent = nullptr) : EntityCacheBase(session, parent) , mCapacity(maxCapacity) { } ~EntityListCache() override { qDeleteAll(mCache); } /** Returns the cached object if available, an empty instance otherwise. */ typename T::List retrieve(const QList &ids) const { typename T::List list; for (typename T::Id id : ids) { EntityListCacheNode *node = mCache.value(id); if (!node || node->pending || node->invalid) { return typename T::List(); } list << node->entity; } return list; } /** Requests the object to be cached if it is not yet in the cache. @returns @c true if it was in the cache already. */ bool ensureCached(const QList &ids, const FetchScope &scope) { QList toRequest; bool result = true; for (typename T::Id id : ids) { EntityListCacheNode *node = mCache.value(id); if (!node) { toRequest << id; continue; } if (node->pending) { result = false; } } if (!toRequest.isEmpty()) { request(toRequest, scope, ids); return false; } return result; } /** Marks the cache entry as invalid, use in case the object has been deleted on the server. */ void invalidate(const QList &ids) { for (typename T::Id id : ids) { EntityListCacheNode *node = mCache.value(id); if (node) { node->invalid = true; } } } /** Triggers a re-fetching of a cache entry, use if it has changed on the server. */ void update(const QList &ids, const FetchScope &scope) { QList toRequest; for (typename T::Id id : ids) { EntityListCacheNode *node = mCache.value(id); if (node) { mCache.remove(id); if (node->pending) { toRequest << id; } delete node; } } if (!toRequest.isEmpty()) { request(toRequest, scope); } } /** Asks the cache to retrieve @p id. @p request is used as a token to indicate which request has been finished in the dataAvailable() signal. */ void request(const QList &ids, const FetchScope &scope, const QList &preserveIds = QList()) { Q_ASSERT(isNotRequested(ids)); shrinkCache(preserveIds); for (typename T::Id id : ids) { EntityListCacheNode *node = new EntityListCacheNode(id); mCache.insert(id, node); } FetchJob *job = createFetchJob(ids, scope); job->setProperty("EntityListCacheIds", QVariant::fromValue>(ids)); connect(job, SIGNAL(result(KJob *)), SLOT(processResult(KJob *))); } bool isNotRequested(const QList &ids) const { for (typename T::Id id : ids) { if (mCache.contains(id)) { return false; } } return true; } /** Object is available in the cache and can be retrieved. */ bool isCached(const QList &ids) const { for (typename T::Id id : ids) { EntityListCacheNode *node = mCache.value(id); if (!node || node->pending) { return false; } } return true; } private: /** Tries to reduce the cache size until at least one more object fits in. */ void shrinkCache(const QList &preserveIds) { typename QHash *>::Iterator iter = mCache.begin(); while (iter != mCache.end() && mCache.size() >= mCapacity) { if (iter.value()->pending || preserveIds.contains(iter.key())) { ++iter; continue; } delete iter.value(); iter = mCache.erase(iter); } } inline FetchJob *createFetchJob(const QList &ids, const FetchScope &scope) { FetchJob *job = new FetchJob(ids, session); job->setFetchScope(scope); return job; } void processResult(KJob *job) override { if (job->error()) { qWarning() << job->errorString(); } const QList ids = job->property("EntityListCacheIds").value>(); typename T::List entities; extractResults(job, entities); for (typename T::Id id : ids) { EntityListCacheNode *node = mCache.value(id); if (!node) { continue; // got replaced in the meantime } node->pending = false; T result; typename T::List::Iterator iter = entities.begin(); for (; iter != entities.end(); ++iter) { if ((*iter).id() == id) { result = *iter; entities.erase(iter); break; } } // make sure we find this node again if something went wrong here, // most likely the object got deleted from the server in the meantime if (!result.isValid()) { node->entity = T(id); node->invalid = true; } else { node->entity = result; } } - emit dataAvailable(); + Q_EMIT dataAvailable(); } void extractResults(KJob *job, typename T::List &entities) const; private: QHash *> mCache; int mCapacity; }; template<> inline void EntityListCache::extractResults(KJob *job, Collection::List &collections) const { CollectionFetchJob *fetch = qobject_cast(job); Q_ASSERT(fetch); collections = fetch->collections(); } template<> inline void EntityListCache::extractResults(KJob *job, Item::List &items) const { ItemFetchJob *fetch = qobject_cast(job); Q_ASSERT(fetch); items = fetch->items(); } template<> inline void EntityListCache::extractResults(KJob *job, Tag::List &tags) const { TagFetchJob *fetch = qobject_cast(job); Q_ASSERT(fetch); tags = fetch->tags(); } template<> inline CollectionFetchJob *EntityListCache::createFetchJob(const QList &ids, const CollectionFetchScope &scope) { CollectionFetchJob *fetch = new CollectionFetchJob(ids, CollectionFetchJob::Base, session); fetch->setFetchScope(scope); return fetch; } typedef EntityListCache CollectionListCache; typedef EntityListCache ItemListCache; typedef EntityListCache TagListCache; } #endif diff --git a/src/core/itemsync.cpp b/src/core/itemsync.cpp index 6f9aa8e1e..28a93be6a 100644 --- a/src/core/itemsync.cpp +++ b/src/core/itemsync.cpp @@ -1,554 +1,554 @@ /* Copyright (c) 2007 Tobias Koenig Copyright (c) 2007 Volker Krause Copyright (c) 2014 Christian Mollekopf 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 "itemsync.h" #include "job_p.h" #include "collection.h" #include "item.h" #include "item_p.h" #include "itemcreatejob.h" #include "itemdeletejob.h" #include "itemfetchjob.h" #include "itemmodifyjob.h" #include "transactionsequence.h" #include "itemfetchscope.h" #include "akonadicore_debug.h" using namespace Akonadi; /** * @internal */ class Akonadi::ItemSyncPrivate : public JobPrivate { public: ItemSyncPrivate(ItemSync *parent) : JobPrivate(parent) , mTransactionMode(ItemSync::SingleTransaction) , mCurrentTransaction(nullptr) , mTransactionJobs(0) , mPendingJobs(0) , mProgress(0) , mTotalItems(-1) , mTotalItemsProcessed(0) , mStreaming(false) , mIncremental(false) , mDeliveryDone(false) , mFinished(false) , mFullListingDone(false) , mProcessingBatch(false) , mDisableAutomaticDeliveryDone(false) , mBatchSize(10) , mMergeMode(Akonadi::ItemSync::RIDMerge) { // we want to fetch all data by default mFetchScope.fetchFullPayload(); mFetchScope.fetchAllAttributes(); } void createOrMerge(const Item &item); void checkDone(); void slotItemsReceived(const Item::List &items); void slotLocalListDone(KJob *job); void slotLocalDeleteDone(KJob *job); void slotLocalChangeDone(KJob *job); void execute(); void processItems(); void processBatch(); void deleteItems(const Item::List &items); void slotTransactionResult(KJob *job); void requestTransaction(); Job *subjobParent() const; void fetchLocalItemsToDelete(); QString jobDebuggingString() const override; bool allProcessed() const; Q_DECLARE_PUBLIC(ItemSync) Collection mSyncCollection; QSet mListedItems; ItemSync::TransactionMode mTransactionMode; TransactionSequence *mCurrentTransaction; int mTransactionJobs; // fetch scope for initial item listing ItemFetchScope mFetchScope; Akonadi::Item::List mRemoteItemQueue; Akonadi::Item::List mRemovedRemoteItemQueue; Akonadi::Item::List mCurrentBatchRemoteItems; Akonadi::Item::List mCurrentBatchRemovedRemoteItems; Akonadi::Item::List mItemsToDelete; // create counter int mPendingJobs; int mProgress; int mTotalItems; int mTotalItemsProcessed; bool mStreaming; bool mIncremental; bool mDeliveryDone; bool mFinished; bool mFullListingDone; bool mProcessingBatch; bool mDisableAutomaticDeliveryDone; int mBatchSize; Akonadi::ItemSync::MergeMode mMergeMode; }; void ItemSyncPrivate::createOrMerge(const Item &item) { Q_Q(ItemSync); // don't try to do anything in error state if (q->error()) { return; } mPendingJobs++; ItemCreateJob *create = new ItemCreateJob(item, mSyncCollection, subjobParent()); ItemCreateJob::MergeOptions merge = ItemCreateJob::Silent; if (mMergeMode == ItemSync::GIDMerge && !item.gid().isEmpty()) { merge |= ItemCreateJob::GID; } else { merge |= ItemCreateJob::RID; } create->setMerge(merge); q->connect(create, &ItemCreateJob::result, q, [this](KJob *job) {slotLocalChangeDone(job);}); } bool ItemSyncPrivate::allProcessed() const { return mDeliveryDone && mCurrentBatchRemoteItems.isEmpty() && mRemoteItemQueue.isEmpty() && mRemovedRemoteItemQueue.isEmpty() && mCurrentBatchRemovedRemoteItems.isEmpty(); } void ItemSyncPrivate::checkDone() { Q_Q(ItemSync); q->setProcessedAmount(KJob::Bytes, mProgress); if (mPendingJobs > 0) { return; } if (mTransactionJobs > 0) { //Commit the current transaction if we're in batch processing mode or done //and wait until the transaction is committed to process the next batch if (mTransactionMode == ItemSync::MultipleTransactions || (mDeliveryDone && mRemoteItemQueue.isEmpty())) { if (mCurrentTransaction) { - q->emit transactionCommitted(); + q->Q_EMIT transactionCommitted(); mCurrentTransaction->commit(); mCurrentTransaction = nullptr; } return; } } mProcessingBatch = false; if (!mRemoteItemQueue.isEmpty()) { execute(); //We don't have enough items, request more if (!mProcessingBatch) { - q->emit readyForNextBatch(mBatchSize - mRemoteItemQueue.size()); + q->Q_EMIT readyForNextBatch(mBatchSize - mRemoteItemQueue.size()); } return; } - q->emit readyForNextBatch(mBatchSize); + q->Q_EMIT readyForNextBatch(mBatchSize); if (allProcessed() && !mFinished) { // prevent double result emission, can happen since checkDone() is called from all over the place qCDebug(AKONADICORE_LOG) << "ItemSync of collection" << mSyncCollection.id() << "finished"; mFinished = true; q->emitResult(); } } ItemSync::ItemSync(const Collection &collection, QObject *parent) : Job(new ItemSyncPrivate(this), parent) { Q_D(ItemSync); d->mSyncCollection = collection; } ItemSync::~ItemSync() { } void ItemSync::setFullSyncItems(const Item::List &items) { /* * We received a list of items from the server: * * fetch all local id's + rid's only * * check each full sync item whether it's locally available * * if it is modify the item * * if it's not create it * * delete all superfluous items */ Q_D(ItemSync); Q_ASSERT(!d->mIncremental); if (!d->mStreaming) { d->mDeliveryDone = true; } d->mRemoteItemQueue += items; d->mTotalItemsProcessed += items.count(); qCDebug(AKONADICORE_LOG) << "Received batch: " << items.count() << "Already processed: " << d->mTotalItemsProcessed << "Expected total amount: " << d->mTotalItems; if (!d->mDisableAutomaticDeliveryDone && (d->mTotalItemsProcessed == d->mTotalItems)) { d->mDeliveryDone = true; } d->execute(); } void ItemSync::setTotalItems(int amount) { Q_D(ItemSync); Q_ASSERT(!d->mIncremental); Q_ASSERT(amount >= 0); setStreamingEnabled(true); qCDebug(AKONADICORE_LOG) << "Expected total amount:" << amount; d->mTotalItems = amount; setTotalAmount(KJob::Bytes, amount); if (!d->mDisableAutomaticDeliveryDone && (d->mTotalItems == 0)) { d->mDeliveryDone = true; d->execute(); } } void ItemSync::setDisableAutomaticDeliveryDone(bool disable) { Q_D(ItemSync); d->mDisableAutomaticDeliveryDone = disable; } void ItemSync::setIncrementalSyncItems(const Item::List &changedItems, const Item::List &removedItems) { /* * We received an incremental listing of items: * * for each changed item: * ** If locally available => modify * ** else => create * * removed items can be removed right away */ Q_D(ItemSync); d->mIncremental = true; if (!d->mStreaming) { d->mDeliveryDone = true; } d->mRemoteItemQueue += changedItems; d->mRemovedRemoteItemQueue += removedItems; d->mTotalItemsProcessed += changedItems.count() + removedItems.count(); qCDebug(AKONADICORE_LOG) << "Received: " << changedItems.count() << "Removed: " << removedItems.count() << "In total: " << d->mTotalItemsProcessed << " Wanted: " << d->mTotalItems; if (!d->mDisableAutomaticDeliveryDone && (d->mTotalItemsProcessed == d->mTotalItems)) { d->mDeliveryDone = true; } d->execute(); } void ItemSync::setFetchScope(ItemFetchScope &fetchScope) { Q_D(ItemSync); d->mFetchScope = fetchScope; } ItemFetchScope &ItemSync::fetchScope() { Q_D(ItemSync); return d->mFetchScope; } void ItemSync::doStart() { } void ItemSyncPrivate::fetchLocalItemsToDelete() { Q_Q(ItemSync); if (mIncremental) { qFatal("This must not be called while in incremental mode"); return; } ItemFetchJob *job = new ItemFetchJob(mSyncCollection, subjobParent()); job->fetchScope().setFetchRemoteIdentification(true); job->fetchScope().setFetchModificationTime(false); job->setDeliveryOption(ItemFetchJob::EmitItemsIndividually); // we only can fetch parts already in the cache, otherwise this will deadlock job->fetchScope().setCacheOnly(true); QObject::connect(job, &ItemFetchJob::itemsReceived, q, [this](const Akonadi::Item::List &lst) { slotItemsReceived(lst); }); QObject::connect(job, &ItemFetchJob::result, q, [this](KJob *job) { slotLocalListDone(job); }); mPendingJobs++; } void ItemSyncPrivate::slotItemsReceived(const Item::List &items) { for (const Akonadi::Item &item : items) { //Don't delete items that have not yet been synchronized if (item.remoteId().isEmpty()) { continue; } if (!mListedItems.contains(item.remoteId())) { mItemsToDelete << Item(item.id()); } } } void ItemSyncPrivate::slotLocalListDone(KJob *job) { mPendingJobs--; if (job->error()) { qCWarning(AKONADICORE_LOG) << job->errorString(); } deleteItems(mItemsToDelete); checkDone(); } QString ItemSyncPrivate::jobDebuggingString() const { // TODO: also print out mIncremental and mTotalItemsProcessed, but they are set after the job // started, so this requires passing jobDebuggingString to jobEnded(). return QStringLiteral("Collection %1 (%2)").arg(mSyncCollection.id()).arg(mSyncCollection.name()); } void ItemSyncPrivate::execute() { //shouldn't happen if (mFinished) { qCWarning(AKONADICORE_LOG) << "Call to execute() on finished job."; Q_ASSERT(false); return; } //not doing anything, start processing if (!mProcessingBatch) { if (mRemoteItemQueue.size() >= mBatchSize || mDeliveryDone) { //we have a new batch to process const int num = qMin(mBatchSize, mRemoteItemQueue.size()); mCurrentBatchRemoteItems.reserve(mBatchSize); std::move(mRemoteItemQueue.begin(), mRemoteItemQueue.begin() + num, std::back_inserter(mCurrentBatchRemoteItems)); mRemoteItemQueue.erase(mRemoteItemQueue.begin(), mRemoteItemQueue.begin() + num); mCurrentBatchRemovedRemoteItems += mRemovedRemoteItemQueue; mRemovedRemoteItemQueue.clear(); } else { //nothing to do, let's wait for more data return; } mProcessingBatch = true; processBatch(); return; } checkDone(); } //process the current batch of items void ItemSyncPrivate::processBatch() { if (mCurrentBatchRemoteItems.isEmpty() && !mDeliveryDone) { return; } //request a transaction, there are items that require processing requestTransaction(); processItems(); // removed if (!mIncremental && allProcessed()) { //the full listing is done and we know which items to remove fetchLocalItemsToDelete(); } else { deleteItems(mCurrentBatchRemovedRemoteItems); mCurrentBatchRemovedRemoteItems.clear(); } checkDone(); } void ItemSyncPrivate::processItems() { // added / updated for (const Item &remoteItem : qAsConst(mCurrentBatchRemoteItems)) { if (remoteItem.remoteId().isEmpty()) { qCWarning(AKONADICORE_LOG) << "Item " << remoteItem.id() << " does not have a remote identifier"; continue; } if (!mIncremental) { mListedItems << remoteItem.remoteId(); } createOrMerge(remoteItem); } mCurrentBatchRemoteItems.clear(); } void ItemSyncPrivate::deleteItems(const Item::List &itemsToDelete) { Q_Q(ItemSync); // if in error state, better not change anything anymore if (q->error()) { return; } if (itemsToDelete.isEmpty()) { return; } mPendingJobs++; ItemDeleteJob *job = new ItemDeleteJob(itemsToDelete, subjobParent()); q->connect(job, &ItemDeleteJob::result, q, [this](KJob *job) { slotLocalDeleteDone(job); }); // It can happen that the groupware servers report us deleted items // twice, in this case this item delete job will fail on the second try. // To avoid a rollback of the complete transaction we gracefully allow the job // to fail :) TransactionSequence *transaction = qobject_cast(subjobParent()); if (transaction) { transaction->setIgnoreJobFailure(job); } } void ItemSyncPrivate::slotLocalDeleteDone(KJob *job) { if (job->error()) { qCWarning(AKONADICORE_LOG) << "Deleting items from the akonadi database failed:" << job->errorString(); } mPendingJobs--; mProgress++; checkDone(); } void ItemSyncPrivate::slotLocalChangeDone(KJob *job) { if (job->error()) { qCWarning(AKONADICORE_LOG) << "Creating/updating items from the akonadi database failed:" << job->errorString(); } mPendingJobs--; mProgress++; checkDone(); } void ItemSyncPrivate::slotTransactionResult(KJob *job) { --mTransactionJobs; if (mCurrentTransaction == job) { mCurrentTransaction = nullptr; } checkDone(); } void ItemSyncPrivate::requestTransaction() { Q_Q(ItemSync); //we never want parallel transactions, single transaction just makes one big transaction, and multi transaction uses multiple transaction sequentially if (!mCurrentTransaction) { ++mTransactionJobs; mCurrentTransaction = new TransactionSequence(q); mCurrentTransaction->setAutomaticCommittingEnabled(false); QObject::connect(mCurrentTransaction, &TransactionSequence::result, q, [this](KJob *job) { slotTransactionResult(job); }); } } Job *ItemSyncPrivate::subjobParent() const { Q_Q(const ItemSync); if (mCurrentTransaction && mTransactionMode != ItemSync::NoTransaction) { return mCurrentTransaction; } return const_cast(q); } void ItemSync::setStreamingEnabled(bool enable) { Q_D(ItemSync); d->mStreaming = enable; } void ItemSync::deliveryDone() { Q_D(ItemSync); Q_ASSERT(d->mStreaming); d->mDeliveryDone = true; d->execute(); } void ItemSync::slotResult(KJob *job) { if (job->error()) { qCWarning(AKONADICORE_LOG) << "Error during ItemSync: " << job->errorString(); // pretend there were no errors Akonadi::Job::removeSubjob(job); // propagate the first error we got but continue, we might still be fed with stuff from a resource if (!error()) { setError(job->error()); setErrorText(job->errorText()); } } else { Akonadi::Job::slotResult(job); } } void ItemSync::rollback() { Q_D(ItemSync); qCDebug(AKONADICORE_LOG) << "The item sync is being rolled-back."; setError(UserCanceled); if (d->mCurrentTransaction) { d->mCurrentTransaction->rollback(); } d->mDeliveryDone = true; // user wont deliver more data d->execute(); // end this in an ordered way, since we have an error set no real change will be done } void ItemSync::setTransactionMode(ItemSync::TransactionMode mode) { Q_D(ItemSync); d->mTransactionMode = mode; } int ItemSync::batchSize() const { Q_D(const ItemSync); return d->mBatchSize; } void ItemSync::setBatchSize(int size) { Q_D(ItemSync); d->mBatchSize = size; } ItemSync::MergeMode ItemSync::mergeMode() const { Q_D(const ItemSync); return d->mMergeMode; } void ItemSync::setMergeMode(MergeMode mergeMode) { Q_D(ItemSync); d->mMergeMode = mergeMode; } #include "moc_itemsync.cpp" diff --git a/src/core/jobs/collectionfetchjob.cpp b/src/core/jobs/collectionfetchjob.cpp index d2b58082e..ba1b47c2c 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()) { - emit q->collectionsReceived(mPendingCollections); + 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(", ")) + QLatin1String(")"); 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()) { 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/jobs/itemfetchjob.cpp b/src/core/jobs/itemfetchjob.cpp index ce3f6f760..ab4361c36 100644 --- a/src/core/jobs/itemfetchjob.cpp +++ b/src/core/jobs/itemfetchjob.cpp @@ -1,301 +1,301 @@ /* 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 "itemfetchjob.h" #include "attributefactory.h" #include "collection.h" #include "itemfetchscope.h" #include "job_p.h" #include "protocolhelper_p.h" #include "session_p.h" #include "tagfetchscope.h" #include "private/protocol_p.h" #include #include using namespace Akonadi; class Akonadi::ItemFetchJobPrivate : public JobPrivate { public: ItemFetchJobPrivate(ItemFetchJob *parent) : JobPrivate(parent) { mCollection = Collection::root(); } ~ItemFetchJobPrivate() override { delete mValuePool; } void init() { Q_Q(ItemFetchJob); mEmitTimer = new QTimer(q); mEmitTimer->setSingleShot(true); mEmitTimer->setInterval(100); q->connect(mEmitTimer, SIGNAL(timeout()), q, SLOT(timeout())); } void aboutToFinish() override { timeout(); } void timeout() { Q_Q(ItemFetchJob); mEmitTimer->stop(); // in case we are called by result() if (!mPendingItems.isEmpty()) { if (!q->error()) { - emit q->itemsReceived(mPendingItems); + Q_EMIT q->itemsReceived(mPendingItems); } mPendingItems.clear(); } } QString jobDebuggingString() const override { if (mRequestedItems.isEmpty()) { QString str = QStringLiteral("All items from collection %1").arg(mCollection.id()); if (mFetchScope.fetchChangedSince().isValid()) { str += QStringLiteral(" changed since %1").arg(mFetchScope.fetchChangedSince().toString()); } return str; } else { try { QString itemStr = QStringLiteral("items id: "); bool firstItem = true; for (const Akonadi::Item &item : qAsConst(mRequestedItems)) { if (firstItem) { firstItem = false; } else { itemStr += QStringLiteral(", "); } itemStr += QString::number(item.id()); const Akonadi::Collection parentCollection = item.parentCollection(); if (parentCollection.isValid()) { itemStr += QStringLiteral(" from collection %1").arg(parentCollection.id()); } } return itemStr; //return QString(); //QString::fromLatin1(ProtocolHelper::entitySetToScope(mRequestedItems)); } catch (const Exception &e) { return QString::fromUtf8(e.what()); } } } Q_DECLARE_PUBLIC(ItemFetchJob) Collection mCollection; Tag mCurrentTag; Item::List mRequestedItems; Item::List mResultItems; ItemFetchScope mFetchScope; Item::List mPendingItems; // items pending for emitting itemsReceived() QTimer *mEmitTimer = nullptr; ProtocolHelperValuePool *mValuePool = nullptr; ItemFetchJob::DeliveryOptions mDeliveryOptions = ItemFetchJob::Default; int mCount = 0; }; ItemFetchJob::ItemFetchJob(const Collection &collection, QObject *parent) : Job(new ItemFetchJobPrivate(this), parent) { Q_D(ItemFetchJob); d->init(); d->mCollection = collection; d->mValuePool = new ProtocolHelperValuePool; // only worth it for lots of results } ItemFetchJob::ItemFetchJob(const Item &item, QObject *parent) : Job(new ItemFetchJobPrivate(this), parent) { Q_D(ItemFetchJob); d->init(); d->mRequestedItems.append(item); } ItemFetchJob::ItemFetchJob(const Item::List &items, QObject *parent) : Job(new ItemFetchJobPrivate(this), parent) { Q_D(ItemFetchJob); d->init(); d->mRequestedItems = items; } ItemFetchJob::ItemFetchJob(const QList &items, QObject *parent) : Job(new ItemFetchJobPrivate(this), parent) { Q_D(ItemFetchJob); d->init(); d->mRequestedItems.reserve(items.size()); for (auto id : items) { d->mRequestedItems.append(Item(id)); } } ItemFetchJob::ItemFetchJob(const QVector &items, QObject *parent) : Job(new ItemFetchJobPrivate(this), parent) { Q_D(ItemFetchJob); d->init(); d->mRequestedItems.reserve(items.size()); for (auto id : items) { d->mRequestedItems.append(Item(id)); } } ItemFetchJob::ItemFetchJob(const Tag &tag, QObject *parent) : Job(new ItemFetchJobPrivate(this), parent) { Q_D(ItemFetchJob); d->init(); d->mCurrentTag = tag; d->mValuePool = new ProtocolHelperValuePool; } ItemFetchJob::~ItemFetchJob() { } void ItemFetchJob::doStart() { Q_D(ItemFetchJob); try { d->sendCommand(Protocol::FetchItemsCommandPtr::create( d->mRequestedItems.isEmpty() ? Scope() : ProtocolHelper::entitySetToScope(d->mRequestedItems), ProtocolHelper::commandContextToProtocol(d->mCollection, d->mCurrentTag, d->mRequestedItems), ProtocolHelper::itemFetchScopeToProtocol(d->mFetchScope), ProtocolHelper::tagFetchScopeToProtocol(d->mFetchScope.tagFetchScope()))); } catch (const Akonadi::Exception &e) { setError(Job::Unknown); setErrorText(QString::fromUtf8(e.what())); emitResult(); return; } } bool ItemFetchJob::doHandleResponse(qint64 tag, const Protocol::CommandPtr &response) { Q_D(ItemFetchJob); if (!response->isResponse() || response->type() != Protocol::Command::FetchItems) { return Job::doHandleResponse(tag, response); } const auto resp = Protocol::cmdCast(response); // Invalid ID marks the last part of the response if (resp.id() < 0) { return true; } const Item item = ProtocolHelper::parseItemFetchResult(resp, nullptr, d->mValuePool); if (!item.isValid()) { return false; } d->mCount++; if (d->mDeliveryOptions & ItemGetter) { d->mResultItems.append(item); } if (d->mDeliveryOptions & EmitItemsInBatches) { d->mPendingItems.append(item); if (!d->mEmitTimer->isActive()) { d->mEmitTimer->start(); } } else if (d->mDeliveryOptions & EmitItemsIndividually) { - emit itemsReceived(Item::List() << item); + Q_EMIT itemsReceived(Item::List() << item); } return false; } Item::List ItemFetchJob::items() const { Q_D(const ItemFetchJob); return d->mResultItems; } void ItemFetchJob::clearItems() { Q_D(ItemFetchJob); d->mResultItems.clear(); } void ItemFetchJob::setFetchScope(const ItemFetchScope &fetchScope) { Q_D(ItemFetchJob); d->mFetchScope = fetchScope; } ItemFetchScope &ItemFetchJob::fetchScope() { Q_D(ItemFetchJob); return d->mFetchScope; } void ItemFetchJob::setCollection(const Akonadi::Collection &collection) { Q_D(ItemFetchJob); d->mCollection = collection; } void ItemFetchJob::setDeliveryOption(DeliveryOptions options) { Q_D(ItemFetchJob); d->mDeliveryOptions = options; } ItemFetchJob::DeliveryOptions ItemFetchJob::deliveryOptions() const { Q_D(const ItemFetchJob); return d->mDeliveryOptions; } int ItemFetchJob::count() const { Q_D(const ItemFetchJob); return d->mCount; } #include "moc_itemfetchjob.cpp" diff --git a/src/core/jobs/itemsearchjob.cpp b/src/core/jobs/itemsearchjob.cpp index e79a94458..ed57004ea 100644 --- a/src/core/jobs/itemsearchjob.cpp +++ b/src/core/jobs/itemsearchjob.cpp @@ -1,284 +1,284 @@ /* Copyright (c) 2009 Tobias Koenig 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 "itemsearchjob.h" #include "itemfetchscope.h" #include "tagfetchscope.h" #include "job_p.h" #include "protocolhelper_p.h" #include "searchquery.h" #include "private/protocol_p.h" #include #include #include using namespace Akonadi; class Akonadi::ItemSearchJobPrivate : public JobPrivate { public: ItemSearchJobPrivate(ItemSearchJob *parent, const SearchQuery &query) : JobPrivate(parent) , mQuery(query) { } void init() { Q_Q(ItemSearchJob); mEmitTimer = new QTimer(q); mEmitTimer->setSingleShot(true); mEmitTimer->setInterval(100); q->connect(mEmitTimer, SIGNAL(timeout()), q, SLOT(timeout())); q->connect(q, SIGNAL(result(KJob*)), q, SLOT(timeout())); } void timeout() { Q_Q(Akonadi::ItemSearchJob); mEmitTimer->stop(); // in case we are called by result() if (!mPendingItems.isEmpty()) { if (!q->error()) { - emit q->itemsReceived(mPendingItems); + Q_EMIT q->itemsReceived(mPendingItems); } mPendingItems.clear(); } } QString jobDebuggingString() const override { QStringList flags; if (mRecursive) { flags.append(QStringLiteral("recursive")); } if (mRemote) { flags.append(QStringLiteral("remote")); } if (mCollections.isEmpty()) { flags.append(QStringLiteral("all collections")); } else { flags.append(QStringLiteral("%1 collections").arg(mCollections.count())); } return QStringLiteral("%1,json=%2").arg(flags.join(QLatin1Char(',')), QString::fromUtf8(mQuery.toJSON())); } Q_DECLARE_PUBLIC(ItemSearchJob) SearchQuery mQuery; Collection::List mCollections; QStringList mMimeTypes; bool mRecursive = false; bool mRemote = false; ItemFetchScope mItemFetchScope; TagFetchScope mTagFetchScope; Item::List mItems; Item::List mPendingItems; // items pending for emitting itemsReceived() QTimer *mEmitTimer = nullptr; }; QThreadStorage instances; static void cleanupDefaultSearchSession() { instances.setLocalData(nullptr); } static Session *defaultSearchSession() { if (!instances.hasLocalData()) { const QByteArray sessionName = Session::defaultSession()->sessionId() + "-SearchSession"; instances.setLocalData(new Session(sessionName)); qAddPostRoutine(cleanupDefaultSearchSession); } return instances.localData(); } static QObject *sessionForJob(QObject *parent) { if (qobject_cast(parent) || qobject_cast(parent)) { return parent; } return defaultSearchSession(); } ItemSearchJob::ItemSearchJob(QObject *parent) : Job(new ItemSearchJobPrivate(this, SearchQuery()), sessionForJob(parent)) { Q_D(ItemSearchJob); d->init(); } ItemSearchJob::ItemSearchJob(const SearchQuery &query, QObject *parent) : Job(new ItemSearchJobPrivate(this, query), sessionForJob(parent)) { Q_D(ItemSearchJob); d->init(); } ItemSearchJob::~ItemSearchJob() { } void ItemSearchJob::setQuery(const SearchQuery &query) { Q_D(ItemSearchJob); d->mQuery = query; } void ItemSearchJob::setFetchScope(const ItemFetchScope &fetchScope) { Q_D(ItemSearchJob); d->mItemFetchScope = fetchScope; } ItemFetchScope &ItemSearchJob::fetchScope() { Q_D(ItemSearchJob); return d->mItemFetchScope; } void ItemSearchJob::setTagFetchScope(const TagFetchScope &fetchScope) { Q_D(ItemSearchJob); d->mTagFetchScope = fetchScope; } TagFetchScope &ItemSearchJob::tagFetchScope() { Q_D(ItemSearchJob); return d->mTagFetchScope; } void ItemSearchJob::setSearchCollections(const Collection::List &collections) { Q_D(ItemSearchJob); d->mCollections = collections; } Collection::List ItemSearchJob::searchCollections() const { return d_func()->mCollections; } void ItemSearchJob::setMimeTypes(const QStringList &mimeTypes) { Q_D(ItemSearchJob); d->mMimeTypes = mimeTypes; } QStringList ItemSearchJob::mimeTypes() const { return d_func()->mMimeTypes; } void ItemSearchJob::setRecursive(bool recursive) { Q_D(ItemSearchJob); d->mRecursive = recursive; } bool ItemSearchJob::isRecursive() const { return d_func()->mRecursive; } void ItemSearchJob::setRemoteSearchEnabled(bool enabled) { Q_D(ItemSearchJob); d->mRemote = enabled; } bool ItemSearchJob::isRemoteSearchEnabled() const { return d_func()->mRemote; } void ItemSearchJob::doStart() { Q_D(ItemSearchJob); auto cmd = Protocol::SearchCommandPtr::create(); cmd->setMimeTypes(d->mMimeTypes); if (!d->mCollections.isEmpty()) { QVector ids; ids.reserve(d->mCollections.size()); for (const Collection &col : qAsConst(d->mCollections)) { ids << col.id(); } cmd->setCollections(ids); } cmd->setRecursive(d->mRecursive); cmd->setRemote(d->mRemote); cmd->setQuery(QString::fromUtf8(d->mQuery.toJSON())); cmd->setItemFetchScope(ProtocolHelper::itemFetchScopeToProtocol(d->mItemFetchScope)); cmd->setTagFetchScope(ProtocolHelper::tagFetchScopeToProtocol(d->mTagFetchScope)); d->sendCommand(cmd); } bool ItemSearchJob::doHandleResponse(qint64 tag, const Protocol::CommandPtr &response) { Q_D(ItemSearchJob); if (response->isResponse() && response->type() == Protocol::Command::FetchItems) { const Item item = ProtocolHelper::parseItemFetchResult( Protocol::cmdCast(response)); if (!item.isValid()) { return false; } d->mItems.append(item); d->mPendingItems.append(item); if (!d->mEmitTimer->isActive()) { d->mEmitTimer->start(); } return false; } if (response->isResponse() && response->type() == Protocol::Command::Search) { return true; } return Job::doHandleResponse(tag, response); } Item::List ItemSearchJob::items() const { Q_D(const ItemSearchJob); return d->mItems; } #include "moc_itemsearchjob.cpp" diff --git a/src/core/jobs/job.cpp b/src/core/jobs/job.cpp index 5a5520ab7..23354e2c0 100644 --- a/src/core/jobs/job.cpp +++ b/src/core/jobs/job.cpp @@ -1,415 +1,415 @@ /* Copyright (c) 2006 Tobias Koenig 2006 Marc Mutz 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 "job.h" #include "job_p.h" #include "akonadicore_debug.h" #include "KDBusConnectionPool" #include #include "private/protocol_p.h" #include "private/instance_p.h" #include "session.h" #include "session_p.h" #include #include #include #include using namespace Akonadi; static QDBusAbstractInterface *s_jobtracker = nullptr; //@cond PRIVATE void JobPrivate::handleResponse(qint64 tag, const Protocol::CommandPtr &response) { Q_Q(Job); if (mCurrentSubJob) { mCurrentSubJob->d_ptr->handleResponse(tag, response); return; } if (tag == mTag) { if (response->isResponse()) { const auto &resp = Protocol::cmdCast(response); if (resp.isError()) { q->setError(Job::Unknown); q->setErrorText(resp.errorMessage()); q->emitResult(); return; } } } if (mTag != tag) { qCWarning(AKONADICORE_LOG) << "Received response with a different tag!"; qCDebug(AKONADICORE_LOG) << "Response tag:" << tag << ", response type:" << response->type(); qCDebug(AKONADICORE_LOG) << "Job tag:" << mTag << ", job:" << q; return; } if (mStarted) { if (mReadingFinished) { qCWarning(AKONADICORE_LOG) << "Received response for a job that does not expect any more data, ignoring"; qCDebug(AKONADICORE_LOG) << "Response tag:" << tag << ", response type:" << response->type(); qCDebug(AKONADICORE_LOG) << "Job tag:" << mTag << ", job:" << q; Q_ASSERT(!mReadingFinished); return; } if (q->doHandleResponse(tag, response)) { mReadingFinished = true; QTimer::singleShot(0, q, [this]() {delayedEmitResult(); }); } } } void JobPrivate::init(QObject *parent) { Q_Q(Job); mParentJob = qobject_cast(parent); mSession = qobject_cast(parent); if (!mSession) { if (!mParentJob) { mSession = Session::defaultSession(); } else { mSession = mParentJob->d_ptr->mSession; } } if (!mParentJob) { mSession->d->addJob(q); } else { mParentJob->addSubjob(q); } publishJob(); } void JobPrivate::publishJob() { Q_Q(Job); // if there's a job tracker running, tell it about the new job if (!s_jobtracker) { // Let's only check for the debugging console every 3 seconds, otherwise every single job // makes a dbus call to the dbus daemon, doesn't help performance. static QTime s_lastTime; if (s_lastTime.isNull() || s_lastTime.elapsed() > 3000) { if (s_lastTime.isNull()) { s_lastTime.start(); } const QString suffix = Akonadi::Instance::identifier().isEmpty() ? QString() : QLatin1Char('-') + Akonadi::Instance::identifier(); if (KDBusConnectionPool::threadConnection().interface()->isServiceRegistered(QStringLiteral("org.kde.akonadiconsole") + suffix)) { s_jobtracker = new QDBusInterface(QStringLiteral("org.kde.akonadiconsole") + suffix, QStringLiteral("/jobtracker"), QStringLiteral("org.freedesktop.Akonadi.JobTracker"), KDBusConnectionPool::threadConnection(), nullptr); mSession->d->publishOtherJobs(q); } else { s_lastTime.restart(); } } // Note: we never reset s_jobtracker to 0 when a call fails; but if we did // then we should restart s_lastTime. } QMetaObject::invokeMethod(q, "signalCreationToJobTracker", Qt::QueuedConnection); } void JobPrivate::signalCreationToJobTracker() { Q_Q(Job); if (s_jobtracker) { // We do these dbus calls manually, so as to avoid having to install (or copy) the console's // xml interface document. Since this is purely a debugging aid, that seems preferable to // publishing something not intended for public consumption. // WARNING: for any signature change here, apply it to resourcescheduler.cpp too const QList argumentList = QList() << QLatin1String(mSession->sessionId()) << QString::number(reinterpret_cast(q), 16) << (mParentJob ? QString::number(reinterpret_cast(mParentJob), 16) : QString()) << QString::fromLatin1(q->metaObject()->className()) << jobDebuggingString(); QDBusPendingCall call = s_jobtracker->asyncCallWithArgumentList(QStringLiteral("jobCreated"), argumentList); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, s_jobtracker); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, s_jobtracker, [](QDBusPendingCallWatcher *w) { QDBusPendingReply reply = *w; if (reply.isError() && s_jobtracker) { qDebug() << reply.error().name() << reply.error().message(); s_jobtracker->deleteLater(); s_jobtracker = nullptr; } w->deleteLater(); }); } } void JobPrivate::signalStartedToJobTracker() { Q_Q(Job); if (s_jobtracker) { // if there's a job tracker running, tell it a job started const QList argumentList = { QString::number(reinterpret_cast(q), 16) }; s_jobtracker->callWithArgumentList(QDBus::NoBlock, QStringLiteral("jobStarted"), argumentList); } } void JobPrivate::aboutToFinish() { // Dummy } void JobPrivate::delayedEmitResult() { Q_Q(Job); if (q->hasSubjobs()) { // We still have subjobs, wait for them to finish mFinishPending = true; } else { aboutToFinish(); q->emitResult(); } } void JobPrivate::startQueued() { Q_Q(Job); mStarted = true; - emit q->aboutToStart(q); + Q_EMIT q->aboutToStart(q); q->doStart(); QTimer::singleShot(0, q, [this]() { startNext(); }); QMetaObject::invokeMethod(q, "signalStartedToJobTracker", Qt::QueuedConnection); } void JobPrivate::lostConnection() { Q_Q(Job); if (mCurrentSubJob) { mCurrentSubJob->d_ptr->lostConnection(); } else { q->setError(Job::ConnectionFailed); q->emitResult(); } } void JobPrivate::slotSubJobAboutToStart(Job *job) { Q_ASSERT(mCurrentSubJob == nullptr); mCurrentSubJob = job; } void JobPrivate::startNext() { Q_Q(Job); if (mStarted && !mCurrentSubJob && q->hasSubjobs()) { Job *job = qobject_cast(q->subjobs().at(0)); Q_ASSERT(job); job->d_ptr->startQueued(); } else if (mFinishPending && !q->hasSubjobs()) { // The last subjob we've been waiting for has finished, emitResult() finally QTimer::singleShot(0, q, [this]() {delayedEmitResult(); }); } } qint64 JobPrivate::newTag() { if (mParentJob) { mTag = mParentJob->d_ptr->newTag(); } else { mTag = mSession->d->nextTag(); } return mTag; } qint64 JobPrivate::tag() const { return mTag; } void JobPrivate::sendCommand(qint64 tag, const Protocol::CommandPtr &cmd) { if (mParentJob) { mParentJob->d_ptr->sendCommand(tag, cmd); } else { mSession->d->sendCommand(tag, cmd); } } void JobPrivate::sendCommand(const Protocol::CommandPtr &cmd) { sendCommand(newTag(), cmd); } void JobPrivate::itemRevisionChanged(Akonadi::Item::Id itemId, int oldRevision, int newRevision) { mSession->d->itemRevisionChanged(itemId, oldRevision, newRevision); } void JobPrivate::updateItemRevision(Akonadi::Item::Id itemId, int oldRevision, int newRevision) { Q_Q(Job); foreach (KJob *j, q->subjobs()) { Akonadi::Job *job = qobject_cast(j); if (job) { job->d_ptr->updateItemRevision(itemId, oldRevision, newRevision); } } doUpdateItemRevision(itemId, oldRevision, newRevision); } void JobPrivate::doUpdateItemRevision(Akonadi::Item::Id itemId, int oldRevision, int newRevision) { Q_UNUSED(itemId); Q_UNUSED(oldRevision); Q_UNUSED(newRevision); } int JobPrivate::protocolVersion() const { return mSession->d->protocolVersion; } //@endcond Job::Job(QObject *parent) : KCompositeJob(parent) , d_ptr(new JobPrivate(this)) { d_ptr->init(parent); } Job::Job(JobPrivate *dd, QObject *parent) : KCompositeJob(parent) , d_ptr(dd) { d_ptr->init(parent); } Job::~Job() { delete d_ptr; // if there is a job tracer listening, tell it the job is done now if (s_jobtracker) { const QList argumentList = {QString::number(reinterpret_cast(this), 16), errorString()}; s_jobtracker->callWithArgumentList(QDBus::NoBlock, QStringLiteral("jobEnded"), argumentList); } } void Job::start() { } bool Job::doKill() { Q_D(Job); if (d->mStarted) { // the only way to cancel an already started job is reconnecting to the server d->mSession->d->forceReconnect(); } d->mStarted = false; return true; } QString Job::errorString() const { QString str; switch (error()) { case NoError: break; case ConnectionFailed: str = i18n("Cannot connect to the Akonadi service."); break; case ProtocolVersionMismatch: str = i18n("The protocol version of the Akonadi server is incompatible. Make sure you have a compatible version installed."); break; case UserCanceled: str = i18n("User canceled operation."); break; case Unknown: return errorText(); case UserError: str = i18n("Unknown error."); break; } if (!errorText().isEmpty()) { str += QStringLiteral(" (%1)").arg(errorText()); } return str; } bool Job::addSubjob(KJob *job) { bool rv = KCompositeJob::addSubjob(job); if (rv) { connect(job, SIGNAL(aboutToStart(Akonadi::Job*)), SLOT(slotSubJobAboutToStart(Akonadi::Job*))); QTimer::singleShot(0, this, [this]() { d_ptr->startNext(); }); } return rv; } bool Job::removeSubjob(KJob *job) { bool rv = KCompositeJob::removeSubjob(job); if (job == d_ptr->mCurrentSubJob) { d_ptr->mCurrentSubJob = nullptr; QTimer::singleShot(0, this, [this]() { d_ptr->startNext(); }); } return rv; } bool Akonadi::Job::doHandleResponse(qint64 tag, const Akonadi::Protocol::CommandPtr &response) { qCDebug(AKONADICORE_LOG) << this << "Unhandled response: " << tag << Protocol::debugString(response); setError(Unknown); setErrorText(i18n("Unexpected response")); emitResult(); return true; } void Job::slotResult(KJob *job) { if (d_ptr->mCurrentSubJob == job) { // current job finished, start the next one d_ptr->mCurrentSubJob = nullptr; KCompositeJob::slotResult(job); if (!job->error()) { QTimer::singleShot(0, this, [this]() { d_ptr->startNext(); }); } } else { // job that was still waiting for execution finished, probably canceled, // so just remove it from the queue and move on without caring about // its error code KCompositeJob::removeSubjob(job); } } void Job::emitWriteFinished() { d_ptr->mWriteFinished = true; - emit writeFinished(this); + Q_EMIT writeFinished(this); } #include "moc_job.cpp" diff --git a/src/core/jobs/relationfetchjob.cpp b/src/core/jobs/relationfetchjob.cpp index 12d92999d..c6bacb634 100644 --- a/src/core/jobs/relationfetchjob.cpp +++ b/src/core/jobs/relationfetchjob.cpp @@ -1,135 +1,135 @@ /* Copyright (c) 2014 Christian Mollekopf 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 "relationfetchjob.h" #include "job_p.h" #include "relation.h" #include "protocolhelper_p.h" #include "private/protocol_p.h" #include using namespace Akonadi; class Akonadi::RelationFetchJobPrivate : public JobPrivate { public: RelationFetchJobPrivate(RelationFetchJob *parent) : JobPrivate(parent) , mEmitTimer(nullptr) { } void init() { Q_Q(RelationFetchJob); mEmitTimer = new QTimer(q); mEmitTimer->setSingleShot(true); mEmitTimer->setInterval(100); q->connect(mEmitTimer, SIGNAL(timeout()), q, SLOT(timeout())); } void aboutToFinish() override { timeout(); } void timeout() { Q_Q(RelationFetchJob); mEmitTimer->stop(); // in case we are called by result() if (!mPendingRelations.isEmpty()) { if (!q->error()) { - emit q->relationsReceived(mPendingRelations); + Q_EMIT q->relationsReceived(mPendingRelations); } mPendingRelations.clear(); } } Q_DECLARE_PUBLIC(RelationFetchJob) Relation::List mResultRelations; Relation::List mPendingRelations; // relation pending for emitting itemsReceived() QTimer *mEmitTimer = nullptr; QVector mTypes; QString mResource; Relation mRequestedRelation; }; RelationFetchJob::RelationFetchJob(const Relation &relation, QObject *parent) : Job(new RelationFetchJobPrivate(this), parent) { Q_D(RelationFetchJob); d->init(); d->mRequestedRelation = relation; } RelationFetchJob::RelationFetchJob(const QVector &types, QObject *parent) : Job(new RelationFetchJobPrivate(this), parent) { Q_D(RelationFetchJob); d->init(); d->mTypes = types; } void RelationFetchJob::doStart() { Q_D(RelationFetchJob); d->sendCommand(Protocol::FetchRelationsCommandPtr::create( d->mRequestedRelation.left().id(), d->mRequestedRelation.right().id(), (d->mTypes.isEmpty() && !d->mRequestedRelation.type().isEmpty()) ? QVector() << d->mRequestedRelation.type() : d->mTypes, d->mResource)); } bool RelationFetchJob::doHandleResponse(qint64 tag, const Protocol::CommandPtr &response) { Q_D(RelationFetchJob); if (!response->isResponse() || response->type() != Protocol::Command::FetchRelations) { return Job::doHandleResponse(tag, response); } const Relation rel = ProtocolHelper::parseRelationFetchResult( Protocol::cmdCast(response)); // Invalid response means there will be no more responses if (!rel.isValid()) { return true; } d->mResultRelations.append(rel); d->mPendingRelations.append(rel); if (!d->mEmitTimer->isActive()) { d->mEmitTimer->start(); } return false; } Relation::List RelationFetchJob::relations() const { Q_D(const RelationFetchJob); return d->mResultRelations; } void RelationFetchJob::setResource(const QString &identifier) { Q_D(RelationFetchJob); d->mResource = identifier; } #include "moc_relationfetchjob.cpp" diff --git a/src/core/jobs/tagfetchjob.cpp b/src/core/jobs/tagfetchjob.cpp index ea0aebf1c..65f0088c8 100644 --- a/src/core/jobs/tagfetchjob.cpp +++ b/src/core/jobs/tagfetchjob.cpp @@ -1,169 +1,169 @@ /* Copyright (c) 2014 Christian Mollekopf 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 "tagfetchjob.h" #include "job_p.h" #include "tag.h" #include "protocolhelper_p.h" #include "tagfetchscope.h" #include "attributefactory.h" #include using namespace Akonadi; class Akonadi::TagFetchJobPrivate : public JobPrivate { public: TagFetchJobPrivate(TagFetchJob *parent) : JobPrivate(parent) { } void init() { Q_Q(TagFetchJob); mEmitTimer = new QTimer(q); mEmitTimer->setSingleShot(true); mEmitTimer->setInterval(100); q->connect(mEmitTimer, &QTimer::timeout, q, [this]() { timeout(); }); } void aboutToFinish() override { timeout(); } void timeout() { Q_Q(TagFetchJob); mEmitTimer->stop(); // in case we are called by result() if (!mPendingTags.isEmpty()) { if (!q->error()) { - emit q->tagsReceived(mPendingTags); + Q_EMIT q->tagsReceived(mPendingTags); } mPendingTags.clear(); } } Q_DECLARE_PUBLIC(TagFetchJob) Tag::List mRequestedTags; Tag::List mResultTags; Tag::List mPendingTags; // items pending for emitting itemsReceived() QTimer *mEmitTimer = nullptr; TagFetchScope mFetchScope; }; TagFetchJob::TagFetchJob(QObject *parent) : Job(new TagFetchJobPrivate(this), parent) { Q_D(TagFetchJob); d->init(); } TagFetchJob::TagFetchJob(const Tag &tag, QObject *parent) : Job(new TagFetchJobPrivate(this), parent) { Q_D(TagFetchJob); d->init(); d->mRequestedTags << tag; } TagFetchJob::TagFetchJob(const Tag::List &tags, QObject *parent) : Job(new TagFetchJobPrivate(this), parent) { Q_D(TagFetchJob); d->init(); d->mRequestedTags = tags; } TagFetchJob::TagFetchJob(const QList &ids, QObject *parent) : Job(new TagFetchJobPrivate(this), parent) { Q_D(TagFetchJob); d->init(); for (Tag::Id id : ids) { d->mRequestedTags << Tag(id); } } void TagFetchJob::setFetchScope(const TagFetchScope &fetchScope) { Q_D(TagFetchJob); d->mFetchScope = fetchScope; } TagFetchScope &TagFetchJob::fetchScope() { Q_D(TagFetchJob); return d->mFetchScope; } void TagFetchJob::doStart() { Q_D(TagFetchJob); Protocol::FetchTagsCommandPtr cmd; if (d->mRequestedTags.isEmpty()) { cmd = Protocol::FetchTagsCommandPtr::create(Scope(ImapInterval(1, 0))); } else { try { cmd = Protocol::FetchTagsCommandPtr::create(ProtocolHelper::entitySetToScope(d->mRequestedTags)); } catch (const Exception &e) { setError(Job::Unknown); setErrorText(QString::fromUtf8(e.what())); emitResult(); return; } } cmd->setFetchScope(ProtocolHelper::tagFetchScopeToProtocol(d->mFetchScope)); d->sendCommand(cmd); } bool TagFetchJob::doHandleResponse(qint64 _tag, const Protocol::CommandPtr &response) { Q_D(TagFetchJob); if (!response->isResponse() || response->type() != Protocol::Command::FetchTags) { return Job::doHandleResponse(_tag, response); } const auto &resp = Protocol::cmdCast(response); // Invalid tag in response marks the last response if (resp.id() < 0) { return true; } const Tag tag = ProtocolHelper::parseTagFetchResult(resp); d->mResultTags.append(tag); d->mPendingTags.append(tag); if (!d->mEmitTimer->isActive()) { d->mEmitTimer->start(); } return false; } Tag::List TagFetchJob::tags() const { Q_D(const TagFetchJob); return d->mResultTags; } #include "moc_tagfetchjob.cpp" diff --git a/src/core/models/agentinstancemodel.cpp b/src/core/models/agentinstancemodel.cpp index 1d2a83086..96938be1e 100644 --- a/src/core/models/agentinstancemodel.cpp +++ b/src/core/models/agentinstancemodel.cpp @@ -1,250 +1,250 @@ /* Copyright (c) 2006 Tobias Koenig 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 "agentinstancemodel.h" #include "agentinstance.h" #include "agentmanager.h" #include #include using namespace Akonadi; /** * @internal */ class Q_DECL_HIDDEN AgentInstanceModel::Private { public: Private(AgentInstanceModel *parent) : mParent(parent) { } AgentInstanceModel *mParent = nullptr; AgentInstance::List mInstances; void instanceAdded(const AgentInstance &); void instanceRemoved(const AgentInstance &); void instanceChanged(const AgentInstance &); }; void AgentInstanceModel::Private::instanceAdded(const AgentInstance &instance) { mParent->beginInsertRows(QModelIndex(), mInstances.count(), mInstances.count()); mInstances.append(instance); mParent->endInsertRows(); } void AgentInstanceModel::Private::instanceRemoved(const AgentInstance &instance) { const int index = mInstances.indexOf(instance); if (index == -1) { return; } mParent->beginRemoveRows(QModelIndex(), index, index); mInstances.removeAll(instance); mParent->endRemoveRows(); } void AgentInstanceModel::Private::instanceChanged(const AgentInstance &instance) { const int numberOfInstance(mInstances.count()); for (int i = 0; i < numberOfInstance; ++i) { if (mInstances[i] == instance) { //TODO why reassign it if equals ? mInstances[i] = instance; const QModelIndex idx = mParent->index(i, 0); - emit mParent->dataChanged(idx, idx); + Q_EMIT mParent->dataChanged(idx, idx); return; } } } AgentInstanceModel::AgentInstanceModel(QObject *parent) : QAbstractItemModel(parent) , d(new Private(this)) { d->mInstances = AgentManager::self()->instances(); connect(AgentManager::self(), &AgentManager::instanceAdded, this, [this](const Akonadi::AgentInstance &inst) { d->instanceAdded(inst);}); connect(AgentManager::self(), &AgentManager::instanceRemoved, this, [this](const Akonadi::AgentInstance &inst) { d->instanceRemoved(inst);}); connect(AgentManager::self(), &AgentManager::instanceStatusChanged, this, [this](const Akonadi::AgentInstance &inst) { d->instanceChanged(inst);}); connect(AgentManager::self(), &AgentManager::instanceProgressChanged, this, [this](const Akonadi::AgentInstance &inst) { d->instanceChanged(inst);}); connect(AgentManager::self(), &AgentManager::instanceNameChanged, this, [this](const Akonadi::AgentInstance &inst) { d->instanceChanged(inst);}); connect(AgentManager::self(), SIGNAL(instanceOnline(Akonadi::AgentInstance,bool)), this, SLOT(instanceChanged(Akonadi::AgentInstance))); } AgentInstanceModel::~AgentInstanceModel() { delete d; } QHash AgentInstanceModel::roleNames() const { QHash roles = QAbstractItemModel::roleNames(); roles.insert(StatusRole, "status"); roles.insert(StatusMessageRole, "statusMessage"); roles.insert(ProgressRole, "progress"); roles.insert(OnlineRole, "online"); return roles; } int AgentInstanceModel::columnCount(const QModelIndex &index) const { return index.isValid() ? 0 : 1; } int AgentInstanceModel::rowCount(const QModelIndex &index) const { return index.isValid() ? 0 : d->mInstances.count(); } QVariant AgentInstanceModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (index.row() < 0 || index.row() >= d->mInstances.count()) { return QVariant(); } const AgentInstance &instance = d->mInstances[index.row()]; switch (role) { case Qt::DisplayRole: return instance.name(); case Qt::DecorationRole: return instance.type().icon(); case InstanceRole: { QVariant var; var.setValue(instance); return var; } case InstanceIdentifierRole: return instance.identifier(); case Qt::ToolTipRole: return QStringLiteral("

%1

%2
").arg(instance.name(), instance.type().description()); case StatusRole: return instance.status(); case StatusMessageRole: return instance.statusMessage(); case ProgressRole: return instance.progress(); case OnlineRole: return instance.isOnline(); case TypeRole: { QVariant var; var.setValue(instance.type()); return var; } case TypeIdentifierRole: return instance.type().identifier(); case DescriptionRole: return instance.type().description(); case CapabilitiesRole: return instance.type().capabilities(); case MimeTypesRole: return instance.type().mimeTypes(); } return QVariant(); } QVariant AgentInstanceModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Vertical) { return QVariant(); } if (role != Qt::DisplayRole) { return QVariant(); } switch (section) { case 0: return i18nc("@title:column, name of a thing", "Name"); default: return QVariant(); } } QModelIndex AgentInstanceModel::index(int row, int column, const QModelIndex &) const { if (row < 0 || row >= d->mInstances.count()) { return QModelIndex(); } if (column != 0) { return QModelIndex(); } return createIndex(row, column); } QModelIndex AgentInstanceModel::parent(const QModelIndex &) const { return QModelIndex(); } Qt::ItemFlags AgentInstanceModel::flags(const QModelIndex &index) const { if (!index.isValid() || index.row() < 0 || index.row() >= d->mInstances.count()) { return QAbstractItemModel::flags(index); } return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; } bool AgentInstanceModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid()) { return false; } if (index.row() < 0 || index.row() >= d->mInstances.count()) { return false; } AgentInstance &instance = d->mInstances[index.row()]; switch (role) { case OnlineRole: instance.setIsOnline(value.toBool()); - emit dataChanged(index, index); + Q_EMIT dataChanged(index, index); return true; default: return false; } return false; } #include "moc_agentinstancemodel.cpp" diff --git a/src/core/models/agenttypemodel.cpp b/src/core/models/agenttypemodel.cpp index c47228d3e..dbf629fc8 100644 --- a/src/core/models/agenttypemodel.cpp +++ b/src/core/models/agenttypemodel.cpp @@ -1,152 +1,152 @@ /* Copyright (c) 2006 Tobias Koenig 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 "agenttypemodel.h" #include "agenttype.h" #include "agentmanager.h" #include using namespace Akonadi; /** * @internal */ class Q_DECL_HIDDEN AgentTypeModel::Private { public: Private(AgentTypeModel *parent) : mParent(parent) { mTypes = AgentManager::self()->types(); } AgentTypeModel *mParent = nullptr; AgentType::List mTypes; void typeAdded(const AgentType &agentType); void typeRemoved(const AgentType &agentType); }; void AgentTypeModel::Private::typeAdded(const AgentType &agentType) { mTypes.append(agentType); - emit mParent->layoutChanged(); + Q_EMIT mParent->layoutChanged(); } void AgentTypeModel::Private::typeRemoved(const AgentType &agentType) { mTypes.removeAll(agentType); - emit mParent->layoutChanged(); + Q_EMIT mParent->layoutChanged(); } AgentTypeModel::AgentTypeModel(QObject *parent) : QAbstractItemModel(parent) , d(new Private(this)) { connect(AgentManager::self(), &AgentManager::typeAdded, this, [this](const Akonadi::AgentType &type ) { d->typeAdded(type); }); connect(AgentManager::self(), &AgentManager::typeRemoved, this, [this](const Akonadi::AgentType &type ) { d->typeRemoved(type); }); } AgentTypeModel::~AgentTypeModel() { delete d; } int AgentTypeModel::columnCount(const QModelIndex &) const { return 1; } int AgentTypeModel::rowCount(const QModelIndex &) const { return d->mTypes.count(); } QVariant AgentTypeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (index.row() < 0 || index.row() >= d->mTypes.count()) { return QVariant(); } const AgentType &type = d->mTypes[index.row()]; switch (role) { case Qt::DisplayRole: return type.name(); case Qt::DecorationRole: return type.icon(); case TypeRole: { QVariant var; var.setValue(type); return var; } case IdentifierRole: return type.identifier(); case DescriptionRole: return type.description(); case MimeTypesRole: return type.mimeTypes(); case CapabilitiesRole: return type.capabilities(); default: break; } return QVariant(); } QModelIndex AgentTypeModel::index(int row, int column, const QModelIndex &) const { if (row < 0 || row >= d->mTypes.count()) { return QModelIndex(); } if (column != 0) { return QModelIndex(); } return createIndex(row, column); } QModelIndex AgentTypeModel::parent(const QModelIndex &) const { return QModelIndex(); } Qt::ItemFlags AgentTypeModel::flags(const QModelIndex &index) const { if (!index.isValid() || index.row() < 0 || index.row() >= d->mTypes.count()) { return QAbstractItemModel::flags(index); } const AgentType &type = d->mTypes[index.row()]; if (type.capabilities().contains(QLatin1String("Unique")) && AgentManager::self()->instance(type.identifier()).isValid()) { return QAbstractItemModel::flags(index) & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled); } return QAbstractItemModel::flags(index); } #include "moc_agenttypemodel.cpp" diff --git a/src/core/models/collectionmodel_p.cpp b/src/core/models/collectionmodel_p.cpp index 8f863bebb..8d9291be3 100644 --- a/src/core/models/collectionmodel_p.cpp +++ b/src/core/models/collectionmodel_p.cpp @@ -1,357 +1,357 @@ /* Copyright (c) 2006 - 2008 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. */ //@cond PRIVATE #include "collectionmodel_p.h" #include "collectionmodel.h" #include "collectionutils.h" #include "collectionfetchjob.h" #include "collectionstatistics.h" #include "collectionstatisticsjob.h" #include "monitor.h" #include "session.h" #include "collectionfetchscope.h" #include "akonadicore_debug.h" #include #include #include #include using namespace Akonadi; void CollectionModelPrivate::collectionRemoved(const Akonadi::Collection &collection) { Q_Q(CollectionModel); QModelIndex colIndex = indexForId(collection.id()); if (colIndex.isValid()) { QModelIndex parentIndex = q->parent(colIndex); // collection is still somewhere in the hierarchy removeRowFromModel(colIndex.row(), parentIndex); } else { if (collections.contains(collection.id())) { // collection is orphan, ie. the parent has been removed already collections.remove(collection.id()); childCollections.remove(collection.id()); } } } void CollectionModelPrivate::collectionChanged(const Akonadi::Collection &collection) { Q_Q(CollectionModel); // What kind of change is it ? Collection::Id oldParentId = collections.value(collection.id()).parentCollection().id(); Collection::Id newParentId = collection.parentCollection().id(); if (newParentId != oldParentId && oldParentId >= 0) { // It's a move removeRowFromModel(indexForId(collections[collection.id()].id()).row(), indexForId(oldParentId)); Collection newParent; if (newParentId == Collection::root().id()) { newParent = Collection::root(); } else { newParent = collections.value(newParentId); } CollectionFetchJob *job = new CollectionFetchJob(newParent, CollectionFetchJob::Recursive, session); job->fetchScope().setListFilter(unsubscribed ? CollectionFetchScope::NoFilter : CollectionFetchScope::Enabled); job->fetchScope().setIncludeStatistics(fetchStatistics); q->connect(job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), q, SLOT(collectionsChanged(Akonadi::Collection::List))); q->connect(job, SIGNAL(result(KJob*)), q, SLOT(listDone(KJob*))); } else { // It's a simple change CollectionFetchJob *job = new CollectionFetchJob(collection, CollectionFetchJob::Base, session); job->fetchScope().setListFilter(unsubscribed ? CollectionFetchScope::NoFilter : CollectionFetchScope::Enabled); job->fetchScope().setIncludeStatistics(fetchStatistics); q->connect(job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), q, SLOT(collectionsChanged(Akonadi::Collection::List))); q->connect(job, SIGNAL(result(KJob*)), q, SLOT(listDone(KJob*))); } } void CollectionModelPrivate::updateDone(KJob *job) { if (job->error()) { // TODO: handle job errors qCWarning(AKONADICORE_LOG) << "Job error:" << job->errorString(); } else { CollectionStatisticsJob *csjob = static_cast(job); Collection result = csjob->collection(); collectionStatisticsChanged(result.id(), csjob->statistics()); } } void CollectionModelPrivate::collectionStatisticsChanged(Collection::Id collection, const Akonadi::CollectionStatistics &statistics) { Q_Q(CollectionModel); if (collections.contains(collection)) { collections[collection].setStatistics(statistics); Collection col = collections.value(collection); QModelIndex startIndex = indexForId(col.id()); QModelIndex endIndex = indexForId(col.id(), q->columnCount(q->parent(startIndex)) - 1); - emit q->dataChanged(startIndex, endIndex); + Q_EMIT q->dataChanged(startIndex, endIndex); } else { qCWarning(AKONADICORE_LOG) << "Got statistics response for non-existing collection:" << collection; } } void CollectionModelPrivate::listDone(KJob *job) { if (job->error()) { qCWarning(AKONADICORE_LOG) << "Job error: " << job->errorString() << endl; } } void CollectionModelPrivate::editDone(KJob *job) { if (job->error()) { qCWarning(AKONADICORE_LOG) << "Edit failed: " << job->errorString(); } } void CollectionModelPrivate::dropResult(KJob *job) { if (job->error()) { qCWarning(AKONADICORE_LOG) << "Paste failed:" << job->errorString(); // TODO: error handling } } void CollectionModelPrivate::collectionsChanged(const Collection::List &cols) { Q_Q(CollectionModel); foreach (Collection col, cols) { //krazy:exclude=foreach non-const is needed here if (collections.contains(col.id())) { // If the collection is already known to the model, we simply update it... col.setStatistics(collections.value(col.id()).statistics()); collections[col.id()] = col; QModelIndex startIndex = indexForId(col.id()); QModelIndex endIndex = indexForId(col.id(), q->columnCount(q->parent(startIndex)) - 1); - emit q->dataChanged(startIndex, endIndex); + Q_EMIT q->dataChanged(startIndex, endIndex); continue; } // ... otherwise we add it to the set of collections we need to handle. m_newChildCollections[col.parentCollection().id()].append(col.id()); m_newCollections.insert(col.id(), col); } // Handle the collections in m_newChildCollections. If the collections // parent is already in the model, the collection can be added to the model. // Otherwise it is persisted until it has a valid parent in the model. int currentSize = m_newChildCollections.size(); while (currentSize > 0) { int lastSize = currentSize; QMutableHashIterator< Collection::Id, QVector< Collection::Id > > i(m_newChildCollections); while (i.hasNext()) { i.next(); // the key is the parent of new collections. It may itself also be new, // but that will be handled later. Collection::Id colId = i.key(); QVector< Collection::Id > newChildCols = i.value(); int newChildCount = newChildCols.size(); // if ( newChildCount == 0 ) // { // // Sanity check. // qCDebug(AKONADICORE_LOG) << "No new child collections have been added to the collection:" << colId; // i.remove(); // currentSize--; // break; // } if (collections.contains(colId) || colId == Collection::root().id()) { QModelIndex parentIndex = indexForId(colId); int currentChildCount = childCollections.value(colId).size(); q->beginInsertRows(parentIndex, currentChildCount, // Start index is at the end of existing collections. currentChildCount + newChildCount - 1); // End index is the result of the insertion. foreach (Collection::Id id, newChildCols) { Collection c = m_newCollections.take(id); collections.insert(id, c); } childCollections[colId] << newChildCols; q->endInsertRows(); i.remove(); currentSize--; break; } } // We iterated through once without adding any more collections to the model. if (currentSize == lastSize) { // The remaining collections in the list do not have a valid parent in the model yet. They // might arrive in the next batch from the monitor, so they're still in m_newCollections // and m_newChildCollections. qCDebug(AKONADICORE_LOG) << "Some collections did not have a parent in the model yet!"; break; } } } QModelIndex CollectionModelPrivate::indexForId(Collection::Id id, int column) const { Q_Q(const CollectionModel); if (!collections.contains(id)) { return QModelIndex(); } Collection::Id parentId = collections.value(id).parentCollection().id(); // check if parent still exist or if this is an orphan collection if (parentId != Collection::root().id() && !collections.contains(parentId)) { return QModelIndex(); } const QVector list = childCollections.value(parentId); int row = list.indexOf(id); if (row >= 0) { return q->createIndex(row, column, reinterpret_cast(collections.value(list.at(row)).id())); } return QModelIndex(); } bool CollectionModelPrivate::removeRowFromModel(int row, const QModelIndex &parent) { Q_Q(CollectionModel); QVector list; Collection parentCol; if (parent.isValid()) { parentCol = collections.value(parent.internalId()); Q_ASSERT(parentCol.id() == static_cast(parent.internalId())); list = childCollections.value(parentCol.id()); } else { parentCol = Collection::root(); list = childCollections.value(Collection::root().id()); } if (row < 0 || row >= list.size()) { qCWarning(AKONADICORE_LOG) << "Index out of bounds:" << row << " parent:" << parentCol.id(); return false; } q->beginRemoveRows(parent, row, row); const Collection::Id delColId = list[row]; list.remove(row); foreach (Collection::Id childColId, childCollections[delColId]) { collections.remove(childColId); } collections.remove(delColId); childCollections.remove(delColId); // remove children of deleted collection childCollections.insert(parentCol.id(), list); // update children of parent q->endRemoveRows(); return true; } bool CollectionModelPrivate::supportsContentType(const QModelIndex &index, const QStringList &contentTypes) { if (!index.isValid()) { return false; } const Collection col = collections.value(index.internalId()); Q_ASSERT(col.isValid()); const QStringList ct = col.contentMimeTypes(); for (const QString &a : ct) { if (contentTypes.contains(a)) { return true; } } return false; } void CollectionModelPrivate::init() { Q_Q(CollectionModel); session = new Session(QCoreApplication::instance()->applicationName().toUtf8() + QByteArray("-CollectionModel-") + QByteArray::number(qrand()), q); QTimer::singleShot(0, q, [this] { startFirstListJob(); }); // monitor collection changes monitor = new Monitor(); monitor->setObjectName(QStringLiteral("CollectionModelMonitor")); monitor->setCollectionMonitored(Collection::root()); monitor->fetchCollection(true); // ### Hack to get the kmail resource folder icons KIconLoader::global()->addAppDir(QStringLiteral("kmail")); // monitor collection changes q->connect(monitor, SIGNAL(collectionChanged(Akonadi::Collection)), q, SLOT(collectionChanged(Akonadi::Collection))); q->connect(monitor, SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection)), q, SLOT(collectionChanged(Akonadi::Collection))); q->connect(monitor, SIGNAL(collectionRemoved(Akonadi::Collection)), q, SLOT(collectionRemoved(Akonadi::Collection))); q->connect(monitor, SIGNAL(collectionStatisticsChanged(Akonadi::Collection::Id,Akonadi::CollectionStatistics)), q, SLOT(collectionStatisticsChanged(Akonadi::Collection::Id,Akonadi::CollectionStatistics))); } QIcon CollectionModelPrivate::iconForCollection(const Collection &col) const { // Reset the cache when icon theme changes if (mIconThemeName != QIcon::themeName()) { mIconThemeName = QIcon::themeName(); mIconCache.clear(); } QString iconName; if (col.hasAttribute()) { iconName = col.attribute()->iconName(); } if (iconName.isEmpty()) { iconName = CollectionUtils::defaultIconName(col); } QIcon &icon = mIconCache[iconName]; if (icon.isNull()) { icon = QIcon::fromTheme(iconName); } return icon; } void CollectionModelPrivate::startFirstListJob() { Q_Q(CollectionModel); // start a list job CollectionFetchJob *job = new CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive, session); job->fetchScope().setListFilter(unsubscribed ? CollectionFetchScope::NoFilter : CollectionFetchScope::Enabled); job->fetchScope().setIncludeStatistics(fetchStatistics); q->connect(job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), q, SLOT(collectionsChanged(Akonadi::Collection::List))); q->connect(job, SIGNAL(result(KJob*)), q, SLOT(listDone(KJob*))); } //@endcond diff --git a/src/core/models/entitytreemodel_p.cpp b/src/core/models/entitytreemodel_p.cpp index a9338a1d9..036898292 100644 --- a/src/core/models/entitytreemodel_p.cpp +++ b/src/core/models/entitytreemodel_p.cpp @@ -1,1978 +1,1978 @@ /* Copyright (c) 2008 Stephen Kelly 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 "entitytreemodel_p.h" #include "entitytreemodel.h" #include "agentmanagerinterface.h" #include "monitor_p.h" // For friend ref/deref #include "servermanager.h" #include "vectorhelper.h" #include #include "agentmanager.h" #include "agenttype.h" #include "monitor.h" #include "changerecorder.h" #include "collectioncopyjob.h" #include "collectionfetchjob.h" #include "collectionfetchscope.h" #include "collectionmovejob.h" #include "collectionstatistics.h" #include "collectionstatisticsjob.h" #include "entityhiddenattribute.h" #include "itemcopyjob.h" #include "itemfetchjob.h" #include "itemmodifyjob.h" #include "itemmovejob.h" #include "linkjob.h" #include "session.h" #include "private/protocol_p.h" #include "akonadicore_debug.h" #include #include QHash jobTimeTracker; Q_LOGGING_CATEGORY(DebugETM, "org.kde.pim.akonadi.ETM", QtInfoMsg) using namespace Akonadi; static CollectionFetchJob::Type getFetchType(EntityTreeModel::CollectionFetchStrategy strategy) { switch (strategy) { case EntityTreeModel::FetchFirstLevelChildCollections: return CollectionFetchJob::FirstLevel; case EntityTreeModel::InvisibleCollectionFetch: case EntityTreeModel::FetchCollectionsRecursive: default: break; } return CollectionFetchJob::Recursive; } EntityTreeModelPrivate::EntityTreeModelPrivate(EntityTreeModel *parent) : q_ptr(parent) , m_monitor(nullptr) , m_rootNode(nullptr) , m_collectionFetchStrategy(EntityTreeModel::FetchCollectionsRecursive) , m_itemPopulation(EntityTreeModel::ImmediatePopulation) , m_listFilter(CollectionFetchScope::NoFilter) , m_includeStatistics(false) , m_showRootCollection(false) , m_collectionTreeFetched(false) , m_session(nullptr) , m_showSystemEntities(false) { // using collection as a parameter of a queued call in runItemFetchJob() qRegisterMetaType(); Akonadi::AgentManager *agentManager = Akonadi::AgentManager::self(); QObject::connect(agentManager, SIGNAL(instanceRemoved(Akonadi::AgentInstance)), q_ptr, SLOT(agentInstanceRemoved(Akonadi::AgentInstance))); } EntityTreeModelPrivate::~EntityTreeModelPrivate() { if (m_needDeleteRootNode) { delete m_rootNode; } m_rootNode = nullptr; } void EntityTreeModelPrivate::init(Monitor *monitor) { Q_Q(EntityTreeModel); Q_ASSERT(!m_monitor); m_monitor = monitor; // The default is to FetchCollectionsRecursive, so we tell the monitor to fetch collections // That way update signals from the monitor will contain the full collection. // This may be updated if the CollectionFetchStrategy is changed. m_monitor->fetchCollection(true); m_session = m_monitor->session(); m_rootCollectionDisplayName = QStringLiteral("[*]"); if (Akonadi::ChangeRecorder *cr = qobject_cast(m_monitor)) { cr->setChangeRecordingEnabled(false); } m_includeStatistics = true; m_monitor->fetchCollectionStatistics(true); m_monitor->collectionFetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::All); q->connect(monitor, SIGNAL(mimeTypeMonitored(QString,bool)), SLOT(monitoredMimeTypeChanged(QString,bool))); q->connect(monitor, SIGNAL(collectionMonitored(Akonadi::Collection,bool)), SLOT(monitoredCollectionsChanged(Akonadi::Collection,bool))); q->connect(monitor, SIGNAL(itemMonitored(Akonadi::Item,bool)), SLOT(monitoredItemsChanged(Akonadi::Item,bool))); q->connect(monitor, SIGNAL(resourceMonitored(QByteArray,bool)), SLOT(monitoredResourcesChanged(QByteArray,bool))); // monitor collection changes q->connect(monitor, SIGNAL(collectionChanged(Akonadi::Collection)), SLOT(monitoredCollectionChanged(Akonadi::Collection))); q->connect(monitor, SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection)), SLOT(monitoredCollectionAdded(Akonadi::Collection,Akonadi::Collection))); q->connect(monitor, SIGNAL(collectionRemoved(Akonadi::Collection)), SLOT(monitoredCollectionRemoved(Akonadi::Collection))); q->connect(monitor, SIGNAL(collectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection)), SLOT(monitoredCollectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection))); // Monitor item changes. q->connect(monitor, SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection)), SLOT(monitoredItemAdded(Akonadi::Item,Akonadi::Collection))); q->connect(monitor, SIGNAL(itemChanged(Akonadi::Item,QSet)), SLOT(monitoredItemChanged(Akonadi::Item,QSet))); q->connect(monitor, SIGNAL(itemRemoved(Akonadi::Item)), SLOT(monitoredItemRemoved(Akonadi::Item))); q->connect(monitor, SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)), SLOT(monitoredItemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection))); q->connect(monitor, SIGNAL(itemLinked(Akonadi::Item,Akonadi::Collection)), SLOT(monitoredItemLinked(Akonadi::Item,Akonadi::Collection))); q->connect(monitor, SIGNAL(itemUnlinked(Akonadi::Item,Akonadi::Collection)), SLOT(monitoredItemUnlinked(Akonadi::Item,Akonadi::Collection))); q->connect(monitor, SIGNAL(collectionStatisticsChanged(Akonadi::Collection::Id,Akonadi::CollectionStatistics)), SLOT(monitoredCollectionStatisticsChanged(Akonadi::Collection::Id,Akonadi::CollectionStatistics))); Akonadi::ServerManager *serverManager = Akonadi::ServerManager::self(); q->connect(serverManager, SIGNAL(started()), SLOT(serverStarted())); fillModel(); } void EntityTreeModelPrivate::serverStarted() { // Don't emit about to be reset. Too late for that endResetModel(); } void EntityTreeModelPrivate::changeFetchState(const Collection &parent) { Q_Q(EntityTreeModel); const QModelIndex collectionIndex = indexForCollection(parent); if (!collectionIndex.isValid()) { // Because we are called delayed, it is possible that @p parent has been deleted. return; } q->dataChanged(collectionIndex, collectionIndex); } void EntityTreeModelPrivate::agentInstanceRemoved(const Akonadi::AgentInstance &instance) { Q_Q(EntityTreeModel); if (!instance.type().capabilities().contains(QLatin1String("Resource"))) { return; } if (m_rootCollection.isValid()) { if (m_rootCollection != Collection::root()) { if (m_rootCollection.resource() == instance.identifier()) { q->clearAndReset(); } return; } foreach (Node *node, m_childEntities[Collection::root().id()]) { Q_ASSERT(node->type == Node::Collection); const Collection collection = m_collections[node->id]; if (collection.resource() == instance.identifier()) { monitoredCollectionRemoved(collection); } } } } void EntityTreeModelPrivate::fetchItems(const Collection &parent) { Q_Q(const EntityTreeModel); Q_ASSERT(parent.isValid()); Q_ASSERT(m_collections.contains(parent.id())); // TODO: Use a more specific fetch scope to get only the envelope for mails etc. ItemFetchJob *itemFetchJob = new Akonadi::ItemFetchJob(parent, m_session); itemFetchJob->setFetchScope(m_monitor->itemFetchScope()); itemFetchJob->fetchScope().setAncestorRetrieval(ItemFetchScope::All); itemFetchJob->fetchScope().setIgnoreRetrievalErrors(true); itemFetchJob->setDeliveryOption(ItemFetchJob::EmitItemsInBatches); itemFetchJob->setProperty(FetchCollectionId().constData(), QVariant(parent.id())); if (m_showRootCollection || parent != m_rootCollection) { m_pendingCollectionRetrieveJobs.insert(parent.id()); // If collections are not in the model, there will be no valid index for them. if ((m_collectionFetchStrategy != EntityTreeModel::InvisibleCollectionFetch) && (m_collectionFetchStrategy != EntityTreeModel::FetchNoCollections)) { // We need to invoke this delayed because we would otherwise be emitting a sequence like // - beginInsertRows // - dataChanged // - endInsertRows // which would confuse proxies. QMetaObject::invokeMethod(const_cast(q), "changeFetchState", Qt::QueuedConnection, Q_ARG(Akonadi::Collection, parent)); } } q->connect(itemFetchJob, SIGNAL(itemsReceived(Akonadi::Item::List)), q, SLOT(itemsFetched(Akonadi::Item::List))); q->connect(itemFetchJob, SIGNAL(result(KJob*)), q, SLOT(itemFetchJobDone(KJob*))); qCDebug(DebugETM) << "collection:" << parent.name(); jobTimeTracker[itemFetchJob].start(); } void EntityTreeModelPrivate::fetchCollections(Akonadi::CollectionFetchJob *job) { Q_Q(EntityTreeModel); job->fetchScope().setListFilter(m_listFilter); job->fetchScope().setContentMimeTypes(m_monitor->mimeTypesMonitored()); m_pendingCollectionFetchJobs.insert(static_cast(job)); if (m_collectionFetchStrategy == EntityTreeModel::InvisibleCollectionFetch) { q->connect(job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), q, SLOT(collectionListFetched(Akonadi::Collection::List))); } else { job->fetchScope().setIncludeStatistics(m_includeStatistics); job->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::All); q->connect(job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), q, SLOT(collectionsFetched(Akonadi::Collection::List))); } q->connect(job, SIGNAL(result(KJob*)), q, SLOT(collectionFetchJobDone(KJob*))); qCDebug(DebugETM) << "collection:" << job->collections(); jobTimeTracker[job].start(); } void EntityTreeModelPrivate::fetchCollections(const Collection::List &collections, CollectionFetchJob::Type type) { fetchCollections(new CollectionFetchJob(collections, type, m_session)); } void EntityTreeModelPrivate::fetchCollections(const Collection &collection, CollectionFetchJob::Type type) { Q_ASSERT(collection.isValid()); CollectionFetchJob *job = new CollectionFetchJob(collection, type, m_session); fetchCollections(job); } namespace Akonadi { template inline bool EntityTreeModelPrivate::isHiddenImpl(const T &entity, Node::Type type) const { if (m_showSystemEntities) { return false; } if (type == Node::Collection && entity.id() == m_rootCollection.id()) { return false; } // entity.hasAttribute() does not compile w/ GCC for // some reason if (entity.hasAttribute(EntityHiddenAttribute().type())) { return true; } const Collection parent = entity.parentCollection(); if (parent.isValid()) { return isHiddenImpl(parent, Node::Collection); } return false; } } bool EntityTreeModelPrivate::isHidden(const Akonadi::Collection &collection) const { return isHiddenImpl(collection, Node::Collection); } bool EntityTreeModelPrivate::isHidden(const Akonadi::Item &item) const { return isHiddenImpl(item, Node::Item); } void EntityTreeModelPrivate::collectionListFetched(const Akonadi::Collection::List &collections) { QVectorIterator it(collections); while (it.hasNext()) { const Collection collection = it.next(); if (isHidden(collection)) { continue; } m_collections.insert(collection.id(), collection); Node *node = new Node; node->id = collection.id(); node->parent = -1; node->type = Node::Collection; m_childEntities[-1].prepend(node); fetchItems(collection); } } static QSet getChildren(Collection::Id parent, const QHash &childParentMap) { QSet children; for (auto it = childParentMap.cbegin(), e = childParentMap.cend(); it != e; ++it) { if (it.value() == parent) { children << it.key(); children += getChildren(it.key(), childParentMap); } } return children; } void EntityTreeModelPrivate::collectionsFetched(const Akonadi::Collection::List &collections) { Q_Q(EntityTreeModel); QTime t; t.start(); QVectorIterator it(collections); QHash collectionsToInsert; while (it.hasNext()) { const Collection collection = it.next(); const Collection::Id collectionId = collection.id(); if (isHidden(collection)) { continue; } auto collectionIt = m_collections.find(collectionId); if (collectionIt != m_collections.end()) { // This is probably the result of a parent of a previous collection already being in the model. // Replace the dummy collection with the real one and move on. // This could also be the result of a monitor signal having already inserted the collection // into this model. There's no way to tell, so we just emit dataChanged. *collectionIt = collection; const QModelIndex collectionIndex = indexForCollection(collection); dataChanged(collectionIndex, collectionIndex); - emit q->collectionFetched(collectionId); + Q_EMIT q->collectionFetched(collectionId); continue; } //If we're monitoring collections somewhere in the tree we need to retrieve their ancestors now if (collection.parentCollection() != m_rootCollection && m_monitor->collectionsMonitored().contains(collection)) { retrieveAncestors(collection, false); } collectionsToInsert.insert(collectionId, collection); } //Build a list of subtrees to insert, with the root of the subtree on the left, and the complete subtree including root on the right QHash > subTreesToInsert; { //Build a child-parent map that allows us to build the subtrees afterwards QHash childParentMap; Q_FOREACH (const Collection &col, collectionsToInsert) { childParentMap.insert(col.id(), col.parentCollection().id()); //Complete the subtree up to the last known parent Collection parent = col.parentCollection(); while (parent.isValid() && parent != m_rootCollection && !m_collections.contains(parent.id())) { childParentMap.insert(parent.id(), parent.parentCollection().id()); if (!collectionsToInsert.contains(parent.id())) { collectionsToInsert.insert(parent.id(), parent); } parent = parent.parentCollection(); } } QSet parents; //Find toplevel parents of the subtrees for (auto it = childParentMap.cbegin(), e = childParentMap.cend(); it != e; ++it) { //The child has a parent without parent (it's a toplevel node that is not yet in m_collections) if (!childParentMap.contains(it.value())) { Q_ASSERT(!m_collections.contains(it.key())); parents << it.key(); } } //Find children of each subtree Q_FOREACH (Collection::Id p, parents) { QSet children; //We add the parent itself as well so it can be inserted below as part of the same loop children << p; children += getChildren(p, childParentMap); subTreesToInsert[p] = children; } } const int row = 0; QHashIterator > collectionIt(subTreesToInsert); while (collectionIt.hasNext()) { collectionIt.next(); const Collection::Id topCollectionId = collectionIt.key(); qCDebug(DebugETM) << "Subtree: " << topCollectionId << collectionIt.value(); Q_ASSERT(!m_collections.contains(topCollectionId)); Collection topCollection = collectionsToInsert.value(topCollectionId); Q_ASSERT(topCollection.isValid()); //The toplevels parent must already be part of the model Q_ASSERT(m_collections.contains(topCollection.parentCollection().id())); const QModelIndex parentIndex = indexForCollection(topCollection.parentCollection()); q->beginInsertRows(parentIndex, row, row); Q_ASSERT(!collectionIt.value().isEmpty()); foreach (Collection::Id collectionId, collectionIt.value()) { const Collection collection = collectionsToInsert.take(collectionId); Q_ASSERT(collection.isValid()); m_collections.insert(collectionId, collection); Node *node = new Node; node->id = collectionId; Q_ASSERT(collection.parentCollection().isValid()); node->parent = collection.parentCollection().id(); node->type = Node::Collection; m_childEntities[node->parent].prepend(node); } q->endInsertRows(); if (m_itemPopulation == EntityTreeModel::ImmediatePopulation) { foreach (const Collection::Id &collectionId, collectionIt.value()) { const auto col = m_collections.value(collectionId); if (!m_mimeChecker.hasWantedMimeTypes() || m_mimeChecker.isWantedCollection(col)) { fetchItems(col); } else { // Consider collections that don't contain relevant mimetypes to be populated m_populatedCols.insert(collectionId); Q_EMIT q_ptr->collectionPopulated(collectionId); const auto idx = indexForCollection(Collection(collectionId)); Q_ASSERT(idx.isValid()); dataChanged(idx, idx); } } } } } void EntityTreeModelPrivate::itemsFetched(const Akonadi::Item::List &items) { Q_Q(EntityTreeModel); const Collection::Id collectionId = q->sender()->property(FetchCollectionId().constData()).value(); itemsFetched(collectionId, items); } void EntityTreeModelPrivate::itemsFetched(const Collection::Id collectionId, const Akonadi::Item::List &items) { Q_Q(EntityTreeModel); if (!m_collections.contains(collectionId)) { qCWarning(AKONADICORE_LOG) << "Collection has been removed while fetching items"; return; } Item::List itemsToInsert; const Collection collection = m_collections.value(collectionId); Q_ASSERT(collection.isValid()); // if there are any items at all, remove from set of collections known to be empty if (!items.isEmpty()) { m_collectionsWithoutItems.remove(collectionId); } foreach (const Item &item, items) { if (isHidden(item)) { continue; } if ((!m_mimeChecker.hasWantedMimeTypes() || m_mimeChecker.isWantedItem(item))) { // When listing virtual collections we might get results for items which are already in // the model if their concrete collection has already been listed. // In that case the collectionId should be different though. // As an additional complication, new items might be both part of fetch job results and // part of monitor notifications. We only insert items which are not already in the model // considering their (possibly virtual) parent. bool isNewItem = true; auto itemIt = m_items.find(item.id()); if (itemIt != m_items.end()) { const Akonadi::Collection::List parents = getParentCollections(item); for (const Akonadi::Collection &parent : parents) { if (parent.id() == collectionId) { qCWarning(AKONADICORE_LOG) << "Fetched an item which is already in the model"; // Update it in case the revision changed; itemIt->value.apply(item); isNewItem = false; break; } } } if (isNewItem) { itemsToInsert << item; } } } if (!itemsToInsert.isEmpty()) { const Collection::Id colId = m_collectionFetchStrategy == EntityTreeModel::InvisibleCollectionFetch ? m_rootCollection.id() : m_collectionFetchStrategy == EntityTreeModel::FetchNoCollections ? m_rootCollection.id() : collectionId; const int startRow = m_childEntities.value(colId).size(); Q_ASSERT(m_collections.contains(colId)); const QModelIndex parentIndex = indexForCollection(m_collections.value(colId)); q->beginInsertRows(parentIndex, startRow, startRow + itemsToInsert.size() - 1); foreach (const Item &item, itemsToInsert) { const Item::Id itemId = item.id(); m_items.ref(itemId, item); Node *node = new Node; node->id = itemId; node->parent = collectionId; node->type = Node::Item; m_childEntities[colId].append(node); } q->endInsertRows(); } } void EntityTreeModelPrivate::monitoredMimeTypeChanged(const QString &mimeType, bool monitored) { beginResetModel(); if (monitored) { m_mimeChecker.addWantedMimeType(mimeType); } else { m_mimeChecker.removeWantedMimeType(mimeType); } endResetModel(); } void EntityTreeModelPrivate::monitoredCollectionsChanged(const Akonadi::Collection &collection, bool monitored) { if (monitored) { const CollectionFetchJob::Type fetchType = getFetchType(m_collectionFetchStrategy); fetchCollections(collection, CollectionFetchJob::Base); fetchCollections(collection, fetchType); } else { //If a collection is dereferenced and no longer explicitly monitored it might still match other filters if (!shouldBePartOfModel(collection)) { monitoredCollectionRemoved(collection); } } } void EntityTreeModelPrivate::monitoredItemsChanged(const Akonadi::Item &item, bool monitored) { Q_UNUSED(item) Q_UNUSED(monitored) beginResetModel(); endResetModel(); } void EntityTreeModelPrivate::monitoredResourcesChanged(const QByteArray &resource, bool monitored) { Q_UNUSED(resource) Q_UNUSED(monitored) beginResetModel(); endResetModel(); } void EntityTreeModelPrivate::retrieveAncestors(const Akonadi::Collection &collection, bool insertBaseCollection) { Q_Q(EntityTreeModel); Collection parentCollection = collection.parentCollection(); Q_ASSERT(parentCollection.isValid()); Q_ASSERT(parentCollection != Collection::root()); Collection::List ancestors; while (parentCollection != Collection::root() && !m_collections.contains(parentCollection.id())) { // Put a temporary node in the tree later. ancestors.prepend(parentCollection); parentCollection = parentCollection.parentCollection(); } Q_ASSERT(parentCollection.isValid()); // if m_rootCollection is Collection::root(), we always have common ancestor and do the retrival // if we traversed up to Collection::root() but are looking at a subtree only (m_rootCollection != Collection::root()) // we have no common ancestor, and we don't have to retrieve anything if (parentCollection == Collection::root() && m_rootCollection != Collection::root()) { return; } if (ancestors.isEmpty() && !insertBaseCollection) { //Nothing to do, avoid emitting insert signals return; } if (!ancestors.isEmpty()) { // Fetch the real ancestors CollectionFetchJob *job = new CollectionFetchJob(ancestors, CollectionFetchJob::Base, m_session); job->fetchScope().setListFilter(m_listFilter); job->fetchScope().setIncludeStatistics(m_includeStatistics); q->connect(job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), q, SLOT(ancestorsFetched(Akonadi::Collection::List))); q->connect(job, SIGNAL(result(KJob*)), q, SLOT(collectionFetchJobDone(KJob*))); } // Q_ASSERT( parentCollection != m_rootCollection ); const QModelIndex parent = indexForCollection(parentCollection); // Still prepending all collections for now. int row = 0; // Although we insert several Collections here, we only need to notify though the model // about the top-level one. The rest will be found automatically by the view. q->beginInsertRows(parent, row, row); Collection::List::const_iterator it = ancestors.constBegin(); const Collection::List::const_iterator end = ancestors.constEnd(); for (; it != end; ++it) { const Collection ancestor = *it; Q_ASSERT(ancestor.parentCollection().isValid()); m_collections.insert(ancestor.id(), ancestor); Node *node = new Node; node->id = ancestor.id(); node->parent = ancestor.parentCollection().id(); node->type = Node::Collection; m_childEntities[node->parent].prepend(node); } if (insertBaseCollection) { m_collections.insert(collection.id(), collection); Node *node = new Node; node->id = collection.id(); // Can't just use parentCollection because that doesn't necessarily refer to collection. node->parent = collection.parentCollection().id(); node->type = Node::Collection; m_childEntities[node->parent].prepend(node); } q->endInsertRows(); } void EntityTreeModelPrivate::ancestorsFetched(const Akonadi::Collection::List &collectionList) { for (const Collection &collection : collectionList) { m_collections[collection.id()] = collection; const QModelIndex index = indexForCollection(collection); Q_ASSERT(index.isValid()); dataChanged(index, index); } } void EntityTreeModelPrivate::insertCollection(const Akonadi::Collection &collection, const Akonadi::Collection &parent) { Q_ASSERT(collection.isValid()); Q_ASSERT(parent.isValid()); Q_Q(EntityTreeModel); const int row = 0; const QModelIndex parentIndex = indexForCollection(parent); q->beginInsertRows(parentIndex, row, row); m_collections.insert(collection.id(), collection); Node *node = new Node; node->id = collection.id(); node->parent = parent.id(); node->type = Node::Collection; m_childEntities[parent.id()].prepend(node); q->endInsertRows(); } bool EntityTreeModelPrivate::hasChildCollection(const Collection &collection) const { foreach (Node *node, m_childEntities[collection.id()]) { if (node->type == Node::Collection) { const Collection subcol = m_collections[node->id]; if (shouldBePartOfModel(subcol)) { return true; } } } return false; } bool EntityTreeModelPrivate::isAncestorMonitored(const Collection &collection) const { Akonadi::Collection parent = collection.parentCollection(); while (parent.isValid()) { if (m_monitor->collectionsMonitored().contains(parent)) { return true; } parent = parent.parentCollection(); } return false; } bool EntityTreeModelPrivate::shouldBePartOfModel(const Collection &collection) const { if (isHidden(collection)) { return false; } // We want a parent collection if it has at least one child that matches the // wanted mimetype if (hasChildCollection(collection)) { return true; } //Explicitly monitored collection if (m_monitor->collectionsMonitored().contains(collection)) { return true; } //We're explicitly monitoring collections, but didn't match the filter if (!m_mimeChecker.hasWantedMimeTypes() && !m_monitor->collectionsMonitored().isEmpty()) { //The collection should be included if one of the parents is monitored if (isAncestorMonitored(collection)) { return true; } return false; } // Some collection trees contain multiple mimetypes. Even though server side filtering ensures we // only get the ones we're interested in from the job, we have to filter on collections received through signals too. if (m_mimeChecker.hasWantedMimeTypes() && !m_mimeChecker.isWantedCollection(collection)) { return false; } if (m_listFilter == CollectionFetchScope::Enabled) { if (!collection.enabled() && !collection.referenced()) { return false; } } else if (m_listFilter == CollectionFetchScope::Display) { if (!collection.shouldList(Collection::ListDisplay)) { return false; } } else if (m_listFilter == CollectionFetchScope::Sync) { if (!collection.shouldList(Collection::ListSync)) { return false; } } else if (m_listFilter == CollectionFetchScope::Index) { if (!collection.shouldList(Collection::ListIndex)) { return false; } } return true; } void EntityTreeModelPrivate::monitoredCollectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent) { // If the resource is removed while populating the model with it, we might still // get some monitor signals. These stale/out-of-order signals can't be completely eliminated // in the akonadi server due to implementation details, so we also handle such signals in the model silently // in all the monitored slots. // Stephen Kelly, 28, July 2009 // If a fetch job is started and a collection is added to akonadi after the fetch job is started, the // new collection will be added to the fetch job results. It will also be notified through the monitor. // We return early here in that case. if (m_collections.contains(collection.id())) { return; } //If the resource is explicitly monitored all other checks are skipped. topLevelCollectionsFetched still checks the hidden attribute. if (m_monitor->resourcesMonitored().contains(collection.resource().toUtf8()) && collection.parentCollection() == Collection::root()) { return topLevelCollectionsFetched(Collection::List() << collection); } if (!shouldBePartOfModel(collection)) { return; } if (!m_collections.contains(parent.id())) { // The collection we're interested in is contained in a collection we're not interested in. // We download the ancestors of the collection we're interested in to complete the tree. if (collection != Collection::root()) { retrieveAncestors(collection); } if (m_itemPopulation == EntityTreeModel::ImmediatePopulation) { fetchItems(collection); } return; } insertCollection(collection, parent); if (m_itemPopulation == EntityTreeModel::ImmediatePopulation) { fetchItems(collection); } } void EntityTreeModelPrivate::monitoredCollectionRemoved(const Akonadi::Collection &collection) { //if an explicitly monitored collection is removed, we would also have to remove collections which were included to show it (as in the move case) if ((collection == m_rootCollection) || m_monitor->collectionsMonitored().contains(collection)) { beginResetModel(); endResetModel(); return; } Collection::Id parentId = collection.parentCollection().id(); if (parentId < 0) { parentId = -1; } if (!m_collections.contains(parentId)) { return; } // This may be a signal for a collection we've already removed by removing its ancestor. // Or the collection may have been hidden. if (!m_collections.contains(collection.id())) { return; } Q_Q(EntityTreeModel); Q_ASSERT(m_childEntities.contains(parentId)); const int row = indexOf(m_childEntities.value(parentId), collection.id()); Q_ASSERT(row >= 0); Q_ASSERT(m_collections.contains(parentId)); const Collection parentCollection = m_collections.value(parentId); m_populatedCols.remove(collection.id()); const QModelIndex parentIndex = indexForCollection(parentCollection); q->beginRemoveRows(parentIndex, row, row); // Delete all descendant collections and items. removeChildEntities(collection.id()); // Remove deleted collection from its parent. delete m_childEntities[parentId].takeAt(row); // Remove deleted collection itself. m_collections.remove(collection.id()); q->endRemoveRows(); // After removing a collection, check whether it's parent should be removed too if (!shouldBePartOfModel(parentCollection)) { monitoredCollectionRemoved(parentCollection); } } void EntityTreeModelPrivate::removeChildEntities(Collection::Id collectionId) { QList childList = m_childEntities.value(collectionId); QList::const_iterator it = childList.constBegin(); const QList::const_iterator end = childList.constEnd(); for (; it != end; ++it) { if (Node::Item == (*it)->type) { m_items.unref((*it)->id); } else { removeChildEntities((*it)->id); m_collections.remove((*it)->id); m_populatedCols.remove((*it)->id); } } qDeleteAll(m_childEntities.take(collectionId)); } QStringList EntityTreeModelPrivate::childCollectionNames(const Collection &collection) const { QStringList names; foreach (Node *node, m_childEntities[collection.id()]) { if (node->type == Node::Collection) { names << m_collections.value(node->id).name(); } } return names; } void EntityTreeModelPrivate::monitoredCollectionMoved(const Akonadi::Collection &collection, const Akonadi::Collection &sourceCollection, const Akonadi::Collection &destCollection) { if (isHidden(collection)) { return; } if (isHidden(sourceCollection)) { if (isHidden(destCollection)) { return; } monitoredCollectionAdded(collection, destCollection); return; } else if (isHidden(destCollection)) { monitoredCollectionRemoved(collection); return; } if (!m_collections.contains(collection.id())) { return; } if (m_monitor->collectionsMonitored().contains(collection)) { //if we don't reset here, we would have to make sure that destination collection is actually available, //and remove the sources parents if they were only included as parents of the moved collection beginResetModel(); endResetModel(); return; } Q_Q(EntityTreeModel); const QModelIndex srcParentIndex = indexForCollection(sourceCollection); const QModelIndex destParentIndex = indexForCollection(destCollection); Q_ASSERT(collection.parentCollection().isValid()); Q_ASSERT(destCollection.isValid()); Q_ASSERT(collection.parentCollection() == destCollection); const int srcRow = indexOf(m_childEntities.value(sourceCollection.id()), collection.id()); const int destRow = 0; // Prepend collections if (!q->beginMoveRows(srcParentIndex, srcRow, srcRow, destParentIndex, destRow)) { qCWarning(AKONADICORE_LOG) << "Invalid move"; return; } Node *node = m_childEntities[sourceCollection.id()].takeAt(srcRow); // collection has the correct parentCollection etc. We need to set it on the // internal data structure to not corrupt things. m_collections.insert(collection.id(), collection); node->parent = destCollection.id(); m_childEntities[destCollection.id()].prepend(node); q->endMoveRows(); } void EntityTreeModelPrivate::monitoredCollectionChanged(const Akonadi::Collection &collection) { if (!m_collections.contains(collection.id())) { // This can happen if // * we get a change notification after removing the collection. // * a collection of a non-monitored mimetype is changed elsewhere. Monitor does not // filter by content mimetype of Collections so we get notifications for all of them. //We might match the filter now, retry adding the collection monitoredCollectionAdded(collection, collection.parentCollection()); return; } if (!shouldBePartOfModel(collection)) { monitoredCollectionRemoved(collection); return; } m_collections[collection.id()] = collection; if (!m_showRootCollection && collection == m_rootCollection) { // If the root of the model is not Collection::root it might be modified. // But it doesn't exist in the accessible model structure, so we need to early return return; } const QModelIndex index = indexForCollection(collection); Q_ASSERT(index.isValid()); dataChanged(index, index); } void EntityTreeModelPrivate::monitoredCollectionStatisticsChanged(Akonadi::Collection::Id id, const Akonadi::CollectionStatistics &statistics) { if (!m_collections.contains(id)) { return; } m_collections[id].setStatistics(statistics); // if the item count becomes 0, add to set of collections we know to be empty // otherwise remove if in there if (statistics.count() == 0) { m_collectionsWithoutItems.insert(id); } else { m_collectionsWithoutItems.remove(id); } if (!m_showRootCollection && id == m_rootCollection.id()) { // If the root of the model is not Collection::root it might be modified. // But it doesn't exist in the accessible model structure, so we need to early return return; } const QModelIndex index = indexForCollection(m_collections[id]); dataChanged(index, index); } void EntityTreeModelPrivate::monitoredItemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection) { Q_Q(EntityTreeModel); if (isHidden(item)) { return; } if (m_collectionFetchStrategy != EntityTreeModel::InvisibleCollectionFetch && !m_collections.contains(collection.id())) { qCWarning(AKONADICORE_LOG) << "Got a stale 'added' notification for an item whose collection was already removed." << item.id() << item.remoteId(); return; } if (m_items.contains(item.id())) { return; } Q_ASSERT(m_collectionFetchStrategy != EntityTreeModel::InvisibleCollectionFetch ? m_collections.contains(collection.id()) : true); if (m_mimeChecker.hasWantedMimeTypes() && !m_mimeChecker.isWantedItem(item)) { return; } //Adding items to not yet populated collections would block fetchMore, resulting in only new items showing up in the collection //This is only a problem with lazy population, otherwise fetchMore is not used at all if ((m_itemPopulation == EntityTreeModel::LazyPopulation) && !m_populatedCols.contains(collection.id())) { return; } int row; QModelIndex parentIndex; if (m_collectionFetchStrategy != EntityTreeModel::InvisibleCollectionFetch) { row = m_childEntities.value(collection.id()).size(); parentIndex = indexForCollection(m_collections.value(collection.id())); } else { row = q->rowCount(); } q->beginInsertRows(parentIndex, row, row); m_items.ref(item.id(), item); Node *node = new Node; node->id = item.id(); node->parent = collection.id(); node->type = Node::Item; if (m_collectionFetchStrategy != EntityTreeModel::InvisibleCollectionFetch) { m_childEntities[collection.id()].append(node); } else { m_childEntities[m_rootCollection.id()].append(node); } q->endInsertRows(); } void EntityTreeModelPrivate::monitoredItemRemoved(const Akonadi::Item &item, const Akonadi::Collection &parentCollection) { Q_Q(EntityTreeModel); if (isHidden(item)) { return; } if ((m_itemPopulation == EntityTreeModel::LazyPopulation) && !m_populatedCols.contains(parentCollection.isValid() ? parentCollection.id() : item.parentCollection().id())) { return; } const Collection::List parents = getParentCollections(item); if (parents.isEmpty()) { return; } if (!m_items.contains(item.id())) { qCWarning(AKONADICORE_LOG) << "Got a stale 'removed' notification for an item which was already removed." << item.id() << item.remoteId(); return; } for (const auto &collection : parents) { Q_ASSERT(m_collections.contains(collection.id())); Q_ASSERT(m_childEntities.contains(collection.id())); const int row = indexOf(m_childEntities.value(collection.id()), item.id()); Q_ASSERT(row >= 0); const QModelIndex parentIndex = indexForCollection(m_collections.value(collection.id())); q->beginRemoveRows(parentIndex, row, row); m_items.unref(item.id()); delete m_childEntities[collection.id()].takeAt(row); q->endRemoveRows(); } } void EntityTreeModelPrivate::monitoredItemChanged(const Akonadi::Item &item, const QSet &) { if (isHidden(item)) { return; } if ((m_itemPopulation == EntityTreeModel::LazyPopulation) && !m_populatedCols.contains(item.parentCollection().id())) { return; } auto itemIt = m_items.find(item.id()); if (itemIt == m_items.end()) { qCWarning(AKONADICORE_LOG) << "Got a stale 'changed' notification for an item which was already removed." << item.id() << item.remoteId(); return; } itemIt->value.apply(item); // Notifications about itemChange are always dispatched for real collection // and also all virtual collections the item belongs to. In order to preserve // the original storage collection when we need to have special handling for // notifications for virtual collections if (item.parentCollection().isVirtual()) { const Collection originalParent = itemIt->value.parentCollection(); itemIt->value.setParentCollection(originalParent); } const QModelIndexList indexes = indexesForItem(item); for (const QModelIndex &index : indexes) { if (index.isValid()) { dataChanged(index, index); } else { qCWarning(AKONADICORE_LOG) << "item has invalid index:" << item.id() << item.remoteId(); } } } void EntityTreeModelPrivate::monitoredItemMoved(const Akonadi::Item &item, const Akonadi::Collection &sourceCollection, const Akonadi::Collection &destCollection) { if (isHidden(item)) { return; } if (isHidden(sourceCollection)) { if (isHidden(destCollection)) { return; } monitoredItemAdded(item, destCollection); return; } else if (isHidden(destCollection)) { monitoredItemRemoved(item, sourceCollection); return; } else { monitoredItemRemoved(item, sourceCollection); monitoredItemAdded(item, destCollection); return; } // "Temporarily" commented out as it's likely the best course to // avoid the dreaded "reset storm" (or layoutChanged storm). The // whole itemMoved idea is great but not practical until all the // other proxy models play nicely with it, right now they just // transform moved signals in layout changed, which explodes into // a reset of the source model inside of the message list (ouch!) #if 0 if (!m_items.contains(item.id())) { qCWarning(AKONADICORE_LOG) << "Got a stale 'moved' notification for an item which was already removed." << item.id() << item.remoteId(); return; } Q_ASSERT(m_collections.contains(sourceCollection.id())); Q_ASSERT(m_collections.contains(destCollection.id())); const QModelIndex srcIndex = indexForCollection(sourceCollection); const QModelIndex destIndex = indexForCollection(destCollection); // Where should it go? Always append items and prepend collections and reorganize them with separate reactions to Attributes? const Item::Id itemId = item.id(); const int srcRow = indexOf(m_childEntities.value(sourceCollection.id()), itemId); const int destRow = q->rowCount(destIndex); Q_ASSERT(srcRow >= 0); Q_ASSERT(destRow >= 0); if (!q->beginMoveRows(srcIndex, srcRow, srcRow, destIndex, destRow)) { qCWarning(AKONADICORE_LOG) << "Invalid move"; return; } Q_ASSERT(m_childEntities.contains(sourceCollection.id())); Q_ASSERT(m_childEntities[sourceCollection.id()].size() > srcRow); Node *node = m_childEntities[sourceCollection.id()].takeAt(srcRow); m_items.insert(item.id(), item); node->parent = destCollection.id(); m_childEntities[destCollection.id()].append(node); q->endMoveRows(); #endif } void EntityTreeModelPrivate::monitoredItemLinked(const Akonadi::Item &item, const Akonadi::Collection &collection) { Q_Q(EntityTreeModel); if (isHidden(item)) { return; } const Collection::Id collectionId = collection.id(); const Item::Id itemId = item.id(); if (m_collectionFetchStrategy != EntityTreeModel::InvisibleCollectionFetch && !m_collections.contains(collection.id())) { qCWarning(AKONADICORE_LOG) << "Got a stale 'linked' notification for an item whose collection was already removed." << item.id() << item.remoteId(); return; } Q_ASSERT(m_collectionFetchStrategy != EntityTreeModel::InvisibleCollectionFetch ? m_collections.contains(collectionId) : true); if (m_mimeChecker.hasWantedMimeTypes() && !m_mimeChecker.isWantedItem(item)) { return; } //Adding items to not yet populated collections would block fetchMore, resullting in only new items showing up in the collection //This is only a problem with lazy population, otherwise fetchMore is not used at all if ((m_itemPopulation == EntityTreeModel::LazyPopulation) && !m_populatedCols.contains(collectionId)) { return; } QList &collectionEntities = m_childEntities[collectionId]; const int existingPosition = indexOf(collectionEntities, itemId); if (existingPosition > 0) { qCWarning(AKONADICORE_LOG) << "Item with id " << itemId << " already in virtual collection with id " << collectionId; return; } const int row = collectionEntities.size(); const QModelIndex parentIndex = indexForCollection(m_collections.value(collectionId)); q->beginInsertRows(parentIndex, row, row); m_items.ref(itemId, item); Node *node = new Node; node->id = itemId; node->parent = collectionId; node->type = Node::Item; collectionEntities.append(node); q->endInsertRows(); } void EntityTreeModelPrivate::monitoredItemUnlinked(const Akonadi::Item &item, const Akonadi::Collection &collection) { Q_Q(EntityTreeModel); if (isHidden(item)) { return; } if ((m_itemPopulation == EntityTreeModel::LazyPopulation) && !m_populatedCols.contains(item.parentCollection().id())) { return; } if (!m_items.contains(item.id())) { qCWarning(AKONADICORE_LOG) << "Got a stale 'unlinked' notification for an item which was already removed." << item.id() << item.remoteId(); return; } Q_ASSERT(m_collectionFetchStrategy != EntityTreeModel::InvisibleCollectionFetch ? m_collections.contains(collection.id()) : true); const int row = indexOf(m_childEntities.value(collection.id()), item.id()); if (row < 0 || row >= m_childEntities[ collection.id() ].size()) { qCWarning(AKONADICORE_LOG) << "couldn't find index of unlinked item " << item.id() << collection.id() << row; Q_ASSERT(false); return; } const QModelIndex parentIndex = indexForCollection(m_collections.value(collection.id())); q->beginRemoveRows(parentIndex, row, row); delete m_childEntities[collection.id()].takeAt(row); m_items.unref(item.id()); q->endRemoveRows(); } void EntityTreeModelPrivate::collectionFetchJobDone(KJob *job) { m_pendingCollectionFetchJobs.remove(job); CollectionFetchJob *cJob = static_cast(job); if (job->error()) { qCWarning(AKONADICORE_LOG) << "Job error: " << job->errorString() << "for collection:" << cJob->collections() << endl; return; } if (!m_collectionTreeFetched && m_pendingCollectionFetchJobs.isEmpty()) { m_collectionTreeFetched = true; - emit q_ptr->collectionTreeFetched(Akonadi::valuesToVector(m_collections)); + Q_EMIT q_ptr->collectionTreeFetched(Akonadi::valuesToVector(m_collections)); } qCDebug(DebugETM) << "Fetch job took " << jobTimeTracker.take(job).elapsed() << "msec"; qCDebug(DebugETM) << "was collection fetch job: collections:" << cJob->collections().size(); if (!cJob->collections().isEmpty()) { qCDebug(DebugETM) << "first fetched collection:" << cJob->collections().at(0).name(); } } void EntityTreeModelPrivate::itemFetchJobDone(KJob *job) { const Collection::Id collectionId = job->property(FetchCollectionId().constData()).value(); m_pendingCollectionRetrieveJobs.remove(collectionId); if (job->error()) { qCWarning(AKONADICORE_LOG) << "Job error: " << job->errorString() << "for collection:" << collectionId << endl; return; } if (!m_collections.contains(collectionId)) { qCWarning(AKONADICORE_LOG) << "Collection has been removed while fetching items"; return; } ItemFetchJob *iJob = static_cast(job); qCDebug(DebugETM) << "Fetch job took " << jobTimeTracker.take(job).elapsed() << "msec"; qCDebug(DebugETM) << "was item fetch job: items:" << iJob->count(); if (!iJob->count()) { m_collectionsWithoutItems.insert(collectionId); } else { m_collectionsWithoutItems.remove(collectionId); } m_populatedCols.insert(collectionId); - emit q_ptr->collectionPopulated(collectionId); + Q_EMIT q_ptr->collectionPopulated(collectionId); // If collections are not in the model, there will be no valid index for them. if ((m_collectionFetchStrategy != EntityTreeModel::InvisibleCollectionFetch) && (m_collectionFetchStrategy != EntityTreeModel::FetchNoCollections) && !(!m_showRootCollection && collectionId == m_rootCollection.id())) { const QModelIndex index = indexForCollection(Collection(collectionId)); Q_ASSERT(index.isValid()); //To notify about the changed fetch and population state dataChanged(index, index); } } void EntityTreeModelPrivate::pasteJobDone(KJob *job) { if (job->error()) { QString errorMsg; if (qobject_cast(job)) { errorMsg = i18n("Could not copy item:"); } else if (qobject_cast(job)) { errorMsg = i18n("Could not copy collection:"); } else if (qobject_cast(job)) { errorMsg = i18n("Could not move item:"); } else if (qobject_cast(job)) { errorMsg = i18n("Could not move collection:"); } else if (qobject_cast(job)) { errorMsg = i18n("Could not link entity:"); } errorMsg += QLatin1Char(' ') + job->errorString(); QMessageBox::critical(nullptr, i18n("Error"), errorMsg); } } void EntityTreeModelPrivate::updateJobDone(KJob *job) { if (job->error()) { // TODO: handle job errors qCWarning(AKONADICORE_LOG) << "Job error:" << job->errorString(); } } void EntityTreeModelPrivate::rootFetchJobDone(KJob *job) { if (job->error()) { qCWarning(AKONADICORE_LOG) << job->errorString(); return; } CollectionFetchJob *collectionJob = qobject_cast(job); const Collection::List list = collectionJob->collections(); Q_ASSERT(list.size() == 1); m_rootCollection = list.first(); startFirstListJob(); } void EntityTreeModelPrivate::startFirstListJob() { Q_Q(EntityTreeModel); if (!m_collections.isEmpty()) { return; } // Even if the root collection is the invalid collection, we still need to start // the first list job with Collection::root. if (m_showRootCollection) { // Notify the outside that we're putting collection::root into the model. q->beginInsertRows(QModelIndex(), 0, 0); m_collections.insert(m_rootCollection.id(), m_rootCollection); delete m_rootNode; m_rootNode = new Node; m_rootNode->id = m_rootCollection.id(); m_rootNode->parent = -1; m_rootNode->type = Node::Collection; m_childEntities[-1].append(m_rootNode); q->endInsertRows(); } else { // Otherwise store it silently because it's not part of the usable model. delete m_rootNode; m_rootNode = new Node; m_needDeleteRootNode = true; m_rootNode->id = m_rootCollection.id(); m_rootNode->parent = -1; m_rootNode->type = Node::Collection; m_collections.insert(m_rootCollection.id(), m_rootCollection); } const bool noMimetypes = !m_mimeChecker.hasWantedMimeTypes(); const bool noResources = m_monitor->resourcesMonitored().isEmpty(); const bool multipleCollections = m_monitor->collectionsMonitored().size() > 1; const bool generalPopulation = !noMimetypes || noResources; const CollectionFetchJob::Type fetchType = getFetchType(m_collectionFetchStrategy); //Collections can only be monitored if no resources and no mimetypes are monitored if (multipleCollections && noMimetypes && noResources) { fetchCollections(m_monitor->collectionsMonitored(), CollectionFetchJob::Base); fetchCollections(m_monitor->collectionsMonitored(), fetchType); return; } qCDebug(DebugETM) << "GEN" << generalPopulation << noMimetypes << noResources; if (generalPopulation) { fetchCollections(m_rootCollection, fetchType); } // If the root collection is not collection::root, then it could have items, and they will need to be // retrieved now. // Only fetch items NOT if there is NoItemPopulation, or if there is Lazypopulation and the root is visible // (if the root is not visible the lazy population can not be triggered) if ((m_itemPopulation != EntityTreeModel::NoItemPopulation) && !((m_itemPopulation == EntityTreeModel::LazyPopulation) && m_showRootCollection)) { if (m_rootCollection != Collection::root()) { fetchItems(m_rootCollection); } } // Resources which are explicitly monitored won't have appeared yet if their mimetype didn't match. // We fetch the top level collections and examine them for whether to add them. // This fetches virtual collections into the tree. if (!m_monitor->resourcesMonitored().isEmpty()) { fetchTopLevelCollections(); } } void EntityTreeModelPrivate::fetchTopLevelCollections() const { Q_Q(const EntityTreeModel); CollectionFetchJob *job = new CollectionFetchJob(Collection::root(), CollectionFetchJob::FirstLevel, m_session); q->connect(job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), q, SLOT(topLevelCollectionsFetched(Akonadi::Collection::List))); q->connect(job, SIGNAL(result(KJob*)), q, SLOT(collectionFetchJobDone(KJob*))); qCDebug(DebugETM) << "EntityTreeModelPrivate::fetchTopLevelCollections"; jobTimeTracker[job].start(); } void EntityTreeModelPrivate::topLevelCollectionsFetched(const Akonadi::Collection::List &list) { Q_Q(EntityTreeModel); for (const Collection &collection : list) { // These collections have been explicitly shown in the Monitor, // but hidden trumps that for now. This may change in the future if we figure out a use for it. if (isHidden(collection)) { continue; } if (m_monitor->resourcesMonitored().contains(collection.resource().toUtf8()) && !m_collections.contains(collection.id())) { const QModelIndex parentIndex = indexForCollection(collection.parentCollection()); // Prepending new collections. const int row = 0; q->beginInsertRows(parentIndex, row, row); m_collections.insert(collection.id(), collection); Node *node = new Node; node->id = collection.id(); Q_ASSERT(collection.parentCollection() == Collection::root()); node->parent = collection.parentCollection().id(); node->type = Node::Collection; m_childEntities[collection.parentCollection().id()].prepend(node); q->endInsertRows(); if (m_itemPopulation == EntityTreeModel::ImmediatePopulation) { fetchItems(collection); } Q_ASSERT(collection.isValid()); fetchCollections(collection, CollectionFetchJob::Recursive); } } } Akonadi::Collection::List EntityTreeModelPrivate::getParentCollections(const Item &item) const { Collection::List list; QHashIterator > iter(m_childEntities); while (iter.hasNext()) { iter.next(); int nodeIndex = indexOf(iter.value(), item.id()); if (nodeIndex != -1 && iter.value().at(nodeIndex)->type == Node::Item) { list << m_collections.value(iter.key()); } } return list; } void EntityTreeModelPrivate::ref(Collection::Id id) { m_monitor->d_ptr->ref(id); } bool EntityTreeModelPrivate::shouldPurge(Collection::Id id) { // reference counted collections should never be purged // they first have to be deref'ed until they reach 0. // if the collection is buffered, keep it. if (m_monitor->d_ptr->isMonitored(id)) { return false; } // otherwise we can safely purge this item return true; } bool EntityTreeModelPrivate::isMonitored(Collection::Id id) { return m_monitor->d_ptr->isMonitored(id); } bool EntityTreeModelPrivate::isBuffered(Collection::Id id) { return m_monitor->d_ptr->m_buffer.isBuffered(id); } void EntityTreeModelPrivate::deref(Collection::Id id) { const Collection::Id bumpedId = m_monitor->d_ptr->deref(id); if (bumpedId < 0) { return; } //The collection has already been removed, don't purge if (!m_collections.contains(bumpedId)) { return; } if (shouldPurge(bumpedId)) { purgeItems(bumpedId); } } QList::iterator EntityTreeModelPrivate::skipCollections(QList::iterator it, QList::iterator end, int *pos) { for (; it != end; ++it) { if ((*it)->type == Node::Item) { break; } ++(*pos); } return it; } QList::iterator EntityTreeModelPrivate::removeItems(QList::iterator it, QList::iterator end, int *pos, const Collection &collection) { Q_Q(EntityTreeModel); QList::iterator startIt = it; // figure out how many items we will delete int start = *pos; for (; it != end; ++it) { if ((*it)->type != Node::Item) { break; } ++(*pos); } it = startIt; const QModelIndex parentIndex = indexForCollection(collection); q->beginRemoveRows(parentIndex, start, (*pos) - 1); const int toDelete = (*pos) - start; Q_ASSERT(toDelete > 0); QList &es = m_childEntities[collection.id()]; //NOTE: .erase will invalidate all iterators besides "it"! for (int i = 0; i < toDelete; ++i) { Q_ASSERT(es.count(*it) == 1); // don't keep implicitly shared data alive Q_ASSERT(m_items.contains((*it)->id)); m_items.unref((*it)->id); // delete actual node delete *it; it = es.erase(it); } q->endRemoveRows(); return it; } void EntityTreeModelPrivate::purgeItems(Collection::Id id) { QList &childEntities = m_childEntities[id]; const Collection collection = m_collections.value(id); Q_ASSERT(collection.isValid()); QList::iterator begin = childEntities.begin(); QList::iterator end = childEntities.end(); int pos = 0; while ((begin = skipCollections(begin, end, &pos)) != end) { begin = removeItems(begin, end, &pos, collection); end = childEntities.end(); } m_populatedCols.remove(id); //if an empty collection is purged and we leave it in here, itemAdded will be ignored for the collection //and the collection is never populated by fetchMore (but maybe by statistics changed?) m_collectionsWithoutItems.remove(id); } void EntityTreeModelPrivate::dataChanged(const QModelIndex &top, const QModelIndex &bottom) { Q_Q(EntityTreeModel); QModelIndex rightIndex; Node *node = static_cast(bottom.internalPointer()); if (!node) { return; } if (node->type == Node::Collection) { rightIndex = bottom.sibling(bottom.row(), q->entityColumnCount(EntityTreeModel::CollectionTreeHeaders) - 1); } if (node->type == Node::Item) { rightIndex = bottom.sibling(bottom.row(), q->entityColumnCount(EntityTreeModel::ItemListHeaders) - 1); } - emit q->dataChanged(top, rightIndex); + Q_EMIT q->dataChanged(top, rightIndex); } QModelIndex EntityTreeModelPrivate::indexForCollection(const Collection &collection) const { Q_Q(const EntityTreeModel); if (!collection.isValid()) { return QModelIndex(); } if (m_collectionFetchStrategy == EntityTreeModel::InvisibleCollectionFetch) { return QModelIndex(); } // The id of the parent of Collection::root is not guaranteed to be -1 as assumed by startFirstListJob, // we ensure that we use -1 for the invalid Collection. Collection::Id parentId = -1; if ((collection == m_rootCollection)) { if (m_showRootCollection) { return q->createIndex(0, 0, static_cast(m_rootNode)); } return QModelIndex(); } if (collection == Collection::root()) { parentId = -1; } else if (collection.parentCollection().isValid()) { parentId = collection.parentCollection().id(); } else { QHash >::const_iterator it = m_childEntities.constBegin(); const QHash >::const_iterator end = m_childEntities.constEnd(); for (; it != end; ++it) { const int row = indexOf(it.value(), collection.id()); if (row < 0) { continue; } Node *node = it.value().at(row); return q->createIndex(row, 0, static_cast(node)); } return QModelIndex(); } const int row = indexOf(m_childEntities.value(parentId), collection.id()); if (row < 0) { return QModelIndex(); } Node *node = m_childEntities.value(parentId).at(row); return q->createIndex(row, 0, static_cast(node)); } QModelIndexList EntityTreeModelPrivate::indexesForItem(const Item &item) const { Q_Q(const EntityTreeModel); QModelIndexList indexes; if (m_collectionFetchStrategy == EntityTreeModel::FetchNoCollections) { Q_ASSERT(m_childEntities.contains(m_rootCollection.id())); QList nodeList = m_childEntities.value(m_rootCollection.id()); const int row = indexOf(nodeList, item.id()); Q_ASSERT(row >= 0); Q_ASSERT(row < nodeList.size()); Node *node = nodeList.at(row); indexes << q->createIndex(row, 0, static_cast(node)); return indexes; } const Collection::List collections = getParentCollections(item); indexes.reserve(collections.size()); for (const Collection &collection : collections) { const int row = indexOf(m_childEntities.value(collection.id()), item.id()); Q_ASSERT(row >= 0); Q_ASSERT(m_childEntities.contains(collection.id())); QList nodeList = m_childEntities.value(collection.id()); Q_ASSERT(row < nodeList.size()); Node *node = nodeList.at(row); indexes << q->createIndex(row, 0, static_cast(node)); } return indexes; } void EntityTreeModelPrivate::beginResetModel() { Q_Q(EntityTreeModel); q->beginResetModel(); } void EntityTreeModelPrivate::endResetModel() { Q_Q(EntityTreeModel); foreach (Akonadi::Job *job, m_session->findChildren()) { job->disconnect(q); } m_collections.clear(); m_collectionsWithoutItems.clear(); m_populatedCols.clear(); m_items.clear(); m_pendingCollectionFetchJobs.clear(); m_pendingCollectionRetrieveJobs.clear(); m_collectionTreeFetched = false; foreach (const QList &list, m_childEntities) { qDeleteAll(list); } m_childEntities.clear(); if (m_needDeleteRootNode) { m_needDeleteRootNode = false; delete m_rootNode; } m_rootNode = nullptr; q->endResetModel(); fillModel(); } void EntityTreeModelPrivate::monitoredItemsRetrieved(KJob *job) { if (job->error()) { qCWarning(AKONADICORE_LOG) << job->errorString(); return; } Q_Q(EntityTreeModel); ItemFetchJob *fetchJob = qobject_cast(job); Q_ASSERT(fetchJob); Item::List list = fetchJob->items(); q->beginResetModel(); foreach (const Item &item, list) { Node *node = new Node; node->id = item.id(); node->parent = m_rootCollection.id(); node->type = Node::Item; m_childEntities[-1].append(node); m_items.ref(item.id(), item); } q->endResetModel(); } void EntityTreeModelPrivate::fillModel() { Q_Q(EntityTreeModel); m_mimeChecker.setWantedMimeTypes(m_monitor->mimeTypesMonitored()); const Collection::List collections = m_monitor->collectionsMonitored(); if (collections.isEmpty() && m_monitor->numMimeTypesMonitored() == 0 && m_monitor->numResourcesMonitored() == 0 && m_monitor->numItemsMonitored() != 0) { m_rootCollection = Collection(-1); m_collectionTreeFetched = true; - emit q_ptr->collectionTreeFetched(collections); // there are no collections to fetch + Q_EMIT q_ptr->collectionTreeFetched(collections); // there are no collections to fetch Item::List items; items.reserve(m_monitor->itemsMonitoredEx().size()); Q_FOREACH (Item::Id id, m_monitor->itemsMonitoredEx()) { items.append(Item(id)); } ItemFetchJob *itemFetch = new ItemFetchJob(items, m_session); itemFetch->setFetchScope(m_monitor->itemFetchScope()); itemFetch->fetchScope().setIgnoreRetrievalErrors(true); q->connect(itemFetch, SIGNAL(finished(KJob*)), q, SLOT(monitoredItemsRetrieved(KJob*))); return; } // In case there is only a single collection monitored, we can use this // collection as root of the node tree, in all other cases // Collection::root() is used if (collections.size() == 1) { m_rootCollection = collections.first(); } else { m_rootCollection = Collection::root(); } if (m_rootCollection == Collection::root()) { QTimer::singleShot(0, q, SLOT(startFirstListJob())); } else { Q_ASSERT(m_rootCollection.isValid()); CollectionFetchJob *rootFetchJob = new CollectionFetchJob(m_rootCollection, CollectionFetchJob::Base, m_session); q->connect(rootFetchJob, SIGNAL(result(KJob*)), SLOT(rootFetchJobDone(KJob*))); qCDebug(DebugETM) << ""; jobTimeTracker[rootFetchJob].start(); } } bool EntityTreeModelPrivate::canFetchMore(const QModelIndex &parent) const { const Item item = parent.data(EntityTreeModel::ItemRole).value(); if (m_collectionFetchStrategy == EntityTreeModel::InvisibleCollectionFetch) { return false; } if (item.isValid()) { // items can't have more rows. // TODO: Should I use this for fetching more of an item, ie more payload parts? return false; } else { // but collections can... const Collection::Id colId = parent.data(EntityTreeModel::CollectionIdRole).toULongLong(); // But the root collection can't... if (Collection::root().id() == colId) { return false; } // Collections which contain no items at all can't contain more if (m_collectionsWithoutItems.contains(colId)) { return false; } // Don't start the same job multiple times. if (m_pendingCollectionRetrieveJobs.contains(colId)) { return false; } // Can't fetch more if the collection's items have already been fetched if (m_populatedCols.contains(colId)) { return false; } foreach (Node *node, m_childEntities.value(colId)) { if (Node::Item == node->type) { // Only try to fetch more from a collection if we don't already have items in it. // Otherwise we'd spend all the time listing items in collections. return false; } } return true; } } QIcon EntityTreeModelPrivate::iconForName(const QString &name) const { if (m_iconThemeName != QIcon::themeName()) { m_iconThemeName = QIcon::themeName(); m_iconCache.clear(); } QIcon &icon = m_iconCache[name]; if (icon.isNull()) { icon = QIcon::fromTheme(name); } return icon; } diff --git a/src/core/models/favoritecollectionsmodel.cpp b/src/core/models/favoritecollectionsmodel.cpp index 57c0e24c6..67e991f1b 100644 --- a/src/core/models/favoritecollectionsmodel.cpp +++ b/src/core/models/favoritecollectionsmodel.cpp @@ -1,491 +1,491 @@ /* Copyright (c) 2009 Kevin Ottens 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 "favoritecollectionsmodel.h" #include "akonadicore_debug.h" #include #include #include #include #include #include #include #include "entitytreemodel.h" #include "mimetypechecker.h" #include "pastehelper_p.h" #include "favoritecollectionattribute.h" #include "collectionmodifyjob.h" using namespace Akonadi; /** * @internal */ class Q_DECL_HIDDEN FavoriteCollectionsModel::Private { public: Private(const KConfigGroup &group, FavoriteCollectionsModel *parent) : q(parent) , configGroup(group) { } QString labelForCollection(Collection::Id collectionId) const { if (labelMap.contains(collectionId)) { return labelMap[collectionId]; } const QModelIndex collectionIdx = EntityTreeModel::modelIndexForCollection(q->sourceModel(), Collection(collectionId)); QString accountName; const QString nameOfCollection = collectionIdx.data().toString(); QModelIndex idx = collectionIdx.parent(); while (idx != QModelIndex()) { accountName = idx.data(EntityTreeModel::OriginalCollectionNameRole).toString(); idx = idx.parent(); } if (accountName.isEmpty()) { return nameOfCollection; } else { return nameOfCollection + QStringLiteral(" (") + accountName + QLatin1Char(')'); } } void insertIfAvailable(Collection::Id col) { if (collectionIds.contains(col)) { select(col); if (!referencedCollections.contains(col)) { reference(col); } auto idx = EntityTreeModel::modelIndexForCollection(q, Collection{ col }); if (idx.isValid()) { auto c = q->data(idx, EntityTreeModel::CollectionRole).value(); if (c.isValid() && !c.hasAttribute()) { c.addAttribute(new FavoriteCollectionAttribute()); new CollectionModifyJob(c, q); } } } } void insertIfAvailable(const QModelIndex &idx) { insertIfAvailable(idx.data(EntityTreeModel::CollectionIdRole).value()); } /** * Stuff changed (e.g. new rows inserted into sorted model), reload everything. */ void reload() { //don't clear the selection model here. Otherwise we mess up the users selection as collections get removed and re-inserted. for (const Collection::Id &collectionId : qAsConst(collectionIds)) { insertIfAvailable(collectionId); } // If a favorite folder was removed then surely it's gone from the selection model, so no need to do anything about that. } void rowsInserted(const QModelIndex &parent, int begin, int end) { for (int row = begin; row <= end; row++) { const QModelIndex child = q->sourceModel()->index(row, 0, parent); if (!child.isValid()) { continue; } insertIfAvailable(child); const int childRows = q->sourceModel()->rowCount(child); if (childRows > 0) { rowsInserted(child, 0, childRows - 1); } } } void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { for (int row = topLeft.row(); row <= bottomRight.row(); row++) { const QModelIndex idx = topLeft.sibling(row, 0); insertIfAvailable(idx); } } /** * Selects the index in the internal selection model to make the collection visible in the model */ void select(const Collection::Id &collectionId) { const QModelIndex index = EntityTreeModel::modelIndexForCollection(q->sourceModel(), Collection(collectionId)); if (index.isValid()) { q->selectionModel()->select(index, QItemSelectionModel::Select); } } void deselect(const Collection::Id &collectionId) { const QModelIndex idx = EntityTreeModel::modelIndexForCollection(q->sourceModel(), Collection(collectionId)); if (idx.isValid()) { q->selectionModel()->select(idx, QItemSelectionModel::Deselect); } } void reference(const Collection::Id &collectionId) { if (referencedCollections.contains(collectionId)) { qCWarning(AKONADICORE_LOG) << "already referenced " << collectionId; return; } const QModelIndex index = EntityTreeModel::modelIndexForCollection(q->sourceModel(), Collection(collectionId)); if (index.isValid()) { if (q->sourceModel()->setData(index, QVariant(), EntityTreeModel::CollectionRefRole)) { referencedCollections << collectionId; } else { qCWarning(AKONADICORE_LOG) << "failed to reference collection"; } q->sourceModel()->fetchMore(index); } } void dereference(const Collection::Id &collectionId) { if (!referencedCollections.contains(collectionId)) { qCWarning(AKONADICORE_LOG) << "not referenced " << collectionId; return; } const QModelIndex index = EntityTreeModel::modelIndexForCollection(q->sourceModel(), Collection(collectionId)); if (index.isValid()) { q->sourceModel()->setData(index, QVariant(), EntityTreeModel::CollectionDerefRole); referencedCollections.remove(collectionId); } } void clearReferences() { for (const Collection::Id &collectionId : qAsConst(referencedCollections)) { dereference(collectionId); } } /** * Adds a collection to the favorite collections */ void add(const Collection::Id &collectionId) { if (collectionIds.contains(collectionId)) { qCDebug(AKONADICORE_LOG) << "already in model " << collectionId; return; } collectionIds << collectionId; reference(collectionId); select(collectionId); const auto idx = EntityTreeModel::modelIndexForCollection(q, Collection{ collectionId }); if (idx.isValid()) { auto col = q->data(idx, EntityTreeModel::CollectionRole).value(); if (col.isValid() && !col.hasAttribute()) { col.addAttribute(new FavoriteCollectionAttribute()); new CollectionModifyJob(col, q); } } } void remove(const Collection::Id &collectionId) { collectionIds.removeAll(collectionId); labelMap.remove(collectionId); dereference(collectionId); deselect(collectionId); const auto idx = EntityTreeModel::modelIndexForCollection(q, Collection{ collectionId }); if (idx.isValid()) { auto col = q->data(idx, EntityTreeModel::CollectionRole).value(); if (col.isValid() && col.hasAttribute()) { col.removeAttribute(); new CollectionModifyJob(col, q); } } } void set(const QList &collections) { QList colIds = collectionIds; for (const Collection::Id &col : collections) { const int removed = colIds.removeAll(col); const bool isNewCollection = removed <= 0; if (isNewCollection) { add(col); } } //Remove what's left for (Akonadi::Collection::Id colId : qAsConst(colIds)) { remove(colId); } } void set(const Akonadi::Collection::List &collections) { QList colIds; colIds.reserve(collections.count()); for (const Akonadi::Collection &col : collections) { colIds << col.id(); } set(colIds); } void loadConfig() { const QList collections = configGroup.readEntry("FavoriteCollectionIds", QList()); const QStringList labels = configGroup.readEntry("FavoriteCollectionLabels", QStringList()); const int numberOfLabels(labels.size()); for (int i = 0; i < collections.size(); ++i) { if (i < numberOfLabels) { labelMap[collections[i]] = labels[i]; } add(collections[i]); } } void saveConfig() { QStringList labels; labels.reserve(collectionIds.count()); for (const Collection::Id &collectionId : qAsConst(collectionIds)) { labels << labelForCollection(collectionId); } configGroup.writeEntry("FavoriteCollectionIds", collectionIds); configGroup.writeEntry("FavoriteCollectionLabels", labels); configGroup.config()->sync(); } FavoriteCollectionsModel *const q; QList collectionIds; QSet referencedCollections; QHash labelMap; KConfigGroup configGroup; }; /* Implementation note: * * We use KSelectionProxyModel in order to make a flat list of selected folders from the folder tree. * * Attempts to use QSortFilterProxyModel / KRecursiveFilterProxyModel make code somewhat simpler, * but don't work since we then get a filtered tree, not a flat list. Stacking a KDescendantsProxyModel * on top would likely remove explicitly selected parents when one of their child is selected too. */ FavoriteCollectionsModel::FavoriteCollectionsModel(QAbstractItemModel *source, const KConfigGroup &group, QObject *parent) : KSelectionProxyModel(new QItemSelectionModel(source, parent), parent) , d(new Private(group, this)) { setSourceModel(source); setFilterBehavior(ExactSelection); d->loadConfig(); //React to various changes in the source model connect(source, &QAbstractItemModel::modelReset, this, [this]() { d->reload(); }); connect(source, &QAbstractItemModel::layoutChanged, this, [this]() { d->reload(); }); connect(source, &QAbstractItemModel::rowsInserted, this, [this](const QModelIndex &parent, int begin, int end) { d->rowsInserted(parent, begin, end); }); connect(source, &QAbstractItemModel::dataChanged, this, [this](const QModelIndex &tl, const QModelIndex &br) { d->dataChanged(tl, br); }); } FavoriteCollectionsModel::~FavoriteCollectionsModel() { delete d; } void FavoriteCollectionsModel::setCollections(const Collection::List &collections) { d->set(collections); d->saveConfig(); } void FavoriteCollectionsModel::addCollection(const Collection &collection) { d->add(collection.id()); d->saveConfig(); } void FavoriteCollectionsModel::removeCollection(const Collection &collection) { d->remove(collection.id()); d->saveConfig(); } Akonadi::Collection::List FavoriteCollectionsModel::collections() const { Collection::List cols; cols.reserve(d->collectionIds.count()); for (const Collection::Id &colId : qAsConst(d->collectionIds)) { const QModelIndex idx = EntityTreeModel::modelIndexForCollection(sourceModel(), Collection(colId)); const Collection collection = sourceModel()->data(idx, EntityTreeModel::CollectionRole).value(); cols << collection; } return cols; } QList FavoriteCollectionsModel::collectionIds() const { return d->collectionIds; } void Akonadi::FavoriteCollectionsModel::setFavoriteLabel(const Collection &collection, const QString &label) { Q_ASSERT(d->collectionIds.contains(collection.id())); d->labelMap[collection.id()] = label; d->saveConfig(); const QModelIndex idx = EntityTreeModel::modelIndexForCollection(sourceModel(), collection); if (!idx.isValid()) { return; } const QModelIndex index = mapFromSource(idx); - emit dataChanged(index, index); + Q_EMIT dataChanged(index, index); } QVariant Akonadi::FavoriteCollectionsModel::data(const QModelIndex &index, int role) const { if (index.column() == 0 && (role == Qt::DisplayRole || role == Qt::EditRole)) { const QModelIndex sourceIndex = mapToSource(index); const Collection::Id collectionId = sourceModel()->data(sourceIndex, EntityTreeModel::CollectionIdRole).toLongLong(); return d->labelForCollection(collectionId); } else { return KSelectionProxyModel::data(index, role); } } bool FavoriteCollectionsModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (index.isValid() && index.column() == 0 && role == Qt::EditRole) { const QString newLabel = value.toString(); if (newLabel.isEmpty()) { return false; } const QModelIndex sourceIndex = mapToSource(index); const Collection collection = sourceModel()->data(sourceIndex, EntityTreeModel::CollectionRole).value(); setFavoriteLabel(collection, newLabel); return true; } return KSelectionProxyModel::setData(index, value, role); } QString Akonadi::FavoriteCollectionsModel::favoriteLabel(const Akonadi::Collection &collection) { if (!collection.isValid()) { return QString(); } return d->labelForCollection(collection.id()); } QVariant FavoriteCollectionsModel::headerData(int section, Qt::Orientation orientation, int role) const { if (section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole) { return i18n("Favorite Folders"); } else { return KSelectionProxyModel::headerData(section, orientation, role); } } bool FavoriteCollectionsModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(action); Q_UNUSED(row); Q_UNUSED(column); if (data->hasFormat(QStringLiteral("text/uri-list"))) { const QList urls = data->urls(); const QModelIndex sourceIndex = mapToSource(parent); const Collection destCollection = sourceModel()->data(sourceIndex, EntityTreeModel::CollectionRole).value(); MimeTypeChecker mimeChecker; mimeChecker.setWantedMimeTypes(destCollection.contentMimeTypes()); for (const QUrl &url : urls) { const Collection col = Collection::fromUrl(url); if (col.isValid()) { addCollection(col); } else { const Item item = Item::fromUrl(url); if (item.isValid()) { if (item.parentCollection().id() == destCollection.id() && action != Qt::CopyAction) { qCDebug(AKONADICORE_LOG) << "Error: source and destination of move are the same."; return false; } #if 0 if (!mimeChecker.isWantedItem(item)) { qCDebug(AKONADICORE_LOG) << "unwanted item" << mimeChecker.wantedMimeTypes() << item.mimeType(); return false; } #endif KJob *job = PasteHelper::pasteUriList(data, destCollection, action); if (!job) { return false; } connect(job, &KJob::result, this, &FavoriteCollectionsModel::pasteJobDone); // Accept the event so that it doesn't propagate. return true; } } } return true; } return false; } QStringList FavoriteCollectionsModel::mimeTypes() const { QStringList mts = KSelectionProxyModel::mimeTypes(); if (!mts.contains(QLatin1String("text/uri-list"))) { mts.append(QStringLiteral("text/uri-list")); } return mts; } Qt::ItemFlags FavoriteCollectionsModel::flags(const QModelIndex &index) const { Qt::ItemFlags fs = KSelectionProxyModel::flags(index); if (!index.isValid()) { fs |= Qt::ItemIsDropEnabled; } return fs; } void FavoriteCollectionsModel::pasteJobDone(KJob *job) { if (job->error()) { qCDebug(AKONADICORE_LOG) << "Paste job error:" << job->errorString(); } } #include "moc_favoritecollectionsmodel.cpp" diff --git a/src/core/models/itemmodel.cpp b/src/core/models/itemmodel.cpp index 23c84927c..7fad1f85e 100644 --- a/src/core/models/itemmodel.cpp +++ b/src/core/models/itemmodel.cpp @@ -1,470 +1,470 @@ /* 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 "itemmodel.h" #include "akonadicore_debug.h" #include "itemfetchjob.h" #include "collectionfetchjob.h" #include "itemfetchscope.h" #include "monitor.h" #include "pastehelper_p.h" #include "session.h" #include #include #include #include using namespace Akonadi; /** * @internal * * This struct is used for optimization reasons. * because it embeds the row. * * Semantically, we could have used an item instead. */ struct ItemContainer { ItemContainer(const Item &i, int r) : item(i) , row(r) { } Item item; int row; }; /** * @internal */ class Q_DECL_HIDDEN ItemModel::Private { public: Private(ItemModel *parent) : mParent(parent) , monitor(new Monitor()) { session = new Session(QCoreApplication::instance()->applicationName().toUtf8() + QByteArray("-ItemModel-") + QByteArray::number(qrand()), mParent); monitor->setObjectName(QStringLiteral("ItemModelMonitor")); monitor->ignoreSession(session); mParent->connect(monitor, &Monitor::itemChanged, mParent, [this](const Akonadi::Item &item, const QSet &set) { itemChanged(item, set); }); mParent->connect(monitor, SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)), mParent, SLOT(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection))); mParent->connect(monitor, SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection)), mParent, SLOT(itemAdded(Akonadi::Item))); mParent->connect(monitor, &Monitor::itemRemoved, mParent, [this](const Akonadi::Item &item) { itemRemoved(item); }); mParent->connect(monitor, SIGNAL(itemLinked(Akonadi::Item,Akonadi::Collection)), mParent, SLOT(itemAdded(Akonadi::Item))); mParent->connect(monitor, SIGNAL(itemUnlinked(Akonadi::Item,Akonadi::Collection)), mParent, SLOT(itemRemoved(Akonadi::Item))); } ~Private() { delete monitor; } void listingDone(KJob *job); void collectionFetchResult(KJob *job); void itemChanged(const Akonadi::Item &item, const QSet &); void itemsAdded(const Akonadi::Item::List &list); void itemAdded(const Akonadi::Item &item); void itemMoved(const Akonadi::Item &item, const Akonadi::Collection &src, const Akonadi::Collection &dst); void itemRemoved(const Akonadi::Item &item); int rowForItem(const Akonadi::Item &item); bool collectionIsCompatible() const; ItemModel *mParent = nullptr; QList items; QHash itemHash; Collection collection; Monitor *monitor = nullptr; Session *session = nullptr; }; bool ItemModel::Private::collectionIsCompatible() const { // in the generic case, we show any collection if (mParent->mimeTypes() == QStringList(QStringLiteral("text/uri-list"))) { return true; } // if the model's mime types are more specific, limit to those // collections that have matching types const QStringList lstMimetypes = mParent->mimeTypes(); for (const QString &type : lstMimetypes ) { if (collection.contentMimeTypes().contains(type)) { return true; } } return false; } void ItemModel::Private::listingDone(KJob *job) { ItemFetchJob *fetch = static_cast(job); Q_UNUSED(fetch); if (job->error()) { // TODO qCWarning(AKONADICORE_LOG) << "Item query failed:" << job->errorString(); } } void ItemModel::Private::collectionFetchResult(KJob *job) { CollectionFetchJob *fetch = static_cast(job); if (fetch->collections().isEmpty()) { return; } Q_ASSERT(fetch->collections().count() == 1); // we only listed base Collection c = fetch->collections().at(0); // avoid recursion, if this fails for some reason if (!c.contentMimeTypes().isEmpty()) { mParent->setCollection(c); } else { qCWarning(AKONADICORE_LOG) << "Failed to retrieve the contents mime type of the collection: " << c; mParent->setCollection(Collection()); } } int ItemModel::Private::rowForItem(const Akonadi::Item &item) { ItemContainer *container = itemHash.value(item); if (!container) { return -1; } /* Try to find the item directly; If items have been removed, this first try won't succeed because the ItemContainer rows have not been updated (costs too much). */ if (container->row < items.count() && items.at(container->row) == container) { return container->row; } else { // Slow solution if the fist one has not succeeded int row = -1; const int numberOfItems(items.size()); for (int i = 0; i < numberOfItems; ++i) { if (items.at(i)->item == item) { row = i; break; } } return row; } } void ItemModel::Private::itemChanged(const Akonadi::Item &item, const QSet &) { int row = rowForItem(item); if (row < 0) { return; } items[row]->item = item; itemHash.remove(item); itemHash[item] = items[row]; QModelIndex start = mParent->index(row, 0, QModelIndex()); QModelIndex end = mParent->index(row, mParent->columnCount(QModelIndex()) - 1, QModelIndex()); mParent->dataChanged(start, end); } void ItemModel::Private::itemMoved(const Akonadi::Item &item, const Akonadi::Collection &colSrc, const Akonadi::Collection &colDst) { if (colSrc == collection && colDst != collection) { // item leaving this model itemRemoved(item); return; } if (colDst == collection && colSrc != collection) { itemAdded(item); return; } } void ItemModel::Private::itemsAdded(const Akonadi::Item::List &list) { if (list.isEmpty()) { return; } mParent->beginInsertRows(QModelIndex(), items.count(), items.count() + list.count() - 1); for (const Item &item : list) { ItemContainer *c = new ItemContainer(item, items.count()); items.append(c); itemHash[item] = c; } mParent->endInsertRows(); } void ItemModel::Private::itemAdded(const Akonadi::Item &item) { const Item::List l = {item}; itemsAdded(l); } void ItemModel::Private::itemRemoved(const Akonadi::Item &_item) { int row = rowForItem(_item); if (row < 0) { return; } mParent->beginRemoveRows(QModelIndex(), row, row); const Item item = items.at(row)->item; Q_ASSERT(item.isValid()); itemHash.remove(item); delete items.takeAt(row); mParent->endRemoveRows(); } ItemModel::ItemModel(QObject *parent) : QAbstractTableModel(parent) , d(new Private(this)) { } ItemModel::~ItemModel() { delete d; } QVariant ItemModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (index.row() >= d->items.count()) { return QVariant(); } const Item item = d->items.at(index.row())->item; if (!item.isValid()) { return QVariant(); } if (role == Qt::DisplayRole) { switch (index.column()) { case Id: return QString::number(item.id()); case RemoteId: return item.remoteId(); case MimeType: return item.mimeType(); default: return QVariant(); } } if (role == IdRole) { return item.id(); } if (role == ItemRole) { QVariant var; var.setValue(item); return var; } if (role == MimeTypeRole) { return item.mimeType(); } return QVariant(); } int ItemModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return d->items.count(); } return 0; } int ItemModel::columnCount(const QModelIndex &parent) const { if (!parent.isValid()) { return 3; // keep in sync with Column enum } return 0; } QVariant ItemModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch (section) { case Id: return i18n("Id"); case RemoteId: return i18n("Remote Id"); case MimeType: return i18n("MimeType"); default: return QString(); } } return QAbstractTableModel::headerData(section, orientation, role); } void ItemModel::setCollection(const Collection &collection) { if (d->collection == collection) { return; } // if we don't know anything about this collection yet, fetch it if (collection.isValid() && collection.contentMimeTypes().isEmpty()) { CollectionFetchJob *job = new CollectionFetchJob(collection, CollectionFetchJob::Base, this); connect(job, SIGNAL(result(KJob*)), this, SLOT(collectionFetchResult(KJob*))); return; } beginResetModel(); d->monitor->setCollectionMonitored(d->collection, false); d->collection = collection; d->monitor->setCollectionMonitored(d->collection, true); // the query changed, thus everything we have already is invalid qDeleteAll(d->items); d->items.clear(); // stop all running jobs d->session->clear(); endResetModel(); // start listing job if (d->collectionIsCompatible()) { ItemFetchJob *job = new ItemFetchJob(collection, session()); job->setFetchScope(d->monitor->itemFetchScope()); connect(job, SIGNAL(itemsReceived(Akonadi::Item::List)), SLOT(itemsAdded(Akonadi::Item::List))); connect(job, &ItemFetchJob::result, this, [this](KJob *job) { d->listingDone(job); }); } - emit collectionChanged(collection); + Q_EMIT collectionChanged(collection); } void ItemModel::setFetchScope(const ItemFetchScope &fetchScope) { d->monitor->setItemFetchScope(fetchScope); } ItemFetchScope &ItemModel::fetchScope() { return d->monitor->itemFetchScope(); } Item ItemModel::itemForIndex(const QModelIndex &index) const { if (!index.isValid()) { return Akonadi::Item(); } if (index.row() >= d->items.count()) { return Akonadi::Item(); } Item item = d->items.at(index.row())->item; if (item.isValid()) { return item; } else { return Akonadi::Item(); } } Qt::ItemFlags ItemModel::flags(const QModelIndex &index) const { Qt::ItemFlags defaultFlags = QAbstractTableModel::flags(index); if (index.isValid()) { return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags; } else { return Qt::ItemIsDropEnabled | defaultFlags; } } QStringList ItemModel::mimeTypes() const { return {QStringLiteral("text/uri-list")}; } Session *ItemModel::session() const { return d->session; } QMimeData *ItemModel::mimeData(const QModelIndexList &indexes) const { QMimeData *data = new QMimeData(); // Add item uri to the mimedata for dropping in external applications QList urls; for (const QModelIndex &index : indexes) { if (index.column() != 0) { continue; } urls << itemForIndex(index).url(Item::UrlWithMimeType); } data->setUrls(urls); return data; } QModelIndex ItemModel::indexForItem(const Akonadi::Item &item, const int column) const { return index(d->rowForItem(item), column); } bool ItemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(row); Q_UNUSED(column); Q_UNUSED(parent); KJob *job = PasteHelper::paste(data, d->collection, action != Qt::MoveAction); // TODO: error handling return job; } Collection ItemModel::collection() const { return d->collection; } Qt::DropActions ItemModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction; } #include "moc_itemmodel.cpp" diff --git a/src/core/models/statisticsproxymodel.cpp b/src/core/models/statisticsproxymodel.cpp index 22bf64947..d5ff92891 100644 --- a/src/core/models/statisticsproxymodel.cpp +++ b/src/core/models/statisticsproxymodel.cpp @@ -1,343 +1,343 @@ /* Copyright (c) 2009 Kevin Ottens 2016 David Faure 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 "statisticsproxymodel.h" #include "akonadicore_debug.h" #include "kitemmodels_version.h" #include "entitytreemodel.h" #include "collectionutils.h" #include "collectionquotaattribute.h" #include "collectionstatistics.h" #include "entitydisplayattribute.h" #include #include #include #include #include #include using namespace Akonadi; /** * @internal */ class Q_DECL_HIDDEN StatisticsProxyModel::Private { public: Private(StatisticsProxyModel *parent) : q(parent), mToolTipEnabled(false), mExtraColumnsEnabled(false) { } void getCountRecursive(const QModelIndex &index, qint64 &totalSize) const { Collection collection = qvariant_cast(index.data(EntityTreeModel::CollectionRole)); // Do not assert on invalid collections, since a collection may be deleted // in the meantime and deleted collections are invalid. if (collection.isValid()) { CollectionStatistics statistics = collection.statistics(); totalSize += qMax(0LL, statistics.size()); if (index.model()->hasChildren(index)) { const int rowCount = index.model()->rowCount(index); for (int row = 0; row < rowCount; row++) { static const int column = 0; getCountRecursive(index.model()->index(row, column, index), totalSize); } } } } int sourceColumnCount() const { return q->sourceModel()->columnCount(); } QString toolTipForCollection(const QModelIndex &index, const Collection &collection) { const QString bckColor = QApplication::palette().color(QPalette::ToolTipBase).name(); const QString txtColor = QApplication::palette().color(QPalette::ToolTipText).name(); QString tip = QStringLiteral( "\n" ); const QString textDirection = (QApplication::layoutDirection() == Qt::LeftToRight) ? QStringLiteral("left") : QStringLiteral("right"); tip += QStringLiteral( " \n" " \n" " \n" ).arg(txtColor, bckColor, index.data(Qt::DisplayRole).toString(), textDirection); tip += QStringLiteral( " \n" " \n" ).arg(iconPath).arg(icon_size_found); if (QApplication::layoutDirection() == Qt::LeftToRight) { tip += tipInfo + QStringLiteral("" \ "
\n" "
\n" " %3\n" "
\n" "
\n" ).arg(textDirection); QString tipInfo = QStringLiteral( " %1: %2
\n" " %3: %4

\n" ).arg(i18n("Total Messages")).arg(collection.statistics().count()) .arg(i18n("Unread Messages")).arg(collection.statistics().unreadCount()); if (collection.hasAttribute()) { CollectionQuotaAttribute *quota = collection.attribute(); if (quota->currentValue() > -1 && quota->maximumValue() > 0) { qreal percentage = (100.0 * quota->currentValue()) / quota->maximumValue(); if (qAbs(percentage) >= 0.01) { QString percentStr = QString::number(percentage, 'f', 2); tipInfo += QStringLiteral( " %1: %2%
\n" ).arg(i18n("Quota"), percentStr); } } } KFormat formatter; qint64 currentFolderSize(collection.statistics().size()); tipInfo += QStringLiteral( " %1: %2
\n" ).arg(i18n("Storage Size"), formatter.formatByteSize(currentFolderSize)); qint64 totalSize = 0; getCountRecursive(index, totalSize); totalSize -= currentFolderSize; if (totalSize > 0) { tipInfo += QStringLiteral( "%1: %2
" ).arg(i18n("Subfolder Storage Size"), formatter.formatByteSize(totalSize)); } QString iconName = CollectionUtils::defaultIconName(collection); if (collection.hasAttribute() && !collection.attribute()->iconName().isEmpty()) { if (!collection.attribute()->activeIconName().isEmpty() && collection.statistics().unreadCount() > 0) { iconName = collection.attribute()->activeIconName(); } else { iconName = collection.attribute()->iconName(); } } int iconSizes[] = { 32, 22 }; int icon_size_found = 32; QString iconPath; for (int i = 0; i < 2; ++i) { iconPath = KIconLoader::global()->iconPath(iconName, -iconSizes[ i ], true); if (!iconPath.isEmpty()) { icon_size_found = iconSizes[ i ]; break; } } if (iconPath.isEmpty()) { iconPath = KIconLoader::global()->iconPath(QStringLiteral("folder"), -32, false); } QString tipIcon = QStringLiteral( "
\n" " \n" "
\n" "
").arg(textDirection) + tipIcon; } else { tip += tipIcon + QStringLiteral("").arg(textDirection) + tipInfo; } tip += QLatin1String( "
" ); return tip; } void _k_sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); StatisticsProxyModel *q = nullptr; bool mToolTipEnabled; bool mExtraColumnsEnabled; }; void StatisticsProxyModel::Private::_k_sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { QModelIndex proxyTopLeft(q->mapFromSource(topLeft)); QModelIndex proxyBottomRight(q->mapFromSource(bottomRight)); // Emit data changed for the whole row (bug #222292) if (mExtraColumnsEnabled && topLeft.column() == 0) { // in theory we could filter on roles, but ETM doesn't set any yet const int lastColumn = q->columnCount() - 1; proxyBottomRight = proxyBottomRight.sibling(proxyBottomRight.row(), lastColumn); } - emit q->dataChanged(proxyTopLeft, proxyBottomRight, roles); + Q_EMIT q->dataChanged(proxyTopLeft, proxyBottomRight, roles); } void StatisticsProxyModel::setSourceModel(QAbstractItemModel *model) { if (sourceModel()) { disconnect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector)), this, SLOT(_k_sourceDataChanged(QModelIndex,QModelIndex,QVector))); } KExtraColumnsProxyModel::setSourceModel(model); if (model) { // Disconnect the default handling of dataChanged in QIdentityProxyModel, so we can extend it to the whole row disconnect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector)), this, SLOT(_q_sourceDataChanged(QModelIndex,QModelIndex,QVector))); connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector)), this, SLOT(_k_sourceDataChanged(QModelIndex,QModelIndex,QVector))); } } StatisticsProxyModel::StatisticsProxyModel(QObject *parent) : KExtraColumnsProxyModel(parent), d(new Private(this)) { setExtraColumnsEnabled(true); } StatisticsProxyModel::~StatisticsProxyModel() { delete d; } void StatisticsProxyModel::setToolTipEnabled(bool enable) { d->mToolTipEnabled = enable; } bool StatisticsProxyModel::isToolTipEnabled() const { return d->mToolTipEnabled; } void StatisticsProxyModel::setExtraColumnsEnabled(bool enable) { if (d->mExtraColumnsEnabled == enable) { return; } d->mExtraColumnsEnabled = enable; if (enable) { KExtraColumnsProxyModel::appendColumn(i18nc("number of unread entities in the collection", "Unread")); KExtraColumnsProxyModel::appendColumn(i18nc("number of entities in the collection", "Total")); KExtraColumnsProxyModel::appendColumn(i18nc("collection size", "Size")); } else { KExtraColumnsProxyModel::removeExtraColumn(2); KExtraColumnsProxyModel::removeExtraColumn(1); KExtraColumnsProxyModel::removeExtraColumn(0); } } bool StatisticsProxyModel::isExtraColumnsEnabled() const { return d->mExtraColumnsEnabled; } QVariant StatisticsProxyModel::extraColumnData(const QModelIndex &parent, int row, int extraColumn, int role) const { switch (role) { case Qt::DisplayRole: { const QModelIndex firstColumn = index(row, 0, parent); const Collection collection = data(firstColumn, EntityTreeModel::CollectionRole).value(); if (collection.isValid() && collection.statistics().count() >= 0) { const CollectionStatistics stats = collection.statistics(); if (extraColumn == 2) { KFormat formatter; return formatter.formatByteSize(stats.size()); } else if (extraColumn == 1) { return stats.count(); } else if (extraColumn == 0) { if (stats.unreadCount() > 0) { return stats.unreadCount(); } else { return QString(); } } else { qCWarning(AKONADICORE_LOG) << "We shouldn't get there for a column which is not total, unread or size."; } } } break; case Qt::TextAlignmentRole: { return Qt::AlignRight; } default: break; } return QVariant(); } QVariant StatisticsProxyModel::data(const QModelIndex &index, int role) const { if (role == Qt::ToolTipRole && d->mToolTipEnabled) { const QModelIndex firstColumn = index.sibling(index.row(), 0); const Collection collection = data(firstColumn, EntityTreeModel::CollectionRole).value(); if (collection.isValid()) { return d->toolTipForCollection(firstColumn, collection); } } return KExtraColumnsProxyModel::data(index, role); } Qt::ItemFlags StatisticsProxyModel::flags(const QModelIndex &index_) const { if (index_.column() >= d->sourceColumnCount()) { const QModelIndex firstColumn = index_.sibling(index_.row(), 0); return KExtraColumnsProxyModel::flags(firstColumn) & (Qt::ItemIsSelectable | Qt::ItemIsDragEnabled // Allowed flags | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled); } return KExtraColumnsProxyModel::flags(index_); } // Not sure this is still necessary.... QModelIndexList StatisticsProxyModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const { if (role < Qt::UserRole) { return KExtraColumnsProxyModel::match(start, role, value, hits, flags); } QModelIndexList list; QModelIndex proxyIndex; foreach (const QModelIndex &idx, sourceModel()->match(mapToSource(start), role, value, hits, flags)) { proxyIndex = mapFromSource(idx); if (proxyIndex.isValid()) { list << proxyIndex; } } return list; } #include "moc_statisticsproxymodel.cpp" diff --git a/src/core/models/subscriptionmodel.cpp b/src/core/models/subscriptionmodel.cpp index 963856692..693cc8503 100644 --- a/src/core/models/subscriptionmodel.cpp +++ b/src/core/models/subscriptionmodel.cpp @@ -1,198 +1,198 @@ /* Copyright (c) 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 "subscriptionmodel_p.h" #include "collectionfetchjob.h" #include "collectionutils.h" #include "specialcollectionattribute.h" #include "entityhiddenattribute.h" #include "akonadicore_debug.h" #include using namespace Akonadi; /** * @internal */ class SubscriptionModel::Private { public: Private(SubscriptionModel *parent) : q(parent) {} SubscriptionModel *q; QHash subscriptions; QSet changes; bool showHiddenCollection = false; Collection::List changedSubscriptions(bool subscribed) { Collection::List list; for (Collection::Id id : qAsConst(changes)) { if (subscriptions.value(id) == subscribed) { list << Collection(id); } } return list; } void listResult(KJob *job) { if (job->error()) { // TODO qCWarning(AKONADICORE_LOG) << job->errorString(); return; } q->beginResetModel(); const Collection::List cols = static_cast(job)->collections(); for (const Collection &col : cols) { if (!CollectionUtils::isStructural(col)) { subscriptions[ col.id() ] = true; } } q->endResetModel(); - emit q->loaded(); + Q_EMIT q->loaded(); } bool isSubscribable(Collection::Id id) { Collection col = q->collectionForId(id); if (CollectionUtils::isStructural(col) || col.isVirtual() || CollectionUtils::isUnifiedMailbox(col)) { return false; } if (col.hasAttribute()) { return false; } if (col.contentMimeTypes().isEmpty()) { return false; } return true; } }; SubscriptionModel::SubscriptionModel(QObject *parent) : CollectionModel(parent), d(new Private(this)) { includeUnsubscribed(); CollectionFetchJob *job = new CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive, this); connect(job,&CollectionFetchJob::result, this, [this](KJob *job) { d->listResult(job); }); } SubscriptionModel::~SubscriptionModel() { delete d; } QVariant SubscriptionModel::data(const QModelIndex &index, int role) const { switch (role) { case Qt::CheckStateRole: { const Collection::Id col = index.data(CollectionIdRole).toLongLong(); if (!d->isSubscribable(col)) { return QVariant(); } if (d->subscriptions.value(col)) { return Qt::Checked; } return Qt::Unchecked; } case SubscriptionChangedRole: { const Collection::Id col = index.data(CollectionIdRole).toLongLong(); if (d->changes.contains(col)) { return true; } return false; } case Qt::FontRole: { const Collection::Id col = index.data(CollectionIdRole).toLongLong(); QFont font = CollectionModel::data(index, role).value(); font.setBold(d->changes.contains(col)); return font; } } if (role == CollectionIdRole) { return CollectionModel::data(index, CollectionIdRole); } else { const Collection::Id collectionId = index.data(CollectionIdRole).toLongLong(); const Collection collection = collectionForId(collectionId); if (collection.hasAttribute()) { if (d->showHiddenCollection) { return CollectionModel::data(index, role); } else { return QVariant(); } } else { return CollectionModel::data(index, role); } } } Qt::ItemFlags SubscriptionModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = CollectionModel::flags(index); if (d->isSubscribable(index.data(CollectionIdRole).toLongLong())) { return flags | Qt::ItemIsUserCheckable; } return flags; } bool SubscriptionModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (role == Qt::CheckStateRole) { const Collection::Id col = index.data(CollectionIdRole).toLongLong(); if (!d->isSubscribable(col)) { return true; //No change } if (d->subscriptions.contains(col) && d->subscriptions.value(col) == (value == Qt::Checked)) { return true; // no change } d->subscriptions[ col ] = value == Qt::Checked; if (d->changes.contains(col)) { d->changes.remove(col); } else { d->changes.insert(col); } - emit dataChanged(index, index); + Q_EMIT dataChanged(index, index); return true; } return CollectionModel::setData(index, value, role); } Akonadi::Collection::List SubscriptionModel::subscribed() const { return d->changedSubscriptions(true); } Akonadi::Collection::List SubscriptionModel::unsubscribed() const { return d->changedSubscriptions(false); } void SubscriptionModel::showHiddenCollection(bool showHidden) { d->showHiddenCollection = showHidden; } #include "moc_subscriptionmodel_p.cpp" diff --git a/src/core/models/tagmodel_p.cpp b/src/core/models/tagmodel_p.cpp index ac3fcca0b..d44d0ccc6 100644 --- a/src/core/models/tagmodel_p.cpp +++ b/src/core/models/tagmodel_p.cpp @@ -1,247 +1,247 @@ /* Copyright (c) 2014 Daniel Vr??til 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 "tagmodel_p.h" #include "tagmodel.h" #include "monitor.h" #include "session.h" #include "tagfetchjob.h" #include "akonadicore_debug.h" #include using namespace Akonadi; TagModelPrivate::TagModelPrivate(TagModel *parent) : mMonitor(nullptr) , mSession(nullptr) , q_ptr(parent) { // Root tag mTags.insert(-1, Tag()); } TagModelPrivate::~TagModelPrivate() { } void TagModelPrivate::init(Monitor *monitor) { Q_Q(TagModel); mMonitor = monitor; mSession = mMonitor->session(); q->connect(mMonitor, SIGNAL(tagAdded(Akonadi::Tag)), q, SLOT(monitoredTagAdded(Akonadi::Tag))); q->connect(mMonitor, SIGNAL(tagChanged(Akonadi::Tag)), q, SLOT(monitoredTagChanged(Akonadi::Tag))); q->connect(mMonitor, SIGNAL(tagRemoved(Akonadi::Tag)), q, SLOT(monitoredTagRemoved(Akonadi::Tag))); // Delay starting the job to allow unit-tests to set up fake stuff QTimer::singleShot(0, q, [this] { fillModel(); }); } void TagModelPrivate::fillModel() { Q_Q(TagModel); TagFetchJob *fetchJob = new TagFetchJob(mSession); fetchJob->setFetchScope(mMonitor->tagFetchScope()); q->connect(fetchJob, SIGNAL(tagsReceived(Akonadi::Tag::List)), q, SLOT(tagsFetched(Akonadi::Tag::List))); q->connect(fetchJob, SIGNAL(finished(KJob*)), q, SLOT(tagsFetchDone(KJob*))); } QModelIndex TagModelPrivate::indexForTag(const qint64 tagId) const { Q_Q(const TagModel); if (!mTags.contains(tagId)) { return QModelIndex(); } const Tag tag = mTags.value(tagId); if (!tag.isValid()) { return QModelIndex(); } const Tag::Id parentId = tag.parent().id(); const int row = mChildTags.value(parentId).indexOf(tag); if (row != -1) { return q->createIndex(row, 0, (int) parentId); } return QModelIndex(); } Tag TagModelPrivate::tagForIndex(const QModelIndex &index) const { if (!index.isValid()) { return Tag(); } const Tag::Id parentId = index.internalId(); const Tag::List &children = mChildTags.value(parentId); return children.value(index.row()); } void TagModelPrivate::monitoredTagAdded(const Tag &tag) { Q_Q(TagModel); const qint64 parentId = tag.parent().id(); // Parent not yet in model, defer for later if (!mTags.contains(parentId)) { Tag::List &list = mPendingTags[parentId]; list.append(tag); return; } Tag::List &children = mChildTags[parentId]; q->beginInsertRows(indexForTag(parentId), children.count(), children.count()); mTags.insert(tag.id(), tag); children.append(tag); q->endInsertRows(); // If there are any child tags waiting for this parent, insert them if (mPendingTags.contains(tag.id())) { const Tag::List pendingChildren = mPendingTags.take(tag.id()); for (const Tag &pendingTag : pendingChildren) { monitoredTagAdded(pendingTag); } } } void TagModelPrivate::removeTagsRecursively(qint64 tagId) { const Tag tag = mTags.value(tagId); // Remove all children first const Tag::List childTags = mChildTags.take(tagId); for (const Tag &child : childTags) { removeTagsRecursively(child.id()); } // Remove the actual tag Tag::List &siblings = mChildTags[tag.parent().id()]; siblings.removeOne(tag); mTags.remove(tag.id()); } void TagModelPrivate::monitoredTagRemoved(const Tag &tag) { Q_Q(TagModel); if (!tag.isValid()) { qCWarning(AKONADICORE_LOG) << "Attempting to remove root tag?"; return; } // Better lookup parent in our cache auto iter = mTags.constFind(tag.id()); if (iter == mTags.cend()) { qCWarning(AKONADICORE_LOG) << "Got removal notification for unknown tag" << tag.id(); return; } const qint64 parentId = iter->parent().id(); const Tag::List &siblings = mChildTags[parentId]; const int pos = siblings.indexOf(tag); Q_ASSERT(pos != -1); q->beginRemoveRows(indexForTag(parentId), pos, pos); removeTagsRecursively(tag.id()); q->endRemoveRows(); } void TagModelPrivate::monitoredTagChanged(const Tag &tag) { Q_Q(TagModel); if (!mTags.contains(tag.id())) { qCWarning(AKONADICORE_LOG) << "Got change notifications for unknown tag" << tag.id(); return; } const Tag oldTag = mTags.value(tag.id()); // Replace existing tag in cache mTags.insert(tag.id(), tag); // Check whether the tag has been reparented const qint64 oldParent = oldTag.parent().id(); const qint64 newParent = tag.parent().id(); if (oldParent != newParent) { const QModelIndex sourceParent = indexForTag(oldParent); const int sourcePos = mChildTags.value(oldParent).indexOf(oldTag); const QModelIndex destParent = indexForTag(newParent); const int destPos = mChildTags.value(newParent).count(); q->beginMoveRows(sourceParent, sourcePos, sourcePos, destParent, destPos); Tag::List &oldSiblings = mChildTags[oldParent]; oldSiblings.removeAt(sourcePos); Tag::List &newSiblings = mChildTags[newParent]; newSiblings.append(tag); q->endMoveRows(); } else { Tag::List &children = mChildTags[oldParent]; const int sourcePos = children.indexOf(oldTag); if (sourcePos != -1) { children[sourcePos] = tag; } const QModelIndex index = indexForTag(tag.id()); q->dataChanged(index, index); } } void TagModelPrivate::tagsFetched(const Tag::List &tags) { for (const Tag &tag : tags) { monitoredTagAdded(tag); } } void TagModelPrivate::tagsFetchDone(KJob *job) { Q_Q(TagModel); if (job->error()) { qCWarning(AKONADICORE_LOG) << job->errorString(); return; } if (!mPendingTags.isEmpty()) { qCWarning(AKONADICORE_LOG) << "Fetched all tags from server, but there are still" << mPendingTags.count() << "orphan tags:"; for (auto it = mPendingTags.cbegin(), e = mPendingTags.cend(); it != e; ++it) { qCWarning(AKONADICORE_LOG) << "tagId = " << it.key() << "; with list count =" << it.value().count(); } return; } - emit q->populated(); + Q_EMIT q->populated(); } diff --git a/src/core/monitor.cpp b/src/core/monitor.cpp index e4804dccf..5dc9fafb4 100644 --- a/src/core/monitor.cpp +++ b/src/core/monitor.cpp @@ -1,386 +1,386 @@ /* 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 "monitor.h" #include "monitor_p.h" #include "changemediator_p.h" #include "collectionfetchscope.h" #include "itemfetchjob.h" #include "session.h" #include #include using namespace Akonadi; Monitor::Monitor(QObject *parent) : QObject(parent) , d_ptr(new MonitorPrivate(nullptr, this)) { d_ptr->init(); d_ptr->connectToNotificationManager(); ChangeMediator::registerMonitor(this); } //@cond PRIVATE Monitor::Monitor(MonitorPrivate *d, QObject *parent) : QObject(parent) , d_ptr(d) { d_ptr->init(); d_ptr->connectToNotificationManager(); ChangeMediator::registerMonitor(this); } //@endcond Monitor::~Monitor() { ChangeMediator::unregisterMonitor(this); delete d_ptr; } void Monitor::setCollectionMonitored(const Collection &collection, bool monitored) { Q_D(Monitor); if (!d->collections.contains(collection) && monitored) { d->collections << collection; d->pendingModification.startMonitoringCollection(collection.id()); d->scheduleSubscriptionUpdate(); } else if (!monitored) { if (d->collections.removeAll(collection)) { d->pendingModification.stopMonitoringCollection(collection.id()); d->scheduleSubscriptionUpdate(); } } - emit collectionMonitored(collection, monitored); + Q_EMIT collectionMonitored(collection, monitored); } void Monitor::setItemMonitored(const Item &item, bool monitored) { Q_D(Monitor); if (!d->items.contains(item.id()) && monitored) { d->items.insert(item.id()); d->pendingModification.startMonitoringItem(item.id()); d->scheduleSubscriptionUpdate(); } else if (!monitored) { if (d->items.remove(item.id())) { d->pendingModification.stopMonitoringItem(item.id()); d->scheduleSubscriptionUpdate(); } } - emit itemMonitored(item, monitored); + Q_EMIT itemMonitored(item, monitored); } void Monitor::setResourceMonitored(const QByteArray &resource, bool monitored) { Q_D(Monitor); if (!d->resources.contains(resource) && monitored) { d->resources.insert(resource); d->pendingModification.startMonitoringResource(resource); d->scheduleSubscriptionUpdate(); } else if (!monitored) { if (d->resources.remove(resource)) { d->pendingModification.stopMonitoringResource(resource); d->scheduleSubscriptionUpdate(); } } - emit resourceMonitored(resource, monitored); + Q_EMIT resourceMonitored(resource, monitored); } void Monitor::setMimeTypeMonitored(const QString &mimetype, bool monitored) { Q_D(Monitor); if (!d->mimetypes.contains(mimetype) && monitored) { d->mimetypes.insert(mimetype); d->pendingModification.startMonitoringMimeType(mimetype); d->scheduleSubscriptionUpdate(); } else if (!monitored) { if (d->mimetypes.remove(mimetype)) { d->pendingModification.stopMonitoringMimeType(mimetype); d->scheduleSubscriptionUpdate(); } } - emit mimeTypeMonitored(mimetype, monitored); + Q_EMIT mimeTypeMonitored(mimetype, monitored); } void Monitor::setTagMonitored(const Akonadi::Tag &tag, bool monitored) { Q_D(Monitor); if (!d->tags.contains(tag.id()) && monitored) { d->tags.insert(tag.id()); d->pendingModification.startMonitoringTag(tag.id()); d->scheduleSubscriptionUpdate(); } else if (!monitored) { if (d->tags.remove(tag.id())) { d->pendingModification.stopMonitoringTag(tag.id()); d->scheduleSubscriptionUpdate(); } } - emit tagMonitored(tag, monitored); + Q_EMIT tagMonitored(tag, monitored); } void Monitor::setTypeMonitored(Monitor::Type type, bool monitored) { Q_D(Monitor); if (!d->types.contains(type) && monitored) { d->types.insert(type); d->pendingModification.startMonitoringType(MonitorPrivate::monitorTypeToProtocol(type)); d->scheduleSubscriptionUpdate(); } else if (!monitored) { if (d->types.remove(type)) { d->pendingModification.stopMonitoringType(MonitorPrivate::monitorTypeToProtocol(type)); d->scheduleSubscriptionUpdate(); } } - emit typeMonitored(type, monitored); + Q_EMIT typeMonitored(type, monitored); } void Akonadi::Monitor::setAllMonitored(bool monitored) { Q_D(Monitor); if (d->monitorAll == monitored) { return; } d->monitorAll = monitored; d->pendingModification.setAllMonitored(monitored); d->scheduleSubscriptionUpdate(); - emit allMonitored(monitored); + Q_EMIT allMonitored(monitored); } void Monitor::setExclusive(bool exclusive) { Q_D(Monitor); d->exclusive = exclusive; d->pendingModification.setIsExclusive(exclusive); d->scheduleSubscriptionUpdate(); } bool Monitor::exclusive() const { Q_D(const Monitor); return d->exclusive; } void Monitor::ignoreSession(Session *session) { Q_D(Monitor); if (!d->sessions.contains(session->sessionId())) { d->sessions << session->sessionId(); connect(session, SIGNAL(destroyed(QObject*)), this, SLOT(slotSessionDestroyed(QObject*))); d->pendingModification.startIgnoringSession(session->sessionId()); d->scheduleSubscriptionUpdate(); } } void Monitor::fetchCollection(bool enable) { Q_D(Monitor); d->fetchCollection = enable; } void Monitor::fetchCollectionStatistics(bool enable) { Q_D(Monitor); d->fetchCollectionStatistics = enable; } void Monitor::setItemFetchScope(const ItemFetchScope &fetchScope) { Q_D(Monitor); d->mItemFetchScope = fetchScope; d->pendingModificationChanges |= Protocol::ModifySubscriptionCommand::ItemFetchScope; d->scheduleSubscriptionUpdate(); } ItemFetchScope &Monitor::itemFetchScope() { Q_D(Monitor); d->pendingModificationChanges |= Protocol::ModifySubscriptionCommand::ItemFetchScope; d->scheduleSubscriptionUpdate(); return d->mItemFetchScope; } void Monitor::fetchChangedOnly(bool enable) { Q_D(Monitor); d->mFetchChangedOnly = enable; } void Monitor::setCollectionFetchScope(const CollectionFetchScope &fetchScope) { Q_D(Monitor); d->mCollectionFetchScope = fetchScope; d->pendingModificationChanges |= Protocol::ModifySubscriptionCommand::CollectionFetchScope; d->scheduleSubscriptionUpdate(); } CollectionFetchScope &Monitor::collectionFetchScope() { Q_D(Monitor); d->pendingModificationChanges |= Protocol::ModifySubscriptionCommand::CollectionFetchScope; d->scheduleSubscriptionUpdate(); return d->mCollectionFetchScope; } void Monitor::setTagFetchScope(const TagFetchScope &fetchScope) { Q_D(Monitor); d->mTagFetchScope = fetchScope; d->pendingModificationChanges |= Protocol::ModifySubscriptionCommand::TagFetchScope; d->scheduleSubscriptionUpdate(); } TagFetchScope &Monitor::tagFetchScope() { Q_D(Monitor); d->pendingModificationChanges |= Protocol::ModifySubscriptionCommand::TagFetchScope; d->scheduleSubscriptionUpdate(); return d->mTagFetchScope; } Akonadi::Collection::List Monitor::collectionsMonitored() const { Q_D(const Monitor); return d->collections; } QVector Monitor::itemsMonitoredEx() const { Q_D(const Monitor); QVector result; result.reserve(d->items.size()); std::copy(d->items.begin(), d->items.end(), std::back_inserter(result)); return result; } int Monitor::numItemsMonitored() const { Q_D(const Monitor); return d->items.size(); } QVector Monitor::tagsMonitored() const { Q_D(const Monitor); QVector result; result.reserve(d->tags.size()); std::copy(d->tags.begin(), d->tags.end(), std::back_inserter(result)); return result; } QVector Monitor::typesMonitored() const { Q_D(const Monitor); QVector result; result.reserve(d->types.size()); std::copy(d->types.begin(), d->types.end(), std::back_inserter(result)); return result; } QStringList Monitor::mimeTypesMonitored() const { Q_D(const Monitor); return d->mimetypes | toQList; } int Monitor::numMimeTypesMonitored() const { Q_D(const Monitor); return d->mimetypes.count(); } QList Monitor::resourcesMonitored() const { Q_D(const Monitor); return d->resources | toQList; } int Monitor::numResourcesMonitored() const { Q_D(const Monitor); return d->resources.count(); } bool Monitor::isAllMonitored() const { Q_D(const Monitor); return d->monitorAll; } void Monitor::setSession(Akonadi::Session *session) { Q_D(Monitor); if (session == d->session) { return; } if (!session) { d->session = Session::defaultSession(); } else { d->session = session; } d->itemCache->setSession(d->session); d->collectionCache->setSession(d->session); d->tagCache->setSession(d->session); // Reconnect with a new session d->connectToNotificationManager(); } Session *Monitor::session() const { Q_D(const Monitor); return d->session; } void Monitor::setCollectionMoveTranslationEnabled(bool enabled) { Q_D(Monitor); d->collectionMoveTranslationEnabled = enabled; } void Monitor::connectNotify(const QMetaMethod &signal) { Q_D(Monitor); d->updateListeners(signal, MonitorPrivate::AddListener); } void Monitor::disconnectNotify(const QMetaMethod &signal) { Q_D(Monitor); d->updateListeners(signal, MonitorPrivate::RemoveListener); } #include "moc_monitor.cpp" diff --git a/src/core/monitor_p.cpp b/src/core/monitor_p.cpp index 4856174d8..cacddedc5 100644 --- a/src/core/monitor_p.cpp +++ b/src/core/monitor_p.cpp @@ -1,1404 +1,1404 @@ /* Copyright (c) 2007 Tobias Koenig 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. */ // @cond PRIVATE #include "monitor_p.h" #include "collectionfetchjob.h" #include "collectionstatistics.h" #include "itemfetchjob.h" #include "notificationmanagerinterface.h" #include "session.h" #include "changemediator_p.h" #include "vectorhelper.h" #include "akonadicore_debug.h" #include "notificationsubscriber.h" #include "changenotification.h" #include "protocolhelper_p.h" #include #include using namespace Akonadi; class operation; static const int PipelineSize = 5; MonitorPrivate::MonitorPrivate(ChangeNotificationDependenciesFactory *dependenciesFactory_, Monitor *parent) : q_ptr(parent) , dependenciesFactory(dependenciesFactory_ ? dependenciesFactory_ : new ChangeNotificationDependenciesFactory) , ntfConnection(nullptr) , monitorAll(false) , exclusive(false) , mFetchChangedOnly(false) , session(Session::defaultSession()) , collectionCache(nullptr) , itemCache(nullptr) , tagCache(nullptr) , mCommandBuffer(parent, "handleCommands") , pendingModificationChanges(Protocol::ModifySubscriptionCommand::None) , pendingModificationTimer(nullptr) , monitorReady(false) , fetchCollection(false) , fetchCollectionStatistics(false) , collectionMoveTranslationEnabled(true) , useRefCounting(false) { } MonitorPrivate::~MonitorPrivate() { disconnectFromNotificationManager(); delete dependenciesFactory; delete collectionCache; delete itemCache; delete tagCache; } void MonitorPrivate::init() { // needs to be at least 3x pipeline size for the collection move case collectionCache = dependenciesFactory->createCollectionCache(3 * PipelineSize, session); // needs to be at least 1x pipeline size itemCache = dependenciesFactory->createItemListCache(PipelineSize, session); // 20 tags looks like a reasonable amount to keep around tagCache = dependenciesFactory->createTagListCache(20, session); QObject::connect(collectionCache, SIGNAL(dataAvailable()), q_ptr, SLOT(dataAvailable())); QObject::connect(itemCache, SIGNAL(dataAvailable()), q_ptr, SLOT(dataAvailable())); QObject::connect(tagCache, SIGNAL(dataAvailable()), q_ptr, SLOT(dataAvailable())); QObject::connect(ServerManager::self(), SIGNAL(stateChanged(Akonadi::ServerManager::State)), q_ptr, SLOT(serverStateChanged(Akonadi::ServerManager::State))); statisticsCompressionTimer.setSingleShot(true); statisticsCompressionTimer.setInterval(500); QObject::connect(&statisticsCompressionTimer, SIGNAL(timeout()), q_ptr, SLOT(slotFlushRecentlyChangedCollections())); } bool MonitorPrivate::connectToNotificationManager() { if (ntfConnection) { ntfConnection->deleteLater(); ntfConnection = nullptr; } if (!session) { return false; } ntfConnection = dependenciesFactory->createNotificationConnection(session, &mCommandBuffer); if (!ntfConnection) { return false; } slotUpdateSubscription(); ntfConnection->reconnect(); return true; } void MonitorPrivate::disconnectFromNotificationManager() { if (ntfConnection) { ntfConnection->disconnect(q_ptr); dependenciesFactory->destroyNotificationConnection(session, ntfConnection.data()); } } void MonitorPrivate::serverStateChanged(ServerManager::State state) { if (state == ServerManager::Running) { connectToNotificationManager(); } } void MonitorPrivate::invalidateCollectionCache(qint64 id) { collectionCache->update(id, mCollectionFetchScope); } void MonitorPrivate::invalidateItemCache(qint64 id) { itemCache->update({ id }, mItemFetchScope); // Also invalidate content of all any pending notification for given item for (auto it = pendingNotifications.begin(), end = pendingNotifications.end(); it != end; ++it) { if ((*it)->type() == Protocol::Command::ItemChangeNotification) { auto &ntf = Protocol::cmdCast(*it); const auto items = ntf.items(); if (std::any_of(items.cbegin(), items.cend(), [id](const Protocol::FetchItemsResponse &r) { return r.id() == id; })) { ntf.setMustRetrieve(true); } } } } void MonitorPrivate::invalidateTagCache(qint64 id) { tagCache->update({ id }, mTagFetchScope); } int MonitorPrivate::pipelineSize() const { return PipelineSize; } void MonitorPrivate::scheduleSubscriptionUpdate() { if (pendingModificationTimer || !monitorReady) { return; } pendingModificationTimer = new QTimer(); pendingModificationTimer->setSingleShot(true); pendingModificationTimer->setInterval(0); pendingModificationTimer->start(); q_ptr->connect(pendingModificationTimer, SIGNAL(timeout()), q_ptr, SLOT(slotUpdateSubscription())); } void MonitorPrivate::slotUpdateSubscription() { if (pendingModificationTimer) { pendingModificationTimer->stop(); std::exchange(pendingModificationTimer, nullptr)->deleteLater(); } if (pendingModificationChanges & Protocol::ModifySubscriptionCommand::ItemFetchScope) { pendingModification.setItemFetchScope(ProtocolHelper::itemFetchScopeToProtocol(mItemFetchScope)); } if (pendingModificationChanges & Protocol::ModifySubscriptionCommand::CollectionFetchScope) { pendingModification.setCollectionFetchScope(ProtocolHelper::collectionFetchScopeToProtocol(mCollectionFetchScope)); } if (pendingModificationChanges & Protocol::ModifySubscriptionCommand::TagFetchScope) { pendingModification.setTagFetchScope(ProtocolHelper::tagFetchScopeToProtocol(mTagFetchScope)); } pendingModificationChanges = Protocol::ModifySubscriptionCommand::None; if (ntfConnection) { ntfConnection->sendCommand(3, Protocol::ModifySubscriptionCommandPtr::create(pendingModification)); pendingModification = Protocol::ModifySubscriptionCommand(); } } bool MonitorPrivate::isLazilyIgnored(const Protocol::ChangeNotificationPtr &msg, bool allowModifyFlagsConversion) const { if (msg->type() == Protocol::Command::CollectionChangeNotification) { // Lazy fetching can only affects items. return false; } if (msg->type() == Protocol::Command::TagChangeNotification) { const auto op = Protocol::cmdCast(msg).operation(); return ((op == Protocol::TagChangeNotification::Add && !hasListeners(&Monitor::tagAdded)) || (op == Protocol::TagChangeNotification::Modify && !hasListeners(&Monitor::tagChanged)) || (op == Protocol::TagChangeNotification::Remove && !hasListeners(&Monitor::tagRemoved))); } if (!fetchCollectionStatistics && msg->type() == Protocol::Command::ItemChangeNotification) { const auto &itemNtf = Protocol::cmdCast(msg); const auto op = itemNtf.operation(); if ((op == Protocol::ItemChangeNotification::Add && !hasListeners(&Monitor::itemAdded)) || (op == Protocol::ItemChangeNotification::Remove && !hasListeners(&Monitor::itemRemoved) && !hasListeners(&Monitor::itemsRemoved)) || (op == Protocol::ItemChangeNotification::Modify && !hasListeners(&Monitor::itemChanged)) || (op == Protocol::ItemChangeNotification::ModifyFlags && !hasListeners(&Monitor::itemsFlagsChanged) // Newly delivered ModifyFlags notifications will be converted to // itemChanged(item, "FLAGS") for legacy clients. && (!allowModifyFlagsConversion || !hasListeners(&Monitor::itemChanged))) || (op == Protocol::ItemChangeNotification::ModifyTags && !hasListeners(&Monitor::itemsTagsChanged)) || (op == Protocol::ItemChangeNotification::Move && !hasListeners(&Monitor::itemMoved) && !hasListeners(&Monitor::itemsMoved)) || (op == Protocol::ItemChangeNotification::Link && !hasListeners(&Monitor::itemLinked) && !hasListeners(&Monitor::itemsLinked)) || (op == Protocol::ItemChangeNotification::Unlink && !hasListeners(&Monitor::itemUnlinked) && !hasListeners(&Monitor::itemsUnlinked))) { return true; } if (!useRefCounting) { return false; } const Collection::Id parentCollectionId = itemNtf.parentCollection(); if ((op == Protocol::ItemChangeNotification::Add) || (op == Protocol::ItemChangeNotification::Remove) || (op == Protocol::ItemChangeNotification::Modify) || (op == Protocol::ItemChangeNotification::ModifyFlags) || (op == Protocol::ItemChangeNotification::ModifyTags) || (op == Protocol::ItemChangeNotification::Link) || (op == Protocol::ItemChangeNotification::Unlink)) { if (isMonitored(parentCollectionId)) { return false; } } if (op == Protocol::ItemChangeNotification::Move) { if (!isMonitored(parentCollectionId) && !isMonitored(itemNtf.parentDestCollection())) { return true; } // We can't ignore the move. It must be transformed later into a removal or insertion. return false; } return true; } return false; } void MonitorPrivate::checkBatchSupport(const Protocol::ChangeNotificationPtr &msg, bool &needsSplit, bool &batchSupported) const { if (msg->type() != Protocol::Command::ItemChangeNotification) { needsSplit = false; batchSupported = false; return; } const auto &itemNtf = Protocol::cmdCast(msg); const bool isBatch = (itemNtf.items().count() > 1); switch (itemNtf.operation()) { case Protocol::ItemChangeNotification::Add: needsSplit = isBatch; batchSupported = false; return; case Protocol::ItemChangeNotification::Modify: needsSplit = isBatch; batchSupported = false; return; case Protocol::ItemChangeNotification::ModifyFlags: batchSupported = hasListeners(&Monitor::itemsFlagsChanged); needsSplit = isBatch && !batchSupported && hasListeners(&Monitor::itemChanged); return; case Protocol::ItemChangeNotification::ModifyTags: // Tags were added after batch notifications, so they are always supported batchSupported = true; needsSplit = false; return; case Protocol::ItemChangeNotification::ModifyRelations: // Relations were added after batch notifications, so they are always supported batchSupported = true; needsSplit = false; return; case Protocol::ItemChangeNotification::Move: needsSplit = isBatch && hasListeners(&Monitor::itemMoved); batchSupported = hasListeners(&Monitor::itemsMoved); return; case Protocol::ItemChangeNotification::Remove: needsSplit = isBatch && hasListeners(&Monitor::itemRemoved); batchSupported = hasListeners(&Monitor::itemsRemoved); return; case Protocol::ItemChangeNotification::Link: needsSplit = isBatch && hasListeners(&Monitor::itemLinked); batchSupported = hasListeners(&Monitor::itemsLinked); return; case Protocol::ItemChangeNotification::Unlink: needsSplit = isBatch && hasListeners(&Monitor::itemUnlinked); batchSupported = hasListeners(&Monitor::itemsUnlinked); return; default: needsSplit = isBatch; batchSupported = false; qCDebug(AKONADICORE_LOG) << "Unknown operation type" << itemNtf.operation() << "in item change notification"; return; } } Protocol::ChangeNotificationList MonitorPrivate::splitMessage(const Protocol::ItemChangeNotification &msg, bool legacy) const { Protocol::ChangeNotificationList list; Protocol::ItemChangeNotification baseMsg; baseMsg.setSessionId(msg.sessionId()); if (legacy && msg.operation() == Protocol::ItemChangeNotification::ModifyFlags) { baseMsg.setOperation(Protocol::ItemChangeNotification::Modify); baseMsg.setItemParts(QSet() << "FLAGS"); } else { baseMsg.setOperation(msg.operation()); baseMsg.setItemParts(msg.itemParts()); } baseMsg.setParentCollection(msg.parentCollection()); baseMsg.setParentDestCollection(msg.parentDestCollection()); baseMsg.setResource(msg.resource()); baseMsg.setDestinationResource(msg.destinationResource()); baseMsg.setAddedFlags(msg.addedFlags()); baseMsg.setRemovedFlags(msg.removedFlags()); baseMsg.setAddedTags(msg.addedTags()); baseMsg.setRemovedTags(msg.removedTags()); const auto items = msg.items(); list.reserve(items.count()); for (const auto &item : items) { auto copy = Protocol::ItemChangeNotificationPtr::create(baseMsg); copy->setItems({Protocol::FetchItemsResponse(item)}); list.push_back(std::move(copy)); } return list; } bool MonitorPrivate::fetchCollections() const { return fetchCollection; } bool MonitorPrivate::fetchItems() const { return !mItemFetchScope.isEmpty(); } bool MonitorPrivate::ensureDataAvailable(const Protocol::ChangeNotificationPtr &msg) { if (msg->type() == Protocol::Command::TagChangeNotification) { const auto tagMsg = Protocol::cmdCast(msg); if (tagMsg.metadata().contains("FETCH_TAG")) { if (!tagCache->ensureCached({ tagMsg.tag().id() }, mTagFetchScope)) { return false; } } return true; } if (msg->type() == Protocol::Command::RelationChangeNotification) { return true; } if (msg->type() == Protocol::Command::SubscriptionChangeNotification) { return true; } if (msg->type() == Protocol::Command::DebugChangeNotification) { return true; } if (msg->type() == Protocol::Command::CollectionChangeNotification && Protocol::cmdCast(msg).operation() == Protocol::CollectionChangeNotification::Remove) { // For collection removals the collection is gone already, so we can't fetch it, // but we have to at least obtain the ancestor chain. const qint64 parentCollection = Protocol::cmdCast(msg).parentCollection(); return parentCollection <= -1 || collectionCache->ensureCached(parentCollection, mCollectionFetchScope); } bool allCached = true; if (fetchCollections()) { const qint64 parentCollection = (msg->type() == Protocol::Command::ItemChangeNotification) ? Protocol::cmdCast(msg).parentCollection() : (msg->type() == Protocol::Command::CollectionChangeNotification) ? Protocol::cmdCast(msg).parentCollection() : -1; if (parentCollection > -1 && !collectionCache->ensureCached(parentCollection, mCollectionFetchScope)) { allCached = false; } qint64 parentDestCollection = -1; if ((msg->type() == Protocol::Command::ItemChangeNotification) && (Protocol::cmdCast(msg).operation() == Protocol::ItemChangeNotification::Move)) { parentDestCollection = Protocol::cmdCast(msg).parentDestCollection(); } else if ((msg->type() == Protocol::Command::CollectionChangeNotification) && (Protocol::cmdCast(msg).operation() == Protocol::CollectionChangeNotification::Move)) { parentDestCollection = Protocol::cmdCast(msg).parentDestCollection(); } if (parentDestCollection > -1 && !collectionCache->ensureCached(parentDestCollection, mCollectionFetchScope)) { allCached = false; } } if (msg->isRemove()) { return allCached; } if (msg->type() == Protocol::Command::ItemChangeNotification && fetchItems()) { const auto &itemNtf = Protocol::cmdCast(msg); if (mFetchChangedOnly && (itemNtf.operation() == Protocol::ItemChangeNotification::Modify || itemNtf.operation() == Protocol::ItemChangeNotification::ModifyFlags)) { const auto changedParts = itemNtf.itemParts(); const auto requestedParts = mItemFetchScope.payloadParts(); const auto requestedAttrs = mItemFetchScope.attributes(); QSet missingParts, missingAttributes; for (const QByteArray &part : changedParts) { const auto partName = part.mid(4); if (part.startsWith("PLD:") && //krazy:exclude=strings since QByteArray (!mItemFetchScope.fullPayload() || !requestedParts.contains(partName))) { missingParts.insert(partName); } else if (part.startsWith("ATR:") && //krazy:exclude=strings since QByteArray (!mItemFetchScope.allAttributes() || !requestedAttrs.contains(partName))) { missingAttributes.insert(partName); } } if (!missingParts.isEmpty() || !missingAttributes.isEmpty()) { ItemFetchScope scope(mItemFetchScope); scope.fetchFullPayload(false); for (const auto &part : requestedParts) { scope.fetchPayloadPart(part, false); } for (const auto &attr : requestedAttrs) { scope.fetchAttribute(attr, false); } for (const auto &part : missingParts) { scope.fetchPayloadPart(part, true); } for (const auto &attr : missingAttributes) { scope.fetchAttribute(attr, true); } if (!itemCache->ensureCached(Protocol::ChangeNotification::itemsToUids(itemNtf.items()), scope)) { return false; } } return allCached; } // Make sure all tags for ModifyTags operation are in cache too if (itemNtf.operation() == Protocol::ItemChangeNotification::ModifyTags) { if (!tagCache->ensureCached((itemNtf.addedTags() + itemNtf.removedTags()) | toQList, mTagFetchScope)) { return false; } } if (itemNtf.metadata().contains("FETCH_ITEM") || itemNtf.mustRetrieve()) { if (!itemCache->ensureCached(Protocol::ChangeNotification::itemsToUids(itemNtf.items()), mItemFetchScope)) { return false; } } return allCached; } else if (msg->type() == Protocol::Command::CollectionChangeNotification && fetchCollections()) { const auto &colMsg = Protocol::cmdCast(msg); if (colMsg.metadata().contains("FETCH_COLLECTION")) { if (!collectionCache->ensureCached(colMsg.collection().id(), mCollectionFetchScope)) { return false; } } return allCached; } return allCached; } bool MonitorPrivate::emitNotification(const Protocol::ChangeNotificationPtr &msg) { bool someoneWasListening = false; if (msg->type() == Protocol::Command::TagChangeNotification) { const auto &tagNtf = Protocol::cmdCast(msg); const bool fetched = tagNtf.metadata().contains("FETCH_TAG"); Tag tag; if (fetched) { const auto tags = tagCache->retrieve({ tagNtf.tag().id() }); tag = tags.isEmpty() ? Tag() : tags.at(0); } else { tag = ProtocolHelper::parseTag(tagNtf.tag()); } someoneWasListening = emitTagNotification(tagNtf, tag); } else if (msg->type() == Protocol::Command::RelationChangeNotification) { const auto &relNtf = Protocol::cmdCast(msg); const Relation rel = ProtocolHelper::parseRelationFetchResult(relNtf.relation()); someoneWasListening = emitRelationNotification(relNtf, rel); } else if (msg->type() == Protocol::Command::CollectionChangeNotification) { const auto &colNtf = Protocol::cmdCast(msg); const Collection parent = collectionCache->retrieve(colNtf.parentCollection()); Collection destParent; if (colNtf.operation() == Protocol::CollectionChangeNotification::Move) { destParent = collectionCache->retrieve(colNtf.parentDestCollection()); } const bool fetched = colNtf.metadata().contains("FETCH_COLLECTION"); //For removals this will retrieve an invalid collection. We'll deal with that in emitCollectionNotification const Collection col = fetched ? collectionCache->retrieve(colNtf.collection().id()) : ProtocolHelper::parseCollection(colNtf.collection(), true); //It is possible that the retrieval fails also in the non-removal case (e.g. because the item was meanwhile removed while //the changerecorder stored the notification or the notification was in the queue). In order to drop such invalid notifications we have to ignore them. if (col.isValid() || colNtf.operation() == Protocol::CollectionChangeNotification::Remove || !fetchCollections()) { someoneWasListening = emitCollectionNotification(colNtf, col, parent, destParent); } } else if (msg->type() == Protocol::Command::ItemChangeNotification) { const auto &itemNtf = Protocol::cmdCast(msg); const Collection parent = collectionCache->retrieve(itemNtf.parentCollection()); Collection destParent; if (itemNtf.operation() == Protocol::ItemChangeNotification::Move) { destParent = collectionCache->retrieve(itemNtf.parentDestCollection()); } const bool fetched = itemNtf.metadata().contains("FETCH_ITEM") || itemNtf.mustRetrieve(); //For removals this will retrieve an empty set. We'll deal with that in emitItemNotification Item::List items; if (fetched && fetchItems()) { items = itemCache->retrieve(Protocol::ChangeNotification::itemsToUids(itemNtf.items())); } else { const auto ntfItems = itemNtf.items(); items.reserve(ntfItems.size()); for (const auto &ntfItem : ntfItems) { items.push_back(ProtocolHelper::parseItemFetchResult(ntfItem, &mItemFetchScope)); } } //It is possible that the retrieval fails also in the non-removal case (e.g. because the item was meanwhile removed while //the changerecorder stored the notification or the notification was in the queue). In order to drop such invalid notifications we have to ignore them. if (!items.isEmpty() || itemNtf.operation() == Protocol::ItemChangeNotification::Remove || !fetchItems()) { someoneWasListening = emitItemsNotification(itemNtf, items, parent, destParent); } } else if (msg->type() == Protocol::Command::SubscriptionChangeNotification) { const auto &subNtf = Protocol::cmdCast(msg); NotificationSubscriber subscriber; subscriber.setSubscriber(subNtf.subscriber()); subscriber.setSessionId(subNtf.sessionId()); subscriber.setMonitoredCollections(subNtf.collections()); subscriber.setMonitoredItems(subNtf.items()); subscriber.setMonitoredTags(subNtf.tags()); QSet monitorTypes; Q_FOREACH (auto type, subNtf.types()) { if (type == Protocol::ModifySubscriptionCommand::NoType) { continue; } monitorTypes.insert([](Protocol::ModifySubscriptionCommand::ChangeType type) { switch (type) { case Protocol::ModifySubscriptionCommand::ItemChanges: return Monitor::Items; case Protocol::ModifySubscriptionCommand::CollectionChanges: return Monitor::Collections; case Protocol::ModifySubscriptionCommand::TagChanges: return Monitor::Tags; case Protocol::ModifySubscriptionCommand::RelationChanges: return Monitor::Relations; case Protocol::ModifySubscriptionCommand::SubscriptionChanges: return Monitor::Subscribers; case Protocol::ModifySubscriptionCommand::ChangeNotifications: return Monitor::Notifications; default: Q_ASSERT(false); return Monitor::Items; //unreachable } }(type)); } subscriber.setMonitoredTypes(monitorTypes); subscriber.setMonitoredMimeTypes(subNtf.mimeTypes()); subscriber.setMonitoredResources(subNtf.resources()); subscriber.setIgnoredSessions(subNtf.ignoredSessions()); subscriber.setIsAllMonitored(subNtf.allMonitored()); subscriber.setIsExclusive(subNtf.exclusive()); subscriber.setItemFetchScope(ProtocolHelper::parseItemFetchScope(subNtf.itemFetchScope())); subscriber.setCollectionFetchScope(ProtocolHelper::parseCollectionFetchScope(subNtf.collectionFetchScope())); someoneWasListening = emitSubscriptionChangeNotification(subNtf, subscriber); } else if (msg->type() == Protocol::Command::DebugChangeNotification) { const auto &changeNtf = Protocol::cmdCast(msg); ChangeNotification notification; notification.setListeners(changeNtf.listeners()); notification.setTimestamp(QDateTime::fromMSecsSinceEpoch(changeNtf.timestamp())); notification.setNotification(changeNtf.notification()); switch (changeNtf.notification()->type()) { case Protocol::Command::ItemChangeNotification: notification.setType(ChangeNotification::Items); break; case Protocol::Command::CollectionChangeNotification: notification.setType(ChangeNotification::Collection); break; case Protocol::Command::TagChangeNotification: notification.setType(ChangeNotification::Tag); break; case Protocol::Command::RelationChangeNotification: notification.setType(ChangeNotification::Relation); break; case Protocol::Command::SubscriptionChangeNotification: notification.setType(ChangeNotification::Subscription); break; default: Q_ASSERT(false); // huh? return false; } someoneWasListening = emitDebugChangeNotification(changeNtf, notification); } return someoneWasListening; } void MonitorPrivate::updatePendingStatistics(const Protocol::ChangeNotificationPtr &msg) { if (msg->type() == Protocol::Command::ItemChangeNotification) { const auto &itemNtf = Protocol::cmdCast(msg); notifyCollectionStatisticsWatchers(itemNtf.parentCollection(), itemNtf.resource()); // FIXME use the proper resource of the target collection, for cross resource moves notifyCollectionStatisticsWatchers(itemNtf.parentDestCollection(), itemNtf.destinationResource()); } else if (msg->type() == Protocol::Command::CollectionChangeNotification) { const auto &colNtf = Protocol::cmdCast(msg); if (colNtf.operation() == Protocol::CollectionChangeNotification::Remove) { // no need for statistics updates anymore recentlyChangedCollections.remove(colNtf.collection().id()); } } } void MonitorPrivate::slotSessionDestroyed(QObject *object) { Session *objectSession = qobject_cast(object); if (objectSession) { sessions.removeAll(objectSession->sessionId()); pendingModification.stopIgnoringSession(objectSession->sessionId()); scheduleSubscriptionUpdate(); } } void MonitorPrivate::slotStatisticsChangedFinished(KJob *job) { if (job->error()) { qCWarning(AKONADICORE_LOG) << "Error on fetching collection statistics: " << job->errorText(); } else { CollectionStatisticsJob *statisticsJob = static_cast(job); Q_ASSERT(statisticsJob->collection().isValid()); - emit q_ptr->collectionStatisticsChanged(statisticsJob->collection().id(), + Q_EMIT q_ptr->collectionStatisticsChanged(statisticsJob->collection().id(), statisticsJob->statistics()); } } void MonitorPrivate::slotFlushRecentlyChangedCollections() { for (Collection::Id collection : qAsConst(recentlyChangedCollections)) { Q_ASSERT(collection >= 0); if (fetchCollectionStatistics) { fetchStatistics(collection); } else { static const CollectionStatistics dummyStatistics; - emit q_ptr->collectionStatisticsChanged(collection, dummyStatistics); + Q_EMIT q_ptr->collectionStatisticsChanged(collection, dummyStatistics); } } recentlyChangedCollections.clear(); } int MonitorPrivate::translateAndCompress(QQueue ¬ificationQueue, const Protocol::ChangeNotificationPtr &msg) { // Always handle tags and relations if (msg->type() == Protocol::Command::TagChangeNotification || msg->type() == Protocol::Command::RelationChangeNotification) { notificationQueue.enqueue(msg); return 1; } // We have to split moves into insert or remove if the source or destination // is not monitored. if (!msg->isMove()) { notificationQueue.enqueue(msg); return 1; } bool sourceWatched = false; bool destWatched = false; if (msg->type() == Protocol::Command::ItemChangeNotification) { const auto &itemNtf = Protocol::cmdCast(msg); if (useRefCounting) { sourceWatched = isMonitored(itemNtf.parentCollection()); destWatched = isMonitored(itemNtf.parentDestCollection()); } else { if (!resources.isEmpty()) { sourceWatched = resources.contains(itemNtf.resource()); destWatched = isMoveDestinationResourceMonitored(itemNtf); } if (!sourceWatched) { sourceWatched = isCollectionMonitored(itemNtf.parentCollection()); } if (!destWatched) { destWatched = isCollectionMonitored(itemNtf.parentDestCollection()); } } } else if (msg->type() == Protocol::Command::CollectionChangeNotification) { const auto &colNtf = Protocol::cmdCast(msg); if (!resources.isEmpty()) { sourceWatched = resources.contains(colNtf.resource()); destWatched = isMoveDestinationResourceMonitored(colNtf); } if (!sourceWatched) { sourceWatched = isCollectionMonitored(colNtf.parentCollection()); } if (!destWatched) { destWatched = isCollectionMonitored(colNtf.parentDestCollection()); } } else { Q_ASSERT(false); return 0; } if (!sourceWatched && !destWatched) { return 0; } if ((sourceWatched && destWatched) || (!collectionMoveTranslationEnabled && msg->type() == Protocol::Command::CollectionChangeNotification)) { notificationQueue.enqueue(msg); return 1; } if (sourceWatched) { if (msg->type() == Protocol::Command::ItemChangeNotification) { auto removalMessage = Protocol::ItemChangeNotificationPtr::create( Protocol::cmdCast(msg)); removalMessage->setOperation(Protocol::ItemChangeNotification::Remove); removalMessage->setParentDestCollection(-1); notificationQueue.enqueue(removalMessage); return 1; } else { auto removalMessage = Protocol::CollectionChangeNotificationPtr::create( Protocol::cmdCast(msg)); removalMessage->setOperation(Protocol::CollectionChangeNotification::Remove); removalMessage->setParentDestCollection(-1); notificationQueue.enqueue(removalMessage); return 1; } } // Transform into an insertion if (msg->type() == Protocol::Command::ItemChangeNotification) { auto insertionMessage = Protocol::ItemChangeNotificationPtr::create( Protocol::cmdCast(msg)); insertionMessage->setOperation(Protocol::ItemChangeNotification::Add); insertionMessage->setParentCollection(insertionMessage->parentDestCollection()); insertionMessage->setParentDestCollection(-1); // We don't support batch insertion, so we have to do it one by one const auto split = splitMessage(*insertionMessage, false); for (const Protocol::ChangeNotificationPtr &insertion : split) { notificationQueue.enqueue(insertion); } return split.count(); } else if (msg->type() == Protocol::Command::CollectionChangeNotification) { auto insertionMessage = Protocol::CollectionChangeNotificationPtr::create( Protocol::cmdCast(msg)); insertionMessage->setOperation(Protocol::CollectionChangeNotification::Add); insertionMessage->setParentCollection(insertionMessage->parentDestCollection()); insertionMessage->setParentDestCollection(-1); notificationQueue.enqueue(insertionMessage); return 1; } Q_ASSERT(false); return 0; } void MonitorPrivate::handleCommands() { Q_Q(Monitor); CommandBufferLocker lock(&mCommandBuffer); CommandBufferNotifyBlocker notify(&mCommandBuffer); while (!mCommandBuffer.isEmpty()) { const auto cmd = mCommandBuffer.dequeue(); lock.unlock(); const auto command = cmd.command; if (command->isResponse()) { switch (command->type()) { case Protocol::Command::Hello: { qCDebug(AKONADICORE_LOG) << q_ptr << "Connected to notification bus"; QByteArray subname; if (!q->objectName().isEmpty()) { subname = q->objectName().toLatin1(); } else { subname = session->sessionId(); } subname += " - " + QByteArray::number(quintptr(q)); qCDebug(AKONADICORE_LOG) << q_ptr << "Subscribing as \"" << subname << "\""; auto subCmd = Protocol::CreateSubscriptionCommandPtr::create(subname, session->sessionId()); ntfConnection->sendCommand(2, subCmd); break; } case Protocol::Command::CreateSubscription: { auto msubCmd = Protocol::ModifySubscriptionCommandPtr::create(); for (const auto &col : qAsConst(collections)) { msubCmd->startMonitoringCollection(col.id()); } for (const auto &res : qAsConst(resources)) { msubCmd->startMonitoringResource(res); } for (auto itemId : qAsConst(items)) { msubCmd->startMonitoringItem(itemId); } for (auto tagId : qAsConst(tags)) { msubCmd->startMonitoringTag(tagId); } for (auto type : qAsConst(types)) { msubCmd->startMonitoringType(monitorTypeToProtocol(type)); } for (const auto &mimetype : qAsConst(mimetypes)) { msubCmd->startMonitoringMimeType(mimetype); } for (const auto &session : qAsConst(sessions)) { msubCmd->startIgnoringSession(session); } msubCmd->setAllMonitored(monitorAll); msubCmd->setIsExclusive(exclusive); msubCmd->setItemFetchScope(ProtocolHelper::itemFetchScopeToProtocol(mItemFetchScope)); msubCmd->setCollectionFetchScope(ProtocolHelper::collectionFetchScopeToProtocol(mCollectionFetchScope)); msubCmd->setTagFetchScope(ProtocolHelper::tagFetchScopeToProtocol(mTagFetchScope)); pendingModification = Protocol::ModifySubscriptionCommand(); ntfConnection->sendCommand(3, msubCmd); break; } case Protocol::Command::ModifySubscription: // TODO: Handle errors if (!monitorReady) { monitorReady = true; Q_EMIT q_ptr->monitorReady(); } break; default: qCWarning(AKONADICORE_LOG) << "Received an unexpected response on Notification stream: " << Protocol::debugString(command); break; } } else { switch (command->type()) { case Protocol::Command::ItemChangeNotification: case Protocol::Command::CollectionChangeNotification: case Protocol::Command::TagChangeNotification: case Protocol::Command::RelationChangeNotification: case Protocol::Command::SubscriptionChangeNotification: case Protocol::Command::DebugChangeNotification: slotNotify(command.staticCast()); break; default: qCWarning(AKONADICORE_LOG) << "Received an unexpected message on Notification stream:" << Protocol::debugString(command); break; } } lock.relock(); } notify.unblock(); lock.unlock(); } /* server notification --> ?accepted --> pendingNotifications --> ?dataAvailable --> emit | | x --> discard x --> pipeline fetchJobDone --> pipeline ?dataAvailable --> emit */ void MonitorPrivate::slotNotify(const Protocol::ChangeNotificationPtr &msg) { int appendedMessages = 0; int modifiedMessages = 0; int erasedMessages = 0; invalidateCaches(msg); updatePendingStatistics(msg); bool needsSplit = true; bool supportsBatch = false; if (isLazilyIgnored(msg, true)) { return; } checkBatchSupport(msg, needsSplit, supportsBatch); const bool isModifyFlags = (msg->type() == Protocol::Command::ItemChangeNotification && Protocol::cmdCast(msg).operation() == Protocol::ItemChangeNotification::ModifyFlags); if (supportsBatch || (!needsSplit && !supportsBatch && !isModifyFlags) || msg->type() == Protocol::Command::CollectionChangeNotification) { // Make sure the batch msg is always queued before the split notifications const int oldSize = pendingNotifications.size(); const int appended = translateAndCompress(pendingNotifications, msg); if (appended > 0) { appendedMessages += appended; } else { ++modifiedMessages; } // translateAndCompress can remove an existing "modify" when msg is a "delete". // Or it can merge two ModifyFlags and return false. // We need to detect such removals, for ChangeRecorder. if (pendingNotifications.count() != oldSize + appended) { ++erasedMessages; // this count isn't exact, but it doesn't matter } } else if (needsSplit) { // If it's not queued at least make sure we fetch all the items from split // notifications in one go. if (msg->type() == Protocol::Command::ItemChangeNotification) { const auto items = Protocol::cmdCast(msg).items(); itemCache->ensureCached(Protocol::ChangeNotification::itemsToUids(items), mItemFetchScope); } } // if the message contains more items, but we need to emit single-item notification, // split the message into one message per item and queue them // if the message contains only one item, but batches are not supported // (and thus neither is flagsModified), splitMessage() will convert the // notification to regular Modify with "FLAGS" part changed if (needsSplit || (!needsSplit && !supportsBatch && isModifyFlags)) { // Make sure inter-resource move notifications are translated into // Add/Remove notifications if (msg->type() == Protocol::Command::ItemChangeNotification) { const auto &itemNtf = Protocol::cmdCast(msg); if (itemNtf.operation() == Protocol::ItemChangeNotification::Move && itemNtf.resource() != itemNtf.destinationResource()) { if (needsSplit) { const Protocol::ChangeNotificationList split = splitMessage(itemNtf, !supportsBatch); for (const auto &splitMsg : split) { appendedMessages += translateAndCompress(pendingNotifications, splitMsg); } } else { appendedMessages += translateAndCompress(pendingNotifications, msg); } } else { const Protocol::ChangeNotificationList split = splitMessage(itemNtf, !supportsBatch); pendingNotifications << (split | toQList); appendedMessages += split.count(); } } } // tell ChangeRecorder (even if 0 appended, the compression could have made changes to existing messages) if (appendedMessages > 0 || modifiedMessages > 0 || erasedMessages > 0) { if (erasedMessages > 0) { notificationsErased(); } else { notificationsEnqueued(appendedMessages); } } dispatchNotifications(); } void MonitorPrivate::flushPipeline() { while (!pipeline.isEmpty()) { const auto msg = pipeline.head(); if (ensureDataAvailable(msg)) { // dequeue should be before emit, otherwise stuff might happen (like dataAvailable // being called again) and we end up dequeuing an empty pipeline pipeline.dequeue(); emitNotification(msg); } else { break; } } } void MonitorPrivate::dataAvailable() { flushPipeline(); dispatchNotifications(); } void MonitorPrivate::dispatchNotifications() { // Note that this code is not used in a ChangeRecorder (pipelineSize==0) while (pipeline.size() < pipelineSize() && !pendingNotifications.isEmpty()) { const auto msg = pendingNotifications.dequeue(); const bool avail = ensureDataAvailable(msg); if (avail && pipeline.isEmpty()) { emitNotification(msg); } else { pipeline.enqueue(msg); } } } static Relation::List extractRelations(const QSet &rels) { Relation::List relations; if (rels.isEmpty()) { return relations; } relations.reserve(rels.size()); for (const auto &rel : rels) { relations.push_back(Relation(rel.type.toLatin1(), Akonadi::Item(rel.leftId), Akonadi::Item(rel.rightId))); } return relations; } bool MonitorPrivate::emitItemsNotification(const Protocol::ItemChangeNotification &msg_, const Item::List &items, const Collection &collection, const Collection &collectionDest) { Protocol::ItemChangeNotification msg = msg_; Collection col = collection; Collection colDest = collectionDest; if (!col.isValid()) { col = Collection(msg.parentCollection()); col.setResource(QString::fromUtf8(msg.resource())); } if (!colDest.isValid()) { colDest = Collection(msg.parentDestCollection()); // HACK: destination resource is delivered in the parts field... if (!msg.itemParts().isEmpty()) { colDest.setResource(QString::fromLatin1(*(msg.itemParts().cbegin()))); } } const QSet addedFlags = msg.addedFlags(); const QSet removedFlags = msg.removedFlags(); Relation::List addedRelations, removedRelations; if (msg.operation() == Protocol::ItemChangeNotification::ModifyRelations) { addedRelations = extractRelations(msg.addedRelations()); removedRelations = extractRelations(msg.removedRelations()); } Tag::List addedTags, removedTags; if (msg.operation() == Protocol::ItemChangeNotification::ModifyTags) { addedTags = tagCache->retrieve(msg.addedTags() | toQList); removedTags = tagCache->retrieve(msg.removedTags() | toQList); } Item::List its = items; for (auto it = its.begin(), end = its.end(); it != end; ++it) { if (msg.operation() == Protocol::ItemChangeNotification::Move) { it->setParentCollection(colDest); } else { it->setParentCollection(col); } } bool handled = false; switch (msg.operation()) { case Protocol::ItemChangeNotification::Add: return emitToListeners(&Monitor::itemAdded, its.first(), col); case Protocol::ItemChangeNotification::Modify: return emitToListeners(&Monitor::itemChanged, its.first(), msg.itemParts()); case Protocol::ItemChangeNotification::ModifyFlags: return emitToListeners(&Monitor::itemsFlagsChanged, its, msg.addedFlags(), msg.removedFlags()); case Protocol::ItemChangeNotification::Move: handled |= emitToListeners(&Monitor::itemMoved, its.first(), col, colDest); handled |= emitToListeners(&Monitor::itemsMoved, its, col, colDest); return handled; case Protocol::ItemChangeNotification::Remove: handled |= emitToListeners(&Monitor::itemRemoved, its.first()); handled |= emitToListeners(&Monitor::itemsRemoved, its); return handled; case Protocol::ItemChangeNotification::Link: handled |= emitToListeners(&Monitor::itemLinked, its.first(), col); handled |= emitToListeners(&Monitor::itemsLinked, its, col); return handled; case Protocol::ItemChangeNotification::Unlink: handled |= emitToListeners(&Monitor::itemUnlinked, its.first(), col); handled |= emitToListeners(&Monitor::itemsUnlinked, its, col); return handled; case Protocol::ItemChangeNotification::ModifyTags: return emitToListeners(&Monitor::itemsTagsChanged, its, addedTags | toQSet, removedTags | toQSet); case Protocol::ItemChangeNotification::ModifyRelations: return emitToListeners(&Monitor::itemsRelationsChanged, its, addedRelations, removedRelations); default: qCDebug(AKONADICORE_LOG) << "Unknown operation type" << msg.operation() << "in item change notification"; return false; } } bool MonitorPrivate::emitCollectionNotification(const Protocol::CollectionChangeNotification &msg, const Collection &col, const Collection &par, const Collection &dest) { Collection parent = par; if (!parent.isValid()) { parent = Collection(msg.parentCollection()); } Collection destination = dest; if (!destination.isValid()) { destination = Collection(msg.parentDestCollection()); } Collection collection = col; Q_ASSERT(collection.isValid()); if (!collection.isValid()) { qCWarning(AKONADICORE_LOG) << "Failed to get valid Collection for a Collection change!"; return true; // prevent Monitor disconnecting from a signal } if (msg.operation() == Protocol::CollectionChangeNotification::Move) { collection.setParentCollection(destination); } else { collection.setParentCollection(parent); } bool handled = false; switch (msg.operation()) { case Protocol::CollectionChangeNotification::Add: return emitToListeners(&Monitor::collectionAdded, collection, parent); case Protocol::CollectionChangeNotification::Modify: handled |= emitToListeners(QOverload::of(&Monitor::collectionChanged), collection); handled |= emitToListeners(QOverload &>::of(&Monitor::collectionChanged), collection, msg.changedParts()); return handled; case Protocol::CollectionChangeNotification::Move: return emitToListeners(&Monitor::collectionMoved, collection, parent, destination); case Protocol::CollectionChangeNotification::Remove: return emitToListeners(&Monitor::collectionRemoved, collection); case Protocol::CollectionChangeNotification::Subscribe: // ### why?? return !monitorAll && emitToListeners(&Monitor::collectionSubscribed, collection, parent); case Protocol::CollectionChangeNotification::Unsubscribe: // ### why?? return !monitorAll && emitToListeners(&Monitor::collectionUnsubscribed, collection); default: qCDebug(AKONADICORE_LOG) << "Unknown operation type" << msg.operation() << "in collection change notification"; return false; } } bool MonitorPrivate::emitTagNotification(const Protocol::TagChangeNotification &msg, const Tag &tag) { Q_UNUSED(msg); switch (msg.operation()) { case Protocol::TagChangeNotification::Add: return emitToListeners(&Monitor::tagAdded, tag); case Protocol::TagChangeNotification::Modify: return emitToListeners(&Monitor::tagChanged, tag); case Protocol::TagChangeNotification::Remove: return emitToListeners(&Monitor::tagRemoved, tag); default: qCDebug(AKONADICORE_LOG) << "Unknown operation type" << msg.operation() << "in tag change notification"; return false; } } bool MonitorPrivate::emitRelationNotification(const Protocol::RelationChangeNotification &msg, const Relation &relation) { if (!relation.isValid()) { return false; } switch (msg.operation()) { case Protocol::RelationChangeNotification::Add: return emitToListeners(&Monitor::relationAdded, relation); case Protocol::RelationChangeNotification::Remove: return emitToListeners(&Monitor::relationRemoved, relation); default: qCDebug(AKONADICORE_LOG) << "Unknown operation type" << msg.operation() << "in tag change notification"; return false; } } bool MonitorPrivate::emitSubscriptionChangeNotification(const Protocol::SubscriptionChangeNotification &msg, const Akonadi::NotificationSubscriber &subscriber) { if (!subscriber.isValid()) { return false; } switch (msg.operation()) { case Protocol::SubscriptionChangeNotification::Add: return emitToListeners(&Monitor::notificationSubscriberAdded, subscriber); case Protocol::SubscriptionChangeNotification::Modify: return emitToListeners(&Monitor::notificationSubscriberChanged, subscriber); case Protocol::SubscriptionChangeNotification::Remove: return emitToListeners(&Monitor::notificationSubscriberRemoved, subscriber); default: qCDebug(AKONADICORE_LOG) << "Unknown operation type" << msg.operation() << "in subscription change notification"; return false; } } bool MonitorPrivate::emitDebugChangeNotification(const Protocol::DebugChangeNotification &msg, const ChangeNotification &ntf) { Q_UNUSED(msg); if (!ntf.isValid()) { return false; } return emitToListeners(&Monitor::debugNotification, ntf); } void MonitorPrivate::invalidateCaches(const Protocol::ChangeNotificationPtr &msg) { // remove invalidates // modify removes the cache entry, as we need to re-fetch // And subscription modify the visibility of the collection by the collectionFetchScope. switch (msg->type()) { case Protocol::Command::CollectionChangeNotification: { const auto &colNtf = Protocol::cmdCast(msg); switch (colNtf.operation()) { case Protocol::CollectionChangeNotification::Modify: case Protocol::CollectionChangeNotification::Move: case Protocol::CollectionChangeNotification::Subscribe: collectionCache->update(colNtf.collection().id(), mCollectionFetchScope); break; case Protocol::CollectionChangeNotification::Remove: collectionCache->invalidate(colNtf.collection().id()); break; default: break; } } break; case Protocol::Command::ItemChangeNotification: { const auto &itemNtf = Protocol::cmdCast(msg); switch (itemNtf.operation()) { case Protocol::ItemChangeNotification::Modify: case Protocol::ItemChangeNotification::ModifyFlags: case Protocol::ItemChangeNotification::ModifyTags: case Protocol::ItemChangeNotification::ModifyRelations: case Protocol::ItemChangeNotification::Move: itemCache->update(Protocol::ChangeNotification::itemsToUids(itemNtf.items()), mItemFetchScope); break; case Protocol::ItemChangeNotification::Remove: itemCache->invalidate(Protocol::ChangeNotification::itemsToUids(itemNtf.items())); break; default: break; } } break; case Protocol::Command::TagChangeNotification: { const auto &tagNtf = Protocol::cmdCast(msg); switch (tagNtf.operation()) { case Protocol::TagChangeNotification::Modify: tagCache->update({ tagNtf.tag().id() }, mTagFetchScope); break; case Protocol::TagChangeNotification::Remove: tagCache->invalidate({ tagNtf.tag().id() }); break; default: break; } } break; default: break; } } void MonitorPrivate::invalidateCache(const Collection &col) { collectionCache->update(col.id(), mCollectionFetchScope); } void MonitorPrivate::ref(Collection::Id id) { if (!refCountMap.contains(id)) { refCountMap.insert(id, 0); } ++refCountMap[id]; if (m_buffer.isBuffered(id)) { m_buffer.purge(id); } } Akonadi::Collection::Id MonitorPrivate::deref(Collection::Id id) { Q_ASSERT(refCountMap.contains(id)); if (--refCountMap[id] == 0) { refCountMap.remove(id); return m_buffer.buffer(id); } return -1; } void MonitorPrivate::PurgeBuffer::purge(Collection::Id id) { m_buffer.removeOne(id); } Akonadi::Collection::Id MonitorPrivate::PurgeBuffer::buffer(Collection::Id id) { // Ensure that we don't put a duplicate @p id into the buffer. purge(id); Collection::Id bumpedId = -1; if (m_buffer.size() == MAXBUFFERSIZE) { bumpedId = m_buffer.dequeue(); purge(bumpedId); } m_buffer.enqueue(id); return bumpedId; } int MonitorPrivate::PurgeBuffer::buffersize() { return MAXBUFFERSIZE; } bool MonitorPrivate::isMonitored(Collection::Id colId) const { if (!useRefCounting) { return true; } return refCountMap.contains(colId) || m_buffer.isBuffered(colId); } void MonitorPrivate::notifyCollectionStatisticsWatchers(Collection::Id collection, const QByteArray &resource) { if (collection > 0 && (monitorAll || isCollectionMonitored(collection) || resources.contains(resource))) { recentlyChangedCollections.insert(collection); if (!statisticsCompressionTimer.isActive()) { statisticsCompressionTimer.start(); } } } Protocol::ModifySubscriptionCommand::ChangeType MonitorPrivate::monitorTypeToProtocol(Monitor::Type type) { switch (type) { case Monitor::Collections: return Protocol::ModifySubscriptionCommand::CollectionChanges; case Monitor::Items: return Protocol::ModifySubscriptionCommand::ItemChanges; case Monitor::Tags: return Protocol::ModifySubscriptionCommand::TagChanges; case Monitor::Relations: return Protocol::ModifySubscriptionCommand::RelationChanges; case Monitor::Subscribers: return Protocol::ModifySubscriptionCommand::SubscriptionChanges; case Monitor::Notifications: return Protocol::ModifySubscriptionCommand::ChangeNotifications; default: Q_ASSERT(false); return Protocol::ModifySubscriptionCommand::NoType; } } void MonitorPrivate::updateListeners(const QMetaMethod &signal, ListenerAction action) { #define UPDATE_LISTENERS(sig) \ if (signal == QMetaMethod::fromSignal(sig)) { \ updateListener(sig, action); \ return; \ } UPDATE_LISTENERS(&Monitor::itemChanged) UPDATE_LISTENERS(&Monitor::itemChanged) UPDATE_LISTENERS(&Monitor::itemsFlagsChanged) UPDATE_LISTENERS(&Monitor::itemsTagsChanged) UPDATE_LISTENERS(&Monitor::itemsRelationsChanged) UPDATE_LISTENERS(&Monitor::itemMoved) UPDATE_LISTENERS(&Monitor::itemsMoved) UPDATE_LISTENERS(&Monitor::itemAdded) UPDATE_LISTENERS(&Monitor::itemRemoved) UPDATE_LISTENERS(&Monitor::itemsRemoved) UPDATE_LISTENERS(&Monitor::itemLinked) UPDATE_LISTENERS(&Monitor::itemsLinked) UPDATE_LISTENERS(&Monitor::itemUnlinked) UPDATE_LISTENERS(&Monitor::itemsUnlinked) UPDATE_LISTENERS(&Monitor::collectionAdded) UPDATE_LISTENERS(QOverload::of(&Monitor::collectionChanged)) UPDATE_LISTENERS((QOverload &>::of(&Monitor::collectionChanged))) UPDATE_LISTENERS(&Monitor::collectionMoved) UPDATE_LISTENERS(&Monitor::collectionRemoved) UPDATE_LISTENERS(&Monitor::collectionSubscribed) UPDATE_LISTENERS(&Monitor::collectionUnsubscribed) UPDATE_LISTENERS(&Monitor::collectionStatisticsChanged) UPDATE_LISTENERS(&Monitor::tagAdded) UPDATE_LISTENERS(&Monitor::tagChanged) UPDATE_LISTENERS(&Monitor::tagRemoved) UPDATE_LISTENERS(&Monitor::relationAdded) UPDATE_LISTENERS(&Monitor::relationRemoved) UPDATE_LISTENERS(&Monitor::notificationSubscriberAdded) UPDATE_LISTENERS(&Monitor::notificationSubscriberChanged) UPDATE_LISTENERS(&Monitor::notificationSubscriberRemoved) UPDATE_LISTENERS(&Monitor::debugNotification) #undef UPDATE_LISTENERS } // @endcond diff --git a/src/core/partfetcher.cpp b/src/core/partfetcher.cpp index adc52a40d..fb8c5f47f 100644 --- a/src/core/partfetcher.cpp +++ b/src/core/partfetcher.cpp @@ -1,184 +1,184 @@ /* Copyright (c) 2009 Stephen Kelly 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 "partfetcher.h" #include "entitytreemodel.h" #include "session.h" #include "itemfetchjob.h" #include "itemfetchscope.h" #include Q_DECLARE_METATYPE(QSet) using namespace Akonadi; namespace Akonadi { class PartFetcherPrivate { PartFetcherPrivate(PartFetcher *partFetcher, const QModelIndex &index, const QByteArray &partName) : m_persistentIndex(index) , m_partName(partName) , q_ptr(partFetcher) { } void fetchJobDone(KJob *job); void modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); QPersistentModelIndex m_persistentIndex; QByteArray m_partName; Item m_item; Q_DECLARE_PUBLIC(PartFetcher) PartFetcher *q_ptr; }; } void PartFetcherPrivate::fetchJobDone(KJob *job) { Q_Q(PartFetcher); if (job->error()) { q->setError(KJob::UserDefinedError); q->setErrorText(i18n("Unable to fetch item for index")); q->emitResult(); return; } ItemFetchJob *fetchJob = qobject_cast(job); const Item::List list = fetchJob->items(); Q_ASSERT(list.size() == 1); // If m_persistentIndex comes from a selection proxy model, it could become // invalid if the user clicks around a lot. if (!m_persistentIndex.isValid()) { q->setError(KJob::UserDefinedError); q->setErrorText(i18n("Index is no longer available")); q->emitResult(); return; } const QSet loadedParts = m_persistentIndex.data(EntityTreeModel::LoadedPartsRole).value >(); Q_ASSERT(!loadedParts.contains(m_partName)); Item item = m_persistentIndex.data(EntityTreeModel::ItemRole).value(); item.apply(list.at(0)); QAbstractItemModel *model = const_cast(m_persistentIndex.model()); Q_ASSERT(model); QVariant itemVariant = QVariant::fromValue(item); model->setData(m_persistentIndex, itemVariant, EntityTreeModel::ItemRole); m_item = item; - emit q->emitResult(); + Q_EMIT q->emitResult(); } PartFetcher::PartFetcher(const QModelIndex &index, const QByteArray &partName, QObject *parent) : KJob(parent) , d_ptr(new PartFetcherPrivate(this, index, partName)) { } PartFetcher::~PartFetcher() { delete d_ptr; } void PartFetcher::start() { Q_D(PartFetcher); const QModelIndex index = d->m_persistentIndex; const QSet loadedParts = index.data(EntityTreeModel::LoadedPartsRole).value >(); if (loadedParts.contains(d->m_partName)) { d->m_item = d->m_persistentIndex.data(EntityTreeModel::ItemRole).value(); emitResult(); return; } const QSet availableParts = index.data(EntityTreeModel::AvailablePartsRole).value >(); if (!availableParts.contains(d->m_partName)) { setError(UserDefinedError); setErrorText(i18n("Payload part '%1' is not available for this index", QString::fromLatin1(d->m_partName))); emitResult(); return; } Akonadi::Session *session = qobject_cast(qvariant_cast(index.data(EntityTreeModel::SessionRole))); if (!session) { setError(UserDefinedError); setErrorText(i18n("No session available for this index")); emitResult(); return; } const Akonadi::Item item = index.data(EntityTreeModel::ItemRole).value(); if (!item.isValid()) { setError(UserDefinedError); setErrorText(i18n("No item available for this index")); emitResult(); return; } ItemFetchScope scope; scope.fetchPayloadPart(d->m_partName); ItemFetchJob *itemFetchJob = new Akonadi::ItemFetchJob(item, session); itemFetchJob->setFetchScope(scope); connect(itemFetchJob, SIGNAL(result(KJob*)), this, SLOT(fetchJobDone(KJob*))); } QModelIndex PartFetcher::index() const { Q_D(const PartFetcher); return d->m_persistentIndex; } QByteArray PartFetcher::partName() const { Q_D(const PartFetcher); return d->m_partName; } Item PartFetcher::item() const { Q_D(const PartFetcher); return d->m_item; } #include "moc_partfetcher.cpp" diff --git a/src/core/servermanager.cpp b/src/core/servermanager.cpp index 8ef4b6d10..9421f96ed 100644 --- a/src/core/servermanager.cpp +++ b/src/core/servermanager.cpp @@ -1,405 +1,405 @@ /* Copyright (c) 2008 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 "servermanager.h" #include "servermanager_p.h" #include "agenttype.h" #include "agentmanager.h" #include "KDBusConnectionPool" #include "session_p.h" #include "firstrun_p.h" #include "akonadicore_debug.h" #include #include #include "private/protocol_p.h" #include "private/standarddirs_p.h" #include "private/dbus_p.h" #include "private/instance_p.h" #include #include #include #include #include #include #include #include using namespace Akonadi; class Akonadi::ServerManagerPrivate { public: ServerManagerPrivate() : instance(new ServerManager(this)) , mState(ServerManager::NotRunning) , mSafetyTimer(new QTimer) { mState = instance->state(); mSafetyTimer->setSingleShot(true); mSafetyTimer->setInterval(30000); QObject::connect(mSafetyTimer.data(), &QTimer::timeout, instance, [this]() { timeout();}); if (mState == ServerManager::Running && Internal::clientType() == Internal::User && !ServerManager::hasInstanceIdentifier()) { mFirstRunner = new Firstrun(instance); } } ~ServerManagerPrivate() { delete instance; } void serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner) { if (name == ServerManager::serviceName(ServerManager::ControlLock) && !oldOwner.isEmpty() && newOwner.isEmpty()) { // Control.Lock has disappeared during startup, which means that akonadi_control // has terminated, most probably because it was not able to start akonadiserver // process. Don't wait 30 seconds for sefetyTimeout, but go into Broken state // immediately. if (mState == ServerManager::Starting) { setState(ServerManager::Broken); return; } } serverProtocolVersion = -1; checkStatusChanged(); } void checkStatusChanged() { setState(instance->state()); } void setState(ServerManager::State state) { if (mState != state) { mState = state; - emit instance->stateChanged(state); + Q_EMIT instance->stateChanged(state); if (state == ServerManager::Running) { - emit instance->started(); + Q_EMIT instance->started(); if (!mFirstRunner && Internal::clientType() == Internal::User && !ServerManager::hasInstanceIdentifier()) { mFirstRunner = new Firstrun(instance); } } else if (state == ServerManager::NotRunning || state == ServerManager::Broken) { - emit instance->stopped(); + Q_EMIT instance->stopped(); } if (state == ServerManager::Starting || state == ServerManager::Stopping) { QMetaObject::invokeMethod(mSafetyTimer.data(), QOverload<>::of(&QTimer::start), Qt::QueuedConnection); // in case we are in a different thread } else { QMetaObject::invokeMethod(mSafetyTimer.data(), &QTimer::stop, Qt::QueuedConnection); // in case we are in a different thread } } } void timeout() { if (mState == ServerManager::Starting || mState == ServerManager::Stopping) { setState(ServerManager::Broken); } } ServerManager *instance = nullptr; static int serverProtocolVersion; static uint generation; ServerManager::State mState; QScopedPointer mSafetyTimer; Firstrun *mFirstRunner = nullptr; static Internal::ClientType clientType; QString mBrokenReason; }; int ServerManagerPrivate::serverProtocolVersion = -1; uint ServerManagerPrivate::generation = 0; Internal::ClientType ServerManagerPrivate::clientType = Internal::User; Q_GLOBAL_STATIC(ServerManagerPrivate, sInstance) ServerManager::ServerManager(ServerManagerPrivate *dd) : d(dd) { Kdelibs4ConfigMigrator migrate(QStringLiteral("servermanager")); migrate.setConfigFiles(QStringList() << QStringLiteral("akonadi-firstrunrc")); migrate.migrate(); qRegisterMetaType(); QDBusServiceWatcher *watcher = new QDBusServiceWatcher(ServerManager::serviceName(ServerManager::Server), KDBusConnectionPool::threadConnection(), QDBusServiceWatcher::WatchForOwnerChange, this); watcher->addWatchedService(ServerManager::serviceName(ServerManager::Control)); watcher->addWatchedService(ServerManager::serviceName(ServerManager::ControlLock)); watcher->addWatchedService(ServerManager::serviceName(ServerManager::UpgradeIndicator)); // this (and also the two connects below) are queued so that they trigger after AgentManager is done loading // the current agent types and instances // this ensures the invariant of AgentManager reporting a consistent state if ServerManager::state() == Running // that's the case with direct connections as well, but only after you enter the event loop once connect(watcher, SIGNAL(serviceOwnerChanged(QString,QString,QString)), this, SLOT(serviceOwnerChanged(QString,QString,QString)), Qt::QueuedConnection); // AgentManager is dangerous to use for agents themselves if (Internal::clientType() != Internal::User) { return; } connect(AgentManager::self(), SIGNAL(typeAdded(Akonadi::AgentType)), SLOT(checkStatusChanged()), Qt::QueuedConnection); connect(AgentManager::self(), SIGNAL(typeRemoved(Akonadi::AgentType)), SLOT(checkStatusChanged()), Qt::QueuedConnection); } ServerManager *Akonadi::ServerManager::self() { return sInstance->instance; } bool ServerManager::start() { const bool controlRegistered = KDBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Control)); const bool serverRegistered = KDBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Server)); if (controlRegistered && serverRegistered) { return true; } const bool controlLockRegistered = KDBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::ControlLock)); if (controlLockRegistered || controlRegistered) { qCDebug(AKONADICORE_LOG) << "Akonadi server is already starting up"; sInstance->setState(Starting); return true; } qCDebug(AKONADICORE_LOG) << "executing akonadi_control"; QStringList args; if (hasInstanceIdentifier()) { args << QStringLiteral("--instance") << instanceIdentifier(); } const bool ok = QProcess::startDetached(QStringLiteral("akonadi_control"), args); if (!ok) { qCWarning(AKONADICORE_LOG) << "Unable to execute akonadi_control, falling back to D-Bus auto-launch"; QDBusReply reply = KDBusConnectionPool::threadConnection().interface()->startService(ServerManager::serviceName(ServerManager::Control)); if (!reply.isValid()) { qCDebug(AKONADICORE_LOG) << "Akonadi server could not be started via D-Bus either: " << reply.error().message(); return false; } } sInstance->setState(Starting); return true; } bool ServerManager::stop() { QDBusInterface iface(ServerManager::serviceName(ServerManager::Control), QStringLiteral("/ControlManager"), QStringLiteral("org.freedesktop.Akonadi.ControlManager")); if (!iface.isValid()) { return false; } iface.call(QDBus::NoBlock, QStringLiteral("shutdown")); sInstance->setState(Stopping); return true; } void ServerManager::showSelfTestDialog(QWidget *parent) { Q_UNUSED(parent); QProcess::startDetached(QStringLiteral("akonadiselftest")); } bool ServerManager::isRunning() { return state() == Running; } ServerManager::State ServerManager::state() { ServerManager::State previousState = NotRunning; if (sInstance.exists()) { // be careful, this is called from the ServerManager::Private ctor, so using sInstance unprotected can cause infinite recursion previousState = sInstance->mState; sInstance->mBrokenReason.clear(); } const bool serverUpgrading = KDBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::UpgradeIndicator)); if (serverUpgrading) { return Upgrading; } const bool controlRegistered = KDBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Control)); const bool serverRegistered = KDBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::Server)); if (controlRegistered && serverRegistered) { // check if the server protocol is recent enough if (sInstance.exists()) { if (Internal::serverProtocolVersion() >= 0 && Internal::serverProtocolVersion() != Protocol::version()) { sInstance->mBrokenReason = i18n("The Akonadi server protocol version differs from the protocol version used by this application.\n" "If you recently updated your system please log out and back in to make sure all applications use the " "correct protocol version."); return Broken; } } // AgentManager is dangerous to use for agents themselves if (Internal::clientType() == Internal::User) { // besides the running server processes we also need at least one resource to be operational const AgentType::List agentTypes = AgentManager::self()->types(); for (const AgentType &type : agentTypes) { if (type.capabilities().contains(QLatin1String("Resource"))) { return Running; } } if (sInstance.exists()) { sInstance->mBrokenReason = i18n("There are no Akonadi Agents available. Please verify your KDE PIM installation."); } return Broken; } else { return Running; } } const bool controlLockRegistered = KDBusConnectionPool::threadConnection().interface()->isServiceRegistered(ServerManager::serviceName(ServerManager::ControlLock)); if (controlLockRegistered || controlRegistered) { qCDebug(AKONADICORE_LOG) << "Akonadi server is already starting up"; if (previousState == Running) { return NotRunning; // we don't know if it's starting or stopping, probably triggered by someone else } return previousState; } if (serverRegistered) { qCWarning(AKONADICORE_LOG) << "Akonadi server running without control process!"; return Broken; } if (previousState == Starting) { // valid case where nothing is running (yet) return previousState; } return NotRunning; } QString ServerManager::brokenReason() { if (sInstance.exists()) { return sInstance->mBrokenReason; } return QString(); } QString ServerManager::instanceIdentifier() { return Instance::identifier(); } bool ServerManager::hasInstanceIdentifier() { return Instance::hasIdentifier(); } QString ServerManager::serviceName(ServerManager::ServiceType serviceType) { switch (serviceType) { case Server: return DBus::serviceName(DBus::Server); case Control: return DBus::serviceName(DBus::Control); case ControlLock: return DBus::serviceName(DBus::ControlLock); case UpgradeIndicator: return DBus::serviceName(DBus::UpgradeIndicator); } Q_ASSERT(!"WTF?"); return QString(); } QString ServerManager::agentServiceName(ServiceAgentType agentType, const QString &identifier) { switch (agentType) { case Agent: return DBus::agentServiceName(identifier, DBus::Agent); case Resource: return DBus::agentServiceName(identifier, DBus::Resource); case Preprocessor: return DBus::agentServiceName(identifier, DBus::Preprocessor); } Q_ASSERT(!"WTF?"); return QString(); } QString ServerManager::serverConfigFilePath(OpenMode openMode) { return StandardDirs::serverConfigFile( openMode == Akonadi::ServerManager::ReadOnly ? StandardDirs::ReadOnly : StandardDirs::ReadWrite); } QString ServerManager::agentConfigFilePath(const QString &identifier) { return StandardDirs::agentConfigFile(identifier); } QString ServerManager::addNamespace(const QString &string) { if (Instance::hasIdentifier()) { return string % QLatin1Char('_') % Instance::identifier(); } return string; } uint ServerManager::generation() { return Internal::generation(); } int Internal::serverProtocolVersion() { return ServerManagerPrivate::serverProtocolVersion; } void Internal::setServerProtocolVersion(int version) { ServerManagerPrivate::serverProtocolVersion = version; if (sInstance.exists()) { sInstance->checkStatusChanged(); } } uint Internal::generation() { return ServerManagerPrivate::generation; } void Internal::setGeneration(uint generation) { ServerManagerPrivate::generation = generation; } Internal::ClientType Internal::clientType() { return ServerManagerPrivate::clientType; } void Internal::setClientType(ClientType type) { ServerManagerPrivate::clientType = type; } #include "moc_servermanager.cpp" diff --git a/src/core/specialcollections.cpp b/src/core/specialcollections.cpp index fee345b4a..08487332c 100644 --- a/src/core/specialcollections.cpp +++ b/src/core/specialcollections.cpp @@ -1,280 +1,280 @@ /* Copyright (c) 2009 Constantin Berzan 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 "specialcollections.h" #include "akonadicore_debug.h" #include "specialcollections_p.h" #include "specialcollectionattribute.h" #include "agentinstance.h" #include "agentmanager.h" #include "collectionmodifyjob.h" #include "collectionfetchjob.h" #include "monitor.h" #include "collectionfetchscope.h" #include #include #include using namespace Akonadi; SpecialCollectionsPrivate::SpecialCollectionsPrivate(KCoreConfigSkeleton *settings, SpecialCollections *qq) : q(qq) , mSettings(settings) , mBatchMode(false) { mMonitor = new Monitor(q); mMonitor->setObjectName(QStringLiteral("SpecialCollectionsMonitor")); mMonitor->fetchCollectionStatistics(true); /// In order to know if items are added or deleted /// from one of our specialcollection folders, /// we have to watch all mail item add/move/delete notifications /// and check for the parent to see if it is one we care about QObject::connect(mMonitor, &Monitor::collectionRemoved, q, [this](const Akonadi::Collection &col) { collectionRemoved(col); }); QObject::connect(mMonitor, &Monitor::collectionStatisticsChanged, q, [this](Akonadi::Collection::Id id, const Akonadi::CollectionStatistics &statistics) { collectionStatisticsChanged(id, statistics); }); } SpecialCollectionsPrivate::~SpecialCollectionsPrivate() { } QString SpecialCollectionsPrivate::defaultResourceId() const { if (mDefaultResourceId.isEmpty()) { mSettings->load(); const KConfigSkeletonItem *item = mSettings->findItem(QStringLiteral("DefaultResourceId")); Q_ASSERT(item); mDefaultResourceId = item->property().toString(); } return mDefaultResourceId; } void SpecialCollectionsPrivate::emitChanged(const QString &resourceId) { if (mBatchMode) { mToEmitChangedFor.insert(resourceId); } else { qCDebug(AKONADICORE_LOG) << "Emitting changed for" << resourceId; const AgentInstance agentInstance = AgentManager::self()->instance(resourceId); - emit q->collectionsChanged(agentInstance); + Q_EMIT q->collectionsChanged(agentInstance); // first compare with local value then with config value (which also updates the local value) if (resourceId == mDefaultResourceId || resourceId == defaultResourceId()) { qCDebug(AKONADICORE_LOG) << "Emitting defaultFoldersChanged."; - emit q->defaultCollectionsChanged(); + Q_EMIT q->defaultCollectionsChanged(); } } } void SpecialCollectionsPrivate::collectionRemoved(const Collection &collection) { qCDebug(AKONADICORE_LOG) << "Collection" << collection.id() << "resource" << collection.resource(); if (mFoldersForResource.contains(collection.resource())) { // Retrieve the list of special folders for the resource the collection belongs to QHash &folders = mFoldersForResource[collection.resource()]; { QMutableHashIterator it(folders); while (it.hasNext()) { it.next(); if (it.value() == collection) { // The collection to be removed is a special folder it.remove(); emitChanged(collection.resource()); } } } if (folders.isEmpty()) { // This resource has no more folders, so remove it completely. mFoldersForResource.remove(collection.resource()); } } } void SpecialCollectionsPrivate::collectionStatisticsChanged(Akonadi::Collection::Id collectionId, const Akonadi::CollectionStatistics &statistics) { // need to get the name of the collection in order to be able to check if we are storing it, // but we have the id from the monitor, so fetch the name. Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(Collection(collectionId), Akonadi::CollectionFetchJob::Base); fetchJob->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::None); fetchJob->setProperty("statistics", QVariant::fromValue(statistics)); q->connect(fetchJob, &CollectionFetchJob::result, q, [this](KJob *job) { collectionFetchJobFinished(job); }); } void SpecialCollectionsPrivate::collectionFetchJobFinished(KJob *job) { if (job->error()) { qCWarning(AKONADICORE_LOG) << "Error fetching collection to get name from id for statistics updating in specialcollections!"; return; } const Akonadi::CollectionFetchJob *fetchJob = qobject_cast(job); Q_ASSERT(fetchJob->collections().size() > 0); const Akonadi::Collection collection = fetchJob->collections().at(0); const Akonadi::CollectionStatistics statistics = fetchJob->property("statistics").value(); mFoldersForResource[collection.resource()][collection.name().toUtf8()].setStatistics(statistics); } void SpecialCollectionsPrivate::beginBatchRegister() { Q_ASSERT(!mBatchMode); mBatchMode = true; Q_ASSERT(mToEmitChangedFor.isEmpty()); } void SpecialCollectionsPrivate::endBatchRegister() { Q_ASSERT(mBatchMode); mBatchMode = false; for (const QString &resourceId : qAsConst(mToEmitChangedFor)) { emitChanged(resourceId); } mToEmitChangedFor.clear(); } void SpecialCollectionsPrivate::forgetFoldersForResource(const QString &resourceId) { if (mFoldersForResource.contains(resourceId)) { foreach (const Collection &collection, mFoldersForResource[resourceId]) { mMonitor->setCollectionMonitored(collection, false); } mFoldersForResource.remove(resourceId); emitChanged(resourceId); } } AgentInstance SpecialCollectionsPrivate::defaultResource() const { const QString identifier = defaultResourceId(); return AgentManager::self()->instance(identifier); } SpecialCollections::SpecialCollections(KCoreConfigSkeleton *settings, QObject *parent) : QObject(parent) , d(new SpecialCollectionsPrivate(settings, this)) { } SpecialCollections::~SpecialCollections() { delete d; } bool SpecialCollections::hasCollection(const QByteArray &type, const AgentInstance &instance) const { return d->mFoldersForResource.value(instance.identifier()).contains(type); } Akonadi::Collection SpecialCollections::collection(const QByteArray &type, const AgentInstance &instance) const { return d->mFoldersForResource.value(instance.identifier()).value(type); } void SpecialCollections::setSpecialCollectionType(const QByteArray &type, const Akonadi::Collection &collection) { if (!collection.hasAttribute() || collection.attribute()->collectionType() != type) { Collection attributeCollection(collection); SpecialCollectionAttribute *attribute = attributeCollection.attribute(Collection::AddIfMissing); attribute->setCollectionType(type); new CollectionModifyJob(attributeCollection); } } void SpecialCollections::unsetSpecialCollection(const Akonadi::Collection &collection) { if (collection.hasAttribute()) { Collection attributeCollection(collection); attributeCollection.removeAttribute(); new CollectionModifyJob(attributeCollection); } } bool SpecialCollections::unregisterCollection(const Collection &collection) { if (!collection.isValid()) { qCWarning(AKONADICORE_LOG) << "Invalid collection."; return false; } const QString &resourceId = collection.resource(); if (resourceId.isEmpty()) { qCWarning(AKONADICORE_LOG) << "Collection has empty resourceId."; return false; } unsetSpecialCollection(collection); d->mMonitor->setCollectionMonitored(collection, false); //Remove from list of collection d->collectionRemoved(collection); return true; } bool SpecialCollections::registerCollection(const QByteArray &type, const Collection &collection) { if (!collection.isValid()) { qCWarning(AKONADICORE_LOG) << "Invalid collection."; return false; } const QString &resourceId = collection.resource(); if (resourceId.isEmpty()) { qCWarning(AKONADICORE_LOG) << "Collection has empty resourceId."; return false; } setSpecialCollectionType(type, collection); const Collection oldCollection = d->mFoldersForResource.value(resourceId).value(type); if (oldCollection != collection) { if (oldCollection.isValid()) { d->mMonitor->setCollectionMonitored(oldCollection, false); } d->mMonitor->setCollectionMonitored(collection, true); d->mFoldersForResource[resourceId].insert(type, collection); d->emitChanged(resourceId); } return true; } bool SpecialCollections::hasDefaultCollection(const QByteArray &type) const { return hasCollection(type, d->defaultResource()); } Akonadi::Collection SpecialCollections::defaultCollection(const QByteArray &type) const { return collection(type, d->defaultResource()); } #include "moc_specialcollections.cpp" diff --git a/src/widgets/agentactionmanager.cpp b/src/widgets/agentactionmanager.cpp index 2f4bd1a9c..dcb50ef2e 100644 --- a/src/widgets/agentactionmanager.cpp +++ b/src/widgets/agentactionmanager.cpp @@ -1,365 +1,365 @@ /* Copyright (c) 2010 Tobias Koenig 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 "agentactionmanager.h" #include "agentfilterproxymodel.h" #include "agentinstancecreatejob.h" #include "agentinstancemodel.h" #include "agentmanager.h" #include "agenttypedialog.h" #include "metatypes.h" #include #include #include #include #include #include #include using namespace Akonadi; //@cond PRIVATE static const struct { const char *name; const char *label; const char *icon; int shortcut; const char *slot; } agentActionData[] = { { "akonadi_agentinstance_create", I18N_NOOP("&New Agent Instance..."), "folder-new", 0, SLOT(slotCreateAgentInstance()) }, { "akonadi_agentinstance_delete", I18N_NOOP("&Delete Agent Instance"), "edit-delete", 0, SLOT(slotDeleteAgentInstance()) }, { "akonadi_agentinstance_configure", I18N_NOOP("&Configure Agent Instance"), "configure", 0, SLOT(slotConfigureAgentInstance()) } }; static const int numAgentActionData = sizeof agentActionData / sizeof * agentActionData; static_assert(numAgentActionData == AgentActionManager::LastType, "agentActionData table does not match AgentActionManager types"); /** * @internal */ class Q_DECL_HIDDEN AgentActionManager::Private { public: Private(AgentActionManager *parent) : q(parent) , mActionCollection(nullptr) , mParentWidget(nullptr) , mSelectionModel(nullptr) { mActions.fill(nullptr, AgentActionManager::LastType); setContextText(AgentActionManager::CreateAgentInstance, AgentActionManager::DialogTitle, i18nc("@title:window", "New Agent Instance")); setContextText(AgentActionManager::CreateAgentInstance, AgentActionManager::ErrorMessageText, ki18n("Could not create agent instance: %1")); setContextText(AgentActionManager::CreateAgentInstance, AgentActionManager::ErrorMessageTitle, i18n("Agent instance creation failed")); setContextText(AgentActionManager::DeleteAgentInstance, AgentActionManager::MessageBoxTitle, i18nc("@title:window", "Delete Agent Instance?")); setContextText(AgentActionManager::DeleteAgentInstance, AgentActionManager::MessageBoxText, i18n("Do you really want to delete the selected agent instance?")); } void enableAction(AgentActionManager::Type type, bool enable) { Q_ASSERT(type >= 0 && type < AgentActionManager::LastType); if (QAction *act = mActions[type]) { act->setEnabled(enable); } } void updateActions() { const AgentInstance::List instances = selectedAgentInstances(); const bool createActionEnabled = true; bool deleteActionEnabled = true; bool configureActionEnabled = true; if (instances.isEmpty()) { deleteActionEnabled = false; configureActionEnabled = false; } if (instances.count() == 1) { const AgentInstance instance = instances.first(); if (instance.type().capabilities().contains(QStringLiteral("NoConfig"))) { configureActionEnabled = false; } } enableAction(CreateAgentInstance, createActionEnabled); enableAction(DeleteAgentInstance, deleteActionEnabled); enableAction(ConfigureAgentInstance, configureActionEnabled); - emit q->actionStateUpdated(); + Q_EMIT q->actionStateUpdated(); } AgentInstance::List selectedAgentInstances() const { AgentInstance::List instances; if (!mSelectionModel) { return instances; } const QModelIndexList lstModelIndex = mSelectionModel->selectedRows(); for (const QModelIndex &index : lstModelIndex ) { const AgentInstance instance = index.data(AgentInstanceModel::InstanceRole).value(); if (instance.isValid()) { instances << instance; } } return instances; } void slotCreateAgentInstance() { QPointer dlg(new Akonadi::AgentTypeDialog(mParentWidget)); dlg->setWindowTitle(contextText(AgentActionManager::CreateAgentInstance, AgentActionManager::DialogTitle)); for (const QString &mimeType : qAsConst(mMimeTypeFilter)) { dlg->agentFilterProxyModel()->addMimeTypeFilter(mimeType); } for (const QString &capability : qAsConst(mCapabilityFilter)) { dlg->agentFilterProxyModel()->addCapabilityFilter(capability); } if (dlg->exec() == QDialog::Accepted) { const AgentType agentType = dlg->agentType(); if (agentType.isValid()) { AgentInstanceCreateJob *job = new AgentInstanceCreateJob(agentType, q); q->connect(job, SIGNAL(result(KJob*)), SLOT(slotAgentInstanceCreationResult(KJob*))); job->configure(mParentWidget); job->start(); } } delete dlg; } void slotDeleteAgentInstance() { const AgentInstance::List instances = selectedAgentInstances(); if (!instances.isEmpty()) { if (KMessageBox::questionYesNo( mParentWidget, contextText(AgentActionManager::DeleteAgentInstance, AgentActionManager::MessageBoxText), contextText(AgentActionManager::DeleteAgentInstance, AgentActionManager::MessageBoxTitle), KStandardGuiItem::del(), KStandardGuiItem::cancel(), QString(), KMessageBox::Dangerous) == KMessageBox::Yes) { for (const AgentInstance &instance : instances) { AgentManager::self()->removeInstance(instance); } } } } void slotConfigureAgentInstance() { AgentInstance::List instances = selectedAgentInstances(); if (instances.isEmpty()) { return; } instances.first().configure(mParentWidget); } void slotAgentInstanceCreationResult(KJob *job) { if (job->error()) { KMessageBox::error( mParentWidget, contextText(AgentActionManager::CreateAgentInstance, AgentActionManager::ErrorMessageText).arg(job->errorString()), contextText(AgentActionManager::CreateAgentInstance, AgentActionManager::ErrorMessageTitle)); } } void setContextText(AgentActionManager::Type type, AgentActionManager::TextContext context, const QString &data) { mContextTexts[type].insert(context, data); } void setContextText(AgentActionManager::Type type, AgentActionManager::TextContext context, const KLocalizedString &data) { mContextTexts[type].insert(context, data.toString()); } QString contextText(AgentActionManager::Type type, AgentActionManager::TextContext context) const { return mContextTexts[type].value(context); } AgentActionManager *q = nullptr; KActionCollection *mActionCollection = nullptr; QWidget *mParentWidget = nullptr; QItemSelectionModel *mSelectionModel = nullptr; QVector mActions; QStringList mMimeTypeFilter; QStringList mCapabilityFilter; typedef QHash ContextTexts; QHash mContextTexts; }; //@endcond AgentActionManager::AgentActionManager(KActionCollection *actionCollection, QWidget *parent) : QObject(parent) , d(new Private(this)) { d->mParentWidget = parent; d->mActionCollection = actionCollection; } AgentActionManager::~AgentActionManager() { delete d; } void AgentActionManager::setSelectionModel(QItemSelectionModel *selectionModel) { d->mSelectionModel = selectionModel; connect(selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(updateActions())); } void AgentActionManager::setMimeTypeFilter(const QStringList &mimeTypes) { d->mMimeTypeFilter = mimeTypes; } void AgentActionManager::setCapabilityFilter(const QStringList &capabilities) { d->mCapabilityFilter = capabilities; } QAction *AgentActionManager::createAction(Type type) { Q_ASSERT(type >= 0 && type < LastType); Q_ASSERT(agentActionData[type].name); if (QAction *act = d->mActions[type]) { return act; } QAction *action = new QAction(d->mParentWidget); action->setText(i18n(agentActionData[type].label)); if (agentActionData[type].icon) { action->setIcon(QIcon::fromTheme(QString::fromLatin1(agentActionData[type].icon))); } action->setShortcut(agentActionData[type].shortcut); if (agentActionData[type].slot) { connect(action, SIGNAL(triggered()), agentActionData[type].slot); } d->mActionCollection->addAction(QString::fromLatin1(agentActionData[type].name), action); d->mActions[type] = action; d->updateActions(); return action; } void AgentActionManager::createAllActions() { for (int type = 0; type < LastType; ++type) { createAction(static_cast(type)); } } QAction *AgentActionManager::action(Type type) const { Q_ASSERT(type >= 0 && type < LastType); return d->mActions[type]; } void AgentActionManager::interceptAction(Type type, bool intercept) { Q_ASSERT(type >= 0 && type < LastType); const QAction *action = d->mActions[type]; if (!action) { return; } if (intercept) { disconnect(action, SIGNAL(triggered()), this, agentActionData[type].slot); } else { connect(action, SIGNAL(triggered()), agentActionData[type].slot); } } AgentInstance::List AgentActionManager::selectedAgentInstances() const { return d->selectedAgentInstances(); } void AgentActionManager::setContextText(Type type, TextContext context, const QString &text) { d->setContextText(type, context, text); } void AgentActionManager::setContextText(Type type, TextContext context, const KLocalizedString &text) { d->setContextText(type, context, text); } #include "moc_agentactionmanager.cpp" diff --git a/src/widgets/agentinstancewidget.cpp b/src/widgets/agentinstancewidget.cpp index 240f1e200..579a11e8f 100644 --- a/src/widgets/agentinstancewidget.cpp +++ b/src/widgets/agentinstancewidget.cpp @@ -1,302 +1,302 @@ /* Copyright (c) 2006-2008 Tobias Koenig 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 "agentinstancewidget.h" #include "agentfilterproxymodel.h" #include "agentinstance.h" #include "agentinstancemodel.h" #include #include #include #include #include #include namespace Akonadi { namespace Internal { static void iconsEarlyCleanup(); struct Icons { Icons() : readyPixmap(QIcon::fromTheme(QStringLiteral("user-online")).pixmap(QSize(16, 16))) , syncPixmap(QIcon::fromTheme(QStringLiteral("network-connect")).pixmap(QSize(16, 16))) , errorPixmap(QIcon::fromTheme(QStringLiteral("dialog-error")).pixmap(QSize(16, 16))) , offlinePixmap(QIcon::fromTheme(QStringLiteral("network-disconnect")).pixmap(QSize(16, 16))) { qAddPostRoutine(iconsEarlyCleanup); } QPixmap readyPixmap, syncPixmap, errorPixmap, offlinePixmap; }; Q_GLOBAL_STATIC(Icons, s_icons) // called as a Qt post routine, to prevent pixmap leaking void iconsEarlyCleanup() { Icons *const ic = s_icons; ic->readyPixmap = ic->syncPixmap = ic->errorPixmap = ic->offlinePixmap = QPixmap(); } static const int s_delegatePaddingSize = 7; /** * @internal */ class AgentInstanceWidgetDelegate : public QAbstractItemDelegate { public: explicit AgentInstanceWidgetDelegate(QObject *parent = nullptr); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; }; } using Akonadi::Internal::AgentInstanceWidgetDelegate; /** * @internal */ class Q_DECL_HIDDEN AgentInstanceWidget::Private { public: Private(AgentInstanceWidget *parent) : mParent(parent) , mView(nullptr) , mModel(nullptr) , proxy(nullptr) { } void currentAgentInstanceChanged(const QModelIndex ¤tIndex, const QModelIndex &previousIndex); void currentAgentInstanceDoubleClicked(const QModelIndex ¤tIndex); void currentAgentInstanceClicked(const QModelIndex ¤tIndex); AgentInstanceWidget *mParent = nullptr; QListView *mView = nullptr; AgentInstanceModel *mModel = nullptr; AgentFilterProxyModel *proxy = nullptr; }; void AgentInstanceWidget::Private::currentAgentInstanceChanged(const QModelIndex ¤tIndex, const QModelIndex &previousIndex) { AgentInstance currentInstance; if (currentIndex.isValid()) { currentInstance = currentIndex.data(AgentInstanceModel::InstanceRole).value(); } AgentInstance previousInstance; if (previousIndex.isValid()) { previousInstance = previousIndex.data(AgentInstanceModel::InstanceRole).value(); } - emit mParent->currentChanged(currentInstance, previousInstance); + Q_EMIT mParent->currentChanged(currentInstance, previousInstance); } void AgentInstanceWidget::Private::currentAgentInstanceDoubleClicked(const QModelIndex ¤tIndex) { AgentInstance currentInstance; if (currentIndex.isValid()) { currentInstance = currentIndex.data(AgentInstanceModel::InstanceRole).value(); } - emit mParent->doubleClicked(currentInstance); + Q_EMIT mParent->doubleClicked(currentInstance); } void AgentInstanceWidget::Private::currentAgentInstanceClicked(const QModelIndex ¤tIndex) { AgentInstance currentInstance; if (currentIndex.isValid()) { currentInstance = currentIndex.data(AgentInstanceModel::InstanceRole).value(); } - emit mParent->clicked(currentInstance); + Q_EMIT mParent->clicked(currentInstance); } AgentInstanceWidget::AgentInstanceWidget(QWidget *parent) : QWidget(parent) , d(new Private(this)) { QHBoxLayout *layout = new QHBoxLayout(this); layout->setMargin(0); d->mView = new QListView(this); d->mView->setContextMenuPolicy(Qt::NoContextMenu); d->mView->setItemDelegate(new Internal::AgentInstanceWidgetDelegate(d->mView)); d->mView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); d->mView->setAlternatingRowColors(true); d->mView->setSelectionMode(QAbstractItemView::ExtendedSelection); layout->addWidget(d->mView); d->mModel = new AgentInstanceModel(this); d->proxy = new AgentFilterProxyModel(this); d->proxy->setSourceModel(d->mModel); d->mView->setModel(d->proxy); d->mView->selectionModel()->setCurrentIndex(d->mView->model()->index(0, 0), QItemSelectionModel::Select); d->mView->scrollTo(d->mView->model()->index(0, 0)); connect(d->mView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(currentAgentInstanceChanged(QModelIndex,QModelIndex))); connect(d->mView, &QListView::doubleClicked, this, [this](const QModelIndex ¤tIndex) { d->currentAgentInstanceDoubleClicked(currentIndex); }); connect(d->mView, SIGNAL(clicked(QModelIndex)), this, SLOT(currentAgentInstanceClicked(QModelIndex))); } AgentInstanceWidget::~AgentInstanceWidget() { delete d; } AgentInstance AgentInstanceWidget::currentAgentInstance() const { QItemSelectionModel *selectionModel = d->mView->selectionModel(); if (!selectionModel) { return AgentInstance(); } QModelIndex index = selectionModel->currentIndex(); if (!index.isValid()) { return AgentInstance(); } return index.data(AgentInstanceModel::InstanceRole).value(); } AgentInstance::List AgentInstanceWidget::selectedAgentInstances() const { AgentInstance::List list; QItemSelectionModel *selectionModel = d->mView->selectionModel(); if (!selectionModel) { return list; } const QModelIndexList indexes = selectionModel->selection().indexes(); list.reserve(indexes.count()); for (const QModelIndex &index : indexes) { list.append(index.data(AgentInstanceModel::InstanceRole).value()); } return list; } QAbstractItemView *AgentInstanceWidget::view() const { return d->mView; } AgentFilterProxyModel *AgentInstanceWidget::agentFilterProxyModel() const { return d->proxy; } AgentInstanceWidgetDelegate::AgentInstanceWidgetDelegate(QObject *parent) : QAbstractItemDelegate(parent) { } void AgentInstanceWidgetDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (!index.isValid()) { return; } QStyle *style = QApplication::style(); style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, nullptr); QIcon icon = index.data(Qt::DecorationRole).value(); const QString name = index.model()->data(index, Qt::DisplayRole).toString(); int status = index.model()->data(index, AgentInstanceModel::StatusRole).toInt(); uint progress = index.model()->data(index, AgentInstanceModel::ProgressRole).toUInt(); QString statusMessage = index.model()->data(index, AgentInstanceModel::StatusMessageRole).toString(); QPixmap statusPixmap; if (!index.data(AgentInstanceModel::OnlineRole).toBool()) { statusPixmap = s_icons->offlinePixmap; } else if (status == AgentInstance::Idle) { statusPixmap = s_icons->readyPixmap; } else if (status == AgentInstance::Running) { statusPixmap = s_icons->syncPixmap; } else { statusPixmap = s_icons->errorPixmap; } if (status == 1) { statusMessage.append(QStringLiteral(" (%1%)").arg(progress)); } const QPixmap iconPixmap = icon.pixmap(IconSize(KIconLoader::Desktop), IconSize(KIconLoader::Desktop)); QRect innerRect = option.rect.adjusted(s_delegatePaddingSize, s_delegatePaddingSize, -s_delegatePaddingSize, -s_delegatePaddingSize); //add some padding round entire delegate const QSize decorationSize = iconPixmap.size(); const QSize statusIconSize = statusPixmap.size();//= KIconLoader::global()->currentSize(KIconLoader::Small); QFont nameFont = option.font; nameFont.setBold(true); QFont statusTextFont = option.font; const QRect decorationRect(innerRect.left(), innerRect.top(), decorationSize.width(), innerRect.height()); const QRect nameTextRect(decorationRect.topRight() + QPoint(4, 0), innerRect.topRight() + QPoint(0, innerRect.height() / 2)); const QRect statusTextRect(decorationRect.bottomRight() + QPoint(4, - innerRect.height() / 2), innerRect.bottomRight()); QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled; if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) { cg = QPalette::Inactive; } if (option.state & QStyle::State_Selected) { painter->setPen(option.palette.color(cg, QPalette::HighlightedText)); } else { painter->setPen(option.palette.color(cg, QPalette::Text)); } painter->drawPixmap(style->itemPixmapRect(decorationRect, Qt::AlignCenter, iconPixmap), iconPixmap); painter->setFont(nameFont); painter->drawText(nameTextRect, Qt::AlignVCenter | Qt::AlignLeft, name); painter->setFont(statusTextFont); painter->drawText(statusTextRect.adjusted(statusIconSize.width() + 4, 0, 0, 0), Qt::AlignVCenter | Qt::AlignLeft, statusMessage); painter->drawPixmap(style->itemPixmapRect(statusTextRect, Qt::AlignVCenter | Qt::AlignLeft, statusPixmap), statusPixmap); } QSize AgentInstanceWidgetDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index); const int iconHeight = KIconLoader::global()->currentSize(KIconLoader::Desktop) + (s_delegatePaddingSize * 2); //icon height + padding either side const int textHeight = option.fontMetrics.height() + qMax(option.fontMetrics.height(), 16) + (s_delegatePaddingSize * 2); //height of text + icon/text + padding either side return QSize(1, qMax(iconHeight, textHeight)); //any width,the view will give us the whole thing in list mode } } #include "moc_agentinstancewidget.cpp" diff --git a/src/widgets/agenttypewidget.cpp b/src/widgets/agenttypewidget.cpp index 9eb6f5abd..0d367271f 100644 --- a/src/widgets/agenttypewidget.cpp +++ b/src/widgets/agenttypewidget.cpp @@ -1,278 +1,278 @@ /* Copyright (c) 2006-2008 Tobias Koenig 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 "agenttypewidget.h" #include #include #include #include #include "agentfilterproxymodel.h" #include "agenttype.h" #include "agenttypemodel.h" namespace Akonadi { namespace Internal { /** * @internal */ class AgentTypeWidgetDelegate : public QAbstractItemDelegate { public: explicit AgentTypeWidgetDelegate(QObject *parent = nullptr); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; private: void drawFocus(QPainter *, const QStyleOptionViewItem &, const QRect &) const; }; } using Akonadi::Internal::AgentTypeWidgetDelegate; /** * @internal */ class Q_DECL_HIDDEN AgentTypeWidget::Private { public: Private(AgentTypeWidget *parent) : mParent(parent), mView(nullptr), mModel(nullptr), proxyModel(nullptr) { } void currentAgentTypeChanged(const QModelIndex &, const QModelIndex &); void typeActivated(const QModelIndex &index) { if (index.flags() & (Qt::ItemIsSelectable | Qt::ItemIsEnabled)) { - emit mParent->activated(); + Q_EMIT mParent->activated(); } } AgentTypeWidget *mParent = nullptr; QListView *mView = nullptr; AgentTypeModel *mModel = nullptr; AgentFilterProxyModel *proxyModel = nullptr; }; void AgentTypeWidget::Private::currentAgentTypeChanged(const QModelIndex ¤tIndex, const QModelIndex &previousIndex) { AgentType currentType; if (currentIndex.isValid()) { currentType = currentIndex.data(AgentTypeModel::TypeRole).value(); } AgentType previousType; if (previousIndex.isValid()) { previousType = previousIndex.data(AgentTypeModel::TypeRole).value(); } - emit mParent->currentChanged(currentType, previousType); + Q_EMIT mParent->currentChanged(currentType, previousType); } AgentTypeWidget::AgentTypeWidget(QWidget *parent) : QWidget(parent) , d(new Private(this)) { QHBoxLayout *layout = new QHBoxLayout(this); layout->setMargin(0); d->mView = new QListView(this); d->mView->setItemDelegate(new AgentTypeWidgetDelegate(d->mView)); d->mView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); d->mView->setAlternatingRowColors(true); layout->addWidget(d->mView); d->mModel = new AgentTypeModel(d->mView); d->proxyModel = new AgentFilterProxyModel(this); d->proxyModel->setSourceModel(d->mModel); d->proxyModel->sort(0); d->mView->setModel(d->proxyModel); d->mView->selectionModel()->setCurrentIndex(d->mView->model()->index(0, 0), QItemSelectionModel::Select); d->mView->scrollTo(d->mView->model()->index(0, 0)); connect(d->mView->selectionModel(), &QItemSelectionModel::currentChanged, this, [this](const QModelIndex &start, const QModelIndex &end) {d->currentAgentTypeChanged(start, end);}); connect(d->mView, QOverload::of(&QListView::activated), this, [this](const QModelIndex &index) { d->typeActivated(index); }); } AgentTypeWidget::~AgentTypeWidget() { delete d; } AgentType AgentTypeWidget::currentAgentType() const { QItemSelectionModel *selectionModel = d->mView->selectionModel(); if (!selectionModel) { return AgentType(); } QModelIndex index = selectionModel->currentIndex(); if (!index.isValid()) { return AgentType(); } return index.data(AgentTypeModel::TypeRole).value(); } AgentFilterProxyModel *AgentTypeWidget::agentFilterProxyModel() const { return d->proxyModel; } /** * AgentTypeWidgetDelegate */ AgentTypeWidgetDelegate::AgentTypeWidgetDelegate(QObject *parent) : QAbstractItemDelegate(parent) { } void AgentTypeWidgetDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (!index.isValid()) { return; } painter->setRenderHint(QPainter::Antialiasing); const QString name = index.model()->data(index, Qt::DisplayRole).toString(); const QString comment = index.model()->data(index, AgentTypeModel::DescriptionRole).toString(); const QVariant data = index.model()->data(index, Qt::DecorationRole); QPixmap pixmap; if (data.isValid() && data.type() == QVariant::Icon) { pixmap = qvariant_cast(data).pixmap(64, 64); } const QFont oldFont = painter->font(); QFont boldFont(oldFont); boldFont.setBold(true); painter->setFont(boldFont); QFontMetrics fm = painter->fontMetrics(); int hn = fm.boundingRect(0, 0, 0, 0, Qt::AlignLeft, name).height(); int wn = fm.boundingRect(0, 0, 0, 0, Qt::AlignLeft, name).width(); painter->setFont(oldFont); fm = painter->fontMetrics(); int hc = fm.boundingRect(0, 0, 0, 0, Qt::AlignLeft, comment).height(); int wc = fm.boundingRect(0, 0, 0, 0, Qt::AlignLeft, comment).width(); int wp = pixmap.width(); QStyleOptionViewItem opt(option); opt.showDecorationSelected = true; QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter); QPen pen = painter->pen(); QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled; if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) { cg = QPalette::Inactive; } if (option.state & QStyle::State_Selected) { painter->setPen(option.palette.color(cg, QPalette::HighlightedText)); } else { painter->setPen(option.palette.color(cg, QPalette::Text)); } painter->setFont(option.font); painter->drawPixmap(option.rect.x() + 5, option.rect.y() + 5, pixmap); painter->setFont(boldFont); if (!name.isEmpty()) { painter->drawText(option.rect.x() + 5 + wp + 5, option.rect.y() + 7, wn, hn, Qt::AlignLeft, name); } painter->setFont(oldFont); if (!comment.isEmpty()) { painter->drawText(option.rect.x() + 5 + wp + 5, option.rect.y() + 7 + hn, wc, hc, Qt::AlignLeft, comment); } painter->setPen(pen); drawFocus(painter, option, option.rect); } QSize AgentTypeWidgetDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { if (!index.isValid()) { return QSize(0, 0); } const QString name = index.model()->data(index, Qt::DisplayRole).toString(); const QString comment = index.model()->data(index, AgentTypeModel::DescriptionRole).toString(); QFontMetrics fm = option.fontMetrics; int hn = fm.boundingRect(0, 0, 0, 0, Qt::AlignLeft, name).height(); int wn = fm.boundingRect(0, 0, 0, 0, Qt::AlignLeft, name).width(); int hc = fm.boundingRect(0, 0, 0, 0, Qt::AlignLeft, comment).height(); int wc = fm.boundingRect(0, 0, 0, 0, Qt::AlignLeft, comment).width(); int width = 0; int height = 0; if (!name.isEmpty()) { height += hn; width = qMax(width, wn); } if (!comment.isEmpty()) { height += hc; width = qMax(width, wc); } height = qMax(height, 64) + 10; width += 64 + 15; return QSize(width, height); } void AgentTypeWidgetDelegate::drawFocus(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect) const { if (option.state & QStyle::State_HasFocus) { QStyleOptionFocusRect o; o.QStyleOption::operator=(option); o.rect = rect; o.state |= QStyle::State_KeyboardFocusChange; QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled; o.backgroundColor = option.palette.color(cg, (option.state & QStyle::State_Selected) ? QPalette::Highlight : QPalette::Background); QApplication::style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter); } } } #include "moc_agenttypewidget.cpp" diff --git a/src/widgets/collectioncombobox.cpp b/src/widgets/collectioncombobox.cpp index 5ae6e628d..e43d76cb2 100644 --- a/src/widgets/collectioncombobox.cpp +++ b/src/widgets/collectioncombobox.cpp @@ -1,190 +1,190 @@ /* This file is part of Akonadi Contact. Copyright (c) 2007-2009 Tobias Koenig 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 "collectioncombobox.h" #include "asyncselectionhandler_p.h" #include "collectiondialog.h" #include "monitor.h" #include "collectionfetchscope.h" #include "collectionfilterproxymodel.h" #include "entityrightsfiltermodel.h" #include "entitytreemodel.h" #include "session.h" #include "collectionutils.h" #include #include using namespace Akonadi; class Q_DECL_HIDDEN CollectionComboBox::Private { public: Private(QAbstractItemModel *customModel, CollectionComboBox *parent) : mParent(parent) { if (customModel) { mBaseModel = customModel; } else { mMonitor = new Akonadi::Monitor(mParent); mMonitor->setObjectName(QStringLiteral("CollectionComboBoxMonitor")); mMonitor->fetchCollection(true); mMonitor->setCollectionMonitored(Akonadi::Collection::root()); // This ETM will be set to only show collections with the wanted mimetype in setMimeTypeFilter mModel = new EntityTreeModel(mMonitor, mParent); mModel->setItemPopulationStrategy(EntityTreeModel::NoItemPopulation); mModel->setListFilter(CollectionFetchScope::Display); mBaseModel = mModel; } // Flatten the tree, e.g. // Kolab // Kolab / Inbox // Kolab / Inbox / Calendar KDescendantsProxyModel *proxyModel = new KDescendantsProxyModel(parent); proxyModel->setDisplayAncestorData(true); proxyModel->setSourceModel(mBaseModel); // Filter it by mimetype again, to only keep // Kolab / Inbox / Calendar mMimeTypeFilterModel = new CollectionFilterProxyModel(parent); mMimeTypeFilterModel->setSourceModel(proxyModel); // Filter by access rights. TODO: maybe this functionality could be provided by CollectionFilterProxyModel, to save one proxy? mRightsFilterModel = new EntityRightsFilterModel(parent); mRightsFilterModel->setSourceModel(mMimeTypeFilterModel); mParent->setModel(mRightsFilterModel); mParent->model()->sort(mParent->modelColumn()); mSelectionHandler = new AsyncSelectionHandler(mRightsFilterModel, mParent); mParent->connect(mSelectionHandler, SIGNAL(collectionAvailable(QModelIndex)), mParent, SLOT(activated(QModelIndex))); mParent->connect(mParent, SIGNAL(activated(int)), mParent, SLOT(activated(int))); } ~Private() { } void activated(int index); void activated(const QModelIndex &index); CollectionComboBox *mParent = nullptr; Monitor *mMonitor = nullptr; EntityTreeModel *mModel = nullptr; QAbstractItemModel *mBaseModel = nullptr; CollectionFilterProxyModel *mMimeTypeFilterModel = nullptr; EntityRightsFilterModel *mRightsFilterModel = nullptr; AsyncSelectionHandler *mSelectionHandler = nullptr; }; void CollectionComboBox::Private::activated(int index) { const QModelIndex modelIndex = mParent->model()->index(index, 0); if (modelIndex.isValid()) { - emit mParent->currentChanged(modelIndex.data(EntityTreeModel::CollectionRole).value()); + Q_EMIT mParent->currentChanged(modelIndex.data(EntityTreeModel::CollectionRole).value()); } } void CollectionComboBox::Private::activated(const QModelIndex &index) { mParent->setCurrentIndex(index.row()); } CollectionComboBox::CollectionComboBox(QWidget *parent) : QComboBox(parent) , d(new Private(nullptr, this)) { } CollectionComboBox::CollectionComboBox(QAbstractItemModel *model, QWidget *parent) : QComboBox(parent) , d(new Private(model, this)) { } CollectionComboBox::~CollectionComboBox() { delete d; } void CollectionComboBox::setMimeTypeFilter(const QStringList &contentMimeTypes) { d->mMimeTypeFilterModel->clearFilters(); d->mMimeTypeFilterModel->addMimeTypeFilters(contentMimeTypes); if (d->mMonitor) { for (const QString &mimeType : contentMimeTypes) { d->mMonitor->setMimeTypeMonitored(mimeType, true); } } } QStringList CollectionComboBox::mimeTypeFilter() const { return d->mMimeTypeFilterModel->mimeTypeFilters(); } void CollectionComboBox::setAccessRightsFilter(Collection::Rights rights) { d->mRightsFilterModel->setAccessRights(rights); } Akonadi::Collection::Rights CollectionComboBox::accessRightsFilter() const { return d->mRightsFilterModel->accessRights(); } void CollectionComboBox::setDefaultCollection(const Collection &collection) { d->mSelectionHandler->waitForCollection(collection); } Akonadi::Collection CollectionComboBox::currentCollection() const { const QModelIndex modelIndex = model()->index(currentIndex(), 0); if (modelIndex.isValid()) { return modelIndex.data(Akonadi::EntityTreeModel::CollectionRole).value(); } else { return Akonadi::Collection(); } } void CollectionComboBox::setExcludeVirtualCollections(bool b) { d->mMimeTypeFilterModel->setExcludeVirtualCollections(b); } bool CollectionComboBox::excludeVirtualCollections() const { return d->mMimeTypeFilterModel->excludeVirtualCollections(); } #include "moc_collectioncombobox.cpp" diff --git a/src/widgets/collectionrequester.cpp b/src/widgets/collectionrequester.cpp index adebaadd5..597287806 100644 --- a/src/widgets/collectionrequester.cpp +++ b/src/widgets/collectionrequester.cpp @@ -1,265 +1,265 @@ /* Copyright 2008 Ingo Klöcker 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 "collectionrequester.h" #include "collectiondialog.h" #include "entitydisplayattribute.h" #include "collectionfetchjob.h" #include "collectionfetchscope.h" #include #include #include #include #include #include #include using namespace Akonadi; class Q_DECL_HIDDEN CollectionRequester::Private { public: Private(CollectionRequester *parent) : q(parent) { } ~Private() { } void fetchCollection(const Collection &collection); void init(); // slots void _k_slotOpenDialog(); void _k_collectionReceived(KJob *job); void _k_collectionsNamesReceived(KJob *job); CollectionRequester *q = nullptr; Collection collection; QLineEdit *edit = nullptr; QPushButton *button = nullptr; CollectionDialog *collectionDialog = nullptr; }; void CollectionRequester::Private::fetchCollection(const Collection &collection) { CollectionFetchJob *job = new CollectionFetchJob(collection, Akonadi::CollectionFetchJob::Base, q); job->setProperty("OriginalCollectionId", collection.id()); job->fetchScope().setAncestorRetrieval(CollectionFetchScope::All); connect(job, &CollectionFetchJob::finished, q, [this](KJob *job) {_k_collectionReceived(job);}); } void CollectionRequester::Private::_k_collectionReceived(KJob *job) { CollectionFetchJob *fetch = qobject_cast(job); if (!fetch) { return; } if (fetch->collections().size() == 1) { Collection::List chain; Collection currentCollection = fetch->collections().at(0); while (currentCollection.isValid()) { chain << currentCollection; currentCollection = Collection(currentCollection.parentCollection()); } CollectionFetchJob *namesFetch = new CollectionFetchJob(chain, CollectionFetchJob::Base, q); namesFetch->setProperty("OriginalCollectionId", job->property("OriginalCollectionId")); namesFetch->fetchScope().setAncestorRetrieval(CollectionFetchScope::Parent); connect(namesFetch, &CollectionFetchJob::finished, q, [this](KJob *job) {_k_collectionsNamesReceived(job);}); } else { _k_collectionsNamesReceived(job); } } void CollectionRequester::Private::_k_collectionsNamesReceived(KJob *job) { CollectionFetchJob *fetch = qobject_cast(job); const qint64 originalId = fetch->property("OriginalCollectionId").toLongLong(); QMap names; const Akonadi::Collection::List lstCols = fetch->collections(); for (const Collection &collection : lstCols) { names.insert(collection.id(), collection); } QStringList namesList; Collection currentCollection = names.take(originalId); while (currentCollection.isValid()) { namesList.prepend(currentCollection.displayName()); currentCollection = names.take(currentCollection.parentCollection().id()); } edit->setText(namesList.join(QLatin1Char('/'))); } void CollectionRequester::Private::init() { QHBoxLayout *hbox = new QHBoxLayout(q); hbox->setMargin(0); edit = new QLineEdit(q); edit->setReadOnly(true); edit->setPlaceholderText(i18n("No Folder")); edit->setClearButtonEnabled(false); edit->setFocusPolicy(Qt::NoFocus); hbox->addWidget(edit); button = new QPushButton(q); button->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); const int buttonSize = edit->sizeHint().height(); button->setFixedSize(buttonSize, buttonSize); button->setToolTip(i18n("Open collection dialog")); hbox->addWidget(button); hbox->setSpacing(-1); edit->installEventFilter(q); q->setFocusProxy(button); q->setFocusPolicy(Qt::StrongFocus); q->connect(button, &QPushButton::clicked, q, [this]() { _k_slotOpenDialog(); }); QAction *openAction = new QAction(q); openAction->setShortcut(KStandardShortcut::Open); q->connect(openAction, &QAction::triggered, q, [this]() { _k_slotOpenDialog(); }); collectionDialog = new CollectionDialog(q); collectionDialog->setWindowIcon(QIcon::fromTheme(QStringLiteral("akonadi"))); collectionDialog->setWindowTitle(i18n("Select a collection")); collectionDialog->setSelectionMode(QAbstractItemView::SingleSelection); collectionDialog->changeCollectionDialogOptions(CollectionDialog::KeepTreeExpanded); } void CollectionRequester::Private::_k_slotOpenDialog() { CollectionDialog *dlg = collectionDialog; if (dlg->exec() != QDialog::Accepted) { return; } const Akonadi::Collection collection = dlg->selectedCollection(); q->setCollection(collection); - emit q->collectionChanged(collection); + Q_EMIT q->collectionChanged(collection); } CollectionRequester::CollectionRequester(QWidget *parent) : QWidget(parent) , d(new Private(this)) { d->init(); } CollectionRequester::CollectionRequester(const Akonadi::Collection &collection, QWidget *parent) : QWidget(parent) , d(new Private(this)) { d->init(); setCollection(collection); } CollectionRequester::~CollectionRequester() { delete d; } Collection CollectionRequester::collection() const { return d->collection; } void CollectionRequester::setCollection(const Collection &collection) { d->collection = collection; QString name; if (collection.isValid()) { name = collection.displayName(); } d->edit->setText(name); - emit collectionChanged(collection); + Q_EMIT collectionChanged(collection); d->fetchCollection(collection); } void CollectionRequester::setMimeTypeFilter(const QStringList &mimeTypes) { if (d->collectionDialog) { d->collectionDialog->setMimeTypeFilter(mimeTypes); } } QStringList CollectionRequester::mimeTypeFilter() const { if (d->collectionDialog) { return d->collectionDialog->mimeTypeFilter(); } else { return QStringList(); } } void CollectionRequester::setAccessRightsFilter(Collection::Rights rights) { if (d->collectionDialog) { d->collectionDialog->setAccessRightsFilter(rights); } } Collection::Rights CollectionRequester::accessRightsFilter() const { if (d->collectionDialog) { return d->collectionDialog->accessRightsFilter(); } else { return Akonadi::Collection::ReadOnly; } } void CollectionRequester::changeCollectionDialogOptions(CollectionDialog::CollectionDialogOptions options) { if (d->collectionDialog) { d->collectionDialog->changeCollectionDialogOptions(options); } } void CollectionRequester::setContentMimeTypes(const QStringList &mimetypes) { if (d->collectionDialog) { d->collectionDialog->setContentMimeTypes(mimetypes); } } void CollectionRequester::changeEvent(QEvent *event) { if (event->type() == QEvent::WindowTitleChange) { if (d->collectionDialog) { d->collectionDialog->setWindowTitle(windowTitle()); } } else if (event->type() == QEvent::EnabledChange) { if (d->collectionDialog) { d->collectionDialog->setEnabled(true); } } QWidget::changeEvent(event); } #include "moc_collectionrequester.cpp" diff --git a/src/widgets/collectionview.cpp b/src/widgets/collectionview.cpp index 029c24981..3efede030 100644 --- a/src/widgets/collectionview.cpp +++ b/src/widgets/collectionview.cpp @@ -1,263 +1,263 @@ /* 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 "collectionview.h" #include "collection.h" #include "collectionmodel.h" #include "controlgui.h" #include #include #include "akonadiwidgets_debug.h" #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; /** * @internal */ class Q_DECL_HIDDEN CollectionView::Private { public: Private(CollectionView *parent) : mParent(parent) { } void init(); void dragExpand(); void itemClicked(const QModelIndex &index); void itemCurrentChanged(const QModelIndex &index); bool hasParent(const QModelIndex &idx, Collection::Id parentId); CollectionView *mParent = nullptr; QModelIndex dragOverIndex; QTimer dragExpandTimer; KXMLGUIClient *xmlGuiClient = nullptr; }; void CollectionView::Private::init() { mParent->header()->setSectionsClickable(true); mParent->header()->setStretchLastSection(false); mParent->setSortingEnabled(true); mParent->sortByColumn(0, Qt::AscendingOrder); mParent->setEditTriggers(QAbstractItemView::EditKeyPressed); mParent->setAcceptDrops(true); mParent->setDropIndicatorShown(true); mParent->setDragDropMode(DragDrop); mParent->setDragEnabled(true); dragExpandTimer.setSingleShot(true); mParent->connect(&dragExpandTimer, &QTimer::timeout, mParent, [this]() { dragExpand(); }); mParent->connect(mParent, SIGNAL(clicked(QModelIndex)), mParent, SLOT(itemClicked(QModelIndex))); ControlGui::widgetNeedsAkonadi(mParent); } bool CollectionView::Private::hasParent(const QModelIndex &idx, Collection::Id parentId) { QModelIndex idx2 = idx; while (idx2.isValid()) { if (mParent->model()->data(idx2, CollectionModel::CollectionIdRole).toLongLong() == parentId) { return true; } idx2 = idx2.parent(); } return false; } void CollectionView::Private::dragExpand() { mParent->setExpanded(dragOverIndex, true); dragOverIndex = QModelIndex(); } void CollectionView::Private::itemClicked(const QModelIndex &index) { if (!index.isValid()) { return; } const Collection collection = index.model()->data(index, CollectionModel::CollectionRole).value(); if (!collection.isValid()) { return; } - emit mParent->clicked(collection); + Q_EMIT mParent->clicked(collection); } void CollectionView::Private::itemCurrentChanged(const QModelIndex &index) { if (!index.isValid()) { return; } const Collection collection = index.model()->data(index, CollectionModel::CollectionRole).value(); if (!collection.isValid()) { return; } - emit mParent->currentChanged(collection); + Q_EMIT mParent->currentChanged(collection); } CollectionView::CollectionView(QWidget *parent) : QTreeView(parent) , d(new Private(this)) { d->init(); } CollectionView::CollectionView(KXMLGUIClient *xmlGuiClient, QWidget *parent) : QTreeView(parent) , d(new Private(this)) { d->xmlGuiClient = xmlGuiClient; d->init(); } CollectionView::~CollectionView() { delete d; } void CollectionView::setModel(QAbstractItemModel *model) { QTreeView::setModel(model); header()->setStretchLastSection(true); connect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(itemCurrentChanged(QModelIndex))); } void CollectionView::dragMoveEvent(QDragMoveEvent *event) { QModelIndex index = indexAt(event->pos()); if (d->dragOverIndex != index) { d->dragExpandTimer.stop(); if (index.isValid() && !isExpanded(index) && itemsExpandable()) { d->dragExpandTimer.start(QApplication::startDragTime()); d->dragOverIndex = index; } } // Check if the collection under the cursor accepts this data type const QStringList supportedContentTypes = model()->data(index, CollectionModel::CollectionRole).value().contentMimeTypes(); const QMimeData *mimeData = event->mimeData(); if (!mimeData) { return; } const QList urls = mimeData->urls(); for (const QUrl &url : urls) { const Collection collection = Collection::fromUrl(url); if (collection.isValid()) { if (!supportedContentTypes.contains(QLatin1String("inode/directory"))) { break; } // Check if we don't try to drop on one of the children if (d->hasParent(index, collection.id())) { break; } } else { const QList > query = QUrlQuery(url).queryItems(); const int numberOfQuery(query.count()); for (int i = 0; i < numberOfQuery; ++i) { if (query.at(i).first == QLatin1String("type")) { const QString type = query.at(i).second; if (!supportedContentTypes.contains(type)) { break; } } } } QTreeView::dragMoveEvent(event); return; } event->setDropAction(Qt::IgnoreAction); } void CollectionView::dragLeaveEvent(QDragLeaveEvent *event) { d->dragExpandTimer.stop(); d->dragOverIndex = QModelIndex(); QTreeView::dragLeaveEvent(event); } void CollectionView::dropEvent(QDropEvent *event) { d->dragExpandTimer.stop(); d->dragOverIndex = QModelIndex(); // open a context menu offering different drop actions (move, copy and cancel) // TODO If possible, hide non available actions ... QMenu popup(this); QAction *moveDropAction = popup.addAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18n("&Move here")); QAction *copyDropAction = popup.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("&Copy here")); popup.addSeparator(); popup.addAction(QIcon::fromTheme(QStringLiteral("process-stop")), i18n("Cancel")); QAction *activatedAction = popup.exec(QCursor::pos()); if (activatedAction == moveDropAction) { event->setDropAction(Qt::MoveAction); } else if (activatedAction == copyDropAction) { event->setDropAction(Qt::CopyAction); } else { return; } QTreeView::dropEvent(event); } void CollectionView::contextMenuEvent(QContextMenuEvent *event) { if (!d->xmlGuiClient) { return; } QMenu *popup = static_cast(d->xmlGuiClient->factory()->container( QStringLiteral("akonadi_collectionview_contextmenu"), d->xmlGuiClient)); if (popup) { popup->exec(event->globalPos()); } } void CollectionView::setXmlGuiClient(KXMLGUIClient *xmlGuiClient) { d->xmlGuiClient = xmlGuiClient; } #include "moc_collectionview.cpp" diff --git a/src/widgets/entitylistview.cpp b/src/widgets/entitylistview.cpp index 9f70ad24a..34a93693f 100644 --- a/src/widgets/entitylistview.cpp +++ b/src/widgets/entitylistview.cpp @@ -1,252 +1,252 @@ /* Copyright (c) 2006 - 2007 Volker Krause Copyright (c) 2008 Stephen Kelly Copyright (c) 2009 Kevin Ottens 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 "entitylistview.h" #include "dragdropmanager_p.h" #include #include #include "akonadiwidgets_debug.h" #include #include #include "collection.h" #include "controlgui.h" #include "item.h" #include "entitytreemodel.h" #include "progressspinnerdelegate_p.h" using namespace Akonadi; /** * @internal */ class Q_DECL_HIDDEN EntityListView::Private { public: Private(EntityListView *parent) : mParent(parent) #ifndef QT_NO_DRAGANDDROP , mDragDropManager(new DragDropManager(mParent)) #endif { } void init(); void itemClicked(const QModelIndex &index); void itemDoubleClicked(const QModelIndex &index); void itemCurrentChanged(const QModelIndex &index); EntityListView *mParent = nullptr; DragDropManager *mDragDropManager = nullptr; KXMLGUIClient *mXmlGuiClient = nullptr; }; void EntityListView::Private::init() { mParent->setEditTriggers(QAbstractItemView::EditKeyPressed); mParent->setAcceptDrops(true); #ifndef QT_NO_DRAGANDDROP mParent->setDropIndicatorShown(true); mParent->setDragDropMode(DragDrop); mParent->setDragEnabled(true); #endif mParent->connect(mParent, SIGNAL(clicked(QModelIndex)), mParent, SLOT(itemClicked(QModelIndex))); mParent->connect(mParent, SIGNAL(doubleClicked(QModelIndex)), mParent, SLOT(itemDoubleClicked(QModelIndex))); DelegateAnimator *animator = new DelegateAnimator(mParent); ProgressSpinnerDelegate *customDelegate = new ProgressSpinnerDelegate(animator, mParent); mParent->setItemDelegate(customDelegate); ControlGui::widgetNeedsAkonadi(mParent); } void EntityListView::Private::itemClicked(const QModelIndex &index) { if (!index.isValid()) { return; } const Collection collection = index.model()->data(index, EntityTreeModel::CollectionRole).value(); if (collection.isValid()) { - emit mParent->clicked(collection); + Q_EMIT mParent->clicked(collection); } else { const Item item = index.model()->data(index, EntityTreeModel::ItemRole).value(); if (item.isValid()) { - emit mParent->clicked(item); + Q_EMIT mParent->clicked(item); } } } void EntityListView::Private::itemDoubleClicked(const QModelIndex &index) { if (!index.isValid()) { return; } const Collection collection = index.model()->data(index, EntityTreeModel::CollectionRole).value(); if (collection.isValid()) { - emit mParent->doubleClicked(collection); + Q_EMIT mParent->doubleClicked(collection); } else { const Item item = index.model()->data(index, EntityTreeModel::ItemRole).value(); if (item.isValid()) { - emit mParent->doubleClicked(item); + Q_EMIT mParent->doubleClicked(item); } } } void EntityListView::Private::itemCurrentChanged(const QModelIndex &index) { if (!index.isValid()) { return; } const Collection collection = index.model()->data(index, EntityTreeModel::CollectionRole).value(); if (collection.isValid()) { - emit mParent->currentChanged(collection); + Q_EMIT mParent->currentChanged(collection); } else { const Item item = index.model()->data(index, EntityTreeModel::ItemRole).value(); if (item.isValid()) { - emit mParent->currentChanged(item); + Q_EMIT mParent->currentChanged(item); } } } EntityListView::EntityListView(QWidget *parent) : QListView(parent) , d(new Private(this)) { setSelectionMode(QAbstractItemView::SingleSelection); d->init(); } EntityListView::EntityListView(KXMLGUIClient *xmlGuiClient, QWidget *parent) : QListView(parent) , d(new Private(this)) { d->mXmlGuiClient = xmlGuiClient; d->init(); } EntityListView::~EntityListView() { delete d->mDragDropManager; delete d; } void EntityListView::setModel(QAbstractItemModel *model) { if (selectionModel()) { disconnect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(itemCurrentChanged(QModelIndex))); } QListView::setModel(model); connect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), SLOT(itemCurrentChanged(QModelIndex))); } #ifndef QT_NO_DRAGANDDROP void EntityListView::dragMoveEvent(QDragMoveEvent *event) { if (d->mDragDropManager->dropAllowed(event)) { // All urls are supported. process the event. QListView::dragMoveEvent(event); return; } event->setDropAction(Qt::IgnoreAction); } void EntityListView::dropEvent(QDropEvent *event) { bool menuCanceled = false; if (d->mDragDropManager->processDropEvent(event, menuCanceled) && !menuCanceled) { QListView::dropEvent(event); } } #endif #ifndef QT_NO_CONTEXTMENU void EntityListView::contextMenuEvent(QContextMenuEvent *event) { if (!d->mXmlGuiClient) { return; } const QModelIndex index = indexAt(event->pos()); QMenu *popup = nullptr; // check if the index under the cursor is a collection or item const Collection collection = model()->data(index, EntityTreeModel::CollectionRole).value(); if (collection.isValid()) { popup = static_cast(d->mXmlGuiClient->factory()->container( QStringLiteral("akonadi_favoriteview_contextmenu"), d->mXmlGuiClient)); } else { popup = static_cast(d->mXmlGuiClient->factory()->container( QStringLiteral("akonadi_favoriteview_emptyselection_contextmenu"), d->mXmlGuiClient)); } if (popup) { popup->exec(event->globalPos()); } } #endif void EntityListView::setXmlGuiClient(KXMLGUIClient *xmlGuiClient) { d->mXmlGuiClient = xmlGuiClient; } KXMLGUIClient *EntityListView::xmlGuiClient() const { return d->mXmlGuiClient; } #ifndef QT_NO_DRAGANDDROP void EntityListView::startDrag(Qt::DropActions supportedActions) { d->mDragDropManager->startDrag(supportedActions); } #endif void EntityListView::setDropActionMenuEnabled(bool enabled) { #ifndef QT_NO_DRAGANDDROP d->mDragDropManager->setShowDropActionMenu(enabled); #endif } bool EntityListView::isDropActionMenuEnabled() const { #ifndef QT_NO_DRAGANDDROP return d->mDragDropManager->showDropActionMenu(); #else return false; #endif } #include "moc_entitylistview.cpp" diff --git a/src/widgets/entitytreeview.cpp b/src/widgets/entitytreeview.cpp index 1ec8316d1..35ff3c069 100644 --- a/src/widgets/entitytreeview.cpp +++ b/src/widgets/entitytreeview.cpp @@ -1,339 +1,339 @@ /* Copyright (c) 2006 - 2007 Volker Krause Copyright (c) 2008 Stephen Kelly Copyright (C) 2012-2019 Laurent Montel 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 "entitytreeview.h" #include "dragdropmanager_p.h" #include #include #include #include #include #include "collection.h" #include "controlgui.h" #include "item.h" #include "entitytreemodel.h" #include #include #include "progressspinnerdelegate_p.h" using namespace Akonadi; /** * @internal */ class Q_DECL_HIDDEN EntityTreeView::Private { public: Private(EntityTreeView *parent) : mParent(parent) #ifndef QT_NO_DRAGANDDROP , mDragDropManager(new DragDropManager(mParent)) #endif , mDefaultPopupMenu(QStringLiteral("akonadi_collectionview_contextmenu")) { } void init(); void itemClicked(const QModelIndex &index); void itemDoubleClicked(const QModelIndex &index); void itemCurrentChanged(const QModelIndex &index); void slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); EntityTreeView *mParent = nullptr; QBasicTimer mDragExpandTimer; DragDropManager *mDragDropManager = nullptr; KXMLGUIClient *mXmlGuiClient = nullptr; QString mDefaultPopupMenu; }; void EntityTreeView::Private::init() { Akonadi::DelegateAnimator *animator = new Akonadi::DelegateAnimator(mParent); Akonadi::ProgressSpinnerDelegate *customDelegate = new Akonadi::ProgressSpinnerDelegate(animator, mParent); mParent->setItemDelegate(customDelegate); mParent->header()->setSectionsClickable(true); mParent->header()->setStretchLastSection(false); // mParent->setRootIsDecorated( false ); // QTreeView::autoExpandDelay has very strange behaviour. It toggles the collapse/expand state // of the item the cursor is currently over when a timer event fires. // The behaviour we want is to expand a collapsed row on drag-over, but not collapse it. // mDragExpandTimer is used to achieve this. // mParent->setAutoExpandDelay ( QApplication::startDragTime() ); mParent->setSortingEnabled(true); mParent->sortByColumn(0, Qt::AscendingOrder); mParent->setEditTriggers(QAbstractItemView::EditKeyPressed); mParent->setAcceptDrops(true); #ifndef QT_NO_DRAGANDDROP mParent->setDropIndicatorShown(true); mParent->setDragDropMode(DragDrop); mParent->setDragEnabled(true); #endif mParent->connect(mParent, SIGNAL(clicked(QModelIndex)), mParent, SLOT(itemClicked(QModelIndex))); mParent->connect(mParent, SIGNAL(doubleClicked(QModelIndex)), mParent, SLOT(itemDoubleClicked(QModelIndex))); ControlGui::widgetNeedsAkonadi(mParent); } void EntityTreeView::Private::slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { Q_UNUSED(deselected) const int column = 0; for (const QItemSelectionRange &range : selected) { const QModelIndex index = range.topLeft(); if (index.column() > 0) { continue; } for (int row = index.row(); row <= range.bottomRight().row(); ++row) { // Don't use canFetchMore here. We need to bypass the check in // the EntityFilterModel when it shows only collections. mParent->model()->fetchMore(index.sibling(row, column)); } } if (selected.size() == 1) { const QItemSelectionRange &range = selected.first(); if (range.topLeft().row() == range.bottomRight().row()) { mParent->scrollTo(range.topLeft(), QTreeView::EnsureVisible); } } } void EntityTreeView::Private::itemClicked(const QModelIndex &index) { if (!index.isValid()) { return; } QModelIndex idx = index.sibling(index.row(), 0); const Collection collection = idx.model()->data(idx, EntityTreeModel::CollectionRole).value(); if (collection.isValid()) { - emit mParent->clicked(collection); + Q_EMIT mParent->clicked(collection); } else { const Item item = idx.model()->data(idx, EntityTreeModel::ItemRole).value(); if (item.isValid()) { - emit mParent->clicked(item); + Q_EMIT mParent->clicked(item); } } } void EntityTreeView::Private::itemDoubleClicked(const QModelIndex &index) { if (!index.isValid()) { return; } QModelIndex idx = index.sibling(index.row(), 0); const Collection collection = idx.model()->data(idx, EntityTreeModel::CollectionRole).value(); if (collection.isValid()) { - emit mParent->doubleClicked(collection); + Q_EMIT mParent->doubleClicked(collection); } else { const Item item = idx.model()->data(idx, EntityTreeModel::ItemRole).value(); if (item.isValid()) { - emit mParent->doubleClicked(item); + Q_EMIT mParent->doubleClicked(item); } } } void EntityTreeView::Private::itemCurrentChanged(const QModelIndex &index) { if (!index.isValid()) { return; } QModelIndex idx = index.sibling(index.row(), 0); const Collection collection = idx.model()->data(idx, EntityTreeModel::CollectionRole).value(); if (collection.isValid()) { - emit mParent->currentChanged(collection); + Q_EMIT mParent->currentChanged(collection); } else { const Item item = idx.model()->data(idx, EntityTreeModel::ItemRole).value(); if (item.isValid()) { - emit mParent->currentChanged(item); + Q_EMIT mParent->currentChanged(item); } } } EntityTreeView::EntityTreeView(QWidget *parent) : QTreeView(parent) , d(new Private(this)) { setSelectionMode(QAbstractItemView::SingleSelection); d->init(); } EntityTreeView::EntityTreeView(KXMLGUIClient *xmlGuiClient, QWidget *parent) : QTreeView(parent) , d(new Private(this)) { d->mXmlGuiClient = xmlGuiClient; d->init(); } EntityTreeView::~EntityTreeView() { delete d->mDragDropManager; delete d; } void EntityTreeView::setModel(QAbstractItemModel *model) { if (selectionModel()) { disconnect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(itemCurrentChanged(QModelIndex))); disconnect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(slotSelectionChanged(QItemSelection,QItemSelection))); } QTreeView::setModel(model); header()->setStretchLastSection(true); connect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), SLOT(itemCurrentChanged(QModelIndex))); connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(slotSelectionChanged(QItemSelection,QItemSelection))); } void EntityTreeView::timerEvent(QTimerEvent *event) { if (event->timerId() == d->mDragExpandTimer.timerId()) { const QPoint pos = viewport()->mapFromGlobal(QCursor::pos()); if (state() == QAbstractItemView::DraggingState && viewport()->rect().contains(pos)) { setExpanded(indexAt(pos), true); } } QTreeView::timerEvent(event); } #ifndef QT_NO_DRAGANDDROP void EntityTreeView::dragMoveEvent(QDragMoveEvent *event) { d->mDragExpandTimer.start(QApplication::startDragTime(), this); if (d->mDragDropManager->dropAllowed(event)) { // All urls are supported. process the event. QTreeView::dragMoveEvent(event); return; } event->setDropAction(Qt::IgnoreAction); } void EntityTreeView::dropEvent(QDropEvent *event) { d->mDragExpandTimer.stop(); bool menuCanceled = false; if (d->mDragDropManager->processDropEvent(event, menuCanceled, (dropIndicatorPosition() == QAbstractItemView::OnItem))) { QTreeView::dropEvent(event); } } #endif #ifndef QT_NO_CONTEXTMENU void EntityTreeView::contextMenuEvent(QContextMenuEvent *event) { if (!d->mXmlGuiClient || !model()) { return; } const QModelIndex index = indexAt(event->pos()); QString popupName = d->mDefaultPopupMenu; if (index.isValid()) { // popup not over empty space // check whether the index under the cursor is a collection or item const Item item = model()->data(index, EntityTreeModel::ItemRole).value(); popupName = (item.isValid() ? QStringLiteral("akonadi_itemview_contextmenu") : QStringLiteral("akonadi_collectionview_contextmenu")); } QMenu *popup = static_cast(d->mXmlGuiClient->factory()->container(popupName, d->mXmlGuiClient)); if (popup) { popup->exec(event->globalPos()); } } #endif void EntityTreeView::setXmlGuiClient(KXMLGUIClient *xmlGuiClient) { d->mXmlGuiClient = xmlGuiClient; } KXMLGUIClient *EntityTreeView::xmlGuiClient() const { return d->mXmlGuiClient; } #ifndef QT_NO_DRAGANDDROP void EntityTreeView::startDrag(Qt::DropActions supportedActions) { d->mDragDropManager->startDrag(supportedActions); } #endif void EntityTreeView::setDropActionMenuEnabled(bool enabled) { #ifndef QT_NO_DRAGANDDROP d->mDragDropManager->setShowDropActionMenu(enabled); #endif } bool EntityTreeView::isDropActionMenuEnabled() const { #ifndef QT_NO_DRAGANDDROP return d->mDragDropManager->showDropActionMenu(); #else return false; #endif } void EntityTreeView::setManualSortingActive(bool active) { #ifndef QT_NO_DRAGANDDROP d->mDragDropManager->setManualSortingActive(active); #endif } bool EntityTreeView::isManualSortingActive() const { #ifndef QT_NO_DRAGANDDROP return d->mDragDropManager->isManualSortingActive(); #else return false; #endif } void EntityTreeView::setDefaultPopupMenu(const QString &name) { d->mDefaultPopupMenu = name; } #include "moc_entitytreeview.cpp" diff --git a/src/widgets/itemview.cpp b/src/widgets/itemview.cpp index 2e99732e4..0ca155329 100644 --- a/src/widgets/itemview.cpp +++ b/src/widgets/itemview.cpp @@ -1,182 +1,182 @@ /* Copyright (c) 2007 Tobias Koenig 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 "itemview.h" #include "controlgui.h" #include "itemmodel.h" #include #include #include #include #include using namespace Akonadi; /** * @internal */ class Q_DECL_HIDDEN ItemView::Private { public: Private(ItemView *parent) : mParent(parent) { } void init(); void itemActivated(const QModelIndex &index); void itemCurrentChanged(const QModelIndex &index); void itemClicked(const QModelIndex &index); void itemDoubleClicked(const QModelIndex &index); Item itemForIndex(const QModelIndex &index); KXMLGUIClient *xmlGuiClient = nullptr; private: ItemView *mParent = nullptr; }; void ItemView::Private::init() { mParent->setRootIsDecorated(false); mParent->header()->setSectionsClickable(true); mParent->header()->setStretchLastSection(true); mParent->connect(mParent, SIGNAL(activated(QModelIndex)), mParent, SLOT(itemActivated(QModelIndex))); mParent->connect(mParent, SIGNAL(clicked(QModelIndex)), mParent, SLOT(itemClicked(QModelIndex))); mParent->connect(mParent, QOverload::of(&QAbstractItemView::doubleClicked), mParent, [this](const QModelIndex &index) { itemDoubleClicked(index); }); ControlGui::widgetNeedsAkonadi(mParent); } Item ItemView::Private::itemForIndex(const QModelIndex &index) { if (!index.isValid()) { return Item(); } const Item::Id currentItem = index.sibling(index.row(), ItemModel::Id).data(ItemModel::IdRole).toLongLong(); if (currentItem <= 0) { return Item(); } const QString remoteId = index.sibling(index.row(), ItemModel::RemoteId).data(ItemModel::IdRole).toString(); const QString mimeType = index.sibling(index.row(), ItemModel::MimeType).data(ItemModel::MimeTypeRole).toString(); Item item(currentItem); item.setRemoteId(remoteId); item.setMimeType(mimeType); return item; } void ItemView::Private::itemActivated(const QModelIndex &index) { const Item item = itemForIndex(index); if (!item.isValid()) { return; } - emit mParent->activated(item); + Q_EMIT mParent->activated(item); } void ItemView::Private::itemCurrentChanged(const QModelIndex &index) { const Item item = itemForIndex(index); if (!item.isValid()) { return; } - emit mParent->currentChanged(item); + Q_EMIT mParent->currentChanged(item); } void ItemView::Private::itemClicked(const QModelIndex &index) { const Item item = itemForIndex(index); if (!item.isValid()) { return; } - emit mParent->clicked(item); + Q_EMIT mParent->clicked(item); } void ItemView::Private::itemDoubleClicked(const QModelIndex &index) { const Item item = itemForIndex(index); if (!item.isValid()) { return; } - emit mParent->doubleClicked(item); + Q_EMIT mParent->doubleClicked(item); } ItemView::ItemView(QWidget *parent) : QTreeView(parent) , d(new Private(this)) { d->init(); } ItemView::ItemView(KXMLGUIClient *xmlGuiClient, QWidget *parent) : QTreeView(parent) , d(new Private(this)) { d->xmlGuiClient = xmlGuiClient; d->init(); } ItemView::~ItemView() { delete d; } void ItemView::setModel(QAbstractItemModel *model) { QTreeView::setModel(model); connect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(itemCurrentChanged(QModelIndex))); } void ItemView::contextMenuEvent(QContextMenuEvent *event) { if (!d->xmlGuiClient) { return; } QMenu *popup = static_cast(d->xmlGuiClient->factory()->container( QStringLiteral("akonadi_itemview_contextmenu"), d->xmlGuiClient)); if (popup) { popup->exec(event->globalPos()); } } void ItemView::setXmlGuiClient(KXMLGUIClient *xmlGuiClient) { d->xmlGuiClient = xmlGuiClient; } #include "moc_itemview.cpp" diff --git a/src/widgets/standardactionmanager.cpp b/src/widgets/standardactionmanager.cpp index c09286dd4..8ce28fd27 100644 --- a/src/widgets/standardactionmanager.cpp +++ b/src/widgets/standardactionmanager.cpp @@ -1,1829 +1,1829 @@ /* Copyright (c) 2008 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 "standardactionmanager.h" #include "actionstatemanager_p.h" #include "agentfilterproxymodel.h" #include "agentinstancecreatejob.h" #include "agentmanager.h" #include "agenttypedialog.h" #include "collectioncreatejob.h" #include "collectiondeletejob.h" #include "collectiondialog.h" #include "collectionmodel.h" #include "collectionutils.h" #include "entitytreemodel.h" #include "favoritecollectionsmodel.h" #include "itemdeletejob.h" #include "itemmodel.h" #include "metatypes.h" #include "pastehelper_p.h" #include "specialcollectionattribute.h" #include "collectionpropertiesdialog.h" #include "subscriptiondialog.h" #include "renamefavoritedialog.h" #include "trashjob.h" #include "trashrestorejob.h" #include "entitydeletedattribute.h" #include "recentcollectionaction_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; //@cond PRIVATE enum ActionType { NormalAction, ActionWithAlternative, //Normal action, but with an alternative state ActionAlternative, //Alternative state of the ActionWithAlternative MenuAction, ToggleAction }; static const struct { const char *name; const char *label; const char *iconLabel; const char *icon; int shortcut; const char *slot; ActionType actionType; } standardActionData[] = { { "akonadi_collection_create", I18N_NOOP("&New Folder..."), I18N_NOOP("New"), "folder-new", 0, SLOT(slotCreateCollection()), NormalAction }, { "akonadi_collection_copy", nullptr, nullptr, "edit-copy", 0, SLOT(slotCopyCollections()), NormalAction }, { "akonadi_collection_delete", I18N_NOOP("&Delete Folder"), I18N_NOOP("Delete"), "edit-delete", 0, SLOT(slotDeleteCollection()), NormalAction }, { "akonadi_collection_sync", I18N_NOOP("&Synchronize Folder"), I18N_NOOP("Synchronize"), "view-refresh", Qt::Key_F5, SLOT(slotSynchronizeCollection()), NormalAction }, { "akonadi_collection_properties", I18N_NOOP("Folder &Properties"), I18N_NOOP("Properties"), "configure", 0, SLOT(slotCollectionProperties()), NormalAction }, { "akonadi_item_copy", nullptr, nullptr, "edit-copy", 0, SLOT(slotCopyItems()), NormalAction }, { "akonadi_paste", I18N_NOOP("&Paste"), I18N_NOOP("Paste"), "edit-paste", Qt::CTRL + Qt::Key_V, SLOT(slotPaste()), NormalAction }, { "akonadi_item_delete", nullptr, nullptr, "edit-delete", 0, SLOT(slotDeleteItems()), NormalAction }, { "akonadi_manage_local_subscriptions", I18N_NOOP("Manage Local &Subscriptions..."), I18N_NOOP("Manage Local Subscriptions"), "folder-bookmarks", 0, SLOT(slotLocalSubscription()), NormalAction }, { "akonadi_collection_add_to_favorites", I18N_NOOP("Add to Favorite Folders"), I18N_NOOP("Add to Favorite"), "bookmark-new", 0, SLOT(slotAddToFavorites()), NormalAction }, { "akonadi_collection_remove_from_favorites", I18N_NOOP("Remove from Favorite Folders"), I18N_NOOP("Remove from Favorite"), "edit-delete", 0, SLOT(slotRemoveFromFavorites()), NormalAction }, { "akonadi_collection_rename_favorite", I18N_NOOP("Rename Favorite..."), I18N_NOOP("Rename"), "edit-rename", 0, SLOT(slotRenameFavorite()), NormalAction }, { "akonadi_collection_copy_to_menu", I18N_NOOP("Copy Folder To..."), I18N_NOOP("Copy To"), "edit-copy", 0, SLOT(slotCopyCollectionTo(QAction*)), MenuAction }, { "akonadi_item_copy_to_menu", I18N_NOOP("Copy Item To..."), I18N_NOOP("Copy To"), "edit-copy", 0, SLOT(slotCopyItemTo(QAction*)), MenuAction }, { "akonadi_item_move_to_menu", I18N_NOOP("Move Item To..."), I18N_NOOP("Move To"), "go-jump", 0, SLOT(slotMoveItemTo(QAction*)), MenuAction }, { "akonadi_collection_move_to_menu", I18N_NOOP("Move Folder To..."), I18N_NOOP("Move To"), "go-jump", 0, SLOT(slotMoveCollectionTo(QAction*)), MenuAction }, { "akonadi_item_cut", I18N_NOOP("&Cut Item"), I18N_NOOP("Cut"), "edit-cut", Qt::CTRL + Qt::Key_X, SLOT(slotCutItems()), NormalAction }, { "akonadi_collection_cut", I18N_NOOP("&Cut Folder"), I18N_NOOP("Cut"), "edit-cut", Qt::CTRL + Qt::Key_X, SLOT(slotCutCollections()), NormalAction }, { "akonadi_resource_create", I18N_NOOP("Create Resource"), nullptr, "folder-new", 0, SLOT(slotCreateResource()), NormalAction }, { "akonadi_resource_delete", I18N_NOOP("Delete Resource"), nullptr, "edit-delete", 0, SLOT(slotDeleteResource()), NormalAction }, { "akonadi_resource_properties", I18N_NOOP("&Resource Properties"), I18N_NOOP("Properties"), "configure", 0, SLOT(slotResourceProperties()), NormalAction }, { "akonadi_resource_synchronize", I18N_NOOP("Synchronize Resource"), I18N_NOOP("Synchronize"), "view-refresh", 0, SLOT(slotSynchronizeResource()), NormalAction }, { "akonadi_work_offline", I18N_NOOP("Work Offline"), nullptr, "user-offline", 0, SLOT(slotToggleWorkOffline(bool)), ToggleAction }, { "akonadi_collection_copy_to_dialog", I18N_NOOP("Copy Folder To..."), I18N_NOOP("Copy To"), "edit-copy", 0, SLOT(slotCopyCollectionTo()), NormalAction }, { "akonadi_collection_move_to_dialog", I18N_NOOP("Move Folder To..."), I18N_NOOP("Move To"), "go-jump", 0, SLOT(slotMoveCollectionTo()), NormalAction }, { "akonadi_item_copy_to_dialog", I18N_NOOP("Copy Item To..."), I18N_NOOP("Copy To"), "edit-copy", 0, SLOT(slotCopyItemTo()), NormalAction }, { "akonadi_item_move_to_dialog", I18N_NOOP("Move Item To..."), I18N_NOOP("Move To"), "go-jump", 0, SLOT(slotMoveItemTo()), NormalAction }, { "akonadi_collection_sync_recursive", I18N_NOOP("&Synchronize Folder Recursively"), I18N_NOOP("Synchronize Recursively"), "view-refresh", Qt::CTRL + Qt::Key_F5, SLOT(slotSynchronizeCollectionRecursive()), NormalAction }, { "akonadi_move_collection_to_trash", I18N_NOOP("&Move Folder To Trash"), I18N_NOOP("Move Folder To Trash"), "edit-delete", 0, SLOT(slotMoveCollectionToTrash()), NormalAction }, { "akonadi_move_item_to_trash", I18N_NOOP("&Move Item To Trash"), I18N_NOOP("Move Item To Trash"), "edit-delete", 0, SLOT(slotMoveItemToTrash()), NormalAction }, { "akonadi_restore_collection_from_trash", I18N_NOOP("&Restore Folder From Trash"), I18N_NOOP("Restore Folder From Trash"), "view-refresh", 0, SLOT(slotRestoreCollectionFromTrash()), NormalAction }, { "akonadi_restore_item_from_trash", I18N_NOOP("&Restore Item From Trash"), I18N_NOOP("Restore Item From Trash"), "view-refresh", 0, SLOT(slotRestoreItemFromTrash()), NormalAction }, { "akonadi_collection_trash_restore", I18N_NOOP("&Restore Folder From Trash"), I18N_NOOP("Restore Folder From Trash"), "edit-delete", 0, SLOT(slotTrashRestoreCollection()), ActionWithAlternative }, { nullptr, I18N_NOOP("&Restore Collection From Trash"), I18N_NOOP("Restore Collection From Trash"), "view-refresh", 0, nullptr, ActionAlternative }, { "akonadi_item_trash_restore", I18N_NOOP("&Restore Item From Trash"), I18N_NOOP("Restore Item From Trash"), "edit-delete", 0, SLOT(slotTrashRestoreItem()), ActionWithAlternative }, { nullptr, I18N_NOOP("&Restore Item From Trash"), I18N_NOOP("Restore Item From Trash"), "view-refresh", 0, nullptr, ActionAlternative }, { "akonadi_collection_sync_favorite_folders", I18N_NOOP("&Synchronize Favorite Folders"), I18N_NOOP("Synchronize Favorite Folders"), "view-refresh", Qt::CTRL + Qt::SHIFT + Qt::Key_L, SLOT(slotSynchronizeFavoriteCollections()), NormalAction }, { "akonadi_resource_synchronize_collectiontree", I18N_NOOP("Synchronize Folder Tree"), I18N_NOOP("Synchronize"), "view-refresh", 0, SLOT(slotSynchronizeCollectionTree()), NormalAction } }; static const int numStandardActionData = sizeof standardActionData / sizeof * standardActionData; static_assert(numStandardActionData == StandardActionManager::LastType, "StandardActionData table does not match StandardActionManager types"); static bool canCreateCollection(const Akonadi::Collection &collection) { if (!(collection.rights() & Akonadi::Collection::CanCreateCollection)) { return false; } return true; } static void setWorkOffline(bool offline) { KConfig config(QStringLiteral("akonadikderc")); KConfigGroup group(&config, QStringLiteral("Actions")); group.writeEntry("WorkOffline", offline); } static bool workOffline() { KConfig config(QStringLiteral("akonadikderc")); const KConfigGroup group(&config, QStringLiteral("Actions")); return group.readEntry("WorkOffline", false); } static QModelIndexList safeSelectedRows(QItemSelectionModel *selectionModel) { QModelIndexList selectedRows = selectionModel->selectedRows(); if (!selectedRows.isEmpty()) { return selectedRows; } // try harder for selected rows that don't span the full row for some reason (e.g. due to buggy column adding proxy models etc) foreach (const QItemSelectionRange &range, selectionModel->selection()) { if (!range.isValid() || range.isEmpty()) { continue; } const QModelIndex parent = range.parent(); for (int row = range.top(); row <= range.bottom(); ++row) { const QModelIndex index = range.model()->index(row, range.left(), parent); const Qt::ItemFlags flags = range.model()->flags(index); if ((flags & Qt::ItemIsSelectable) && (flags & Qt::ItemIsEnabled)) { selectedRows.push_back(index); } } } return selectedRows; } /** * @internal */ class Q_DECL_HIDDEN StandardActionManager::Private { public: Private(StandardActionManager *parent) : q(parent) , actionCollection(nullptr) , parentWidget(nullptr) , collectionSelectionModel(nullptr) , itemSelectionModel(nullptr) , favoritesModel(nullptr) , favoriteSelectionModel(nullptr) , insideSelectionSlot(false) { actions.fill(nullptr, StandardActionManager::LastType); pluralLabels.insert(StandardActionManager::CopyCollections, ki18np("&Copy Folder", "&Copy %1 Folders")); pluralLabels.insert(StandardActionManager::CopyItems, ki18np("&Copy Item", "&Copy %1 Items")); pluralLabels.insert(StandardActionManager::CutItems, ki18np("&Cut Item", "&Cut %1 Items")); pluralLabels.insert(StandardActionManager::CutCollections, ki18np("&Cut Folder", "&Cut %1 Folders")); pluralLabels.insert(StandardActionManager::DeleteItems, ki18np("&Delete Item", "&Delete %1 Items")); pluralLabels.insert(StandardActionManager::DeleteCollections, ki18np("&Delete Folder", "&Delete %1 Folders")); pluralLabels.insert(StandardActionManager::SynchronizeCollections, ki18np("&Synchronize Folder", "&Synchronize %1 Folders")); pluralLabels.insert(StandardActionManager::DeleteResources, ki18np("&Delete Resource", "&Delete %1 Resources")); pluralLabels.insert(StandardActionManager::SynchronizeResources, ki18np("&Synchronize Resource", "&Synchronize %1 Resources")); pluralIconLabels.insert(StandardActionManager::CopyCollections, ki18np("Copy Folder", "Copy %1 Folders")); pluralIconLabels.insert(StandardActionManager::CopyItems, ki18np("Copy Item", "Copy %1 Items")); pluralIconLabels.insert(StandardActionManager::CutItems, ki18np("Cut Item", "Cut %1 Items")); pluralIconLabels.insert(StandardActionManager::CutCollections, ki18np("Cut Folder", "Cut %1 Folders")); pluralIconLabels.insert(StandardActionManager::DeleteItems, ki18np("Delete Item", "Delete %1 Items")); pluralIconLabels.insert(StandardActionManager::DeleteCollections, ki18np("Delete Folder", "Delete %1 Folders")); pluralIconLabels.insert(StandardActionManager::SynchronizeCollections, ki18np("Synchronize Folder", "Synchronize %1 Folders")); pluralIconLabels.insert(StandardActionManager::DeleteResources, ki18np("Delete Resource", "Delete %1 Resources")); pluralIconLabels.insert(StandardActionManager::SynchronizeResources, ki18np("Synchronize Resource", "Synchronize %1 Resources")); setContextText(StandardActionManager::CreateCollection, StandardActionManager::DialogTitle, i18nc("@title:window", "New Folder")); setContextText(StandardActionManager::CreateCollection, StandardActionManager::DialogText, i18nc("@label:textbox name of Akonadi folder", "Name")); setContextText(StandardActionManager::CreateCollection, StandardActionManager::ErrorMessageText, ki18n("Could not create folder: %1")); setContextText(StandardActionManager::CreateCollection, StandardActionManager::ErrorMessageTitle, i18n("Folder creation failed")); setContextText(StandardActionManager::DeleteCollections, StandardActionManager::MessageBoxText, ki18np("Do you really want to delete this folder and all its sub-folders?", "Do you really want to delete %1 folders and all their sub-folders?")); setContextText(StandardActionManager::DeleteCollections, StandardActionManager::MessageBoxTitle, ki18ncp("@title:window", "Delete folder?", "Delete folders?")); setContextText(StandardActionManager::DeleteCollections, StandardActionManager::ErrorMessageText, ki18n("Could not delete folder: %1")); setContextText(StandardActionManager::DeleteCollections, StandardActionManager::ErrorMessageTitle, i18n("Folder deletion failed")); setContextText(StandardActionManager::CollectionProperties, StandardActionManager::DialogTitle, ki18nc("@title:window", "Properties of Folder %1")); setContextText(StandardActionManager::DeleteItems, StandardActionManager::MessageBoxText, ki18np("Do you really want to delete the selected item?", "Do you really want to delete %1 items?")); setContextText(StandardActionManager::DeleteItems, StandardActionManager::MessageBoxTitle, ki18ncp("@title:window", "Delete item?", "Delete items?")); setContextText(StandardActionManager::DeleteItems, StandardActionManager::ErrorMessageText, ki18n("Could not delete item: %1")); setContextText(StandardActionManager::DeleteItems, StandardActionManager::ErrorMessageTitle, i18n("Item deletion failed")); setContextText(StandardActionManager::RenameFavoriteCollection, StandardActionManager::DialogTitle, i18nc("@title:window", "Rename Favorite")); setContextText(StandardActionManager::RenameFavoriteCollection, StandardActionManager::DialogText, i18nc("@label:textbox name of the folder", "Name:")); setContextText(StandardActionManager::CreateResource, StandardActionManager::DialogTitle, i18nc("@title:window", "New Resource")); setContextText(StandardActionManager::CreateResource, StandardActionManager::ErrorMessageText, ki18n("Could not create resource: %1")); setContextText(StandardActionManager::CreateResource, StandardActionManager::ErrorMessageTitle, i18n("Resource creation failed")); setContextText(StandardActionManager::DeleteResources, StandardActionManager::MessageBoxText, ki18np("Do you really want to delete this resource?", "Do you really want to delete %1 resources?")); setContextText(StandardActionManager::DeleteResources, StandardActionManager::MessageBoxTitle, ki18ncp("@title:window", "Delete Resource?", "Delete Resources?")); setContextText(StandardActionManager::Paste, StandardActionManager::ErrorMessageText, ki18n("Could not paste data: %1")); setContextText(StandardActionManager::Paste, StandardActionManager::ErrorMessageTitle, i18n("Paste failed")); qRegisterMetaType("Akonadi::Item::List"); } void enableAction(int type, bool enable) // private slot, called by ActionStateManager { enableAction(static_cast(type), enable); } void enableAction(StandardActionManager::Type type, bool enable) { Q_ASSERT(type < StandardActionManager::LastType); if (actions[type]) { actions[type]->setEnabled(enable); } // Update the action menu KActionMenu *actionMenu = qobject_cast(actions[type]); if (actionMenu) { //get rid of the submenus, they are re-created in enableAction. clear() is not enough, doesn't remove the submenu object instances. QMenu *menu = actionMenu->menu(); //Not necessary to delete and recreate menu when it was not created if (menu->property("actionType").isValid() && menu->isEmpty()) { return; } mRecentCollectionsMenu.remove(type); delete menu; menu = new QMenu(); menu->setProperty("actionType", static_cast(type)); q->connect(menu, SIGNAL(aboutToShow()), SLOT(aboutToShowMenu())); q->connect(menu, SIGNAL(triggered(QAction*)), standardActionData[type].slot); actionMenu->setMenu(menu); } } void aboutToShowMenu() { QMenu *menu = qobject_cast(q->sender()); if (!menu) { return; } if (!menu->isEmpty()) { return; } // collect all selected collections const Akonadi::Collection::List selectedCollectionsList = selectedCollections(); const StandardActionManager::Type type = static_cast(menu->property("actionType").toInt()); QPointer recentCollection = new RecentCollectionAction(type, selectedCollectionsList, collectionSelectionModel->model(), menu); mRecentCollectionsMenu.insert(type, recentCollection); const QSet mimeTypes = mimeTypesOfSelection(type); fillFoldersMenu(selectedCollectionsList, mimeTypes, type, menu, collectionSelectionModel->model(), QModelIndex()); } void createActionFolderMenu(QMenu *menu, StandardActionManager::Type type) { if (type == CopyCollectionToMenu || type == CopyItemToMenu || type == MoveItemToMenu || type == MoveCollectionToMenu) { new RecentCollectionAction(type, Akonadi::Collection::List(), collectionSelectionModel->model(), menu); Collection::List selectedCollectionsList = selectedCollections(); const QSet mimeTypes = mimeTypesOfSelection(type); fillFoldersMenu(selectedCollectionsList, mimeTypes, type, menu, collectionSelectionModel->model(), QModelIndex()); } } void updateAlternatingAction(int type) // private slot, called by ActionStateManager { updateAlternatingAction(static_cast(type)); } void updateAlternatingAction(StandardActionManager::Type type) { Q_ASSERT(type < StandardActionManager::LastType); if (!actions[type]) { return; } /* * The same action is stored at the ActionWithAlternative indexes as well as the corresponding ActionAlternative indexes in the actions array. * The following simply changes the standardActionData */ if ((standardActionData[type].actionType == ActionWithAlternative) || (standardActionData[type].actionType == ActionAlternative)) { actions[type]->setText(i18n(standardActionData[type].label)); actions[type]->setIcon(QIcon::fromTheme(QString::fromLatin1(standardActionData[type].icon))); if (pluralLabels.contains(type) && !pluralLabels.value(type).isEmpty()) { actions[type]->setText(pluralLabels.value(type).subs(1).toString()); } else if (standardActionData[type].label) { actions[type]->setText(i18n(standardActionData[type].label)); } if (pluralIconLabels.contains(type) && !pluralIconLabels.value(type).isEmpty()) { actions[type]->setIconText(pluralIconLabels.value(type).subs(1).toString()); } else if (standardActionData[type].iconLabel) { actions[type]->setIconText(i18n(standardActionData[type].iconLabel)); } if (standardActionData[type].icon) { actions[type]->setIcon(QIcon::fromTheme(QString::fromLatin1(standardActionData[type].icon))); } //actions[type]->setShortcut( standardActionData[type].shortcut ); /*if ( standardActionData[type].slot ) { switch ( standardActionData[type].actionType ) { case NormalAction: case ActionWithAlternative: connect( action, SIGNAL(triggered()), standardActionData[type].slot ); break; } }*/ } } void updatePluralLabel(int type, int count) // private slot, called by ActionStateManager { updatePluralLabel(static_cast(type), count); } void updatePluralLabel(StandardActionManager::Type type, int count) // private slot, called by ActionStateManager { Q_ASSERT(type < StandardActionManager::LastType); if (actions[type] && pluralLabels.contains(type) && !pluralLabels.value(type).isEmpty()) { actions[type]->setText(pluralLabels.value(type).subs(qMax(count, 1)).toString()); } } bool isFavoriteCollection(const Akonadi::Collection &collection) // private slot, called by ActionStateManager { if (!favoritesModel) { return false; } return favoritesModel->collectionIds().contains(collection.id()); } void encodeToClipboard(QItemSelectionModel *selectionModel, bool cut = false) { Q_ASSERT(selectionModel); if (safeSelectedRows(selectionModel).isEmpty()) { return; } #ifndef QT_NO_CLIPBOARD QAbstractItemModel *model = const_cast(selectionModel->model()); QMimeData *mimeData = selectionModel->model()->mimeData(safeSelectedRows(selectionModel)); model->setData(QModelIndex(), false, EntityTreeModel::PendingCutRole); markCutAction(mimeData, cut); QApplication::clipboard()->setMimeData(mimeData); foreach (const QModelIndex &index, safeSelectedRows(selectionModel)) { model->setData(index, true, EntityTreeModel::PendingCutRole); } #endif } static Akonadi::Collection::List collectionsForIndexes(const QModelIndexList& list) { Akonadi::Collection::List collectionList; for (const QModelIndex &index : list) { Collection collection = index.data(EntityTreeModel::CollectionRole).value(); if (!collection.isValid()) { continue; } const Collection parentCollection = index.data(EntityTreeModel::ParentCollectionRole).value(); collection.setParentCollection(parentCollection); collectionList << std::move(collection); } return collectionList; } void updateActions() { // favorite collections Collection::List selectedFavoriteCollectionsList; if (favoriteSelectionModel) { const QModelIndexList rows = safeSelectedRows(favoriteSelectionModel); selectedFavoriteCollectionsList = collectionsForIndexes(rows); } // collect all selected collections Collection::List selectedCollectionsList; if (collectionSelectionModel) { const QModelIndexList rows = safeSelectedRows(collectionSelectionModel); selectedCollectionsList = collectionsForIndexes(rows); } // collect all selected items Item::List selectedItems; if (itemSelectionModel) { const QModelIndexList rows = safeSelectedRows(itemSelectionModel); for (const QModelIndex &index : rows) { Item item = index.data(EntityTreeModel::ItemRole).value(); if (!item.isValid()) { continue; } const Collection parentCollection = index.data(EntityTreeModel::ParentCollectionRole).value(); item.setParentCollection(parentCollection); selectedItems << item; } } mActionStateManager.updateState(selectedCollectionsList, selectedFavoriteCollectionsList, selectedItems); if (favoritesModel) { enableAction(StandardActionManager::SynchronizeFavoriteCollections, (favoritesModel->rowCount() > 0)); } - emit q->actionStateUpdated(); + Q_EMIT q->actionStateUpdated(); } #ifndef QT_NO_CLIPBOARD void clipboardChanged(QClipboard::Mode mode) { if (mode == QClipboard::Clipboard) { updateActions(); } } #endif QItemSelection mapToEntityTreeModel(const QAbstractItemModel *model, const QItemSelection &selection) const { const QAbstractProxyModel *proxy = qobject_cast(model); if (proxy) { return mapToEntityTreeModel(proxy->sourceModel(), proxy->mapSelectionToSource(selection)); } else { return selection; } } QItemSelection mapFromEntityTreeModel(const QAbstractItemModel *model, const QItemSelection &selection) const { const QAbstractProxyModel *proxy = qobject_cast(model); if (proxy) { const QItemSelection select = mapFromEntityTreeModel(proxy->sourceModel(), selection); return proxy->mapSelectionFromSource(select); } else { return selection; } } // RAII class for setting insideSelectionSlot to true on entering, and false on exiting, the two slots below. class InsideSelectionSlotBlocker { public: InsideSelectionSlotBlocker(Private *p) : _p(p) { Q_ASSERT(!p->insideSelectionSlot); p->insideSelectionSlot = true; } ~InsideSelectionSlotBlocker() { Q_ASSERT(_p->insideSelectionSlot); _p->insideSelectionSlot = false; } private: Q_DISABLE_COPY(InsideSelectionSlotBlocker) Private *_p; }; void collectionSelectionChanged() { if (insideSelectionSlot) { return; } InsideSelectionSlotBlocker block(this); if (favoriteSelectionModel) { QItemSelection selection = collectionSelectionModel->selection(); selection = mapToEntityTreeModel(collectionSelectionModel->model(), selection); selection = mapFromEntityTreeModel(favoriteSelectionModel->model(), selection); favoriteSelectionModel->select(selection, QItemSelectionModel::ClearAndSelect); } updateActions(); } void favoriteSelectionChanged() { if (insideSelectionSlot) { return; } QItemSelection selection = favoriteSelectionModel->selection(); if (selection.isEmpty()) { return; } selection = mapToEntityTreeModel(favoriteSelectionModel->model(), selection); selection = mapFromEntityTreeModel(collectionSelectionModel->model(), selection); InsideSelectionSlotBlocker block(this); collectionSelectionModel->select(selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); // Also set the current index. This will trigger KMMainWidget::slotFolderChanged in kmail, which we want. if (!selection.indexes().isEmpty()) { collectionSelectionModel->setCurrentIndex(selection.indexes().first(), QItemSelectionModel::NoUpdate); } updateActions(); } void slotCreateCollection() { Q_ASSERT(collectionSelectionModel); if (collectionSelectionModel->selection().indexes().isEmpty()) { return; } const QModelIndex index = collectionSelectionModel->selection().indexes().at(0); Q_ASSERT(index.isValid()); const Collection parentCollection = index.data(CollectionModel::CollectionRole).value(); Q_ASSERT(parentCollection.isValid()); if (!canCreateCollection(parentCollection)) { return; } QString name = QInputDialog::getText(parentWidget, contextText(StandardActionManager::CreateCollection, StandardActionManager::DialogTitle), contextText(StandardActionManager::CreateCollection, StandardActionManager::DialogText)); name = name.trimmed(); if (name.isEmpty()) { return; } if (name.contains(QLatin1Char('/'))) { KMessageBox::error(parentWidget, i18n("We can not add \"/\" in folder name."), i18n("Create new folder error")); return; } if (name.startsWith(QLatin1Char('.')) || name.endsWith(QLatin1Char('.'))) { KMessageBox::error(parentWidget, i18n("We can not add \".\" at begin or end of folder name."), i18n("Create new folder error")); return; } Collection collection; collection.setName(name); collection.setParentCollection(parentCollection); if (actions[StandardActionManager::CreateCollection]) { const QStringList mts = actions[StandardActionManager::CreateCollection]->property("ContentMimeTypes").toStringList(); if (!mts.isEmpty()) { collection.setContentMimeTypes(mts); } } if (parentCollection.contentMimeTypes().contains(Collection::virtualMimeType())) { collection.setVirtual(true); collection.setContentMimeTypes(collection.contentMimeTypes() << Collection::virtualMimeType()); } CollectionCreateJob *job = new CollectionCreateJob(collection); q->connect(job, SIGNAL(result(KJob*)), q, SLOT(collectionCreationResult(KJob*))); } void slotCopyCollections() { encodeToClipboard(collectionSelectionModel); } void slotCutCollections() { encodeToClipboard(collectionSelectionModel, true); } Collection::List selectedCollections() { Collection::List collections; Q_ASSERT(collectionSelectionModel); const QModelIndexList indexes = safeSelectedRows(collectionSelectionModel); collections.reserve(indexes.count()); for (const QModelIndex &index : indexes) { Q_ASSERT(index.isValid()); const Collection collection = index.data(CollectionModel::CollectionRole).value(); Q_ASSERT(collection.isValid()); collections << collection; } return collections; } void slotDeleteCollection() { const Collection::List collections = selectedCollections(); if (collections.isEmpty()) { return; } const QString collectionName = collections.first().name(); const QString text = contextText(StandardActionManager::DeleteCollections, StandardActionManager::MessageBoxText, collections.count(), collectionName); if (KMessageBox::questionYesNo(parentWidget, text, contextText(StandardActionManager::DeleteCollections, StandardActionManager::MessageBoxTitle, collections.count(), collectionName), KStandardGuiItem::del(), KStandardGuiItem::cancel(), QString(), KMessageBox::Dangerous) != KMessageBox::Yes) { return; } for (const Collection &collection : collections) { CollectionDeleteJob *job = new CollectionDeleteJob(collection, q); q->connect(job, &CollectionDeleteJob::result, q, [this](KJob* job) { collectionDeletionResult(job); }); } } void slotMoveCollectionToTrash() { const Collection::List collections = selectedCollections(); if (collections.isEmpty()) { return; } for (const Collection &collection : collections) { TrashJob *job = new TrashJob(collection, q); q->connect(job, &TrashJob::result, q, [this](KJob *job) { moveCollectionToTrashResult(job);}); } } void slotRestoreCollectionFromTrash() { const Collection::List collections = selectedCollections(); if (collections.isEmpty()) { return; } for (const Collection &collection : collections) { TrashRestoreJob *job = new TrashRestoreJob(collection, q); q->connect(job, &TrashRestoreJob::result, q, [this](KJob*job) {moveCollectionToTrashResult(job);}); } } Item::List selectedItems() const { Item::List items; Q_ASSERT(itemSelectionModel); const QModelIndexList indexes = safeSelectedRows(itemSelectionModel); items.reserve(indexes.count()); for (const QModelIndex &index : indexes) { Q_ASSERT(index.isValid()); const Item item = index.data(ItemModel::ItemRole).value(); Q_ASSERT(item.isValid()); items << item; } return items; } void slotMoveItemToTrash() { const Item::List items = selectedItems(); if (items.isEmpty()) { return; } TrashJob *job = new TrashJob(items, q); q->connect(job, &TrashJob::result, q, [this](KJob *job) {moveItemToTrashResult(job); }); } void slotRestoreItemFromTrash() { const Item::List items = selectedItems(); if (items.isEmpty()) { return; } TrashRestoreJob *job = new TrashRestoreJob(items, q); q->connect(job, &TrashRestoreJob::result, q, [this](KJob *job) {moveItemToTrashResult(job);}); } void slotTrashRestoreCollection() { const Collection::List collections = selectedCollections(); if (collections.isEmpty()) { return; } bool collectionsAreInTrash = false; for (const Collection &collection : collections) { if (collection.hasAttribute()) { collectionsAreInTrash = true; break; } } if (collectionsAreInTrash) { slotRestoreCollectionFromTrash(); } else { slotMoveCollectionToTrash(); } } void slotTrashRestoreItem() { const Item::List items = selectedItems(); if (items.isEmpty()) { return; } bool itemsAreInTrash = false; for (const Item &item : items) { if (item.hasAttribute()) { itemsAreInTrash = true; break; } } if (itemsAreInTrash) { slotRestoreItemFromTrash(); } else { slotMoveItemToTrash(); } } void slotSynchronizeCollection() { Q_ASSERT(collectionSelectionModel); const QModelIndexList list = safeSelectedRows(collectionSelectionModel); if (list.isEmpty()) { return; } const Collection::List collections = selectedCollections(); if (collections.isEmpty()) { return; } for (const Collection &collection : collections) { if (!testAndSetOnlineResources(collection)) { break; } AgentManager::self()->synchronizeCollection(collection, false); } } bool testAndSetOnlineResources(const Akonadi::Collection &collection) { // Shortcut for the Search resource, which is a virtual resource and thus // is always online (but AgentManager does not know about it, so it returns // an invalid AgentInstance, which is "offline"). // // FIXME: AgentManager should return a valid AgentInstance even // for virtual resources, which would be always online. if (collection.resource() == QLatin1String("akonadi_search_resource")) { return true; } Akonadi::AgentInstance instance = Akonadi::AgentManager::self()->instance(collection.resource()); if (!instance.isOnline()) { if (KMessageBox::questionYesNo(parentWidget, i18n("Before syncing folder \"%1\" it is necessary to have the resource online. Do you want to make it online?", collection.displayName()), i18n("Account \"%1\" is offline", instance.name()), KGuiItem(i18nc("@action:button", "Go Online")), KStandardGuiItem::cancel()) != KMessageBox::Yes) { return false; } instance.setIsOnline(true); } return true; } void slotSynchronizeCollectionRecursive() { Q_ASSERT(collectionSelectionModel); const QModelIndexList list = safeSelectedRows(collectionSelectionModel); if (list.isEmpty()) { return; } const Collection::List collections = selectedCollections(); if (collections.isEmpty()) { return; } for (const Collection &collection : collections) { if (!testAndSetOnlineResources(collection)) { break; } AgentManager::self()->synchronizeCollection(collection, true); } } void slotCollectionProperties() { const QModelIndexList list = safeSelectedRows(collectionSelectionModel); if (list.isEmpty()) { return; } const QModelIndex index = list.first(); Q_ASSERT(index.isValid()); const Collection collection = index.data(CollectionModel::CollectionRole).value(); Q_ASSERT(collection.isValid()); CollectionPropertiesDialog *dlg = new CollectionPropertiesDialog(collection, mCollectionPropertiesPageNames, parentWidget); dlg->setWindowTitle(contextText(StandardActionManager::CollectionProperties, StandardActionManager::DialogTitle, collection.displayName())); dlg->show(); } void slotCopyItems() { encodeToClipboard(itemSelectionModel); } void slotCutItems() { encodeToClipboard(itemSelectionModel, true); } void slotPaste() { Q_ASSERT(collectionSelectionModel); const QModelIndexList list = safeSelectedRows(collectionSelectionModel); if (list.isEmpty()) { return; } const QModelIndex index = list.first(); Q_ASSERT(index.isValid()); #ifndef QT_NO_CLIPBOARD // TODO: Copy or move? We can't seem to cut yet QAbstractItemModel *model = const_cast(collectionSelectionModel->model()); const QMimeData *mimeData = QApplication::clipboard()->mimeData(); model->dropMimeData(mimeData, isCutAction(mimeData) ? Qt::MoveAction : Qt::CopyAction, -1, -1, index); model->setData(QModelIndex(), false, EntityTreeModel::PendingCutRole); QApplication::clipboard()->clear(); #endif } void slotDeleteItems() { Q_ASSERT(itemSelectionModel); Item::List items; const QModelIndexList indexes = safeSelectedRows(itemSelectionModel); items.reserve(indexes.count()); for (const QModelIndex &index : indexes) { bool ok; const qlonglong id = index.data(ItemModel::IdRole).toLongLong(&ok); Q_ASSERT(ok); items << Item(id); } if (items.isEmpty()) { return; } QMetaObject::invokeMethod(q, [this, items] {slotDeleteItemsDeferred(items); }, Qt::QueuedConnection); } void slotDeleteItemsDeferred(const Akonadi::Item::List &items) { Q_ASSERT(itemSelectionModel); if (KMessageBox::questionYesNo(parentWidget, contextText(StandardActionManager::DeleteItems, StandardActionManager::MessageBoxText, items.count(), QString()), contextText(StandardActionManager::DeleteItems, StandardActionManager::MessageBoxTitle, items.count(), QString()), KStandardGuiItem::del(), KStandardGuiItem::cancel(), QString(), KMessageBox::Dangerous) != KMessageBox::Yes) { return; } ItemDeleteJob *job = new ItemDeleteJob(items, q); q->connect(job, &ItemDeleteJob::result, q, [this](KJob*job) {itemDeletionResult(job);}); } void slotLocalSubscription() { SubscriptionDialog *dlg = new SubscriptionDialog(mMimeTypeFilter, parentWidget); dlg->showHiddenCollection(true); dlg->show(); } void slotAddToFavorites() { Q_ASSERT(collectionSelectionModel); Q_ASSERT(favoritesModel); const QModelIndexList list = safeSelectedRows(collectionSelectionModel); if (list.isEmpty()) { return; } for (const QModelIndex &index : list) { Q_ASSERT(index.isValid()); const Collection collection = index.data(CollectionModel::CollectionRole).value(); Q_ASSERT(collection.isValid()); favoritesModel->addCollection(collection); } updateActions(); } void slotRemoveFromFavorites() { Q_ASSERT(favoriteSelectionModel); Q_ASSERT(favoritesModel); const QModelIndexList list = safeSelectedRows(favoriteSelectionModel); if (list.isEmpty()) { return; } for (const QModelIndex &index : list) { Q_ASSERT(index.isValid()); const Collection collection = index.data(CollectionModel::CollectionRole).value(); Q_ASSERT(collection.isValid()); favoritesModel->removeCollection(collection); } updateActions(); } void slotRenameFavorite() { Q_ASSERT(favoriteSelectionModel); Q_ASSERT(favoritesModel); const QModelIndexList list = safeSelectedRows(favoriteSelectionModel); if (list.isEmpty()) { return; } const QModelIndex index = list.first(); Q_ASSERT(index.isValid()); const Collection collection = index.data(CollectionModel::CollectionRole).value(); Q_ASSERT(collection.isValid()); QPointer dlg(new RenameFavoriteDialog(contextText(StandardActionManager::RenameFavoriteCollection, StandardActionManager::DialogTitle), contextText(StandardActionManager::RenameFavoriteCollection, StandardActionManager::DialogText), favoritesModel->favoriteLabel(collection), collection.displayName(), parentWidget)); if (dlg->exec() == QDialog::Accepted) { favoritesModel->setFavoriteLabel(collection, dlg->newName()); } delete dlg; } void slotSynchronizeFavoriteCollections() { Q_ASSERT(favoritesModel); foreach (const Collection &collection, favoritesModel->collections()) { // there might be virtual collections in favorites which cannot be checked // so let's be safe here, agentmanager asserts otherwise if (!collection.resource().isEmpty()) { AgentManager::self()->synchronizeCollection(collection, false); } } } void slotCopyCollectionTo() { pasteTo(collectionSelectionModel, collectionSelectionModel->model(), CopyCollectionToMenu, Qt::CopyAction); } void slotCopyItemTo() { pasteTo(itemSelectionModel, collectionSelectionModel->model(), CopyItemToMenu, Qt::CopyAction); } void slotMoveCollectionTo() { pasteTo(collectionSelectionModel, collectionSelectionModel->model(), MoveCollectionToMenu, Qt::MoveAction); } void slotMoveItemTo() { pasteTo(itemSelectionModel, collectionSelectionModel->model(), MoveItemToMenu, Qt::MoveAction); } void slotCopyCollectionTo(QAction *action) { pasteTo(collectionSelectionModel, action, Qt::CopyAction); } void slotCopyItemTo(QAction *action) { pasteTo(itemSelectionModel, action, Qt::CopyAction); } void slotMoveCollectionTo(QAction *action) { pasteTo(collectionSelectionModel, action, Qt::MoveAction); } void slotMoveItemTo(QAction *action) { pasteTo(itemSelectionModel, action, Qt::MoveAction); } AgentInstance::List selectedAgentInstances() const { AgentInstance::List instances; Q_ASSERT(collectionSelectionModel); if (collectionSelectionModel->selection().indexes().isEmpty()) { return instances; } const QModelIndexList lstIndexes = collectionSelectionModel->selection().indexes(); for (const QModelIndex &index : lstIndexes) { Q_ASSERT(index.isValid()); const Collection collection = index.data(CollectionModel::CollectionRole).value(); Q_ASSERT(collection.isValid()); if (collection.isValid()) { const QString identifier = collection.resource(); instances << AgentManager::self()->instance(identifier); } } return instances; } AgentInstance selectedAgentInstance() const { const AgentInstance::List instances = selectedAgentInstances(); if (instances.isEmpty()) { return AgentInstance(); } return instances.first(); } void slotCreateResource() { QPointer dlg(new Akonadi::AgentTypeDialog(parentWidget)); dlg->setWindowTitle(contextText(StandardActionManager::CreateResource, StandardActionManager::DialogTitle)); for (const QString &mimeType : qAsConst(mMimeTypeFilter)) { dlg->agentFilterProxyModel()->addMimeTypeFilter(mimeType); } for (const QString &capability : qAsConst(mCapabilityFilter)) { dlg->agentFilterProxyModel()->addCapabilityFilter(capability); } if (dlg->exec() == QDialog::Accepted) { const AgentType agentType = dlg->agentType(); if (agentType.isValid()) { AgentInstanceCreateJob *job = new AgentInstanceCreateJob(agentType, q); q->connect(job, SIGNAL(result(KJob*)), SLOT(resourceCreationResult(KJob*))); job->configure(parentWidget); job->start(); } } delete dlg; } void slotDeleteResource() { const AgentInstance::List instances = selectedAgentInstances(); if (instances.isEmpty()) { return; } if (KMessageBox::questionYesNo(parentWidget, contextText(StandardActionManager::DeleteResources, StandardActionManager::MessageBoxText, instances.count(), instances.first().name()), contextText(StandardActionManager::DeleteResources, StandardActionManager::MessageBoxTitle, instances.count(), instances.first().name()), KStandardGuiItem::del(), KStandardGuiItem::cancel(), QString(), KMessageBox::Dangerous) != KMessageBox::Yes) { return; } for (const AgentInstance &instance : instances) { AgentManager::self()->removeInstance(instance); } } void slotSynchronizeResource() { const AgentInstance::List instances = selectedAgentInstances(); if (instances.isEmpty()) { return; } for (AgentInstance instance : instances) { instance.synchronize(); } } void slotSynchronizeCollectionTree() { const AgentInstance::List instances = selectedAgentInstances(); if (instances.isEmpty()) { return; } for (AgentInstance instance : instances) { instance.synchronizeCollectionTree(); } } void slotResourceProperties() { AgentInstance instance = selectedAgentInstance(); if (!instance.isValid()) { return; } instance.configure(parentWidget); } void slotToggleWorkOffline(bool offline) { setWorkOffline(offline); const AgentInstance::List instances = AgentManager::self()->instances(); for (AgentInstance instance : instances) { instance.setIsOnline(!offline); } } void pasteTo(QItemSelectionModel *selectionModel, const QAbstractItemModel *model, StandardActionManager::Type type, Qt::DropAction dropAction) { const QSet mimeTypes = mimeTypesOfSelection(type); QPointer dlg(new CollectionDialog(const_cast(model))); dlg->setMimeTypeFilter(mimeTypes.toList()); if (type == CopyItemToMenu || type == MoveItemToMenu) { dlg->setAccessRightsFilter(Collection::CanCreateItem); } else if (type == CopyCollectionToMenu || type == MoveCollectionToMenu) { dlg->setAccessRightsFilter(Collection::CanCreateCollection); } if (dlg->exec() == QDialog::Accepted && dlg != nullptr) { const QModelIndex index = EntityTreeModel::modelIndexForCollection(collectionSelectionModel->model(), dlg->selectedCollection()); if (!index.isValid()) { delete dlg; return; } const QMimeData *mimeData = selectionModel->model()->mimeData(safeSelectedRows(selectionModel)); QAbstractItemModel *model = const_cast(index.model()); model->dropMimeData(mimeData, dropAction, -1, -1, index); } delete dlg; } void pasteTo(QItemSelectionModel *selectionModel, QAction *action, Qt::DropAction dropAction) { Q_ASSERT(selectionModel); Q_ASSERT(action); if (safeSelectedRows(selectionModel).count() <= 0) { return; } const QMimeData *mimeData = selectionModel->model()->mimeData(selectionModel->selectedRows()); const QModelIndex index = action->data().toModelIndex(); Q_ASSERT(index.isValid()); QAbstractItemModel *model = const_cast(index.model()); const Collection collection = index.data(EntityTreeModel::CollectionRole).value(); addRecentCollection(collection.id()); model->dropMimeData(mimeData, dropAction, -1, -1, index); } void addRecentCollection(Akonadi::Collection::Id id) { QMapIterator > item(mRecentCollectionsMenu); while (item.hasNext()) { item.next(); if (item.value().data()) { item.value().data()->addRecentCollection(item.key(), id); } } } void collectionCreationResult(KJob *job) { if (job->error()) { KMessageBox::error(parentWidget, contextText(StandardActionManager::CreateCollection, StandardActionManager::ErrorMessageText, job->errorString()), contextText(StandardActionManager::CreateCollection, StandardActionManager::ErrorMessageTitle)); } } void collectionDeletionResult(KJob *job) { if (job->error()) { KMessageBox::error(parentWidget, contextText(StandardActionManager::DeleteCollections, StandardActionManager::ErrorMessageText, job->errorString()), contextText(StandardActionManager::DeleteCollections, StandardActionManager::ErrorMessageTitle)); } } void moveCollectionToTrashResult(KJob *job) { if (job->error()) { KMessageBox::error(parentWidget, contextText(StandardActionManager::MoveCollectionsToTrash, StandardActionManager::ErrorMessageText, job->errorString()), contextText(StandardActionManager::MoveCollectionsToTrash, StandardActionManager::ErrorMessageTitle)); } } void moveItemToTrashResult(KJob *job) { if (job->error()) { KMessageBox::error(parentWidget, contextText(StandardActionManager::MoveItemsToTrash, StandardActionManager::ErrorMessageText, job->errorString()), contextText(StandardActionManager::MoveItemsToTrash, StandardActionManager::ErrorMessageTitle)); } } void itemDeletionResult(KJob *job) { if (job->error()) { KMessageBox::error(parentWidget, contextText(StandardActionManager::DeleteItems, StandardActionManager::ErrorMessageText, job->errorString()), contextText(StandardActionManager::DeleteItems, StandardActionManager::ErrorMessageTitle)); } } void resourceCreationResult(KJob *job) { if (job->error()) { KMessageBox::error(parentWidget, contextText(StandardActionManager::CreateResource, StandardActionManager::ErrorMessageText, job->errorString()), contextText(StandardActionManager::CreateResource, StandardActionManager::ErrorMessageTitle)); } } void pasteResult(KJob *job) { if (job->error()) { KMessageBox::error(parentWidget, contextText(StandardActionManager::Paste, StandardActionManager::ErrorMessageText, job->errorString()), contextText(StandardActionManager::Paste, StandardActionManager::ErrorMessageTitle)); } } /** * Returns a set of mime types of the entities that are currently selected. */ QSet mimeTypesOfSelection(StandardActionManager::Type type) const { QModelIndexList list; QSet mimeTypes; const bool isItemAction = (type == CopyItemToMenu || type == MoveItemToMenu); const bool isCollectionAction = (type == CopyCollectionToMenu || type == MoveCollectionToMenu); if (isItemAction) { list = safeSelectedRows(itemSelectionModel); mimeTypes.reserve(list.count()); for (const QModelIndex &index : qAsConst(list)) { mimeTypes << index.data(EntityTreeModel::MimeTypeRole).toString(); } } if (isCollectionAction) { list = safeSelectedRows(collectionSelectionModel); for (const QModelIndex &index : qAsConst(list)) { const Collection collection = index.data(EntityTreeModel::CollectionRole).value(); // The mimetypes that the selected collection can possibly contain mimeTypes = AgentManager::self()->instance(collection.resource()).type().mimeTypes().toSet(); } } return mimeTypes; } /** * Returns whether items with the given @p mimeTypes can be written to the given @p collection. */ bool isWritableTargetCollectionForMimeTypes(const Collection &collection, const QSet &mimeTypes, StandardActionManager::Type type) const { if (collection.isVirtual()) { return false; } const bool isItemAction = (type == CopyItemToMenu || type == MoveItemToMenu); const bool isCollectionAction = (type == CopyCollectionToMenu || type == MoveCollectionToMenu); const bool canContainRequiredMimeTypes = collection.contentMimeTypes().toSet().intersects(mimeTypes); const bool canCreateNewItems = (collection.rights() & Collection::CanCreateItem); const bool canCreateNewCollections = (collection.rights() & Collection::CanCreateCollection); const bool canContainCollections = collection.contentMimeTypes().contains(Collection::mimeType()) || collection.contentMimeTypes().contains(Collection::virtualMimeType()); const bool resourceAllowsRequiredMimeTypes = AgentManager::self()->instance(collection.resource()).type().mimeTypes().toSet().contains(mimeTypes); const bool isReadOnlyForItems = (isItemAction && (!canCreateNewItems || !canContainRequiredMimeTypes)); const bool isReadOnlyForCollections = (isCollectionAction && (!canCreateNewCollections || !canContainCollections || !resourceAllowsRequiredMimeTypes)); return !(CollectionUtils::isStructural(collection) || isReadOnlyForItems || isReadOnlyForCollections); } void fillFoldersMenu(const Akonadi::Collection::List &selectedCollectionsList, const QSet &mimeTypes, StandardActionManager::Type type, QMenu *menu, const QAbstractItemModel *model, const QModelIndex &parentIndex) { const int rowCount = model->rowCount(parentIndex); for (int row = 0; row < rowCount; ++row) { const QModelIndex index = model->index(row, 0, parentIndex); const Collection collection = model->data(index, CollectionModel::CollectionRole).value(); if (collection.isVirtual()) { continue; } const bool readOnly = !isWritableTargetCollectionForMimeTypes(collection, mimeTypes, type); const bool collectionIsSelected = selectedCollectionsList.contains(collection); if (type == MoveCollectionToMenu && collectionIsSelected) { continue; } QString label = model->data(index).toString(); label.replace(QLatin1Char('&'), QStringLiteral("&&")); const QIcon icon = model->data(index, Qt::DecorationRole).value(); if (model->rowCount(index) > 0) { // new level QMenu *popup = new QMenu(menu); const bool moveAction = (type == MoveCollectionToMenu || type == MoveItemToMenu); popup->setObjectName(QStringLiteral("subMenu")); popup->setTitle(label); popup->setIcon(icon); fillFoldersMenu(selectedCollectionsList, mimeTypes, type, popup, model, index); if (!(type == CopyCollectionToMenu && collectionIsSelected)) { if (!readOnly) { popup->addSeparator(); QAction *action = popup->addAction(moveAction ? i18n("Move to This Folder") : i18n("Copy to This Folder")); action->setData(QVariant::fromValue(index)); } } if (!popup->isEmpty()) { menu->addMenu(popup); } } else { // insert an item QAction *action = menu->addAction(icon, label); action->setData(QVariant::fromValue(index)); action->setEnabled(!readOnly && !collectionIsSelected); } } } void checkModelsConsistency() { if (favoritesModel == nullptr || favoriteSelectionModel == nullptr) { // No need to check when the favorite collections feature is not used return; } // find the base ETM of the favourites view const QAbstractItemModel *favModel = favoritesModel; while (const QAbstractProxyModel *proxy = qobject_cast(favModel)) { favModel = proxy->sourceModel(); } // Check that the collection selection model maps to the same // EntityTreeModel than favoritesModel if (collectionSelectionModel != nullptr) { const QAbstractItemModel *model = collectionSelectionModel->model(); while (const QAbstractProxyModel *proxy = qobject_cast(model)) { model = proxy->sourceModel(); } Q_ASSERT(model == favModel); } // Check that the favorite selection model maps to favoritesModel const QAbstractItemModel *model = favoriteSelectionModel->model(); while (const QAbstractProxyModel *proxy = qobject_cast(model)) { model = proxy->sourceModel(); } Q_ASSERT(model == favModel); } void markCutAction(QMimeData *mimeData, bool cut) const { if (!cut) { return; } const QByteArray cutSelectionData = "1"; //krazy:exclude=doublequote_chars mimeData->setData(QStringLiteral("application/x-kde.akonadi-cutselection"), cutSelectionData); } bool isCutAction(const QMimeData *mimeData) const { const QByteArray data = mimeData->data(QStringLiteral("application/x-kde.akonadi-cutselection")); if (data.isEmpty()) { return false; } else { return (data.at(0) == '1'); // true if 1 } } void setContextText(StandardActionManager::Type type, StandardActionManager::TextContext context, const QString &data) { ContextTextEntry entry; entry.text = data; contextTexts[type].insert(context, entry); } void setContextText(StandardActionManager::Type type, StandardActionManager::TextContext context, const KLocalizedString &data) { ContextTextEntry entry; entry.localizedText = data; contextTexts[type].insert(context, entry); } QString contextText(StandardActionManager::Type type, StandardActionManager::TextContext context) const { return contextTexts[type].value(context).text; } QString contextText(StandardActionManager::Type type, StandardActionManager::TextContext context, const QString &value) const { KLocalizedString text = contextTexts[type].value(context).localizedText; if (text.isEmpty()) { return contextTexts[type].value(context).text; } return text.subs(value).toString(); } QString contextText(StandardActionManager::Type type, StandardActionManager::TextContext context, int count, const QString &value) const { KLocalizedString text = contextTexts[type].value(context).localizedText; if (text.isEmpty()) { return contextTexts[type].value(context).text; } const QString str = text.subs(count).toString(); const int argCount = str.count(QRegExp(QStringLiteral("%[0-9]"))); if (argCount > 0) { return text.subs(count).subs(value).toString(); } else { return text.subs(count).toString(); } } StandardActionManager *q; KActionCollection *actionCollection; QWidget *parentWidget; QItemSelectionModel *collectionSelectionModel; QItemSelectionModel *itemSelectionModel; FavoriteCollectionsModel *favoritesModel; QItemSelectionModel *favoriteSelectionModel; bool insideSelectionSlot; QVector actions; QHash pluralLabels; QHash pluralIconLabels; struct ContextTextEntry { QString text; KLocalizedString localizedText; bool isLocalized; }; typedef QHash ContextTexts; QHash contextTexts; ActionStateManager mActionStateManager; QStringList mMimeTypeFilter; QStringList mCapabilityFilter; QStringList mCollectionPropertiesPageNames; QMap > mRecentCollectionsMenu; }; //@endcond StandardActionManager::StandardActionManager(KActionCollection *actionCollection, QWidget *parent) : QObject(parent) , d(new Private(this)) { d->parentWidget = parent; d->actionCollection = actionCollection; d->mActionStateManager.setReceiver(this); #ifndef QT_NO_CLIPBOARD connect(QApplication::clipboard(), SIGNAL(changed(QClipboard::Mode)), SLOT(clipboardChanged(QClipboard::Mode))); #endif } StandardActionManager::~StandardActionManager() { delete d; } void StandardActionManager::setCollectionSelectionModel(QItemSelectionModel *selectionModel) { d->collectionSelectionModel = selectionModel; connect(selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(collectionSelectionChanged())); d->checkModelsConsistency(); } void StandardActionManager::setItemSelectionModel(QItemSelectionModel *selectionModel) { d->itemSelectionModel = selectionModel; connect(selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(updateActions())); } void StandardActionManager::setFavoriteCollectionsModel(FavoriteCollectionsModel *favoritesModel) { d->favoritesModel = favoritesModel; d->checkModelsConsistency(); } void StandardActionManager::setFavoriteSelectionModel(QItemSelectionModel *selectionModel) { d->favoriteSelectionModel = selectionModel; connect(selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SLOT(favoriteSelectionChanged())); d->checkModelsConsistency(); } QAction *StandardActionManager::createAction(Type type) { Q_ASSERT(type < LastType); if (d->actions[type]) { return d->actions[type]; } QAction *action = nullptr; switch (standardActionData[type].actionType) { case NormalAction: case ActionWithAlternative: action = new QAction(d->parentWidget); break; case ActionAlternative: d->actions[type] = d->actions[type - 1]; Q_ASSERT(d->actions[type]); if ((LastType > type + 1) && (standardActionData[type + 1].actionType == ActionAlternative)) { createAction(static_cast(type + 1)); //ensure that alternative actions are initialized when not created by createAllActions } return d->actions[type]; case MenuAction: action = new KActionMenu(d->parentWidget); break; case ToggleAction: action = new KToggleAction(d->parentWidget); break; } if (d->pluralLabels.contains(type) && !d->pluralLabels.value(type).isEmpty()) { action->setText(d->pluralLabels.value(type).subs(1).toString()); } else if (standardActionData[type].label) { action->setText(i18n(standardActionData[type].label)); } if (d->pluralIconLabels.contains(type) && !d->pluralIconLabels.value(type).isEmpty()) { action->setIconText(d->pluralIconLabels.value(type).subs(1).toString()); } else if (standardActionData[type].iconLabel) { action->setIconText(i18n(standardActionData[type].iconLabel)); } if (standardActionData[type].icon) { action->setIcon(QIcon::fromTheme(QString::fromLatin1(standardActionData[type].icon))); } if (d->actionCollection) { d->actionCollection->setDefaultShortcut(action, QKeySequence(standardActionData[type].shortcut)); } else { action->setShortcut(standardActionData[type].shortcut); } if (standardActionData[type].slot) { switch (standardActionData[type].actionType) { case NormalAction: case ActionWithAlternative: connect(action, SIGNAL(triggered()), standardActionData[type].slot); break; case MenuAction: { KActionMenu *actionMenu = qobject_cast(action); connect(actionMenu->menu(), SIGNAL(triggered(QAction*)), standardActionData[type].slot); break; } case ToggleAction: { connect(action, SIGNAL(triggered(bool)), standardActionData[type].slot); break; } case ActionAlternative: Q_ASSERT(0); } } if (type == ToggleWorkOffline) { // inititalize the action state with information from config file disconnect(action, SIGNAL(triggered(bool)), this, standardActionData[type].slot); action->setChecked(workOffline()); connect(action, SIGNAL(triggered(bool)), this, standardActionData[type].slot); //TODO: find a way to check for updates to the config file } Q_ASSERT(standardActionData[type].name); d->actionCollection->addAction(QString::fromLatin1(standardActionData[type].name), action); d->actions[type] = action; if ((standardActionData[type].actionType == ActionWithAlternative) && (standardActionData[type + 1].actionType == ActionAlternative)) { createAction(static_cast(type + 1)); //ensure that alternative actions are initialized when not created by createAllActions } d->updateActions(); return action; } void StandardActionManager::createAllActions() { for (uint i = 0; i < LastType; ++i) { createAction((Type)i); } } QAction *StandardActionManager::action(Type type) const { Q_ASSERT(type < LastType); return d->actions[type]; } void StandardActionManager::setActionText(Type type, const KLocalizedString &text) { Q_ASSERT(type < LastType); d->pluralLabels.insert(type, text); d->updateActions(); } void StandardActionManager::interceptAction(Type type, bool intercept) { Q_ASSERT(type < LastType); const QAction *action = d->actions[type]; if (!action) { return; } if (intercept) { disconnect(action, SIGNAL(triggered()), this, standardActionData[type].slot); } else { connect(action, SIGNAL(triggered()), standardActionData[type].slot); } } Akonadi::Collection::List StandardActionManager::selectedCollections() const { Collection::List collections; if (!d->collectionSelectionModel) { return collections; } const QModelIndexList lst = safeSelectedRows(d->collectionSelectionModel); for (const QModelIndex &index : lst) { const Collection collection = index.data(EntityTreeModel::CollectionRole).value(); if (collection.isValid()) { collections << collection; } } return collections; } Item::List StandardActionManager::selectedItems() const { Item::List items; if (!d->itemSelectionModel) { return items; } const QModelIndexList lst = safeSelectedRows(d->itemSelectionModel); for (const QModelIndex &index : lst) { const Item item = index.data(EntityTreeModel::ItemRole).value(); if (item.isValid()) { items << item; } } return items; } void StandardActionManager::setContextText(Type type, TextContext context, const QString &text) { d->setContextText(type, context, text); } void StandardActionManager::setContextText(Type type, TextContext context, const KLocalizedString &text) { d->setContextText(type, context, text); } void StandardActionManager::setMimeTypeFilter(const QStringList &mimeTypes) { d->mMimeTypeFilter = mimeTypes; } void StandardActionManager::setCapabilityFilter(const QStringList &capabilities) { d->mCapabilityFilter = capabilities; } void StandardActionManager::setCollectionPropertiesPageNames(const QStringList &names) { d->mCollectionPropertiesPageNames = names; } void StandardActionManager::createActionFolderMenu(QMenu *menu, Type type) { d->createActionFolderMenu(menu, type); } #include "moc_standardactionmanager.cpp" diff --git a/src/widgets/tagwidget.cpp b/src/widgets/tagwidget.cpp index 4d0c39e82..8ab0120d8 100644 --- a/src/widgets/tagwidget.cpp +++ b/src/widgets/tagwidget.cpp @@ -1,152 +1,152 @@ /* This file is part of Akonadi Copyright (c) 2010 Tobias Koenig Copyright (c) 2014 Christian Mollekopf Copyright (C) 2016-2019 Laurent Montel 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 "tagwidget.h" #include "tagmodel.h" #include "changerecorder.h" #include "tagselectiondialog.h" #include #include #include #include #include using namespace Akonadi; class Q_DECL_HIDDEN TagWidget::Private { public: Private() { } Akonadi::Tag::List mTags; TagView *mTagView = nullptr; Akonadi::TagModel *mModel = nullptr; QToolButton *mEditButton = nullptr; }; TagView::TagView(QWidget *parent) : QLineEdit(parent) { setPlaceholderText(i18n("Click to add tags")); setReadOnly(true); } void TagView::contextMenuEvent(QContextMenuEvent *event) { if (text().isEmpty()) { return; } QMenu menu; menu.addAction(i18n("Clear"), this, SIGNAL(clearTags())); menu.exec(event->globalPos()); } TagWidget::TagWidget(QWidget *parent) : QWidget(parent) , d(new Private) { Monitor *monitor = new Monitor(this); monitor->setObjectName(QStringLiteral("TagWidgetMonitor")); monitor->setTypeMonitored(Monitor::Tags); d->mModel = new Akonadi::TagModel(monitor, this); connect(monitor, &Monitor::tagAdded, this, &TagWidget::updateView); QHBoxLayout *layout = new QHBoxLayout(this); layout->setMargin(0); d->mTagView = new TagView(this); connect(d->mTagView, &TagView::clearTags, this, &TagWidget::clearTags); layout->addWidget(d->mTagView); d->mEditButton = new QToolButton(this); d->mEditButton->setText(i18n("...")); layout->addWidget(d->mEditButton, Qt::AlignRight); layout->setStretch(0, 10); connect(d->mEditButton, &QToolButton::clicked, this, &TagWidget::editTags); connect(d->mModel, &Akonadi::TagModel::populated, this, &TagWidget::updateView); } TagWidget::~TagWidget() { } void TagWidget::clearTags() { if (!d->mTags.isEmpty()) { d->mTags.clear(); d->mTagView->clear(); - emit selectionChanged(d->mTags); + Q_EMIT selectionChanged(d->mTags); } } void TagWidget::setSelection(const Akonadi::Tag::List &tags) { if (d->mTags != tags) { d->mTags = tags; updateView(); } } Akonadi::Tag::List TagWidget::selection() const { return d->mTags; } void TagWidget::setReadOnly(bool readOnly) { d->mEditButton->setEnabled(!readOnly); //d->mTagView is always readOnly => not change it. } void TagWidget::editTags() { QScopedPointer dlg(new TagSelectionDialog(this)); dlg->setSelection(d->mTags); if (dlg->exec() == QDialog::Accepted) { d->mTags = dlg->selection(); updateView(); - emit selectionChanged(d->mTags); + Q_EMIT selectionChanged(d->mTags); } } void TagWidget::updateView() { QStringList tagsNames; // Load the real tag names from the model for (int i = 0; i < d->mModel->rowCount(); ++i) { const QModelIndex index = d->mModel->index(i, 0); const Akonadi::Tag tag = d->mModel->data(index, Akonadi::TagModel::TagRole).value(); if (d->mTags.contains(tag)) { tagsNames << tag.name(); } } d->mTagView->setText(tagsNames.join(QStringLiteral(", "))); }