diff --git a/src/agentbase/agentbase.cpp b/src/agentbase/agentbase.cpp index 5385dc926..409ce6ea7 100644 --- a/src/agentbase/agentbase.cpp +++ b/src/agentbase/agentbase.cpp @@ -1,1299 +1,1299 @@ /* 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 "akonadifull-version.h" #include "agentmanager.h" #include "changerecorder.h" #include "controladaptor.h" #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 #include #if defined __GLIBC__ # include // for dumping memory information #endif #ifdef Q_OS_WIN #include #endif #include #include 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 disconnect(sAgentBase->changeRecorder(), &Monitor::itemsRelationsChanged, sAgentBase->d_ptr, &AgentBasePrivate::itemsRelationsChanged); 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"), QDBusConnection::sessionBus(), q); new Akonadi__ControlAdaptor(q); new Akonadi__StatusAdaptor(q); if (!QDBusConnection::sessionBus().registerObject(QStringLiteral("/"), q, QDBusConnection::ExportAdaptors)) { Q_EMIT q->error(i18n("Unable to register object at dbus: %1", QDBusConnection::sessionBus().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, qOverload(&ChangeRecorder::collectionChanged), this, qOverload(&AgentBasePrivate::collectionChanged)); connect(mChangeRecorder, qOverload &>(&ChangeRecorder::collectionChanged), this, qOverload &>(&AgentBasePrivate::collectionChanged)); 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, qOverload(&AgentBase::status), this, &AgentBasePrivate::slotStatus); connect(q, &AgentBase::percent, this, &AgentBasePrivate::slotPercent); connect(q, &AgentBase::warning, this, &AgentBasePrivate::slotWarning); connect(q, &AgentBase::error, this, &AgentBasePrivate::slotError); 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()), SLOT(slotResumedFromSuspend())); // clazy:exclude=old-style-connect + connect(mPowerInterface, SIGNAL(resumingFromSuspend()), this, SLOT(slotResumedFromSuspend())); // clazy:exclude=old-style-connect } 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 (!QDBusConnection::sessionBus().registerService(serviceId)) { qCCritical(AKONADIAGENTBASE_LOG) << "Unable to register service" << serviceId << "at dbus:" << QDBusConnection::sessionBus().lastError().message(); } q->setOnlineInternal(mDesiredOnlineState); QDBusConnection::sessionBus().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 if (!mObserver) { 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_FULL_VERSION)); 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"); KAboutData::setApplicationData(r.aboutData()); return qApp->exec(); } 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, &QNetworkConfigurationManager::onlineStateChanged, d, &AgentBasePrivate::slotNetworkStatusChange, 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, d, &AgentBasePrivate::slotTemporaryOfflineTimeout); } d->mTemporaryOfflineTimer->setInterval(std::chrono::seconds{makeOnlineInSeconds}); 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) { Q_EMIT status(d->mStatusCode, newMessage); } doSetOnline(state); Q_EMIT onlineChanged(state); } void AgentBase::doSetOnline(bool online) { Q_UNUSED(online); } KAboutData AgentBase::aboutData() const { return KAboutData(qApp->applicationName(), agentName(), qApp->applicationVersion()); } 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 = QDBusConnection::sessionBus().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(); 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() { Q_EMIT abortRequested(); } void AgentBase::reconfigure() { Q_EMIT reloadConfiguration(); } #include "moc_agentbase.cpp" #include "moc_agentbase_p.cpp" diff --git a/src/agentbase/agentbase_p.h b/src/agentbase/agentbase_p.h index 3ffbba8f1..1ab1881e9 100644 --- a/src/agentbase/agentbase_p.h +++ b/src/agentbase/agentbase_p.h @@ -1,157 +1,159 @@ /* 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_P_H #define AKONADI_AGENTBASE_P_H #include "agentbase.h" #include "tracerinterface.h" #include class QSettings; class QTimer; class QNetworkConfigurationManager; namespace Akonadi { /** * @internal */ class AgentBasePrivate : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.dfaure") public: explicit AgentBasePrivate(AgentBase *parent); ~AgentBasePrivate() override; void init(); virtual void delayedInit(); +public Q_SLOTS: void slotStatus(int status, const QString &message); void slotPercent(int progress); void slotWarning(const QString &message); void slotError(const QString &message); void slotNetworkStatusChange(bool isOnline); void slotResumedFromSuspend(); void slotTemporaryOfflineTimeout(); virtual void changeProcessed(); QString defaultReadyMessage() const { if (mOnline) { return i18nc("@info:status Application ready for work", "Ready"); } return i18nc("@info:status", "Offline"); } QString defaultSyncingMessage() const { return i18nc("@info:status", "Syncing..."); } QString defaultErrorMessage() const { return i18nc("@info:status", "Error."); } QString defaultUnconfiguredMessage() const { return i18nc("@info:status", "Not configured"); } void setProgramName(); +public: AgentBase *q_ptr; Q_DECLARE_PUBLIC(AgentBase) QString mId; QString mName; QString mResourceTypeName; int mStatusCode; QString mStatusMessage; int mProgress; QString mProgressMessage; bool mNeedsNetwork; bool mOnline; bool mDesiredOnlineState; QSettings *mSettings = nullptr; ChangeRecorder *mChangeRecorder = nullptr; org::freedesktop::Akonadi::Tracer *mTracer = nullptr; AgentBase::Observer *mObserver = nullptr; QDBusInterface *mPowerInterface = nullptr; QTimer *mTemporaryOfflineTimer = nullptr; QEventLoopLocker *mEventLoopLocker = nullptr; QNetworkConfigurationManager *mNetworkManager = nullptr; public Q_SLOTS: // Dump the contents of the current ChangeReplay Q_SCRIPTABLE QString dumpNotificationListToString() const; Q_SCRIPTABLE void dumpMemoryInfo() const; Q_SCRIPTABLE QString dumpMemoryInfoToString() const; virtual void itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection); virtual void itemChanged(const Akonadi::Item &item, const QSet &partIdentifiers); virtual void itemMoved(const Akonadi::Item &item, const Akonadi::Collection &source, const Akonadi::Collection &destination); virtual void itemRemoved(const Akonadi::Item &item); void itemLinked(const Akonadi::Item &item, const Akonadi::Collection &collection); void itemUnlinked(const Akonadi::Item &item, const Akonadi::Collection &collection); virtual void itemsFlagsChanged(const Akonadi::Item::List &items, const QSet &addedFlags, const QSet &removedFlags); virtual void itemsMoved(const Akonadi::Item::List &items, const Akonadi::Collection &source, const Akonadi::Collection &destination); virtual void itemsRemoved(const Akonadi::Item::List &items); virtual void itemsLinked(const Akonadi::Item::List &items, const Akonadi::Collection &collection); virtual void itemsUnlinked(const Akonadi::Item::List &items, const Akonadi::Collection &collection); virtual void collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent); virtual void collectionChanged(const Akonadi::Collection &collection); virtual void collectionChanged(const Akonadi::Collection &collection, const QSet &changedAttributes); virtual void collectionMoved(const Akonadi::Collection &collection, const Akonadi::Collection &source, const Akonadi::Collection &destination); virtual void collectionRemoved(const Akonadi::Collection &collection); void collectionSubscribed(const Akonadi::Collection &collection, const Akonadi::Collection &parent); void collectionUnsubscribed(const Akonadi::Collection &collection); virtual void tagAdded(const Akonadi::Tag &tag); virtual void tagChanged(const Akonadi::Tag &tag); virtual void tagRemoved(const Akonadi::Tag &tag); virtual void itemsTagsChanged(const Akonadi::Item::List &items, const QSet &addedTags, const QSet &removedTags); virtual void relationAdded(const Akonadi::Relation &relation); virtual void relationRemoved(const Akonadi::Relation &relation); virtual void itemsRelationsChanged(const Akonadi::Item::List &items, const Akonadi::Relation::List &addedRelations, const Akonadi::Relation::List &removedRelations); }; } #endif diff --git a/src/core/jobs/collectionfetchjob.cpp b/src/core/jobs/collectionfetchjob.cpp index eec487264..2a27af558 100644 --- a/src/core/jobs/collectionfetchjob.cpp +++ b/src/core/jobs/collectionfetchjob.cpp @@ -1,416 +1,421 @@ /* 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) { mEmitTimer.setSingleShot(true); mEmitTimer.setInterval(std::chrono::milliseconds{100}); - q_ptr->connect(&mEmitTimer, &QTimer::timeout, q_ptr, [this]() { timeout(); }); + } + + void init() + { + QObject::connect(&mEmitTimer, &QTimer::timeout, q_ptr, [this]() { timeout(); }); } Q_DECLARE_PUBLIC(CollectionFetchJob) - CollectionFetchJob::Type mType; + CollectionFetchJob::Type mType = CollectionFetchJob::Base; Collection mBase; Collection::List mBaseList; Collection::List mCollections; CollectionFetchScope mScope; Collection::List mPendingCollections; QTimer mEmitTimer; bool mBasePrefetch = false; Collection::List mPrefetchList; void aboutToFinish() override { timeout(); } void timeout() { Q_Q(CollectionFetchJob); mEmitTimer.stop(); // in case we are called by result() if (!mPendingCollections.isEmpty()) { if (!q->error() || mScope.ignoreRetrievalErrors()) { Q_EMIT q->collectionsReceived(mPendingCollections); } mPendingCollections.clear(); } } void subJobCollectionReceived(const Akonadi::Collection::List &collections) { mPendingCollections += collections; if (!mEmitTimer.isActive()) { mEmitTimer.start(); } } QString jobDebuggingString() const override { if (mBase.isValid()) { return QStringLiteral("Collection Id %1").arg(mBase.id()); } else if (CollectionUtils::hasValidHierarchicalRID(mBase)) { //return QLatin1String("(") + ProtocolHelper::hierarchicalRidToScope(mBase).hridChain().join(QLatin1String(", ")) + QLatin1Char(')'); return QStringLiteral("HRID chain"); } else { return QStringLiteral("Collection RemoteId %1").arg(mBase.remoteId()); } } bool jobFailed(KJob *job) { Q_Q(CollectionFetchJob); if (mScope.ignoreRetrievalErrors()) { int error = job->error(); if (error && !q->error()) { q->setError(error); q->setErrorText(job->errorText()); } if (error == Job::ConnectionFailed || error == Job::ProtocolVersionMismatch || error == Job::UserCanceled) { return true; } return false; } else { return job->error(); } } }; CollectionFetchJob::CollectionFetchJob(const Collection &collection, Type type, QObject *parent) : Job(new CollectionFetchJobPrivate(this), parent) { Q_D(CollectionFetchJob); + d->init(); d->mBase = collection; d->mType = type; } CollectionFetchJob::CollectionFetchJob(const Collection::List &cols, QObject *parent) : Job(new CollectionFetchJobPrivate(this), parent) { Q_D(CollectionFetchJob); + d->init(); Q_ASSERT(!cols.isEmpty()); if (cols.size() == 1) { d->mBase = cols.first(); } else { d->mBaseList = cols; } d->mType = CollectionFetchJob::Base; } CollectionFetchJob::CollectionFetchJob(const Collection::List &cols, Type type, QObject *parent) : Job(new CollectionFetchJobPrivate(this), parent) { Q_D(CollectionFetchJob); + d->init(); Q_ASSERT(!cols.isEmpty()); if (cols.size() == 1) { d->mBase = cols.first(); } else { d->mBaseList = cols; } d->mType = type; } CollectionFetchJob::CollectionFetchJob(const QList &cols, Type type, QObject *parent) : Job(new CollectionFetchJobPrivate(this), parent) { Q_D(CollectionFetchJob); + d->init(); Q_ASSERT(!cols.isEmpty()); if (cols.size() == 1) { d->mBase = Collection(cols.first()); } else { for (Collection::Id id : cols) { d->mBaseList.append(Collection(id)); } } d->mType = type; } -CollectionFetchJob::~CollectionFetchJob() -{ -} +CollectionFetchJob::~CollectionFetchJob() = default; 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, &CollectionFetchJob::collectionsReceived, this, [d](const auto &cols) { d->subJobCollectionReceived(cols); }); 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 (std::binary_search(ancestors.cbegin(), ancestors.cend(), collection.id())) { excludeList.insert(list.at(i).id()); } ++i; } } for (const Collection &collection : list) { if (!excludeList.contains(collection.id())) { result.append(collection); } } return result; } void CollectionFetchJob::slotResult(KJob *job) { Q_D(CollectionFetchJob); CollectionFetchJob *list = qobject_cast(job); Q_ASSERT(job); if (d->mType == NonOverlappingRoots) { d->mPrefetchList += list->collections(); } else if (!d->mBasePrefetch) { d->mCollections += list->collections(); } if (d_ptr->mCurrentSubJob == job && !d->jobFailed(job)) { if (job->error()) { qCWarning(AKONADICORE_LOG) << "Error during CollectionFetchJob: " << job->errorString(); } d_ptr->mCurrentSubJob = nullptr; removeSubjob(job); QTimer::singleShot(0, this, [d]() { d->startNext(); }); } else { Job::slotResult(job); } if (d->mBasePrefetch) { d->mBasePrefetch = false; const Collection::List roots = list->collections(); Q_ASSERT(!hasSubjobs()); if (!job->error()) { for (const Collection &col : roots) { CollectionFetchJob *subJob = new CollectionFetchJob(col, d->mType, this); connect(subJob, &CollectionFetchJob::collectionsReceived, this, [d](const auto &cols) { d->subJobCollectionReceived(cols); }); 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 a5109e0a1..f159dc49a 100644 --- a/src/core/jobs/itemfetchjob.cpp +++ b/src/core/jobs/itemfetchjob.cpp @@ -1,288 +1,295 @@ /* 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 using namespace Akonadi; class Akonadi::ItemFetchJobPrivate : public JobPrivate { public: ItemFetchJobPrivate(ItemFetchJob *parent) : JobPrivate(parent) { mCollection = Collection::root(); mEmitTimer.setSingleShot(true); mEmitTimer.setInterval(std::chrono::milliseconds{100}); - QObject::connect(&mEmitTimer, &QTimer::timeout, q_ptr, [this]() { timeout(); }); } ~ItemFetchJobPrivate() override { delete mValuePool; } + void init() + { + QObject::connect(&mEmitTimer, &QTimer::timeout, q_ptr, [this]() { 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()) { 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; 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() -{ -} +ItemFetchJob::~ItemFetchJob() = default; 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) { 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 8930e974c..e9fb38484 100644 --- a/src/core/jobs/itemsearchjob.cpp +++ b/src/core/jobs/itemsearchjob.cpp @@ -1,272 +1,282 @@ /* 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) { mEmitTimer.setSingleShot(true); mEmitTimer.setInterval(std::chrono::milliseconds{100}); + } + + void init() + { QObject::connect(&mEmitTimer, &QTimer::timeout, q_ptr, [this]() { timeout(); }); } void aboutToFinish() override { timeout(); } void timeout() { Q_Q(Akonadi::ItemSearchJob); mEmitTimer.stop(); // in case we are called by result() if (!mPendingItems.isEmpty()) { if (!q->error()) { 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; }; 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() = default; 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/relationfetchjob.cpp b/src/core/jobs/relationfetchjob.cpp index 9116a7ec5..91de6e71e 100644 --- a/src/core/jobs/relationfetchjob.cpp +++ b/src/core/jobs/relationfetchjob.cpp @@ -1,127 +1,133 @@ /* 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) { mEmitTimer.setSingleShot(true); mEmitTimer.setInterval(std::chrono::milliseconds{100}); + } + + void init() + { QObject::connect(&mEmitTimer, &QTimer::timeout, q_ptr, [this]() { 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()) { Q_EMIT q->relationsReceived(mPendingRelations); } mPendingRelations.clear(); } } Q_DECLARE_PUBLIC(RelationFetchJob) Relation::List mResultRelations; Relation::List mPendingRelations; // relation pending for emitting itemsReceived() QTimer mEmitTimer; 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"