diff --git a/src/resources/akonadiresource.h b/src/resources/akonadiresource.h index ed10be1e..4b3ba09e 100644 --- a/src/resources/akonadiresource.h +++ b/src/resources/akonadiresource.h @@ -1,371 +1,372 @@ /* * akonadiresource.h - class for an Akonadi alarm calendar resource * Program: kalarm * Copyright © 2019-2020 David Jarvie * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADIRESOURCE_H #define AKONADIRESOURCE_H #include "resource.h" #include "kalarm_debug.h" #include #include #include #include +#include #include #include class KJob; class DuplicateResourceObject; using namespace KAlarmCal; /** Class for an alarm calendar resource accessed through Akonadi. * Public access to this class is normally via the Resource class. */ class AkonadiResource : public ResourceType { Q_OBJECT public: /** Construct a new AkonadiResource. * The supplied collection must be up to date. */ static Resource create(const Akonadi::Collection&); protected: /** Constructor. * The supplied collection must be up to date. */ explicit AkonadiResource(const Akonadi::Collection&); public: ~AkonadiResource() override; static Resource nullResource(); /** Return whether the resource has a valid configuration. */ bool isValid() const override; /** Return the resource's collection. */ Akonadi::Collection collection() const; /** Return the type of storage used by the resource. */ StorageType storageType() const override; /** Return the type of the resource (file, remote file, etc.) * for display purposes. * @param description true for description (e.g. "Remote file"), * false for brief label (e.g. "URL"). */ QString storageTypeString(bool description) const override; /** Return the location(s) of the resource (URL, file path, etc.) */ QUrl location() const override; /** Return the location of the resource (URL, file path, etc.) * for display purposes. */ QString displayLocation() const override; /** Return the resource's display name. */ QString displayName() const override; /** Return the resource's configuration identifier, which for this class is * the Akonadi resource identifier. This is not the name normally * displayed to the user. */ QString configName() const override; /** Return which types of alarms the resource can contain. */ CalEvent::Types alarmTypes() const override; /** Return which alarm types (active, archived or template) the * resource is enabled for. */ CalEvent::Types enabledTypes() const override; /** Set the enabled/disabled state of the resource and its alarms, * for a specified alarm type (active, archived or template). The * enabled/disabled state for other alarm types is not affected. * The alarms of that type in a disabled resource are ignored, and * not displayed in the alarm list. The standard status for that type * for a disabled resource is automatically cleared. * @param type alarm type * @param enabled true to set enabled, false to set disabled. */ void setEnabled(CalEvent::Type type, bool enabled) override; /** Set which alarm types (active, archived or template) the resource * is enabled for. * @param types alarm types */ void setEnabled(CalEvent::Types types) override; /** Return whether the resource is configured as read-only or is * read-only on disc. */ bool readOnly() const override; /** Return whether the resource is both enabled and fully writable for a * given alarm type, i.e. not read-only, and compatible with the current * KAlarm calendar format. * * @param type alarm type to check for, or EMPTY to check for any type. * @return 1 = fully enabled and writable, * 0 = enabled and writable except that backend calendar is in an * old KAlarm format, * -1 = read-only, disabled or incompatible format. */ int writableStatus(CalEvent::Type type = CalEvent::EMPTY) const override; using ResourceType::isWritable; /** Return whether the user has chosen not to update the resource's * calendar storage format. */ bool keepFormat() const override; /** Set or clear whether the user has chosen not to update the resource's * calendar storage format. */ void setKeepFormat(bool keep) override; /** Return the background colour used to display alarms belonging to * this resource. * @return display colour, or invalid if none specified */ QColor backgroundColour() const override; /** Set the background colour used to display alarms belonging to this * resource. * @param colour display colour, or invalid to use the default colour */ void setBackgroundColour(const QColor& colour) override; /** Return whether the resource is set in its Akonadi attribute to be the * standard resource for a specified alarm type (active, archived or * template). There is no check for whether the resource is enabled, is * writable, or is the only resource set as standard. * * @note To determine whether the resource is actually the standard * resource, call Resources::isStandard(). * * @param type alarm type */ bool configIsStandard(CalEvent::Type type) const override; /** Return which alarm types (active, archived or template) the resource * is standard for, as set in its Akonadi attribute. This is restricted to * the alarm types which the resource can contain (@see alarmTypes()). * There is no check for whether the resource is enabled, is writable, or * is the only resource set as standard. * * @note To determine what alarm types the resource is actually the standard * resource for, call Resources::standardTypes(). * * @return alarm types. */ CalEvent::Types configStandardTypes() const override; /** Set or clear the resource as the standard resource for a specified alarm * type (active, archived or template), storing the setting in its Akonadi * attribute. There is no check for whether the resource is eligible to be * set as standard, or to ensure that it is the only standard resource for * the type. * * @note To set the resource's standard status and ensure that it is * eligible and the only standard resource for the type, call * Resources::setStandard(). * * @param type alarm type * @param standard true to set as standard, false to clear standard status. */ void configSetStandard(CalEvent::Type type, bool standard) override; /** Set which alarm types (active, archived or template) the resource is * the standard resource for, storing the setting in its Akonadi attribute. * There is no check for whether the resource is eligible to be set as * standard, or to ensure that it is the only standard resource for the * types. * * @note To set the resource's standard status and ensure that it is * eligible and the only standard resource for the types, call * Resources::setStandard(). * * @param types alarm types.to set as standard */ void configSetStandard(CalEvent::Types types) override; /** Return whether the resource is in a different format from the * current KAlarm format, in which case it cannot be written to. * Note that isWritable() takes account of incompatible format * as well as read-only and enabled statuses. * @param versionString Receives calendar's KAlarm version as a string. */ KACalendar::Compat compatibilityVersion(QString& versionString) const override; /** Edit the resource's configuration. */ void editResource(QWidget* dialogParent) override; /** Remove the resource. The calendar file is not removed. * @note The instance will be invalid once it has been removed. * @return true if the resource has been removed or a removal job has been scheduled. */ bool removeResource() override; /** Load the resource from storage, and fetch all events. * Not applicable to AkonadiResource, since the Akonadi resource handles * loading automatically. * @return true. */ bool load(bool readThroughCache = true) override; /** Reload the resource. Any cached data is first discarded. */ bool reload() override; /** Return whether the resource has fully loaded. */ bool isPopulated() const override; /** Save the resource. * Not applicable to AkonadiResource, since AkonadiDataModel handles saving * automatically. * @return true. */ bool save(bool writeThroughCache = true, bool force = false) override; /** Add an event to the resource, and add it to Akonadi. */ bool addEvent(const KAEvent&) override; /** Update an event in the resource, and update it in Akonadi. * Its UID must be unchanged. */ bool updateEvent(const KAEvent&) override; /** Delete an event from the resource, and from Akonadi. */ bool deleteEvent(const KAEvent&) override; /** Called to notify the resource that an event's command error has changed. */ void handleCommandErrorChange(const KAEvent&) override; /*----------------------------------------------------------------------------- * The methods below are all particular to the AkonadiResource class, and in * order to be accessible to clients are defined as 'static'. *----------------------------------------------------------------------------*/ /****************************************************************************** * Return a reference to the Collection held by an Akonadi resource. * @reply Reference to the Collection, which belongs to AkonadiDataModel and * whose ID must not be changed. */ static Akonadi::Collection& collection(Resource&); static const Akonadi::Collection& collection(const Resource&); // Akonadi::Collection& collection() { return mCollection; } /** Return the event for an Akonadi Item belonging to a resource. */ static KAEvent event(Resource&, const Akonadi::Item&); using QObject::event; // prevent warning about hidden virtual method /** Check for, and remove, Akonadi resources which duplicate use of * calendar files/directories. */ static void removeDuplicateResources(); /** Called to notify that a resource's Collection has been populated. * @param events The full list of events in the Collection. */ static void notifyCollectionLoaded(ResourceId, const QList& events); /** Called to notify that a resource's Collection has changed. */ static void notifyCollectionChanged(Resource&, const Akonadi::Collection&, bool checkCompatibility); /** Called to notify that events have been added or updated in Akonadi. */ static void notifyEventsChanged(Resource&, const QList&); /** Called to notify that events are to be deleted from Akonadi. */ static void notifyEventsToBeDeleted(Resource&, const QList&); /** Called to notify that an Akonadi Item belonging to a resource has * changed or been created. * @note notifyEventChanged() should also be called to signal the * new or changed event to interested parties. * @param created true if the item has been created; false if changed. */ static void notifyItemChanged(Resource&, const Akonadi::Item&, bool created); /** Create a D-Bus interface to an Akonadi resource. * @return If success: interface; * If error: null, with 'errorMessage' containing the error message. */ template static Interface* getAgentInterface(const Akonadi::AgentInstance& agent, QString& errorMessage, QObject* parent); /** The resource type for a single file Akonadi resource. */ static const QString KALARM_RESOURCE; /** The resource type for a directory Akonadi resource. */ static const QString KALARM_DIR_RESOURCE; private Q_SLOTS: void slotCollectionRemoved(const Akonadi::Collection&); void itemJobDone(KJob*); void modifyCollectionAttrJobDone(KJob*); private: void queueItemModifyJob(const Akonadi::Item&); void checkQueuedItemModifyJob(const Akonadi::Item&); void fetchCollectionAttribute(bool refresh) const; void modifyCollectionAttribute(); static DuplicateResourceObject* mDuplicateResourceObject; // QObject used by removeDuplicateResources() mutable Akonadi::Collection mCollection; // the Akonadi Collection represented by this resource mutable CollectionAttribute mCollectionAttribute; // current set value of CollectionAttribute QHash mPendingItemJobs; // pending item creation/deletion jobs, with event ID QHash mItemModifyJobQueue; // pending item modification jobs, invalid item = queue empty but job active QList mItemsBeingCreated; // new items not fully initialised yet bool mValid; // whether the collection is valid and belongs to an Akonadi resource mutable bool mHaveCollectionAttribute {false}; // whether the collection has a CollectionAttribute bool mHaveCompatibilityAttribute {false}; // whether the collection has a CompatibilityAttribute CalEvent::Types mLastEnabled {CalEvent::EMPTY}; // last known enabled status mutable bool mNewEnabled {false}; bool mCollectionAttrChecked {false}; // CollectionAttribute has been processed first time }; /*============================================================================= * Template definitions. *============================================================================*/ /****************************************************************************** * Create a D-Bus interface to an Akonadi resource. * Reply = interface if success * = 0 if error: 'errorMessage' contains the error message. */ template Interface* AkonadiResource::getAgentInterface(const Akonadi::AgentInstance& agent, QString& errorMessage, QObject* parent) { - Interface* iface = new Interface(QLatin1String("org.freedesktop.Akonadi.Resource.") + agent.identifier(), - QStringLiteral("/Settings"), QDBusConnection::sessionBus(), parent); + const auto service = Akonadi::ServerManager::agentServiceName(Akonadi::ServerManager::Resource, agent.identifier()); + Interface* iface = new Interface(service, QStringLiteral("/Settings"), QDBusConnection::sessionBus(), parent); if (!iface->isValid()) { errorMessage = iface->lastError().message(); qCDebug(KALARM_LOG) << "AkonadiResource::getAgentInterface: D-Bus error accessing resource:" << errorMessage; delete iface; return nullptr; } return iface; } #endif // AKONADIRESOURCE_H // vim: et sw=4: diff --git a/src/resources/akonadiresourcecreator.cpp b/src/resources/akonadiresourcecreator.cpp index 8b0a5f3a..14a7c35a 100644 --- a/src/resources/akonadiresourcecreator.cpp +++ b/src/resources/akonadiresourcecreator.cpp @@ -1,214 +1,215 @@ /* * akonadiresourcecreator.cpp - interactively create an Akonadi resource * Program: kalarm * Copyright © 2011-2020 David Jarvie * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "akonadiresourcecreator.h" #include "resources/resources.h" #include "resources/akonadiresource.h" #include "lib/autoqpointer.h" #include "kalarmsettings.h" #include "kalarmdirsettings.h" #include "kalarm_debug.h" #include #include #include +#include #include #include #include #include using namespace Akonadi; using namespace KAlarmCal; AkonadiResourceCreator::AkonadiResourceCreator(CalEvent::Type defaultType, QWidget* parent) : ResourceCreator(defaultType, parent) { } /****************************************************************************** * Create a new resource. The user will be prompted to enter its configuration. */ void AkonadiResourceCreator::doCreateResource() { qCDebug(KALARM_LOG) << "AkonadiResourceCreator::doCreateResource: Type:" << mDefaultType; // Use AutoQPointer to guard against crash on application exit while // the dialogue is still open. It prevents double deletion (both on // deletion of parent, and on return from this function). AutoQPointer dlg = new AgentTypeDialog(mParent); QString mimeType; switch (mDefaultType) { case CalEvent::ACTIVE: mimeType = KAlarmCal::MIME_ACTIVE; break; case CalEvent::ARCHIVED: mimeType = KAlarmCal::MIME_ARCHIVED; break; case CalEvent::TEMPLATE: mimeType = KAlarmCal::MIME_TEMPLATE; break; default: deleteLater(); // error result return; } dlg->agentFilterProxyModel()->addMimeTypeFilter(mimeType); dlg->agentFilterProxyModel()->addCapabilityFilter(QStringLiteral("Resource")); if (dlg->exec() == QDialog::Accepted) { mAgentType = dlg->agentType(); if (mAgentType.isValid()) { connect(Resources::instance(), &Resources::resourceAdded, this, &AkonadiResourceCreator::slotResourceAdded); AgentInstanceCreateJob* job = new AgentInstanceCreateJob(mAgentType, mParent); connect(job, &AgentInstanceCreateJob::result, this, &AkonadiResourceCreator::agentInstanceCreated); job->start(); return; } } deleteLater(); // error result } /****************************************************************************** * Called when an agent creation job has completed. * Checks for any error. */ void AkonadiResourceCreator::agentInstanceCreated(KJob* j) { AgentInstanceCreateJob* job = static_cast(j); if (j->error()) { qCCritical(KALARM_LOG) << "AkonadiResourceCreator::agentInstanceCreated: Failed to create new calendar resource:" << j->errorString(); KMessageBox::error(nullptr, xi18nc("@info", "%1(%2)", i18nc("@info", "Failed to create new calendar resource"), j->errorString())); deleteLater(); // error result return; } // Set the default alarm type for the resource config dialog mAgentInstance = job->instance(); const QString type = mAgentInstance.type().identifier(); qCDebug(KALARM_LOG) << "AkonadiResourceCreator::agentInstanceCreated:" << type; bool result = true; bool dirResource = (type == QLatin1String("akonadi_kalarm_dir_resource")); if (dirResource) setResourceAlarmType(); else if (type == QLatin1String("akonadi_kalarm_resource")) setResourceAlarmType(); else result = false; if (result) { const QVector resources = Resources::allResources(); QPointer dlg = new AgentConfigurationDialog(mAgentInstance, mParent); result = (dlg->exec() == QDialog::Accepted); delete dlg; if (result) { // Ensure that the new resource doesn't use the same file or directory // as an existing resource. This would result in duplicate resource // executables eating up processing time for no purpose. QString path = dirResource ? getResourcePath() : getResourcePath(); const QUrl url = QUrl::fromUserInput(path, QString(), QUrl::AssumeLocalFile); if (url.isLocalFile()) path = url.path(); for (const Resource& res : resources) { if (res.location() == url) { qCWarning(KALARM_LOG) << "AkonadiResourceCreator::agentInstanceCreated: Duplicate path for new resource:" << path; AgentManager::self()->removeInstance(mAgentInstance); KMessageBox::sorry(nullptr, xi18nc("@info", "The file or directory is already used by an existing resource:%1", path)); deleteLater(); // error result return; } } } } if (!result) { // User has clicked cancel in the resource configuration dialog, or // other error, so remove the newly created agent instance. AgentManager::self()->removeInstance(mAgentInstance); deleteLater(); // error result return; } // AkonadiModel will notify when it has added the resource, which will // call slotResourceAdded(). } /****************************************************************************** * Called when a collection is added to the AkonadiModel. * If it's the resource which has just been created, notify the fact. */ void AkonadiResourceCreator::slotResourceAdded(Resource& resource) { if (resource.isValid() && resource.is()) { AgentInstance agent = AgentManager::self()->instance(resource.configName()); if (agent == mAgentInstance) Q_EMIT resourceAdded(resource, mDefaultType); } // This object has done its job, so delete it. deleteLater(); } /****************************************************************************** * Set the alarm type for an Akonadi resource. */ template void AkonadiResourceCreator::setResourceAlarmType() { - Settings iface(QStringLiteral("org.freedesktop.Akonadi.Resource.") + mAgentInstance.identifier(), - QStringLiteral("/Settings"), QDBusConnection::sessionBus(), this); + const auto service = Akonadi::ServerManager::agentServiceName(Akonadi::ServerManager::Resource, mAgentInstance.identifier()); + Settings iface(service, QStringLiteral("/Settings"), QDBusConnection::sessionBus(), this); if (!iface.isValid()) qCCritical(KALARM_LOG) << "AkonadiResourceCreator::setResourceAlarmType: Error creating D-Bus interface for" << mAgentInstance.identifier() << "resource configuration."; else { iface.setAlarmTypes(CalEvent::mimeTypes(mDefaultType)); iface.save(); mAgentInstance.reconfigure(); // notify the agent that its configuration has changed } } /****************************************************************************** * Get the path for an Akonadi resource. */ template QString AkonadiResourceCreator::getResourcePath() { - Settings iface(QStringLiteral("org.freedesktop.Akonadi.Resource.") + mAgentInstance.identifier(), - QStringLiteral("/Settings"), QDBusConnection::sessionBus(), this); + const auto service = Akonadi::ServerManager::agentServiceName(Akonadi::ServerManager::Resource, mAgentInstance.identifier()); + Settings iface(service, QStringLiteral("/Settings"), QDBusConnection::sessionBus(), this); if (!iface.isValid()) { qCCritical(KALARM_LOG) << "AkonadiResourceCreator::getResourcePath: Error creating D-Bus interface for" << mAgentInstance.identifier() << "resource configuration."; return QString(); } return iface.path(); } // vim: et sw=4: