diff --git a/src/agentbase/agentbase.cpp b/src/agentbase/agentbase.cpp index 69743fdd1..47c8e78d0 100644 --- a/src/agentbase/agentbase.cpp +++ b/src/agentbase/agentbase.cpp @@ -1,1297 +1,1297 @@ /* 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 "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 using namespace Akonadi; static AgentBase *sAgentBase = 0; AgentBase::Observer::Observer() { } AgentBase::Observer::~Observer() { } void AgentBase::Observer::itemAdded(const Item &item, const Collection &collection) { Q_UNUSED(item); Q_UNUSED(collection); - if (sAgentBase != 0) { + if (sAgentBase) { sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::Observer::itemChanged(const Item &item, const QSet &partIdentifiers) { Q_UNUSED(item); Q_UNUSED(partIdentifiers); - if (sAgentBase != 0) { + if (sAgentBase) { sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::Observer::itemRemoved(const Item &item) { Q_UNUSED(item); - if (sAgentBase != 0) { + 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 != 0) { + if (sAgentBase) { sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::Observer::collectionChanged(const Collection &collection) { Q_UNUSED(collection); - if (sAgentBase != 0) { + if (sAgentBase) { sAgentBase->d_ptr->changeProcessed(); } } void AgentBase::Observer::collectionRemoved(const Collection &collection) { Q_UNUSED(collection); - if (sAgentBase != 0) { + 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 != 0) { + 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 != 0) { + 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 != 0) { + 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 != 0) { + 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< QByteArray > &addedFlags, const QSet< QByteArray > &removedFlags) { Q_UNUSED(items); Q_UNUSED(addedFlags); Q_UNUSED(removedFlags); - if (sAgentBase != 0) { + 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 != 0) { + 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 != 0) { + 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 != 0) { + 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 != 0) { + 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 != 0) { + 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 != 0) { + 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 != 0) { + 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 != 0) { + 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(0) , mChangeRecorder(0) , mTracer(0) , mObserver(0) , mPowerInterface(0) , mTemporaryOfflineTimer(0) , mEventLoopLocker(0) , mNetworkManager(Q_NULLPTR) { Internal::setClientType(Internal::Agent); } AgentBasePrivate::~AgentBasePrivate() { mChangeRecorder->setConfig(0); 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(QStringLiteral("%1/agent_config_%2").arg(StandardDirs::saveDir("config"), mId), QSettings::IniFormat); mChangeRecorder = new ChangeRecorder(q); 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, SIGNAL(percent(int)), q, SLOT(slotPercent(int))); connect(q, SIGNAL(warning(QString)), q, SLOT(slotWarning(QString))); connect(q, SIGNAL(error(QString)), q, SLOT(slotError(QString))); 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 = 0; } // 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, SLOT(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 != 0) { + if (mObserver) { mObserver->itemAdded(item, collection); } } void AgentBasePrivate::itemChanged(const Akonadi::Item &item, const QSet &partIdentifiers) { - if (mObserver != 0) { + if (mObserver) { mObserver->itemChanged(item, partIdentifiers); } } 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 != 0) { + if (mObserver) { mObserver->itemRemoved(item); } } 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) { 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) { if (!mObserver) { return; } AgentBase::ObserverV4 *observer4 = dynamic_cast(mObserver); if (observer4) { observer4->tagAdded(tag); } else { changeProcessed(); } } void AgentBasePrivate::tagChanged(const Akonadi::Tag &tag) { if (!mObserver) { return; } AgentBase::ObserverV4 *observer4 = dynamic_cast(mObserver); if (observer4) { observer4->tagChanged(tag); } else { changeProcessed(); } } void AgentBasePrivate::tagRemoved(const Akonadi::Tag &tag) { if (!mObserver) { return; } 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) { if (!mObserver) { return; } AgentBase::ObserverV4 *observer4 = dynamic_cast(mObserver); if (observer4) { observer4->itemsTagsChanged(items, addedTags, removedTags); } else { changeProcessed(); } } void AgentBasePrivate::relationAdded(const Akonadi::Relation &relation) { if (!mObserver) { return; } AgentBase::ObserverV4 *observer4 = dynamic_cast(mObserver); if (observer4) { observer4->relationAdded(relation); } else { changeProcessed(); } } void AgentBasePrivate::relationRemoved(const Akonadi::Relation &relation) { if (!mObserver) { return; } 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) { if (!mObserver) { return; } 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 != 0) { + if (mObserver) { mObserver->collectionAdded(collection, parent); } } void AgentBasePrivate::collectionChanged(const Akonadi::Collection &collection) { AgentBase::ObserverV2 *observer2 = dynamic_cast(mObserver); - if (mObserver != 0 && observer2 == 0) { // For ObserverV2 we use the variant with the part identifiers + if (mObserver && observer2 == 0) { // For ObserverV2 we use the variant with the part identifiers mObserver->collectionChanged(collection); } } void AgentBasePrivate::collectionChanged(const Akonadi::Collection &collection, const QSet &changedAttributes) { AgentBase::ObserverV2 *observer2 = dynamic_cast(mObserver); - if (observer2 != 0) { + if (observer2) { observer2->collectionChanged(collection, changedAttributes); } } 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 != 0) { + if (mObserver) { mObserver->collectionRemoved(collection); } } 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; } 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; 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, SIGNAL(timeout()), this, SLOT(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); } doSetOnline(state); emit onlineChanged(state); } void AgentBase::doSetOnline(bool online) { Q_UNUSED(online); } void AgentBase::configure(WId windowId) { Q_UNUSED(windowId); emit configurationDialogAccepted(); } #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(0); d->mSettings->sync(); } delete d->mEventLoopLocker; d->mEventLoopLocker = Q_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(0); delete d->mSettings; d->mSettings = 0; /* * ... then remove the file from hd. */ QFile::remove(fileName); /* * ... and remove the changes file from hd. */ QFile::remove(fileName + QStringLiteral("_changes.dat")); /* * ... and also remove the agent configuration file if there is one. */ QString configFile = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QLatin1Char('/') + config()->name(); QFile::remove(configFile); delete d->mEventLoopLocker; d->mEventLoopLocker = Q_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) != 0); const bool hasObserverV4 = (dynamic_cast(d_ptr->mObserver) != 0); 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); } 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(); } void AgentBase::reconfigure() { emit reloadConfiguration(); } #include "moc_agentbase.cpp" #include "moc_agentbase_p.cpp" diff --git a/src/core/models/entitytreemodel_p.cpp b/src/core/models/entitytreemodel_p.cpp index 6c7dfe68e..fa6795e5f 100644 --- a/src/core/models/entitytreemodel_p.cpp +++ b/src/core/models/entitytreemodel_p.cpp @@ -1,1960 +1,1960 @@ /* 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 "KDBusConnectionPool" #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.akonadi.ETM") 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(0) , m_collectionFetchStrategy(EntityTreeModel::FetchCollectionsRecursive) , m_itemPopulation(EntityTreeModel::ImmediatePopulation) , m_listFilter(CollectionFetchScope::NoFilter) , m_includeStatistics(false) , m_showRootCollection(false) , m_collectionTreeFetched(false) , 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() { } 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(QStringLiteral("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; } if (m_collections.contains(collectionId)) { // 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. m_collections[collectionId] = collection; const QModelIndex collectionIndex = indexForCollection(collection); dataChanged(collectionIndex, collectionIndex); 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()) { fetchItems(m_collections.value(collectionId)); } } } } 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.wantedMimeTypes().isEmpty() || 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; if (m_items.contains(item.id())) { const Akonadi::Collection::List parents = getParentCollections(item); foreach (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; m_items[item.id()].apply(item); isNewItem = false; break; } } } if (isNewItem) { itemsToInsert << item; } } } - if (itemsToInsert.size() > 0) { + 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(); // Don't reinsert when listing virtual collections. if (!m_items.contains(item.id())) { m_items.insert(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 auotmatically 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) { foreach (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.wantedMimeTypes().isEmpty() && !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.wantedMimeTypes().isEmpty() && !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.remove((*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 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.wantedMimeTypes().isEmpty() && !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.insert(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) { Q_Q(EntityTreeModel); if (isHidden(item)) { return; } const Collection::List parents = getParentCollections(item); if (parents.isEmpty()) { return; } if (!m_items.contains(item.id())) { qCWarning(AKONADICORE_LOG) << "Got a stale notification for an item which was already removed." << item.id() << item.remoteId(); return; } // TODO: Iterate over all (virtual) collections. const Collection collection = parents.first(); 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.remove(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_items.contains(item.id())) { qCWarning(AKONADICORE_LOG) << "Got a stale notification for an item which was already removed." << item.id() << item.remoteId(); return; } // 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 = m_items[item.id()].parentCollection(); m_items[item.id()].apply(item); m_items[item.id()].setParentCollection(originalParent); } else { m_items[item.id()].apply(item); } const QModelIndexList indexes = indexesForItem(item); foreach (const QModelIndex &index, indexes) { if (!index.isValid()) { qCWarning(AKONADICORE_LOG) << "item has invalid index:" << item.id() << item.remoteId(); } else { dataChanged(index, index); } } } 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); return; } else { monitoredItemRemoved(item); 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 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(); Q_ASSERT(m_collections.contains(collectionId)); if (!m_mimeChecker.wantedMimeTypes().isEmpty() && !m_mimeChecker.isWantedItem(item)) { return; } QList &collectionEntities = m_childEntities[collectionId]; 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); if (!m_items.contains(itemId)) { m_items.insert(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_items.contains(item.id())) { qCWarning(AKONADICORE_LOG) << "Got a stale notification for an item which was already removed." << item.id() << item.remoteId(); return; } Q_ASSERT(m_collections.contains(collection.id())); 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); 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)); } 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); // 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 emit 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(0, i18n("Error"), errorMsg); } } void EntityTreeModelPrivate::updateJobDone(KJob *job) { if (job->error()) { // TODO: handle job errors qCWarning(AKONADICORE_LOG) << "Job error:" << job->errorString(); } else { //FIXME: This seems pretty pointless since we'll get an update through the monitor anyways ItemModifyJob *modifyJob = qobject_cast(job); if (!modifyJob) { return; } const Item item = modifyJob->item(); Q_ASSERT(item.isValid()); m_items[item.id()].apply(item); const QModelIndexList list = indexesForItem(item); foreach (const QModelIndex &index, list) { dataChanged(index, index); } } } 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.size() > 0) { 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_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.wantedMimeTypes().isEmpty(); const bool noResources = m_monitor->resourcesMonitored().isEmpty(); const bool multipleCollections = m_monitor->collectionsMonitored().size() > 1; const bool generalPopulation = !noMimetypes || (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) << ""; jobTimeTracker[job].start(); } void EntityTreeModelPrivate::topLevelCollectionsFetched(const Akonadi::Collection::List &list) { Q_Q(EntityTreeModel); foreach (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.remove((*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); } 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()); foreach (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(); m_rootNode = 0; 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.insert(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 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/itemmodel.cpp b/src/core/models/itemmodel.cpp index 7e6f9afcf..c397b5ac2 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->ignoreSession(session); mParent->connect(monitor, SIGNAL(itemChanged(Akonadi::Item,QSet)), mParent, SLOT(itemChanged(Akonadi::Item,QSet))); 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, SIGNAL(itemRemoved(Akonadi::Item)), mParent, SLOT(itemRemoved(Akonadi::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; QList items; QHash itemHash; Collection collection; Monitor *monitor; Session *session; }; 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 Q_FOREACH (const QString &type, mParent->mimeTypes()) { 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); foreach (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) { Item::List l; 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) { qCDebug(AKONADICORE_LOG); 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, SIGNAL(result(KJob*)), SLOT(listingDone(KJob*))); } 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 QStringList() << 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; - foreach (const QModelIndex &index, indexes) { + 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/trashfilterproxymodel.cpp b/src/core/models/trashfilterproxymodel.cpp index bcf50ac49..8e8c55a80 100644 --- a/src/core/models/trashfilterproxymodel.cpp +++ b/src/core/models/trashfilterproxymodel.cpp @@ -1,78 +1,78 @@ /* Copyright (c) 2011 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 "trashfilterproxymodel.h" #include "entitydeletedattribute.h" #include "item.h" #include "entitytreemodel.h" using namespace Akonadi; class TrashFilterProxyModel::TrashFilterProxyModelPrivate { public: TrashFilterProxyModelPrivate() : mTrashIsShown(false) { - }; + } bool mTrashIsShown; }; TrashFilterProxyModel::TrashFilterProxyModel(QObject *parent) : KRecursiveFilterProxyModel(parent) , d_ptr(new TrashFilterProxyModelPrivate()) { } TrashFilterProxyModel::~TrashFilterProxyModel() { delete d_ptr; } void TrashFilterProxyModel::showTrash(bool enable) { Q_D(TrashFilterProxyModel); d->mTrashIsShown = enable; invalidateFilter(); } bool TrashFilterProxyModel::trashIsShown() const { Q_D(const TrashFilterProxyModel); return d->mTrashIsShown; } bool TrashFilterProxyModel::acceptRow(int sourceRow, const QModelIndex &sourceParent) const { Q_D(const TrashFilterProxyModel); const QModelIndex &index = sourceModel()->index(sourceRow, 0, sourceParent); const Item &item = index.data(EntityTreeModel::ItemRole).value(); if (item.isValid()) { if (item.hasAttribute()) { return d->mTrashIsShown; } } const Collection &collection = index.data(EntityTreeModel::CollectionRole).value(); if (collection.isValid()) { if (collection.hasAttribute()) { return d->mTrashIsShown; } } return !d->mTrashIsShown; }