Fix dnd email
[kdepim.git] / kalarm / calendarmigrator.cpp
bloba5441762677e727781bf0177deef9c5977d91daf
1 /*
2 * calendarmigrator.cpp - migrates or creates KAlarm Akonadi resources
3 * Program: kalarm
4 * Copyright © 2011-2015 by David Jarvie <djarvie@kde.org>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "calendarmigrator.h"
22 #include "akonadimodel.h"
23 #include "functions.h"
24 #include "kalarmsettings.h"
25 #include "kalarmdirsettings.h"
26 #include "mainwindow.h"
27 #include "messagebox.h"
29 #include <kalarmcal/collectionattribute.h>
30 #include <kalarmcal/compatibilityattribute.h>
31 #include <kalarmcal/version.h>
33 #include <AkonadiCore/agentinstancecreatejob.h>
34 #include <AkonadiCore/agentmanager.h>
35 #include <AkonadiCore/collectionfetchjob.h>
36 #include <AkonadiCore/collectionfetchscope.h>
37 #include <AkonadiCore/collectionmodifyjob.h>
38 #include <AkonadiCore/entitydisplayattribute.h>
39 #include <AkonadiCore/resourcesynchronizationjob.h>
41 #include <KLocalizedString>
42 #include <KConfig>
43 #include <KConfigGroup>
45 #include <QTimer>
46 #include <QStandardPaths>
47 #include "kalarm_debug.h"
49 using namespace Akonadi;
50 using namespace KAlarmCal;
52 namespace
54 const QString KALARM_RESOURCE(QStringLiteral("akonadi_kalarm_resource"));
55 const QString KALARM_DIR_RESOURCE(QStringLiteral("akonadi_kalarm_dir_resource"));
58 // Creates, or migrates from KResources, a single alarm calendar
59 class CalendarCreator : public QObject
61 Q_OBJECT
62 public:
63 // Constructor to migrate a calendar from KResources.
64 CalendarCreator(const QString& resourceType, const KConfigGroup&);
65 // Constructor to create a default Akonadi calendar.
66 CalendarCreator(CalEvent::Type, const QString& file, const QString& name);
67 bool isValid() const { return mAlarmType != CalEvent::EMPTY; }
68 CalEvent::Type alarmType() const { return mAlarmType; }
69 bool newCalendar() const { return mNew; }
70 QString resourceName() const { return mName; }
71 Collection::Id collectionId() const { return mCollectionId; }
72 QString path() const { return mPath; }
73 QString errorMessage() const { return mErrorMessage; }
74 void createAgent(const QString& agentType, QObject* parent);
76 public Q_SLOTS:
77 void agentCreated(KJob*);
79 Q_SIGNALS:
80 void creating(const QString& path);
81 void finished(CalendarCreator*);
83 private Q_SLOTS:
84 void fetchCollection();
85 void collectionFetchResult(KJob*);
86 void resourceSynchronised(KJob*);
87 void modifyCollectionJobDone(KJob*);
89 private:
90 void finish(bool cleanup);
91 bool writeLocalFileConfig();
92 bool writeLocalDirectoryConfig();
93 bool writeRemoteFileConfig();
94 template <class Interface> Interface* writeBasicConfig();
96 enum ResourceType { LocalFile, LocalDir, RemoteFile };
98 AgentInstance mAgent;
99 CalEvent::Type mAlarmType;
100 ResourceType mResourceType;
101 QString mPath;
102 QString mName;
103 QColor mColour;
104 QString mErrorMessage;
105 Collection::Id mCollectionId;
106 int mCollectionFetchRetryCount;
107 bool mReadOnly;
108 bool mEnabled;
109 bool mStandard;
110 const bool mNew; // true if creating default, false if converting
111 bool mFinished;
114 // Updates the backend calendar format of a single alarm calendar
115 class CalendarUpdater : public QObject
117 Q_OBJECT
118 public:
119 CalendarUpdater(const Collection& collection, bool dirResource,
120 bool ignoreKeepFormat, bool newCollection, QObject* parent);
121 ~CalendarUpdater();
122 // Return whether another instance is already updating this collection
123 bool isDuplicate() const { return mDuplicate; }
124 // Check whether any instance is for the given collection ID
125 static bool containsCollection(Collection::Id);
127 public Q_SLOTS:
128 bool update();
130 private:
131 static QList<CalendarUpdater*> mInstances;
132 Akonadi::Collection mCollection;
133 QObject* mParent;
134 const bool mDirResource;
135 const bool mIgnoreKeepFormat;
136 const bool mNewCollection;
137 const bool mDuplicate; // another instance is already updating this collection
141 CalendarMigrator* CalendarMigrator::mInstance = Q_NULLPTR;
142 bool CalendarMigrator::mCompleted = false;
144 CalendarMigrator::CalendarMigrator(QObject* parent)
145 : QObject(parent),
146 mExistingAlarmTypes(0)
150 CalendarMigrator::~CalendarMigrator()
152 qCDebug(KALARM_LOG);
153 mInstance = Q_NULLPTR;
156 /******************************************************************************
157 * Reset to allow migration to be run again.
159 void CalendarMigrator::reset()
161 mCompleted = false;
164 /******************************************************************************
165 * Create and return the unique CalendarMigrator instance.
167 CalendarMigrator* CalendarMigrator::instance()
169 if (!mInstance && !mCompleted)
170 mInstance = new CalendarMigrator;
171 return mInstance;
174 /******************************************************************************
175 * Migrate old KResource calendars, or if none, create default Akonadi resources.
177 void CalendarMigrator::execute()
179 instance()->migrateOrCreate();
182 /******************************************************************************
183 * Migrate old KResource calendars, and create default Akonadi resources.
185 void CalendarMigrator::migrateOrCreate()
187 qCDebug(KALARM_LOG);
189 // First, check whether any Akonadi resources already exist, and if
190 // so, find their alarm types.
191 const AgentInstance::List agents = AgentManager::self()->instances();
192 foreach (const AgentInstance& agent, agents)
194 const QString type = agent.type().identifier();
195 if (type == KALARM_RESOURCE || type == KALARM_DIR_RESOURCE)
197 // Fetch the resource's collection to determine its alarm types
198 CollectionFetchJob* job = new CollectionFetchJob(Collection::root(), CollectionFetchJob::FirstLevel);
199 job->fetchScope().setResource(agent.identifier());
200 mFetchesPending << job;
201 connect(job, &KJob::result, this, &CalendarMigrator::collectionFetchResult);
202 // Note: Once all collections have been fetched, any missing
203 // default resources will be created.
207 if (mFetchesPending.isEmpty())
209 // There are no Akonadi resources, so migrate any KResources alarm
210 // calendars from pre-Akonadi versions of KAlarm.
211 const QString configFile = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/kresources/alarms/stdrc");
212 const KConfig config(configFile, KConfig::SimpleConfig);
214 // Fetch all the KResource identifiers which are actually in use
215 const KConfigGroup group = config.group("General");
216 const QStringList keys = group.readEntry("ResourceKeys", QStringList())
217 + group.readEntry("PassiveResourceKeys", QStringList());
219 // Create an Akonadi resource for each KResource id
220 CalendarCreator* creator;
221 foreach (const QString& id, keys)
223 const KConfigGroup configGroup = config.group(QStringLiteral("Resource_") + id);
224 const QString resourceType = configGroup.readEntry("ResourceType", QString());
225 QString agentType;
226 if (resourceType == QStringLiteral("file"))
227 agentType = KALARM_RESOURCE;
228 else if (resourceType == QStringLiteral("dir"))
229 agentType = KALARM_DIR_RESOURCE;
230 else if (resourceType == QStringLiteral("remote"))
231 agentType = KALARM_RESOURCE;
232 else
233 continue; // unknown resource type - can't convert
235 creator = new CalendarCreator(resourceType, configGroup);
236 if (!creator->isValid())
237 delete creator;
238 else
240 connect(creator, &CalendarCreator::finished, this, &CalendarMigrator::calendarCreated);
241 connect(creator, &CalendarCreator::creating, this, &CalendarMigrator::creatingCalendar);
242 mExistingAlarmTypes |= creator->alarmType();
243 mCalendarsPending << creator;
244 creator->createAgent(agentType, this);
248 // After migrating KResources, create any necessary additional default
249 // Akonadi resources.
250 createDefaultResources();
254 /******************************************************************************
255 * Called when a collection fetch job has completed.
256 * Finds which mime types are handled by the existing collection.
258 void CalendarMigrator::collectionFetchResult(KJob* j)
260 CollectionFetchJob* job = static_cast<CollectionFetchJob*>(j);
261 const QString id = job->fetchScope().resource();
262 if (j->error())
263 qCCritical(KALARM_LOG) << "CollectionFetchJob" << id << "error: " << j->errorString();
264 else
266 const Collection::List collections = job->collections();
267 if (collections.isEmpty())
268 qCCritical(KALARM_LOG) << "No collections found for resource" << id;
269 else
270 mExistingAlarmTypes |= CalEvent::types(collections[0].contentMimeTypes());
272 mFetchesPending.removeAll(job);
274 if (mFetchesPending.isEmpty())
276 // The alarm types of all collections have been found, so now
277 // create any necessary default Akonadi resources.
278 createDefaultResources();
282 /******************************************************************************
283 * Create default Akonadi resources for any alarm types not covered by existing
284 * resources. Normally, this occurs on the first run of KAlarm, but if resources
285 * have been deleted, it could occur on later runs.
286 * If the default calendar files already exist, they will be used; otherwise
287 * they will be created.
289 void CalendarMigrator::createDefaultResources()
291 qCDebug(KALARM_LOG);
292 CalendarCreator* creator;
293 if (!(mExistingAlarmTypes & CalEvent::ACTIVE))
295 creator = new CalendarCreator(CalEvent::ACTIVE, QStringLiteral("calendar.ics"), i18nc("@info", "Active Alarms"));
296 connect(creator, &CalendarCreator::finished, this, &CalendarMigrator::calendarCreated);
297 connect(creator, &CalendarCreator::creating, this, &CalendarMigrator::creatingCalendar);
298 mCalendarsPending << creator;
299 creator->createAgent(KALARM_RESOURCE, this);
301 if (!(mExistingAlarmTypes & CalEvent::ARCHIVED))
303 creator = new CalendarCreator(CalEvent::ARCHIVED, QStringLiteral("expired.ics"), i18nc("@info", "Archived Alarms"));
304 connect(creator, &CalendarCreator::finished, this, &CalendarMigrator::calendarCreated);
305 connect(creator, &CalendarCreator::creating, this, &CalendarMigrator::creatingCalendar);
306 mCalendarsPending << creator;
307 creator->createAgent(KALARM_RESOURCE, this);
309 if (!(mExistingAlarmTypes & CalEvent::TEMPLATE))
311 creator = new CalendarCreator(CalEvent::TEMPLATE, QStringLiteral("template.ics"), i18nc("@info", "Alarm Templates"));
312 connect(creator, &CalendarCreator::finished, this, &CalendarMigrator::calendarCreated);
313 connect(creator, &CalendarCreator::creating, this, &CalendarMigrator::creatingCalendar);
314 mCalendarsPending << creator;
315 creator->createAgent(KALARM_RESOURCE, this);
318 if (mCalendarsPending.isEmpty())
320 mCompleted = true;
321 deleteLater();
325 /******************************************************************************
326 * Called when a calendar resource is about to be created.
327 * Emits the 'creating' signal.
329 void CalendarMigrator::creatingCalendar(const QString& path)
331 Q_EMIT creating(path, -1, false);
334 /******************************************************************************
335 * Called when creation of a migrated or new default calendar resource has
336 * completed or failed.
338 void CalendarMigrator::calendarCreated(CalendarCreator* creator)
340 int i = mCalendarsPending.indexOf(creator);
341 if (i < 0)
342 return; // calendar already finished
344 Q_EMIT creating(creator->path(), creator->collectionId(), true);
346 if (!creator->errorMessage().isEmpty())
348 QString errmsg = creator->newCalendar()
349 ? xi18nc("@info/plain", "Failed to create default calendar <resource>%1</resource>", creator->resourceName())
350 : xi18nc("@info/plain 'Import Alarms' is the name of a menu option",
351 "Failed to convert old configuration for calendar <resource>%1</resource>. "
352 "Please use Import Alarms to load its alarms into a new or existing calendar.", creator->resourceName());
353 const QString locn = i18nc("@info File path or URL", "Location: %1", creator->path());
354 if (creator->errorMessage().isEmpty())
355 errmsg = xi18nc("@info", "<para>%1</para><para>%2</para>", errmsg, locn);
356 else
357 errmsg = xi18nc("@info", "<para>%1</para><para>%2<nl/>(%3)</para>", errmsg, locn, creator->errorMessage());
358 KAMessageBox::error(MainWindow::mainMainWindow(), errmsg);
360 creator->deleteLater();
362 mCalendarsPending.removeAt(i); // remove it from the pending list
363 if (mCalendarsPending.isEmpty())
365 mCompleted = true;
366 deleteLater();
370 /******************************************************************************
371 * If an existing Akonadi resource calendar can be converted to the current
372 * KAlarm format, prompt the user whether to convert it, and if yes, tell the
373 * Akonadi resource to update the backend storage to the current format.
374 * The CollectionAttribute's KeepFormat property will be updated if the user
375 * chooses not to update the calendar.
377 * Note: the collection should be up to date: use AkonadiModel::refresh() before
378 * calling this function.
380 void CalendarMigrator::updateToCurrentFormat(const Collection& collection, bool ignoreKeepFormat, QWidget* parent)
382 qCDebug(KALARM_LOG) << collection.id();
383 if (CalendarUpdater::containsCollection(collection.id()))
384 return; // prevent multiple simultaneous user prompts
385 const AgentInstance agent = AgentManager::self()->instance(collection.resource());
386 const QString id = agent.type().identifier();
387 bool dirResource;
388 if (id == KALARM_RESOURCE)
389 dirResource = false;
390 else if (id == KALARM_DIR_RESOURCE)
391 dirResource = true;
392 else
394 qCCritical(KALARM_LOG) << "Invalid agent type" << id;
395 return;
397 CalendarUpdater* updater = new CalendarUpdater(collection, dirResource, ignoreKeepFormat, false, parent);
398 QTimer::singleShot(0, updater, &CalendarUpdater::update);
402 QList<CalendarUpdater*> CalendarUpdater::mInstances;
404 CalendarUpdater::CalendarUpdater(const Collection& collection, bool dirResource,
405 bool ignoreKeepFormat, bool newCollection, QObject* parent)
406 : mCollection(collection),
407 mParent(parent),
408 mDirResource(dirResource),
409 mIgnoreKeepFormat(ignoreKeepFormat),
410 mNewCollection(newCollection),
411 mDuplicate(containsCollection(collection.id()))
413 mInstances.append(this);
416 CalendarUpdater::~CalendarUpdater()
418 mInstances.removeAll(this);
421 bool CalendarUpdater::containsCollection(Collection::Id id)
423 for (int i = 0, count = mInstances.count(); i < count; ++i)
425 if (mInstances[i]->mCollection.id() == id)
426 return true;
428 return false;
431 bool CalendarUpdater::update()
433 qCDebug(KALARM_LOG) << mCollection.id() << (mDirResource ? "directory" : "file");
434 bool result = true;
435 if (!mDuplicate // prevent concurrent updates
436 && mCollection.hasAttribute<CompatibilityAttribute>()) // must know format to update
438 const CompatibilityAttribute* compatAttr = mCollection.attribute<CompatibilityAttribute>();
439 const KACalendar::Compat compatibility = compatAttr->compatibility();
440 if ((compatibility & ~KACalendar::Converted)
441 // The calendar isn't in the current KAlarm format
442 && !(compatibility & ~(KACalendar::Convertible | KACalendar::Converted)))
444 // The calendar format is convertible to the current KAlarm format
445 if (!mIgnoreKeepFormat
446 && mCollection.hasAttribute<CollectionAttribute>()
447 && mCollection.attribute<CollectionAttribute>()->keepFormat())
448 qCDebug(KALARM_LOG) << "Not updating format (previous user choice)";
449 else
451 // The user hasn't previously said not to convert it
452 const QString versionString = KAlarmCal::getVersionString(compatAttr->version());
453 const QString msg = KAlarm::conversionPrompt(mCollection.name(), versionString, false);
454 qCDebug(KALARM_LOG) << "Version" << versionString;
455 if (KAMessageBox::warningYesNo(qobject_cast<QWidget*>(mParent), msg) != KMessageBox::Yes)
456 result = false; // the user chose not to update the calendar
457 else
459 // Tell the resource to update the backend storage format
460 QString errmsg;
461 if (!mNewCollection)
463 // Refetch the collection's details because anything could
464 // have happened since the prompt was first displayed.
465 if (!AkonadiModel::instance()->refresh(mCollection))
466 errmsg = i18nc("@info", "Invalid collection");
468 if (errmsg.isEmpty())
470 const AgentInstance agent = AgentManager::self()->instance(mCollection.resource());
471 if (mDirResource)
472 CalendarMigrator::updateStorageFormat<OrgKdeAkonadiKAlarmDirSettingsInterface>(agent, errmsg, mParent);
473 else
474 CalendarMigrator::updateStorageFormat<OrgKdeAkonadiKAlarmSettingsInterface>(agent, errmsg, mParent);
476 if (!errmsg.isEmpty())
478 KAMessageBox::error(MainWindow::mainMainWindow(),
479 xi18nc("@info", "%1<nl/>(%2)",
480 xi18nc("@info/plain", "Failed to update format of calendar <resource>%1</resource>", mCollection.name()),
481 errmsg));
484 if (!mNewCollection)
486 // Record the user's choice of whether to update the calendar
487 const QModelIndex ix = AkonadiModel::instance()->collectionIndex(mCollection);
488 AkonadiModel::instance()->setData(ix, !result, AkonadiModel::KeepFormatRole);
493 deleteLater();
494 return result;
497 /******************************************************************************
498 * Tell an Akonadi resource to update the backend storage format to the current
499 * KAlarm format.
500 * Reply = true if success; if false, 'errorMessage' contains the error message.
502 template <class Interface> bool CalendarMigrator::updateStorageFormat(const AgentInstance& agent, QString& errorMessage, QObject* parent)
504 qCDebug(KALARM_LOG);
505 Interface* iface = getAgentInterface<Interface>(agent, errorMessage, parent);
506 if (!iface)
508 qCDebug(KALARM_LOG) << errorMessage;
509 return false;
511 iface->setUpdateStorageFormat(true);
512 iface->save();
513 delete iface;
514 qCDebug(KALARM_LOG) << "true";
515 return true;
518 /******************************************************************************
519 * Create a D-Bus interface to an Akonadi resource.
520 * Reply = interface if success
521 * = 0 if error: 'errorMessage' contains the error message.
523 template <class Interface> Interface* CalendarMigrator::getAgentInterface(const AgentInstance& agent, QString& errorMessage, QObject* parent)
525 Interface* iface = new Interface(QStringLiteral("org.freedesktop.Akonadi.Resource.") + agent.identifier(),
526 QStringLiteral("/Settings"), QDBusConnection::sessionBus(), parent);
527 if (!iface->isValid())
529 errorMessage = iface->lastError().message();
530 qCDebug(KALARM_LOG) << "D-Bus error accessing resource:" << errorMessage;
531 delete iface;
532 return Q_NULLPTR;
534 return iface;
538 /******************************************************************************
539 * Constructor to migrate a KResources calendar, using its parameters.
541 CalendarCreator::CalendarCreator(const QString& resourceType, const KConfigGroup& config)
542 : mAlarmType(CalEvent::EMPTY),
543 mNew(false),
544 mFinished(false)
546 // Read the resource configuration parameters from the config
547 const char* pathKey = Q_NULLPTR;
548 if (resourceType == QStringLiteral("file"))
550 mResourceType = LocalFile;
551 pathKey = "CalendarURL";
553 else if (resourceType == QStringLiteral("dir"))
555 mResourceType = LocalDir;
556 pathKey = "CalendarURL";
558 else if (resourceType == QStringLiteral("remote"))
560 mResourceType = RemoteFile;
561 pathKey = "DownloadUrl";
563 else
565 qCCritical(KALARM_LOG) << "Invalid resource type:" << resourceType;
566 return;
568 mPath = config.readPathEntry(pathKey, QStringLiteral(""));
569 switch (config.readEntry("AlarmType", (int)0))
571 case 1: mAlarmType = CalEvent::ACTIVE; break;
572 case 2: mAlarmType = CalEvent::ARCHIVED; break;
573 case 4: mAlarmType = CalEvent::TEMPLATE; break;
574 default:
575 qCCritical(KALARM_LOG) << "Invalid alarm type for resource";
576 return;
578 mName = config.readEntry("ResourceName", QString());
579 mColour = config.readEntry("Color", QColor());
580 mReadOnly = config.readEntry("ResourceIsReadOnly", true);
581 mEnabled = config.readEntry("ResourceIsActive", false);
582 mStandard = config.readEntry("Standard", false);
583 qCDebug(KALARM_LOG) << "Migrating:" << mName << ", type=" << mAlarmType << ", path=" << mPath;
586 /******************************************************************************
587 * Constructor to create a new default local file resource.
588 * This is created as enabled, read-write, and standard for its alarm type.
590 CalendarCreator::CalendarCreator(CalEvent::Type alarmType, const QString& file, const QString& name)
591 : mAlarmType(alarmType),
592 mResourceType(LocalFile),
593 mName(name),
594 mColour(),
595 mReadOnly(false),
596 mEnabled(true),
597 mStandard(true),
598 mNew(true),
599 mFinished(false)
601 mPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') + file;
602 qCDebug(KALARM_LOG) << "New:" << mName << ", type=" << mAlarmType << ", path=" << mPath;
605 /******************************************************************************
606 * Create the Akonadi agent for this calendar.
608 void CalendarCreator::createAgent(const QString& agentType, QObject* parent)
610 Q_EMIT creating(mPath);
611 AgentInstanceCreateJob* job = new AgentInstanceCreateJob(agentType, parent);
612 connect(job, &KJob::result, this, &CalendarCreator::agentCreated);
613 job->start();
616 /******************************************************************************
617 * Called when the agent creation job for this resource has completed.
618 * Applies the calendar resource configuration to the Akonadi agent.
620 void CalendarCreator::agentCreated(KJob* j)
622 if (j->error())
624 mErrorMessage = j->errorString();
625 qCCritical(KALARM_LOG) << "AgentInstanceCreateJob error:" << mErrorMessage;
626 finish(false);
627 return;
630 // Configure the Akonadi Agent
631 qCDebug(KALARM_LOG) << mName;
632 AgentInstanceCreateJob* job = static_cast<AgentInstanceCreateJob*>(j);
633 mAgent = job->instance();
634 mAgent.setName(mName);
635 bool ok = false;
636 switch (mResourceType)
638 case LocalFile:
639 ok = writeLocalFileConfig();
640 break;
641 case LocalDir:
642 ok = writeLocalDirectoryConfig();
643 break;
644 case RemoteFile:
645 ok = writeRemoteFileConfig();
646 break;
647 default:
648 qCCritical(KALARM_LOG) << "Invalid resource type";
649 break;
651 if (!ok)
653 finish(true);
654 return;
656 mAgent.reconfigure(); // notify the agent that its configuration has been changed
658 // Wait for the resource to create its collection.
659 ResourceSynchronizationJob* sjob = new ResourceSynchronizationJob(mAgent);
660 connect(sjob, &KJob::result, this, &CalendarCreator::resourceSynchronised);
661 sjob->start(); // this is required (not an Akonadi::Job)
664 /******************************************************************************
665 * Called when a resource synchronization job has completed.
666 * Fetches the collection which this agent manages.
668 void CalendarCreator::resourceSynchronised(KJob* j)
670 qCDebug(KALARM_LOG) << mName;
671 if (j->error())
673 // Don't give up on error - we can still try to fetch the collection
674 qCCritical(KALARM_LOG) << "ResourceSynchronizationJob error: " << j->errorString();
676 mCollectionFetchRetryCount = 0;
677 fetchCollection();
680 /******************************************************************************
681 * Find the collection which this agent manages.
683 void CalendarCreator::fetchCollection()
685 CollectionFetchJob* job = new CollectionFetchJob(Collection::root(), CollectionFetchJob::FirstLevel);
686 job->fetchScope().setResource(mAgent.identifier());
687 connect(job, &KJob::result, this, &CalendarCreator::collectionFetchResult);
688 job->start();
691 bool CalendarCreator::writeLocalFileConfig()
693 OrgKdeAkonadiKAlarmSettingsInterface* iface = writeBasicConfig<OrgKdeAkonadiKAlarmSettingsInterface>();
694 if (!iface)
695 return false;
696 iface->setMonitorFile(true);
697 iface->save(); // save the Agent config changes
698 delete iface;
699 return true;
702 bool CalendarCreator::writeLocalDirectoryConfig()
704 OrgKdeAkonadiKAlarmDirSettingsInterface* iface = writeBasicConfig<OrgKdeAkonadiKAlarmDirSettingsInterface>();
705 if (!iface)
706 return false;
707 iface->setMonitorFiles(true);
708 iface->save(); // save the Agent config changes
709 delete iface;
710 return true;
713 bool CalendarCreator::writeRemoteFileConfig()
715 OrgKdeAkonadiKAlarmSettingsInterface* iface = writeBasicConfig<OrgKdeAkonadiKAlarmSettingsInterface>();
716 if (!iface)
717 return false;
718 iface->setMonitorFile(true);
719 iface->save(); // save the Agent config changes
720 delete iface;
721 return true;
724 template <class Interface> Interface* CalendarCreator::writeBasicConfig()
726 Interface* iface = CalendarMigrator::getAgentInterface<Interface>(mAgent, mErrorMessage, this);
727 if (iface)
729 iface->setReadOnly(mReadOnly);
730 iface->setDisplayName(mName);
731 iface->setPath(mPath);
732 iface->setAlarmTypes(CalEvent::mimeTypes(mAlarmType));
733 iface->setUpdateStorageFormat(false);
735 return iface;
738 /******************************************************************************
739 * Called when a collection fetch job has completed.
740 * Obtains the collection handled by the agent, and configures it.
742 void CalendarCreator::collectionFetchResult(KJob* j)
744 qCDebug(KALARM_LOG) << mName;
745 if (j->error())
747 mErrorMessage = j->errorString();
748 qCCritical(KALARM_LOG) << "CollectionFetchJob error: " << mErrorMessage;
749 finish(true);
750 return;
752 CollectionFetchJob* job = static_cast<CollectionFetchJob*>(j);
753 const Collection::List collections = job->collections();
754 if (collections.isEmpty())
756 if (++mCollectionFetchRetryCount >= 10)
758 mErrorMessage = i18nc("@info", "New configuration timed out");
759 qCCritical(KALARM_LOG) << "Timeout fetching collection for resource";
760 finish(true);
761 return;
763 // Need to wait a bit longer until the resource has initialised and
764 // created its collection. Retry after 200ms.
765 qCDebug(KALARM_LOG) << "Retrying";
766 QTimer::singleShot(200, this, &CalendarCreator::fetchCollection);
767 return;
769 if (collections.count() > 1)
771 mErrorMessage = i18nc("@info", "New configuration was corrupt");
772 qCCritical(KALARM_LOG) << "Wrong number of collections for this resource:" << collections.count();
773 finish(true);
774 return;
777 // Set Akonadi Collection attributes
778 Collection collection = collections[0];
779 mCollectionId = collection.id();
780 collection.setContentMimeTypes(CalEvent::mimeTypes(mAlarmType));
781 EntityDisplayAttribute* dattr = collection.attribute<EntityDisplayAttribute>(Collection::AddIfMissing);
782 dattr->setIconName(QStringLiteral("kalarm"));
783 CollectionAttribute* attr = collection.attribute<CollectionAttribute>(Collection::AddIfMissing);
784 attr->setEnabled(mEnabled ? mAlarmType : CalEvent::EMPTY);
785 if (mStandard)
786 attr->setStandard(mAlarmType);
787 if (mColour.isValid())
788 attr->setBackgroundColor(mColour);
790 // Update the calendar to the current KAlarm format if necessary,
791 // and if the user agrees.
792 bool dirResource = false;
793 switch (mResourceType)
795 case LocalFile:
796 case RemoteFile:
797 break;
798 case LocalDir:
799 dirResource = true;
800 break;
801 default:
802 Q_ASSERT(0); // Invalid resource type
803 break;
805 bool keep = false;
806 bool duplicate = false;
807 if (!mReadOnly)
809 CalendarUpdater* updater = new CalendarUpdater(collection, dirResource, false, true, this);
810 duplicate = updater->isDuplicate();
811 keep = !updater->update(); // note that 'updater' will auto-delete when finished
813 if (!duplicate)
815 // Record the user's choice of whether to update the calendar
816 attr->setKeepFormat(keep);
819 // Update the collection's CollectionAttribute value in the Akonadi database.
820 // Note that we can't supply 'collection' to CollectionModifyJob since
821 // that also contains the CompatibilityAttribute value, which is read-only
822 // for applications. So create a new Collection instance and only set a
823 // value for CollectionAttribute.
824 Collection c(collection.id());
825 CollectionAttribute* att = c.attribute<CollectionAttribute>(Collection::AddIfMissing);
826 *att = *attr;
827 CollectionModifyJob* cmjob = new CollectionModifyJob(c, this);
828 connect(cmjob, &KJob::result, this, &CalendarCreator::modifyCollectionJobDone);
831 /******************************************************************************
832 * Called when a collection modification job has completed.
833 * Checks for any error.
835 void CalendarCreator::modifyCollectionJobDone(KJob* j)
837 Collection collection = static_cast<CollectionModifyJob*>(j)->collection();
838 if (j->error())
840 mErrorMessage = j->errorString();
841 qCCritical(KALARM_LOG) << "CollectionFetchJob error: " << mErrorMessage;
842 finish(true);
844 else
846 qCDebug(KALARM_LOG) << "Completed:" << mName;
847 finish(false);
851 /******************************************************************************
852 * Emit the finished() signal. If 'cleanup' is true, delete the newly created
853 * but incomplete Agent.
855 void CalendarCreator::finish(bool cleanup)
857 if (!mFinished)
859 if (cleanup)
860 AgentManager::self()->removeInstance(mAgent);
861 mFinished = true;
862 Q_EMIT finished(this);
866 #include "calendarmigrator.moc"
868 // vim: et sw=4: