diff --git a/resources/kalarm/kalarm/kalarmresource.cpp b/resources/kalarm/kalarm/kalarmresource.cpp index 9d7c4969b..576760835 100644 --- a/resources/kalarm/kalarm/kalarmresource.cpp +++ b/resources/kalarm/kalarm/kalarmresource.cpp @@ -1,473 +1,473 @@ /* * kalarmresource.cpp - Akonadi resource for KAlarm * Program: kalarm - * Copyright © 2009-2019 by David Jarvie + * Copyright © 2009-2019 David Jarvie * * 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 "kalarmresource.h" #include "kalarmresourcecommon.h" #include "alarmtyperadiowidget.h" #include #include #include #include #include #include #include #include #include #include #include "kalarmresource_debug.h" using namespace Akonadi; using namespace Akonadi_KAlarm_Resource; using namespace KAlarmCal; using KAlarmResourceCommon::errorMessage; KAlarmResource::KAlarmResource(const QString &id) : ICalResourceBase(id) , mCompatibility(KACalendar::Incompatible) , mFileCompatibility(KACalendar::Incompatible) , mVersion(KACalendar::MixedFormat) , mFileVersion(KACalendar::IncompatibleFormat) , mHaveReadFile(false) , mFetchedAttributes(false) { - qCDebug(KALARMRESOURCE_LOG) << id; + qCDebug(KALARMRESOURCE_LOG) << "Starting:" << id; KAlarmResourceCommon::initialise(this); initialise(mSettings->alarmTypes(), QStringLiteral("kalarm")); connect(mSettings, &Settings::configChanged, this, &KAlarmResource::settingsChanged); // Start a job to fetch the collection attributes fetchCollection(SLOT(collectionFetchResult(KJob*))); } KAlarmResource::~KAlarmResource() { } /****************************************************************************** * Reimplemented to fetch collection attributes after creating the collection. */ void KAlarmResource::retrieveCollections() { - qCDebug(KALARMRESOURCE_LOG); + qCDebug(KALARMRESOURCE_LOG) << "retrieveCollections"; mSupportedMimetypes = mSettings->alarmTypes(); ICalResourceBase::retrieveCollections(); fetchCollection(SLOT(collectionFetchResult(KJob*))); } /****************************************************************************** * Called when the collection fetch job completes. * Check the calendar file's compatibility status if pending. */ void KAlarmResource::collectionFetchResult(KJob *j) { if (j->error()) { // An error occurred. Note that if it's a new resource, it will complain // about an invalid collection if the collection has not yet been created. - qCDebug(KALARMRESOURCE_LOG) << "Error: " << j->errorString(); + qCDebug(KALARMRESOURCE_LOG) << "Error: collectionFetchResult:" << j->errorString(); } else { bool firstTime = !mFetchedAttributes; mFetchedAttributes = true; CollectionFetchJob *job = static_cast(j); const Collection::List collections = job->collections(); if (collections.isEmpty()) { - qCDebug(KALARMRESOURCE_LOG) << "Error: resource's collection not found"; + qCDebug(KALARMRESOURCE_LOG) << "collectionFetchResult: resource's collection not found"; } else { // Check whether calendar file format needs to be updated - qCDebug(KALARMRESOURCE_LOG) << "Fetched collection"; const Collection &c(collections[0]); + qCDebug(KALARMRESOURCE_LOG) << "collectionFetchResult: Fetched collection" << c.id(); if (firstTime && mSettings->path().isEmpty()) { // Initialising a resource which seems to have no stored // settings config file. Recreate the settings. static const Collection::Rights writableRights = Collection::CanChangeItem | Collection::CanCreateItem | Collection::CanDeleteItem; - qCDebug(KALARMRESOURCE_LOG) << "Recreating config for remote id:" << c.remoteId(); + qCDebug(KALARMRESOURCE_LOG) << "collectionFetchResult: Recreating config for remote id:" << c.remoteId(); mSettings->setPath(c.remoteId()); mSettings->setDisplayName(c.name()); mSettings->setAlarmTypes(c.contentMimeTypes()); mSettings->setReadOnly((c.rights() & writableRights) != writableRights); mSettings->save(); synchronize(); // tell the server to use the new config } checkFileCompatibility(c, true); } } } /****************************************************************************** * Reimplemented to read data from the given file. * This is called every time the resource starts up (see SingleFileResourceBase * constructor). * Find the calendar file's compatibility with the current KAlarm format. * The file is always local; loading from the network is done automatically if * needed. */ bool KAlarmResource::readFromFile(const QString &fileName) { - qCDebug(KALARMRESOURCE_LOG) << fileName; + qCDebug(KALARMRESOURCE_LOG) << "readFromFile:" << fileName; //TODO Notify user if error occurs on next line if (!ICalResourceBase::readFromFile(fileName)) { return false; } if (calendar()->incidences().isEmpty()) { // It's a new file. Set up the KAlarm custom property. KACalendar::setKAlarmVersion(calendar()); } mFileCompatibility = KAlarmResourceCommon::getCompatibility(fileStorage(), mFileVersion); mHaveReadFile = true; if (mFetchedAttributes) { // The old calendar file version and compatibility have been read from // the database. Check if the file format needs to be converted. checkFileCompatibility(); } return true; } /****************************************************************************** * To be called when the collection attributes have been fetched, or if they * have changed. * Check if the recorded calendar version and compatibility are different from * the actual backend file, and if necessary convert the calendar in memory to * the current version. * If 'createAttribute' is true, the CompatibilityAttribute will be created if * it does not already exist. */ void KAlarmResource::checkFileCompatibility(const Collection &collection, bool createAttribute) { if (collection.isValid() && collection.hasAttribute()) { // Update our note of the calendar version and compatibility const CompatibilityAttribute *attr = collection.attribute(); mCompatibility = attr->compatibility(); mVersion = attr->version(); createAttribute = false; } if (mHaveReadFile && (createAttribute || mFileCompatibility != mCompatibility || mFileVersion != mVersion)) { // The actual file's version and compatibility are different from // those in the Akonadi database, so update the database attributes. mCompatibility = mFileCompatibility; mVersion = mFileVersion; const Collection c(collection); if (c.isValid()) { KAlarmResourceCommon::setCollectionCompatibility(c, mCompatibility, mVersion); } else { fetchCollection(SLOT(setCompatibility(KJob*))); } } } /****************************************************************************** * Called when a collection fetch job completes. * Set the compatibility attribute for the collection. */ void KAlarmResource::setCompatibility(KJob *j) { CollectionFetchJob *job = static_cast(j); if (j->error()) { - qCDebug(KALARMRESOURCE_LOG) << "Error: " << j->errorString(); + qCDebug(KALARMRESOURCE_LOG) << "Error: setCompatibility:" << j->errorString(); } else if (job->collections().isEmpty()) { - qCDebug(KALARMRESOURCE_LOG) << "Error: resource's collection not found"; + qCDebug(KALARMRESOURCE_LOG) << "Error: setCompatibility: resource's collection not found"; } else { KAlarmResourceCommon::setCollectionCompatibility(job->collections().at(0), mCompatibility, mVersion); } } /****************************************************************************** * Reimplemented to write data to the given file. * The file is always local. */ bool KAlarmResource::writeToFile(const QString &fileName) { - qCDebug(KALARMRESOURCE_LOG) << fileName; + qCDebug(KALARMRESOURCE_LOG) << "writeToFile:" << fileName; if (calendar() && calendar()->incidences().isEmpty()) { // It's an empty file. Set up the KAlarm custom property. KACalendar::setKAlarmVersion(calendar()); } return ICalResourceBase::writeToFile(fileName); } /****************************************************************************** * Retrieve an event from the calendar, whose uid and Akonadi id are given by * 'item' (item.remoteId() and item.id() respectively). * Set the event into a new item's payload, and signal its retrieval by calling * itemRetrieved(newitem). */ bool KAlarmResource::doRetrieveItem(const Akonadi::Item &item, const QSet &parts) { Q_UNUSED(parts); const QString rid = item.remoteId(); const KCalCore::Event::Ptr kcalEvent = calendar()->event(rid); if (!kcalEvent) { - qCWarning(KALARMRESOURCE_LOG) << "Event not found:" << rid; + qCWarning(KALARMRESOURCE_LOG) << "doRetrieveItem: Event not found:" << rid; Q_EMIT error(errorMessage(KAlarmResourceCommon::UidNotFound, rid)); return false; } if (kcalEvent->alarms().isEmpty()) { - qCWarning(KALARMRESOURCE_LOG) << "KCalCore::Event has no alarms:" << rid; + qCWarning(KALARMRESOURCE_LOG) << "doRetrieveItem: KCalCore::Event has no alarms:" << rid; Q_EMIT error(errorMessage(KAlarmResourceCommon::EventNoAlarms, rid)); return false; } KAEvent event(kcalEvent); const QString mime = CalEvent::mimeType(event.category()); if (mime.isEmpty()) { - qCWarning(KALARMRESOURCE_LOG) << "KAEvent has no alarms:" << rid; + qCWarning(KALARMRESOURCE_LOG) << "doRetrieveItem: KAEvent has no alarms:" << rid; Q_EMIT error(errorMessage(KAlarmResourceCommon::EventNoAlarms, rid)); return false; } event.setCompatibility(mCompatibility); const Item newItem = KAlarmResourceCommon::retrieveItem(item, event); itemRetrieved(newItem); return true; } /****************************************************************************** * Called when the resource settings have changed. * Update the supported mime types if the AlarmTypes setting has changed. * Update the storage format if UpdateStorageFormat setting = true. */ void KAlarmResource::settingsChanged() { - qCDebug(KALARMRESOURCE_LOG); + qCDebug(KALARMRESOURCE_LOG) << "settingsChanged"; const QStringList mimeTypes = mSettings->alarmTypes(); if (mimeTypes != mSupportedMimetypes) { mSupportedMimetypes = mimeTypes; } if (mSettings->updateStorageFormat()) { // This is a flag to request that the backend calendar storage format should // be updated to the current KAlarm format. qCDebug(KALARMRESOURCE_LOG) << "Update storage format"; fetchCollection(SLOT(updateFormat(KJob*))); } } /****************************************************************************** * Called when a collection fetch job completes. * Update the backend calendar storage format to the current KAlarm format. */ void KAlarmResource::updateFormat(KJob *j) { CollectionFetchJob *job = static_cast(j); if (j->error()) { - qCDebug(KALARMRESOURCE_LOG) << "Error: " << j->errorString(); + qCDebug(KALARMRESOURCE_LOG) << "Error: updateFormat:" << j->errorString(); } else if (job->collections().isEmpty()) { - qCDebug(KALARMRESOURCE_LOG) << "Error: resource's collection not found"; + qCDebug(KALARMRESOURCE_LOG) << "Error: updateFormat: resource's collection not found"; } else { const Collection c(job->collections().at(0)); if (c.hasAttribute()) { const CompatibilityAttribute *attr = c.attribute(); if (attr->compatibility() != mCompatibility) { - qCDebug(KALARMRESOURCE_LOG) << "Compatibility changed:" << mCompatibility << "->" << attr->compatibility(); + qCDebug(KALARMRESOURCE_LOG) << "updateFormat: Compatibility changed:" << mCompatibility << "->" << attr->compatibility(); } } switch (mCompatibility) { case KACalendar::Current: - qCWarning(KALARMRESOURCE_LOG) << "Already current storage format"; + qCWarning(KALARMRESOURCE_LOG) << "updateFormat: Already current storage format"; break; case KACalendar::Incompatible: default: - qCWarning(KALARMRESOURCE_LOG) << "Incompatible storage format: compat=" << mCompatibility; + qCWarning(KALARMRESOURCE_LOG) << "updateFormat: Incompatible storage format: compat=" << mCompatibility; break; case KACalendar::Converted: case KACalendar::Convertible: { if (mSettings->readOnly()) { - qCWarning(KALARMRESOURCE_LOG) << "Cannot update storage format for a read-only resource"; + qCWarning(KALARMRESOURCE_LOG) << "updateFormat: Cannot update storage format for a read-only resource"; break; } // Update the backend storage format to the current KAlarm format const QString filename = fileStorage()->fileName(); - qCDebug(KALARMRESOURCE_LOG) << "Updating storage for" << filename; + qCDebug(KALARMRESOURCE_LOG) << "updateFormat: Updating storage for" << filename; KACalendar::setKAlarmVersion(fileStorage()->calendar()); if (!writeToFile(filename)) { - qCWarning(KALARMRESOURCE_LOG) << "Error updating calendar storage format"; + qCWarning(KALARMRESOURCE_LOG) << "updateFormat: Error updating calendar storage format"; break; } // Prevent a new file read being triggered by writeToFile(), which // would replace the current Collection by a new one. mCurrentHash = calculateHash(filename); mCompatibility = mFileCompatibility = KACalendar::Current; mVersion = mFileVersion = KACalendar::CurrentFormat; KAlarmResourceCommon::setCollectionCompatibility(c, mCompatibility, 0); break; } } mSettings->setUpdateStorageFormat(false); mSettings->save(); } } /****************************************************************************** * Called when an item has been added to the collection. * Store the event in the calendar, and set its Akonadi remote ID to the * KAEvent's UID. */ void KAlarmResource::itemAdded(const Akonadi::Item &item, const Akonadi::Collection &) { if (!checkItemAddedChanged(item, CheckForAdded)) { return; } if (mCompatibility != KACalendar::Current) { - qCWarning(KALARMRESOURCE_LOG) << "Calendar not in current format"; + qCWarning(KALARMRESOURCE_LOG) << "itemAdded: Calendar not in current format"; cancelTask(errorMessage(KAlarmResourceCommon::NotCurrentFormat)); return; } const KAEvent event = item.payload(); KCalCore::Event::Ptr kcalEvent(new KCalCore::Event); event.updateKCalEvent(kcalEvent, KAEvent::UID_SET); if (!calendar()->addIncidence(kcalEvent)) { qCritical() << "Error adding event with id" << event.id() << ", item id" << item.id(); cancelTask(errorMessage(KAlarmResourceCommon::CalendarAdd, event.id())); return; } Item it(item); it.setRemoteId(kcalEvent->uid()); scheduleWrite(); changeCommitted(it); } /****************************************************************************** * Called when an item has been changed. * Store the changed event in the calendar, and delete the original event. */ void KAlarmResource::itemChanged(const Akonadi::Item &item, const QSet &parts) { Q_UNUSED(parts) if (!checkItemAddedChanged(item, CheckForChanged)) { return; } QString errorMsg; if (mCompatibility != KACalendar::Current) { - qCWarning(KALARMRESOURCE_LOG) << "Calendar not in current format"; + qCWarning(KALARMRESOURCE_LOG) << "itemChanged: Calendar not in current format"; cancelTask(errorMessage(KAlarmResourceCommon::NotCurrentFormat)); return; } const KAEvent event = KAlarmResourceCommon::checkItemChanged(item, errorMsg); if (!event.isValid()) { if (errorMsg.isEmpty()) { changeProcessed(); } else { cancelTask(errorMsg); } return; } KCalCore::Incidence::Ptr incidence = calendar()->incidence(item.remoteId()); if (incidence) { if (incidence->isReadOnly()) { - qCWarning(KALARMRESOURCE_LOG) << "Event is read only:" << event.id(); + qCWarning(KALARMRESOURCE_LOG) << "itemChanged: Event is read only:" << event.id(); cancelTask(errorMessage(KAlarmResourceCommon::EventReadOnly, event.id())); return; } if (incidence->type() == KCalCore::Incidence::TypeEvent) { calendar()->deleteIncidence(incidence); // it's not an Event incidence.clear(); } else { KCalCore::Event::Ptr ev(incidence.staticCast()); event.updateKCalEvent(ev, KAEvent::UID_SET); calendar()->setModified(true); } } if (!incidence) { // not in the calendar yet, should not happen -> add it KCalCore::Event::Ptr kcalEvent(new KCalCore::Event); event.updateKCalEvent(kcalEvent, KAEvent::UID_SET); calendar()->addIncidence(kcalEvent); } scheduleWrite(); changeCommitted(item); } /****************************************************************************** * Called when a collection has been changed. * Determine the calendar file's storage format. */ void KAlarmResource::collectionChanged(const Akonadi::Collection &collection) { ICalResourceBase::collectionChanged(collection); mFetchedAttributes = true; // Check whether calendar file format needs to be updated checkFileCompatibility(collection); } /****************************************************************************** * Retrieve all events from the calendar, and set each into a new item's * payload. Items are identified by their remote IDs. The Akonadi ID is not * used. * Signal the retrieval of the items by calling itemsRetrieved(items), which * updates Akonadi with any changes to the items. itemsRetrieved() compares * the new and old items, matching them on the remoteId(). If the flags or * payload have changed, or the Item has any new Attributes, the Akonadi * storage is updated. */ void KAlarmResource::doRetrieveItems(const Akonadi::Collection &collection) { - qCDebug(KALARMRESOURCE_LOG); + qCDebug(KALARMRESOURCE_LOG) << "doRetrieveItems"; // Set the collection's compatibility status KAlarmResourceCommon::setCollectionCompatibility(collection, mCompatibility, mVersion); // Retrieve events from the calendar const KCalCore::Event::List events = calendar()->events(); Item::List items; for (const KCalCore::Event::Ptr &kcalEvent : qAsConst(events)) { if (kcalEvent->alarms().isEmpty()) { - qCWarning(KALARMRESOURCE_LOG) << "KCalCore::Event has no alarms:" << kcalEvent->uid(); + qCWarning(KALARMRESOURCE_LOG) << "doRetrieveItems: KCalCore::Event has no alarms:" << kcalEvent->uid(); continue; // ignore events without alarms } const KAEvent event(kcalEvent); const QString mime = CalEvent::mimeType(event.category()); if (mime.isEmpty()) { - qCWarning(KALARMRESOURCE_LOG) << "KAEvent has no alarms:" << event.id(); + qCWarning(KALARMRESOURCE_LOG) << "doRetrieveItems: KAEvent has no alarms:" << event.id(); continue; // event has no usable alarms } Item item(mime); item.setRemoteId(kcalEvent->uid()); item.setPayload(event); items << item; } itemsRetrieved(items); } /****************************************************************************** * Execute a CollectionFetchJob to fetch details of this resource's collection. */ CollectionFetchJob *KAlarmResource::fetchCollection(const char *slot) { CollectionFetchJob *job = new CollectionFetchJob(Collection::root(), CollectionFetchJob::FirstLevel); job->fetchScope().setResource(identifier()); connect(job, SIGNAL(result(KJob*)), slot); return job; } AKONADI_RESOURCE_MAIN(KAlarmResource) diff --git a/resources/kalarm/kalarmdir/kalarmdirresource.cpp b/resources/kalarm/kalarmdir/kalarmdirresource.cpp index 8f4a23b79..8610e21af 100644 --- a/resources/kalarm/kalarmdir/kalarmdirresource.cpp +++ b/resources/kalarm/kalarmdir/kalarmdirresource.cpp @@ -1,1175 +1,1175 @@ /* * kalarmdirresource.cpp - Akonadi directory resource for KAlarm * Program: kalarm - * Copyright © 2011-2016 by David Jarvie + * Copyright © 2011-2019 David Jarvie * Copyright (c) 2008 Tobias Koenig * Copyright (c) 2008 Bertjan Broeksema * * 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 "kalarmdirresource.h" #include "kalarmresourcecommon.h" #include "autoqpointer.h" #include "kalarmdirsettingsadaptor.h" #include "settingsdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kalarmdirresource_debug.h" using namespace Akonadi; using namespace KCalCore; using namespace Akonadi_KAlarm_Dir_Resource; using KAlarmResourceCommon::errorMessage; static const char warningFile[] = "WARNING_README.txt"; -#define DEBUG_DATA \ - qCDebug(KALARMDIRRESOURCE_LOG)<<"ID:Files:"; \ +#define DEBUG_DATA(func) \ + qCDebug(KALARMDIRRESOURCE_LOG)<itemFetchScope().fetchFullPayload(); changeRecorder()->fetchCollection(true); connect(KDirWatch::self(), &KDirWatch::created, this, &KAlarmDirResource::fileCreated); connect(KDirWatch::self(), &KDirWatch::dirty, this, &KAlarmDirResource::fileChanged); connect(KDirWatch::self(), &KDirWatch::deleted, this, &KAlarmDirResource::fileDeleted); // Find the collection which this resource manages CollectionFetchJob *job = new CollectionFetchJob(Collection::root(), CollectionFetchJob::FirstLevel); job->fetchScope().setResource(identifier()); connect(job, &CollectionFetchJob::result, this, &KAlarmDirResource::collectionFetchResult); QTimer::singleShot(0, this, [this] { loadFiles(); }); } KAlarmDirResource::~KAlarmDirResource() { delete mSettings; } void KAlarmDirResource::aboutToQuit() { mSettings->save(); } /****************************************************************************** * Called when the collection fetch job completes. * Check the calendar files' compatibility statuses if pending. */ void KAlarmDirResource::collectionFetchResult(KJob *j) { qCDebug(KALARMDIRRESOURCE_LOG); if (j->error()) { - qCritical() << "CollectionFetchJob error: " << j->errorString(); + qCritical() << "Error: collectionFetchResult: " << j->errorString(); } else { CollectionFetchJob *job = static_cast(j); Collection::List collections = job->collections(); int count = collections.count(); - qCDebug(KALARMDIRRESOURCE_LOG) << "Count:" << count; + qCDebug(KALARMDIRRESOURCE_LOG) << "collectionFetchResult: count:" << count; if (!count) { qCritical() << "Cannot retrieve this resource's collection"; } else { if (count > 1) { qCritical() << "Multiple collections for this resource:" << count; } Collection &c(collections[0]); - qCDebug(KALARMDIRRESOURCE_LOG) << "Id:" << c.id() << ", remote id:" << c.remoteId(); + qCDebug(KALARMDIRRESOURCE_LOG) << "collectionFetchResult: id:" << c.id() << ", remote id:" << c.remoteId(); if (!mCollectionFetched) { bool recreate = mSettings->path().isEmpty(); if (!recreate) { // Remote ID could be path or URL, depending on which version // of Akonadi created it. const QString rid = c.remoteId(); const QUrl url = QUrl::fromLocalFile(mSettings->path()); if (!url.isLocalFile() || (rid != url.toLocalFile() && rid != url.url() && rid != url.toDisplayString())) { qCritical() << "Collection remote ID does not match settings: changing settings"; recreate = true; } } if (recreate) { // Initialising a resource which seems to have no stored // settings config file. Recreate the settings. static const Collection::Rights writableRights = Collection::CanChangeItem | Collection::CanCreateItem | Collection::CanDeleteItem; - qCDebug(KALARMDIRRESOURCE_LOG) << "Recreating config for remote id:" << c.remoteId(); + qCDebug(KALARMDIRRESOURCE_LOG) << "collectionFetchResult: Recreating config for remote id:" << c.remoteId(); mSettings->setPath(c.remoteId()); mSettings->setDisplayName(c.name()); mSettings->setAlarmTypes(c.contentMimeTypes()); mSettings->setReadOnly((c.rights() & writableRights) != writableRights); mSettings->save(); } mCollectionId = c.id(); if (recreate) { // Load items from the backend files now that their location is known loadFiles(true); } // Set collection's format compatibility flag now that the collection // and its attributes have been fetched. KAlarmResourceCommon::setCollectionCompatibility(c, mCompatibility, mVersion); } } } mCollectionFetched = true; if (mWaitingToRetrieve) { mWaitingToRetrieve = false; retrieveCollections(); } } /****************************************************************************** */ void KAlarmDirResource::configure(WId windowId) { - qCDebug(KALARMDIRRESOURCE_LOG); + qCDebug(KALARMDIRRESOURCE_LOG) << "configure"; // Keep note of the old configuration settings QString path = mSettings->path(); QString name = mSettings->displayName(); bool readOnly = mSettings->readOnly(); QStringList types = mSettings->alarmTypes(); // Note: mSettings->monitorFiles() can't change here // 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 SettingsDialog(windowId, mSettings); if (dlg->exec()) { if (path.isEmpty()) { // Creating a new resource clearCache(); // this deletes any existing collection loadFiles(true); synchronizeCollectionTree(); } else if (mSettings->path() != path) { // Directory path change is not allowed for existing resources Q_EMIT configurationDialogRejected(); return; } else { bool modify = false; Collection c(mCollectionId); if (mSettings->alarmTypes() != types) { // Settings have changed which might affect the alarm configuration initializeDirectory(); // should only be needed for new resource, but just in case ... CalEvent::Types newTypes = CalEvent::types(mSettings->alarmTypes()); CalEvent::Types oldTypes = CalEvent::types(types); changeAlarmTypes(~newTypes & oldTypes); c.setContentMimeTypes(mSettings->alarmTypes()); modify = true; } if (mSettings->readOnly() != readOnly || mSettings->displayName() != name) { // Need to change the collection's rights or name c.setRemoteId(directoryName()); setNameRights(c); modify = true; } if (modify) { // Update the Akonadi server with the changes CollectionModifyJob *job = new CollectionModifyJob(c); connect(job, &CollectionFetchJob::result, this, &KAlarmDirResource::jobDone); } } Q_EMIT configurationDialogAccepted(); } else { Q_EMIT configurationDialogRejected(); } } /****************************************************************************** * Add/remove events to ensure that they match the changed alarm types for the * resource. */ void KAlarmDirResource::changeAlarmTypes(CalEvent::Types removed) { - DEBUG_DATA; + DEBUG_DATA("changeAlarmTypes:"); const QString dirPath = directoryName(); - qCDebug(KALARMDIRRESOURCE_LOG) << dirPath; + qCDebug(KALARMDIRRESOURCE_LOG) << "changeAlarmTypes:" << dirPath; const QDir dir(dirPath); // Read and parse each file in turn QDirIterator it(dir); while (it.hasNext()) { it.next(); int removeIfInvalid = 0; QString fileEventId; const QString file = it.fileName(); if (!isFileValid(file)) { continue; } QHash::iterator fit = mFileEventIds.find(file); if (fit != mFileEventIds.end()) { // The file is in the existing file list fileEventId = fit.value(); QHash::ConstIterator it = mEvents.constFind(fileEventId); if (it != mEvents.constEnd()) { // And its event is in the existing events list const EventFile &data = it.value(); if (data.files[0] == file) { // It's the file for a used event if (data.event.category() & removed) { // The event's type is no longer wanted, so remove it deleteItem(data.event); removeEvent(data.event.id(), false); } continue; } else { // The file's event is not currently used - load the // file and use its event if appropriate. removeIfInvalid = 0x03; // remove from mEvents and mFileEventIds } } else { // The file's event isn't in the list of current valid // events - this shouldn't ever happen removeIfInvalid = 0x01; // remove from mFileEventIds } } // Load the file and use its event if appropriate. const QString path = filePath(file); if (QFileInfo(path).isFile()) { if (createItemAndIndex(path, file)) { continue; } } // The event wasn't wanted, so remove from lists if (removeIfInvalid & 0x01) { mFileEventIds.erase(fit); } if (removeIfInvalid & 0x02) { removeEventFile(fileEventId, file); } } - DEBUG_DATA; + DEBUG_DATA("changeAlarmTypes exit:"); setCompatibility(); } /****************************************************************************** * Called when the resource settings have changed. * Update the display name if it has changed. * Stop monitoring the directory if 'monitorFiles' is now false. * Update the storage format if UpdateStorageFormat setting = true. * NOTE: no provision is made for changes to the directory path, since this is * not permitted (would need remote ID changed, plus other complications). */ void KAlarmDirResource::settingsChanged() { - qCDebug(KALARMDIRRESOURCE_LOG); + qCDebug(KALARMDIRRESOURCE_LOG) << "settingsChanged"; const QString display = mSettings->displayName(); if (display != name()) { setName(display); } const QString dirPath = mSettings->path(); if (!dirPath.isEmpty()) { const bool monitoring = KDirWatch::self()->contains(dirPath); if (monitoring && !mSettings->monitorFiles()) { KDirWatch::self()->removeDir(dirPath); } else if (!monitoring && mSettings->monitorFiles()) { KDirWatch::self()->addDir(dirPath, KDirWatch::WatchFiles); } #if 0 if (mSettings->monitorFiles() && !monitor) { // Settings have changed which might affect the alarm configuration qCDebug(KALARMDIRRESOURCE_LOG) << "Monitored changed"; loadFiles(true); // synchronizeCollectionTree(); } #endif } if (mSettings->updateStorageFormat()) { // This is a flag to request that the backend calendar storage format should // be updated to the current KAlarm format. KACalendar::Compat okCompat(KACalendar::Current | KACalendar::Convertible); if (mCompatibility & ~okCompat) { qCWarning(KALARMDIRRESOURCE_LOG) << "Either incompatible storage format or nothing to update"; } else if (mSettings->readOnly()) { qCWarning(KALARMDIRRESOURCE_LOG) << "Cannot update storage format for a read-only resource"; } else { // Update the backend storage format to the current KAlarm format bool ok = true; for (QHash::iterator it = mEvents.begin(); it != mEvents.end(); ++it) { KAEvent &event = it.value().event; if (event.compatibility() == KACalendar::Convertible) { if (writeToFile(event)) { event.setCompatibility(KACalendar::Current); } else { qCWarning(KALARMDIRRESOURCE_LOG) << "Error updating storage format for event id" << event.id(); ok = false; } } } if (ok) { mCompatibility = KACalendar::Current; mVersion = KACalendar::CurrentFormat; const Collection c(mCollectionId); if (c.isValid()) { KAlarmResourceCommon::setCollectionCompatibility(c, mCompatibility, mVersion); } } } mSettings->setUpdateStorageFormat(false); mSettings->save(); } } /****************************************************************************** * Load and parse data from each file in the directory. * The events are cached in mEvents. */ bool KAlarmDirResource::loadFiles(bool sync) { const QString dirPath = directoryName(); if (dirPath.isEmpty()) { return false; } - qCDebug(KALARMDIRRESOURCE_LOG) << dirPath; + qCDebug(KALARMDIRRESOURCE_LOG) << "loadFiles:" << dirPath; const QDir dir(dirPath); // Create the directory if it doesn't exist. // This should only be needed for a new resource, but just in case ... initializeDirectory(); mEvents.clear(); mFileEventIds.clear(); // Set the resource display name to the configured name, else the directory // name, if not already set. QString display = mSettings->displayName(); if (display.isEmpty() && (name().isEmpty() || name() == identifier())) { display = dir.dirName(); } if (!display.isEmpty()) { setName(display); } // Read and parse each file in turn QDirIterator it(dir); while (it.hasNext()) { it.next(); const QString file = it.fileName(); if (isFileValid(file)) { const QString path = filePath(file); if (QFileInfo(path).isFile()) { const KAEvent event = loadFile(path, file); if (event.isValid()) { addEventFile(event, file); mFileEventIds.insert(file, event.id()); } } } } - DEBUG_DATA; + DEBUG_DATA("loadFiles:"); setCompatibility(false); // don't write compatibility - no collection exists yet if (mSettings->monitorFiles()) { // Monitor the directory for changes to the files if (!KDirWatch::self()->contains(dirPath)) { KDirWatch::self()->addDir(dirPath, KDirWatch::WatchFiles); } } if (sync) { // Ensure the Akonadi server is updated with the current list of events synchronize(); } Q_EMIT status(Idle); return true; } /****************************************************************************** * Load and parse data a single file in the directory. * 'path' is the full path of 'file'. * 'file' should not contain any directory component. */ KAEvent KAlarmDirResource::loadFile(const QString &path, const QString &file) { - qCDebug(KALARMDIRRESOURCE_LOG) << path; + qCDebug(KALARMDIRRESOURCE_LOG) << "loadFile:" << path; MemoryCalendar::Ptr calendar(new MemoryCalendar(QTimeZone::utc())); FileStorage::Ptr fileStorage(new FileStorage(calendar, path, new ICalFormat())); if (!fileStorage->load()) { // Don't output an error in the case of the creation of a temporary // file which triggered fileChanged() but no longer exists. if (QFileInfo::exists(path)) { qCWarning(KALARMDIRRESOURCE_LOG) << "Error loading" << path; } return KAEvent(); } const Event::List events = calendar->events(); if (events.isEmpty()) { qCDebug(KALARMDIRRESOURCE_LOG) << "Empty calendar in file" << path; return KAEvent(); } if (events.count() > 1) { qCWarning(KALARMDIRRESOURCE_LOG) << "Deleting" << events.count() - 1 << "excess events found in file" << path; for (int i = 1; i < events.count(); ++i) { calendar->deleteEvent(events[i]); } } const Event::Ptr kcalEvent(events[0]); if (kcalEvent->uid() != file) { qCWarning(KALARMDIRRESOURCE_LOG) << "File" << path << ": event id differs from file name"; } if (kcalEvent->alarms().isEmpty()) { qCWarning(KALARMDIRRESOURCE_LOG) << "File" << path << ": event contains no alarms"; return KAEvent(); } // Convert event in memory to current KAlarm format if possible int version; KACalendar::Compat compat = KAlarmResourceCommon::getCompatibility(fileStorage, version); KAEvent event(kcalEvent); const QString mime = CalEvent::mimeType(event.category()); if (mime.isEmpty()) { - qCWarning(KALARMDIRRESOURCE_LOG) << "KAEvent has no usable alarms:" << event.id(); + qCWarning(KALARMDIRRESOURCE_LOG) << "loadFile: KAEvent has no usable alarms:" << event.id(); return KAEvent(); } if (!mSettings->alarmTypes().contains(mime)) { - qCWarning(KALARMDIRRESOURCE_LOG) << "KAEvent has wrong alarm type for resource:" << mime; + qCWarning(KALARMDIRRESOURCE_LOG) << "loadFile: KAEvent has wrong alarm type for resource:" << mime; return KAEvent(); } event.setCompatibility(compat); return event; } /****************************************************************************** * After a file/event has been removed, load the next file in the list for the * event ID. * Reply = new event, or invalid if none. */ KAEvent KAlarmDirResource::loadNextFile(const QString &eventId, const QString &file) { QString nextFile = file; while (!nextFile.isEmpty()) { // There is another file with the same ID - load it const KAEvent event = loadFile(filePath(nextFile), nextFile); if (event.isValid()) { addEventFile(event, nextFile); mFileEventIds.insert(nextFile, event.id()); return event; } mFileEventIds.remove(nextFile); nextFile = removeEventFile(eventId, nextFile); } return KAEvent(); } /****************************************************************************** * Retrieve an event from the calendar, whose uid and Akonadi id are given by * 'item' (item.remoteId() and item.id() respectively). * Set the event into a new item's payload, and signal its retrieval by calling * itemRetrieved(newitem). */ bool KAlarmDirResource::retrieveItem(const Akonadi::Item &item, const QSet &) { const QString rid = item.remoteId(); QHash::ConstIterator it = mEvents.constFind(rid); if (it == mEvents.constEnd()) { - qCWarning(KALARMDIRRESOURCE_LOG) << "Event not found:" << rid; + qCWarning(KALARMDIRRESOURCE_LOG) << "retrieveItem: Event not found:" << rid; Q_EMIT error(errorMessage(KAlarmResourceCommon::UidNotFound, rid)); return false; } KAEvent event(it.value().event); const Item newItem = KAlarmResourceCommon::retrieveItem(item, event); itemRetrieved(newItem); return true; } /****************************************************************************** * Called when an item has been added to the collection. * Store the event in a file, and set its Akonadi remote ID to the KAEvent's UID. */ void KAlarmDirResource::itemAdded(const Akonadi::Item &item, const Akonadi::Collection &) { - qCDebug(KALARMDIRRESOURCE_LOG) << item.id(); + qCDebug(KALARMDIRRESOURCE_LOG) << "itemAdded:" << item.id(); if (cancelIfReadOnly()) { return; } KAEvent event; if (item.hasPayload()) { event = item.payload(); } if (!event.isValid()) { changeProcessed(); return; } event.setCompatibility(KACalendar::Current); setCompatibility(); if (!writeToFile(event)) { return; } addEventFile(event, event.id()); Item newItem(item); newItem.setRemoteId(event.id()); // scheduleWrite(); //???? is this needed? changeCommitted(newItem); } /****************************************************************************** * Called when an item has been changed. * Store the changed event in a file. */ void KAlarmDirResource::itemChanged(const Akonadi::Item &item, const QSet &) { - qCDebug(KALARMDIRRESOURCE_LOG) << item.id() << ", remote ID:" << item.remoteId(); + qCDebug(KALARMDIRRESOURCE_LOG) << "itemChanged:" << item.id() << ", remote ID:" << item.remoteId(); if (cancelIfReadOnly()) { return; } QHash::iterator it = mEvents.find(item.remoteId()); if (it != mEvents.end()) { if (it.value().event.isReadOnly()) { qCWarning(KALARMDIRRESOURCE_LOG) << "Event is read only:" << item.remoteId(); cancelTask(errorMessage(KAlarmResourceCommon::EventReadOnly, item.remoteId())); return; } if (it.value().event.compatibility() != KACalendar::Current) { qCWarning(KALARMDIRRESOURCE_LOG) << "Event not in current format:" << item.remoteId(); cancelTask(errorMessage(KAlarmResourceCommon::EventNotCurrentFormat, item.remoteId())); return; } } KAEvent event; if (item.hasPayload()) { event = item.payload(); } if (!event.isValid()) { changeProcessed(); return; } #if 0 QString errorMsg; KAEvent event = KAlarmResourceCommon::checkItemChanged(item, errorMsg); if (!event.isValid()) { if (errorMsg.isEmpty()) { changeProcessed(); } else { cancelTask(errorMsg); } return; } #endif event.setCompatibility(KACalendar::Current); if (mCompatibility != KACalendar::Current) { setCompatibility(); } if (!writeToFile(event)) { return; } it.value().event = event; changeCommitted(item); } /****************************************************************************** * Called when an item has been deleted. * Delete the item's file. */ void KAlarmDirResource::itemRemoved(const Akonadi::Item &item) { - qCDebug(KALARMDIRRESOURCE_LOG) << item.id(); + qCDebug(KALARMDIRRESOURCE_LOG) << "itemRemoved:" << item.id(); if (cancelIfReadOnly()) { return; } removeEvent(item.remoteId(), true); setCompatibility(); changeProcessed(); } /****************************************************************************** * Remove an event from the indexes, and optionally delete its file. */ void KAlarmDirResource::removeEvent(const QString &eventId, bool deleteFile) { QString file = eventId; QString nextFile; QHash::iterator it = mEvents.find(eventId); if (it != mEvents.end()) { file = it.value().files[0]; nextFile = removeEventFile(eventId, file); mFileEventIds.remove(file); - DEBUG_DATA; + DEBUG_DATA("removeEvent:"); } if (deleteFile) { QFile::remove(filePath(file)); } loadNextFile(eventId, nextFile); // load any other file with the same event ID } /****************************************************************************** * If the resource is read-only, cancel the task andQ_EMIT an error. * Reply = true if cancelled. */ bool KAlarmDirResource::cancelIfReadOnly() { if (mSettings->readOnly()) { qCWarning(KALARMDIRRESOURCE_LOG) << "Calendar is read-only:" << directoryName(); Q_EMIT error(i18nc("@info", "Trying to write to a read-only calendar: '%1'", directoryName())); cancelTask(); return true; } return false; } /****************************************************************************** * Write an event to a file. The file name is the event's id. */ bool KAlarmDirResource::writeToFile(const KAEvent &event) { Event::Ptr kcalEvent(new Event); event.updateKCalEvent(kcalEvent, KAEvent::UID_SET); MemoryCalendar::Ptr calendar(new MemoryCalendar(QTimeZone::utc())); KACalendar::setKAlarmVersion(calendar); // set the KAlarm custom property if (!calendar->addIncidence(kcalEvent)) { qCritical() << "Error adding event with id" << event.id(); Q_EMIT error(errorMessage(KAlarmResourceCommon::CalendarAdd, event.id())); cancelTask(); return false; } mChangedFiles += event.id(); // suppress KDirWatch processing for this write const QString path = filePath(event.id()); - qCDebug(KALARMDIRRESOURCE_LOG) << event.id() << " File:" << path; + qCDebug(KALARMDIRRESOURCE_LOG) << "writeToFile:" << event.id() << " File:" << path; FileStorage::Ptr fileStorage(new FileStorage(calendar, path, new ICalFormat())); if (!fileStorage->save()) { Q_EMIT error(i18nc("@info", "Failed to save event file: %1", path)); cancelTask(); return false; } return true; } /****************************************************************************** * Create the resource's collection. */ void KAlarmDirResource::retrieveCollections() { QString rid = mSettings->path(); if (!mCollectionFetched && rid.isEmpty()) { // The resource config seems to be missing. Execute this function // once the collection config has been set up. mWaitingToRetrieve = true; return; } - qCDebug(KALARMDIRRESOURCE_LOG); + qCDebug(KALARMDIRRESOURCE_LOG) << "retrieveCollections"; Collection c; c.setParentCollection(Collection::root()); c.setRemoteId(rid); c.setContentMimeTypes(mSettings->alarmTypes()); setNameRights(c); // Don't update CollectionAttribute here, since it hasn't yet been fetched // from Akonadi database. Collection::List list; list << c; collectionsRetrieved(list); } /****************************************************************************** * Set the collection's name and rights. * It is the caller's responsibility to notify the Akonadi server. */ void KAlarmDirResource::setNameRights(Collection &c) { - qCDebug(KALARMDIRRESOURCE_LOG); + qCDebug(KALARMDIRRESOURCE_LOG) << "setNameRights"; const QString display = mSettings->displayName(); c.setName(display.isEmpty() ? name() : display); EntityDisplayAttribute *attr = c.attribute(Collection::AddIfMissing); attr->setDisplayName(name()); attr->setIconName(QStringLiteral("kalarm")); if (mSettings->readOnly()) { c.setRights(Collection::CanChangeCollection); } else { Collection::Rights rights = Collection::ReadOnly; rights |= Collection::CanChangeItem; rights |= Collection::CanCreateItem; rights |= Collection::CanDeleteItem; rights |= Collection::CanChangeCollection; c.setRights(rights); } - qCDebug(KALARMDIRRESOURCE_LOG) << "end"; + qCDebug(KALARMDIRRESOURCE_LOG) << "setNameRights: end"; } /****************************************************************************** * Retrieve all events from the directory, and set each into a new item's * payload. Items are identified by their remote IDs. The Akonadi ID is not * used. * Signal the retrieval of the items by calling itemsRetrieved(items), which * updates Akonadi with any changes to the items. itemsRetrieved() compares * the new and old items, matching them on the remoteId(). If the flags or * payload have changed, or the Item has any new Attributes, the Akonadi * storage is updated. */ void KAlarmDirResource::retrieveItems(const Akonadi::Collection &collection) { mCollectionId = collection.id(); // note the one and only collection for this resource - qCDebug(KALARMDIRRESOURCE_LOG) << "Collection id:" << mCollectionId; + qCDebug(KALARMDIRRESOURCE_LOG) << "retrieveItems: collection" << mCollectionId; // Set the collection's compatibility status KAlarmResourceCommon::setCollectionCompatibility(collection, mCompatibility, mVersion); // Fetch the list of valid mime types const QStringList mimeTypes = mSettings->alarmTypes(); // Retrieve events Item::List items; foreach (const EventFile &data, mEvents) { const KAEvent &event = data.event; const QString mime = CalEvent::mimeType(event.category()); if (mime.isEmpty()) { - qCWarning(KALARMDIRRESOURCE_LOG) << "KAEvent has no alarms:" << event.id(); + qCWarning(KALARMDIRRESOURCE_LOG) << "retrieveItems: KAEvent has no alarms:" << event.id(); continue; // event has no usable alarms } if (!mimeTypes.contains(mime)) { continue; // restrict alarms returned to the defined types } Item item(mime); item.setRemoteId(event.id()); item.setPayload(event); items.append(item); } itemsRetrieved(items); } /****************************************************************************** * Called when the collection has been changed. * Set its display name if that has changed. */ void KAlarmDirResource::collectionChanged(const Akonadi::Collection &collection) { - qCDebug(KALARMDIRRESOURCE_LOG); + qCDebug(KALARMDIRRESOURCE_LOG) << "collectionChanged"; // If the collection has a new display name, set the resource's display // name the same, and save to the settings. const QString newName = collection.displayName(); if (!newName.isEmpty() && newName != name()) { setName(newName); } if (newName != mSettings->displayName()) { mSettings->setDisplayName(newName); mSettings->save(); } changeCommitted(collection); } /****************************************************************************** * Called when a file has been created in the directory. */ void KAlarmDirResource::fileCreated(const QString &path) { - qCDebug(KALARMDIRRESOURCE_LOG) << path; + qCDebug(KALARMDIRRESOURCE_LOG) << "fileCreated:" << path; if (path == directoryName()) { // The directory has been created. Load all files in it, and // tell the Akonadi server to create an Item for each event. loadFiles(true); foreach (const EventFile &data, mEvents) { createItem(data.event); } } else { const QString file = fileName(path); int i = mChangedFiles.indexOf(file); if (i >= 0) { mChangedFiles.removeAt(i); // the file was updated by this resource } else if (isFileValid(file)) { if (createItemAndIndex(path, file)) { setCompatibility(); } - DEBUG_DATA; + DEBUG_DATA("fileCreated:"); } } } /****************************************************************************** * Called when a file has changed in the directory. */ void KAlarmDirResource::fileChanged(const QString &path) { if (path != directoryName()) { - qCDebug(KALARMDIRRESOURCE_LOG) << path; + qCDebug(KALARMDIRRESOURCE_LOG) << "fileChanged:" << path; const QString file = fileName(path); int i = mChangedFiles.indexOf(file); if (i >= 0) { mChangedFiles.removeAt(i); // the file was updated by this resource } else if (isFileValid(file)) { QString nextFile, oldId; KAEvent oldEvent; const KAEvent event = loadFile(path, file); // Get the file's old event ID QHash::iterator fit = mFileEventIds.find(file); if (fit != mFileEventIds.end()) { oldId = fit.value(); if (event.id() != oldId) { // The file's event ID has changed - remove the old event nextFile = removeEventFile(oldId, file, &oldEvent); if (event.isValid()) { fit.value() = event.id(); } else { mFileEventIds.erase(fit); } } } else { // The file didn't contain an event before. if (event.isValid()) { // Save details of the new event. mFileEventIds.insert(file, event.id()); } else { // The file still doesn't contain a recognised event. return; } } addEventFile(event, file); KAEvent e = loadNextFile(oldId, nextFile); // load any other file with the same event ID setCompatibility(); // Tell the Akonadi server to amend the Item for the event if (event.id() != oldId) { if (e.isValid()) { modifyItem(e); } else { deleteItem(oldEvent); } createItem(event); // create a new Item for the new event ID } else { modifyItem(event); } - DEBUG_DATA; + DEBUG_DATA("fileChanged:"); } } } /****************************************************************************** * Called when a file has been deleted in the directory. */ void KAlarmDirResource::fileDeleted(const QString &path) { - qCDebug(KALARMDIRRESOURCE_LOG) << path; + qCDebug(KALARMDIRRESOURCE_LOG) << "fileDeleted:" << path; if (path == directoryName()) { // The directory has been deleted mEvents.clear(); mFileEventIds.clear(); // Tell the Akonadi server to delete all Items in the collection Collection c(mCollectionId); ItemDeleteJob *job = new ItemDeleteJob(c); connect(job, &CollectionFetchJob::result, this, &KAlarmDirResource::jobDone); } else { // A single file has been deleted const QString file = fileName(path); if (isFileValid(file)) { QHash::iterator fit = mFileEventIds.find(file); if (fit != mFileEventIds.end()) { QString eventId = fit.value(); KAEvent event; QString nextFile = removeEventFile(eventId, file, &event); mFileEventIds.erase(fit); KAEvent e = loadNextFile(eventId, nextFile); // load any other file with the same event ID setCompatibility(); if (e.isValid()) { // Tell the Akonadi server to amend the Item for the event modifyItem(e); } else { // Tell the Akonadi server to delete the Item for the event deleteItem(event); } - DEBUG_DATA; + DEBUG_DATA("fileDeleted:"); } } } } /****************************************************************************** * Tell the Akonadi server to create an Item for a given file's event, and add * it to the indexes. */ bool KAlarmDirResource::createItemAndIndex(const QString &path, const QString &file) { const KAEvent event = loadFile(path, file); if (event.isValid()) { // Tell the Akonadi server to create an Item for the event if (createItem(event)) { addEventFile(event, file); mFileEventIds.insert(file, event.id()); return true; } } return false; } /****************************************************************************** * Tell the Akonadi server to create an Item for a given event. */ bool KAlarmDirResource::createItem(const KAEvent &event) { Item item; if (!event.setItemPayload(item, mSettings->alarmTypes())) { - qCWarning(KALARMDIRRESOURCE_LOG) << "Invalid mime type for collection"; + qCWarning(KALARMDIRRESOURCE_LOG) << "createItem: Invalid mime type for collection"; return false; } Collection c(mCollectionId); item.setParentCollection(c); item.setRemoteId(event.id()); ItemCreateJob *job = new ItemCreateJob(item, c); connect(job, &CollectionFetchJob::result, this, &KAlarmDirResource::jobDone); return true; } /****************************************************************************** * Tell the Akonadi server to amend the Item for a given event. */ bool KAlarmDirResource::modifyItem(const KAEvent &event) { Item item; if (!event.setItemPayload(item, mSettings->alarmTypes())) { - qCWarning(KALARMDIRRESOURCE_LOG) << "Invalid mime type for collection"; + qCWarning(KALARMDIRRESOURCE_LOG) << "modifyItem: Invalid mime type for collection"; return false; } Collection c(mCollectionId); item.setParentCollection(c); item.setRemoteId(event.id()); ItemModifyJob *job = new ItemModifyJob(item); job->disableRevisionCheck(); connect(job, &CollectionFetchJob::result, this, &KAlarmDirResource::jobDone); return true; } /****************************************************************************** * Tell the Akonadi server to delete the Item for a given event. */ void KAlarmDirResource::deleteItem(const KAEvent &event) { Item item(CalEvent::mimeType(event.category())); Collection c(mCollectionId); item.setParentCollection(c); item.setRemoteId(event.id()); ItemDeleteJob *job = new ItemDeleteJob(item); connect(job, &CollectionFetchJob::result, this, &KAlarmDirResource::jobDone); } /****************************************************************************** * Called when a collection or item job has completed. * Checks for any error. */ void KAlarmDirResource::jobDone(KJob *j) { if (j->error()) { qCritical() << j->metaObject()->className() << "error:" << j->errorString(); } } /****************************************************************************** * Create the directory if it doesn't already exist, and ensure that it * contains a WARNING_README.txt file. */ void KAlarmDirResource::initializeDirectory() const { - qCDebug(KALARMDIRRESOURCE_LOG); const QDir dir(directoryName()); const QString dirPath = dir.absolutePath(); + qCDebug(KALARMDIRRESOURCE_LOG) << "initializeDirectory" << dirPath; // If folder does not exist, create it if (!dir.exists()) { - qCDebug(KALARMDIRRESOURCE_LOG) << "Creating" << dirPath; + qCDebug(KALARMDIRRESOURCE_LOG) << "initializeDirectory: creating" << dirPath; QDir::root().mkpath(dirPath); } // Check whether warning file is in place... QFile file(dirPath + QDir::separator() + QLatin1String(warningFile)); if (!file.exists()) { // ... if not, create it file.open(QIODevice::WriteOnly); file.write("Important Warning!!!\n" "Do not create or copy items inside this folder manually:\n" "they are managed by the Akonadi framework!\n"); file.close(); } } QString KAlarmDirResource::directoryName() const { return mSettings->path(); } /****************************************************************************** * Return the full path of an event file. * 'file' should not contain any directory component. */ QString KAlarmDirResource::filePath(const QString &file) const { return mSettings->path() + QDir::separator() + file; } /****************************************************************************** * Strip the directory path from a file name. */ QString KAlarmDirResource::fileName(const QString &path) const { const QFileInfo fi(path); if (fi.isDir() || fi.isBundle()) { return QString(); } if (fi.path() == mSettings->path()) { return fi.fileName(); } return path; } /****************************************************************************** * Evaluate the version compatibility status of the calendar. This is the OR of * the statuses of the individual events. */ void KAlarmDirResource::setCompatibility(bool writeAttr) { static const KACalendar::Compat AllCompat(KACalendar::Current | KACalendar::Convertible | KACalendar::Incompatible); const KACalendar::Compat oldCompatibility = mCompatibility; const int oldVersion = mVersion; if (mEvents.isEmpty()) { mCompatibility = KACalendar::Current; } else { mCompatibility = KACalendar::Unknown; foreach (const EventFile &data, mEvents) { const KAEvent &event = data.event; mCompatibility |= event.compatibility(); if ((mCompatibility & AllCompat) == AllCompat) { break; } } } mVersion = (mCompatibility == KACalendar::Current) ? KACalendar::CurrentFormat : KACalendar::MixedFormat; if (writeAttr && (mCompatibility != oldCompatibility || mVersion != oldVersion)) { const Collection c(mCollectionId); if (c.isValid()) { KAlarmResourceCommon::setCollectionCompatibility(c, mCompatibility, mVersion); } } } /****************************************************************************** * Add an event/file combination to the mEvents map. */ void KAlarmDirResource::addEventFile(const KAEvent &event, const QString &file) { if (event.isValid()) { QHash::iterator it = mEvents.find(event.id()); if (it != mEvents.end()) { EventFile &data = it.value(); data.event = event; data.files.removeAll(file); // in case it isn't the first file data.files.prepend(file); } else { mEvents.insert(event.id(), EventFile(event, QStringList(file))); } } } /****************************************************************************** * Remove an event ID/file combination from the mEvents map. * Reply = next file with the same event ID. */ QString KAlarmDirResource::removeEventFile(const QString &eventId, const QString &file, KAEvent *event) { QHash::iterator it = mEvents.find(eventId); if (it != mEvents.end()) { if (event) { *event = it.value().event; } it.value().files.removeAll(file); if (!it.value().files.isEmpty()) { return it.value().files[0]; } mEvents.erase(it); } else if (event) { *event = KAEvent(); } return QString(); } /****************************************************************************** * Check whether a file is to be ignored. * Reply = false if file is to be ignored. */ bool KAlarmDirResource::isFileValid(const QString &file) const { return !file.isEmpty() && !file.startsWith(QLatin1Char('.')) && !file.endsWith(QLatin1Char('~')) && file != QLatin1String(warningFile) && QFileInfo::exists(filePath(file)); // a temporary file may no longer exist } AKONADI_RESOURCE_MAIN(KAlarmDirResource) diff --git a/resources/kalarm/shared/kalarmresourcecommon.cpp b/resources/kalarm/shared/kalarmresourcecommon.cpp index 1ddea0966..bc05b8bf7 100644 --- a/resources/kalarm/shared/kalarmresourcecommon.cpp +++ b/resources/kalarm/shared/kalarmresourcecommon.cpp @@ -1,199 +1,199 @@ /* * kalarmresourcecommon.cpp - common functions for KAlarm Akonadi resources * Program: kalarm * Copyright © 2009-2014 by David Jarvie * * 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 "kalarmresourcecommon.h" #include #include #include #include #include #include #include #include #include using namespace Akonadi; using namespace KCalCore; using namespace KAlarmCal; class Private : public QObject { Q_OBJECT public: Private(QObject *parent) : QObject(parent) { } static Private *mInstance; private Q_SLOTS: void modifyCollectionJobDone(KJob *); }; Private *Private::mInstance = nullptr; namespace KAlarmResourceCommon { /****************************************************************************** * Perform common initialisation for KAlarm resources. */ void initialise(QObject *parent) { // Create an object which can receive signals. if (!Private::mInstance) { Private::mInstance = new Private(parent); } // Set a default start-of-day time for date-only alarms. KAEvent::setStartOfDay(QTime(0, 0, 0)); AttributeFactory::registerAttribute(); AttributeFactory::registerAttribute(); } /****************************************************************************** * Find the compatibility of an existing calendar file, and convert it in * memory to the current KAlarm format (if possible). */ KACalendar::Compat getCompatibility(const FileStorage::Ptr &fileStorage, int &version) { QString versionString; version = KACalendar::updateVersion(fileStorage, versionString); switch (version) { case KACalendar::IncompatibleFormat: return KACalendar::Incompatible; // calendar is not in KAlarm format, or is in a future format case KACalendar::CurrentFormat: return KACalendar::Current; // calendar is in the current format default: return KACalendar::Convertible; // calendar is in an out of date format } } /****************************************************************************** * Set an event into a new item's payload and return the new item. * The caller should signal its retrieval by calling itemRetrieved(newitem). * NOTE: the caller must set the event's compatibility beforehand. */ Item retrieveItem(const Akonadi::Item &item, KAEvent &event) { QString mime = CalEvent::mimeType(event.category()); event.setItemId(item.id()); if (item.hasAttribute()) { event.setCommandError(item.attribute()->commandError()); } Item newItem = item; newItem.setMimeType(mime); newItem.setPayload(event); return newItem; } /****************************************************************************** * Called when an item has been changed to validate it. * Reply = the KAEvent for the item * = invalid if error, in which case errorMsg contains the error message * (which will be empty if the KAEvent is simply invalid). */ KAEvent checkItemChanged(const Akonadi::Item &item, QString &errorMsg) { KAEvent event; if (item.hasPayload()) { event = item.payload(); } if (event.isValid()) { if (item.remoteId() != event.id()) { qWarning() << "Item ID" << item.remoteId() << "differs from payload ID" << event.id(); errorMsg = i18nc("@info", "Item ID %1 differs from payload ID %2.", item.remoteId(), event.id()); return KAEvent(); } } errorMsg.clear(); return event; } /****************************************************************************** * Set a collection's compatibility attribute. * Note that because this parameter is set asynchronously by the resource, it * can't be stored in the same attribute as other collection parameters which * are written by the application. This avoids the resource and application * overwriting each other's changes if they attempt simultaneous updates. */ void setCollectionCompatibility(const Collection &collection, KACalendar::Compat compatibility, int version) { qDebug() << collection.id() << "->" << compatibility << version; // Update the CompatibilityAttribute value only. // Note that we can't supply 'collection' to CollectionModifyJob since // that may also contain the CollectionAttribute value, which is read-only // for the resource. So create a new Collection instance and only set a // value for CompatibilityAttribute. Collection col(collection.id()); if (!collection.isValid()) { col.setParentCollection(collection.parentCollection()); col.setRemoteId(collection.remoteId()); } CompatibilityAttribute *attr = col.attribute(Collection::AddIfMissing); attr->setCompatibility(compatibility); attr->setVersion(version); Q_ASSERT(Private::mInstance); CollectionModifyJob *job = new CollectionModifyJob(col, Private::mInstance->parent()); Private::mInstance->connect(job, SIGNAL(result(KJob*)), SLOT(modifyCollectionJobDone(KJob*))); } /****************************************************************************** * Return an error message common to more than one resource. */ QString errorMessage(ErrorCode code, const QString ¶m) { switch (code) { case UidNotFound: return i18nc("@info", "Event with uid '%1' not found.", param); case NotCurrentFormat: return i18nc("@info", "Calendar is not in current KAlarm format."); case EventNotCurrentFormat: return i18nc("@info", "Event with uid '%1' is not in current KAlarm format.", param); case EventNoAlarms: return i18nc("@info", "Event with uid '%1' contains no usable alarms.", param); case EventReadOnly: return i18nc("@info", "Event with uid '%1' is read only", param); case CalendarAdd: return i18nc("@info", "Failed to add event with uid '%1' to calendar", param); } return QString(); } } // namespace KAlarmResourceCommon /****************************************************************************** * Called when a collection modification job has completed, to report any error. */ void Private::modifyCollectionJobDone(KJob *j) { qDebug(); if (j->error()) { Collection collection = static_cast(j)->collection(); - qCritical() << "Error: collection id" << collection.id() << ":" << j->errorString(); + qCritical() << "Error: modifyCollectionJobDone: collection" << collection.id() << ":" << j->errorString(); } } #include "kalarmresourcecommon.moc"