1 /*
2  *  kalarmresourcecommon.cpp  -  common functions for KAlarm Akonadi resources
3  *  Program:  kalarm
4  *  SPDX-FileCopyrightText: 2009-2019 David Jarvie <djarvie@kde.org>
5  *
6  *  SPDX-License-Identifier: LGPL-2.0-or-later
7  */
8 
9 #include "kalarmresourcecommon.h"
10 
11 #include <KAlarmCal/CompatibilityAttribute>
12 #include <KAlarmCal/EventAttribute>
13 
14 #include <Akonadi/AttributeFactory>
15 #include <Akonadi/CollectionModifyJob>
16 
17 #include <KCalendarCore/FileStorage>
18 #include <KCalendarCore/MemoryCalendar>
19 
20 #include <KLocalizedString>
21 
22 #include <QDebug>
23 #include <QTime>
24 
25 using namespace Akonadi;
26 using namespace KCalendarCore;
27 using namespace KAlarmCal;
28 
29 class Private : public QObject
30 {
31     Q_OBJECT
32 public:
Private(QObject * parent)33     Private(QObject *parent)
34         : QObject(parent)
35     {
36     }
37 
38     static Private *mInstance;
39 
40 private Q_SLOTS:
41     void modifyCollectionJobDone(KJob *);
42 };
43 Private *Private::mInstance = nullptr;
44 
45 namespace KAlarmResourceCommon
46 {
47 /******************************************************************************
48  * Perform common initialisation for KAlarm resources.
49  */
initialise(QObject * parent)50 void initialise(QObject *parent)
51 {
52     // Create an object which can receive signals.
53     if (!Private::mInstance) {
54         Private::mInstance = new Private(parent);
55     }
56 
57     // Set a default start-of-day time for date-only alarms.
58     KAEvent::setStartOfDay(QTime(0, 0, 0));
59 
60     AttributeFactory::registerAttribute<CompatibilityAttribute>();
61     AttributeFactory::registerAttribute<EventAttribute>();
62 }
63 
64 /******************************************************************************
65  * Find the compatibility of an existing calendar file, and convert it in
66  * memory to the current KAlarm format (if possible).
67  */
getCompatibility(const FileStorage::Ptr & fileStorage,int & version)68 KACalendar::Compat getCompatibility(const FileStorage::Ptr &fileStorage, int &version)
69 {
70     QString versionString;
71     version = KACalendar::updateVersion(fileStorage, versionString);
72     switch (version) {
73     case KACalendar::IncompatibleFormat:
74         return KACalendar::Incompatible; // calendar is not in KAlarm format, or is in a future format
75     case KACalendar::CurrentFormat:
76         return KACalendar::Current; // calendar is in the current format
77     default:
78         return KACalendar::Convertible; // calendar is in an out of date format
79     }
80 }
81 
82 /******************************************************************************
83  * Set an event into a new item's payload and return the new item.
84  * The caller should signal its retrieval by calling itemRetrieved(newitem).
85  * NOTE: the caller must set the event's compatibility beforehand.
86  */
retrieveItem(const Akonadi::Item & item,KAEvent & event)87 Item retrieveItem(const Akonadi::Item &item, KAEvent &event)
88 {
89     const QString mime = CalEvent::mimeType(event.category());
90     if (item.hasAttribute<EventAttribute>()) {
91         event.setCommandError(item.attribute<EventAttribute>()->commandError());
92     }
93 
94     Item newItem = item;
95     newItem.setMimeType(mime);
96     newItem.setPayload<KAEvent>(event);
97     return newItem;
98 }
99 
100 /******************************************************************************
101  * Called when an item has been changed to validate it.
102  * Reply = the KAEvent for the item
103  *       = invalid if error, in which case errorMsg contains the error message
104  *         (which will be empty if the KAEvent is simply invalid).
105  */
checkItemChanged(const Akonadi::Item & item,QString & errorMsg)106 KAEvent checkItemChanged(const Akonadi::Item &item, QString &errorMsg)
107 {
108     KAEvent event;
109     if (item.hasPayload<KAEvent>()) {
110         event = item.payload<KAEvent>();
111     }
112     if (event.isValid()) {
113         if (item.remoteId() != event.id()) {
114             qWarning() << "Item ID" << item.remoteId() << "differs from payload ID" << event.id();
115             errorMsg = i18nc("@info", "Item ID %1 differs from payload ID %2.", item.remoteId(), event.id());
116             return KAEvent();
117         }
118     }
119 
120     errorMsg.clear();
121     return event;
122 }
123 
124 /******************************************************************************
125  * Set a collection's compatibility attribute.
126  * Note that because this parameter is set asynchronously by the resource, it
127  * can't be stored in the same attribute as other collection parameters which
128  * are written by the application. This avoids the resource and application
129  * overwriting each other's changes if they attempt simultaneous updates.
130  */
setCollectionCompatibility(const Collection & collection,KACalendar::Compat compatibility,int version)131 void setCollectionCompatibility(const Collection &collection, KACalendar::Compat compatibility, int version)
132 {
133     qDebug() << "KAlarmResourceCommon::setCollectionCompatibility:" << collection.id() << "->" << compatibility << version;
134     // Update the CompatibilityAttribute value only.
135     // Note that we can't supply 'collection' to CollectionModifyJob since
136     // that may also contain the CollectionAttribute value, which is read-only
137     // for the resource. So create a new Collection instance and only set a
138     // value for CompatibilityAttribute.
139     Collection col(collection.id());
140     if (!collection.isValid()) {
141         col.setParentCollection(collection.parentCollection());
142         col.setRemoteId(collection.remoteId());
143     }
144     auto attr = col.attribute<CompatibilityAttribute>(Collection::AddIfMissing);
145     attr->setCompatibility(compatibility);
146     attr->setVersion(version);
147     Q_ASSERT(Private::mInstance);
148     auto job = new CollectionModifyJob(col, Private::mInstance->parent());
149     Private::mInstance->connect(job, SIGNAL(result(KJob *)), SLOT(modifyCollectionJobDone(KJob *)));
150 }
151 
152 /******************************************************************************
153  * Return an error message common to more than one resource.
154  */
errorMessage(ErrorCode code,const QString & param)155 QString errorMessage(ErrorCode code, const QString &param)
156 {
157     switch (code) {
158     case UidNotFound:
159         return i18nc("@info", "Event with uid '%1' not found.", param);
160     case NotCurrentFormat:
161         return i18nc("@info", "Calendar is not in current KAlarm format.");
162     case EventNotCurrentFormat:
163         return i18nc("@info", "Event with uid '%1' is not in current KAlarm format.", param);
164     case EventNoAlarms:
165         return i18nc("@info", "Event with uid '%1' contains no usable alarms.", param);
166     case EventReadOnly:
167         return i18nc("@info", "Event with uid '%1' is read only", param);
168     case CalendarAdd:
169         return i18nc("@info", "Failed to add event with uid '%1' to calendar", param);
170     }
171     return QString();
172 }
173 } // namespace KAlarmResourceCommon
174 
175 /******************************************************************************
176  * Called when a collection modification job has completed, to report any error.
177  */
modifyCollectionJobDone(KJob * j)178 void Private::modifyCollectionJobDone(KJob *j)
179 {
180     qDebug() << "KAlarmResourceCommon::modifyCollectionJobDone";
181     if (j->error()) {
182         Collection collection = static_cast<CollectionModifyJob *>(j)->collection();
183         qCritical() << "Error: modifyCollectionJobDone: collection" << collection.id() << ":" << j->errorString();
184     }
185 }
186 
187 #include "kalarmresourcecommon.moc"
188