1 /*
2 * displaycalendar.cpp - KAlarm display calendar file access
3 * Program: kalarm
4 * SPDX-FileCopyrightText: 2001-2020 David Jarvie <djarvie@kde.org>
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9 #include "displaycalendar.h"
10
11 #include "mainwindow.h"
12 #include "preferences.h"
13 #include "lib/messagebox.h"
14 #include "kalarm_debug.h"
15
16 #include <KCalendarCore/MemoryCalendar>
17 #include <KCalendarCore/ICalFormat>
18
19 #include <KLocalizedString>
20
21 #include <QStandardPaths>
22 #include <QDir>
23
24 using namespace KCalendarCore;
25 using namespace KAlarmCal;
26
27 //clazy:excludeall=non-pod-global-static
28
29
30 namespace
31 {
32 const QString displayCalendarName = QStringLiteral("displaying.ics");
33 }
34
35 bool DisplayCalendar::mInitialised {false};
36 KAEvent::List DisplayCalendar::mEventList;
37 QHash<QString, KAEvent*> DisplayCalendar::mEventMap;
38 KCalendarCore::FileStorage::Ptr DisplayCalendar::mCalendarStorage;
39 QString DisplayCalendar::mDisplayCalPath;
40 QString DisplayCalendar::mDisplayICalPath;
41 DisplayCalendar::CalType DisplayCalendar::mCalType;
42 bool DisplayCalendar::mOpen {false};
43
44
45 /******************************************************************************
46 * Initialise the display alarm calendar.
47 * It is user-specific, and contains details of alarms which are currently being
48 * displayed to that user and which have not yet been acknowledged;
49 */
initialise()50 void DisplayCalendar::initialise()
51 {
52 QDir dir;
53 dir.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
54 mDisplayCalPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1Char('/') + displayCalendarName;
55 mDisplayICalPath = mDisplayCalPath;
56 mDisplayICalPath.replace(QStringLiteral("\\.vcs$"), QStringLiteral(".ics"));
57 mCalType = (mDisplayCalPath == mDisplayICalPath) ? LOCAL_ICAL : LOCAL_VCAL; // is the calendar in ICal or VCal format?
58 mInitialised = true;
59 }
60
61 /******************************************************************************
62 * Terminate access to the display calendar.
63 */
terminate()64 void DisplayCalendar::terminate()
65 {
66 close();
67 mInitialised = false;
68 }
69
70 /******************************************************************************
71 * Open the calendar if not already open, and load it into memory.
72 */
open()73 bool DisplayCalendar::open()
74 {
75 if (mOpen)
76 return true;
77
78 // Open the display calendar.
79 qCDebug(KALARM_LOG) << "DisplayCalendar::open:" << mDisplayCalPath;
80 if (!mCalendarStorage)
81 {
82 MemoryCalendar::Ptr calendar(new MemoryCalendar(Preferences::timeSpecAsZone()));
83 mCalendarStorage = FileStorage::Ptr(new FileStorage(calendar, mDisplayCalPath));
84 }
85
86 // Check for file's existence, assuming that it does exist when uncertain,
87 // to avoid overwriting it.
88 QFileInfo fi(mDisplayCalPath);
89 if (!fi.exists() || !fi.isFile() || load() == 0)
90 {
91 // The calendar file doesn't yet exist, or it's zero length, so create a new one
92 if (saveCal(mDisplayICalPath))
93 load();
94 }
95
96 if (!mOpen)
97 {
98 mCalendarStorage->calendar().clear();
99 mCalendarStorage.clear();
100 }
101 return mOpen;
102 }
103
104 /******************************************************************************
105 * Load the calendar into memory.
106 * Reply = 1 if success
107 * = 0 if zero-length file exists.
108 * = -1 if failure to load calendar file
109 * = -2 if instance uninitialised.
110 */
load()111 int DisplayCalendar::load()
112 {
113 // Load the display calendar.
114 if (!mCalendarStorage)
115 return -2;
116
117 qCDebug(KALARM_LOG) << "DisplayCalendar::load:" << mDisplayCalPath;
118 if (!mCalendarStorage->load())
119 {
120 // Load error. Check if the file is zero length
121 QFileInfo fi(mDisplayCalPath);
122 if (fi.exists() && !fi.size())
123 return 0; // file is zero length
124
125 qCCritical(KALARM_LOG) << "DisplayCalendar::load: Error loading calendar file '" << mDisplayCalPath <<"'";
126 KAMessageBox::error(MainWindow::mainMainWindow(),
127 xi18nc("@info", "<para>Error loading calendar:</para><para><filename>%1</filename></para><para>Please fix or delete the file.</para>", mDisplayCalPath));
128 // load() could have partially populated the calendar, so clear it out
129 mCalendarStorage->calendar()->close();
130 mCalendarStorage->calendar().clear();
131 mCalendarStorage.clear();
132 mOpen = false;
133 return -1;
134 }
135 QString versionString;
136 KACalendar::updateVersion(mCalendarStorage, versionString); // convert events to current KAlarm format for when calendar is saved
137 updateKAEvents();
138
139 mOpen = true;
140 return 1;
141 }
142
143 /******************************************************************************
144 * Save the calendar.
145 */
save()146 bool DisplayCalendar::save()
147 {
148 return saveCal();
149 }
150
151 /******************************************************************************
152 * Save the calendar from memory to file.
153 * If a filename is specified, create a new calendar file.
154 */
saveCal(const QString & newFile)155 bool DisplayCalendar::saveCal(const QString& newFile)
156 {
157 if (!mCalendarStorage)
158 return false;
159 if (!mOpen && newFile.isEmpty())
160 return false;
161
162 qCDebug(KALARM_LOG) << "DisplayCalendar::saveCal:" << "\"" << newFile;
163 QString saveFilename = newFile.isEmpty() ? mDisplayCalPath : newFile;
164 if (mCalType == LOCAL_VCAL && newFile.isNull())
165 saveFilename = mDisplayICalPath;
166 mCalendarStorage->setFileName(saveFilename);
167 mCalendarStorage->setSaveFormat(new ICalFormat);
168 if (!mCalendarStorage->save())
169 {
170 qCCritical(KALARM_LOG) << "DisplayCalendar::saveCal: Saving" << saveFilename << "failed.";
171 KAMessageBox::error(MainWindow::mainMainWindow(),
172 xi18nc("@info", "Failed to save calendar to <filename>%1</filename>", mDisplayICalPath));
173 return false;
174 }
175
176 if (mCalType == LOCAL_VCAL)
177 {
178 // The file was in vCalendar format, but has now been saved in iCalendar format.
179 mDisplayCalPath = mDisplayICalPath;
180 mCalType = LOCAL_ICAL;
181 }
182 return true;
183 }
184
185 /******************************************************************************
186 * Close display calendar file at program exit.
187 */
close()188 void DisplayCalendar::close()
189 {
190 if (mCalendarStorage)
191 {
192 mCalendarStorage->calendar()->close();
193 mCalendarStorage->calendar().clear();
194 mCalendarStorage.clear();
195 }
196
197 // Flag as closed now to prevent removeKAEvents() doing silly things
198 // when it's called again
199 mOpen = false;
200
201 // Events list should be empty, but just in case...
202 for (KAEvent* event : std::as_const(mEventList))
203 {
204 mEventMap.remove(event->id());
205 delete event;
206 }
207 mEventList.clear();
208 }
209
210 /******************************************************************************
211 * Create a KAEvent instance corresponding to each KCalendarCore::Event in the
212 * display calendar, and store them in the event map in place of the old set.
213 * Called after the display calendar has completed loading.
214 */
updateKAEvents()215 void DisplayCalendar::updateKAEvents()
216 {
217 qCDebug(KALARM_LOG) << "DisplayCalendar::updateKAEvents";
218 for (KAEvent* event : std::as_const(mEventList))
219 {
220 mEventMap.remove(event->id());
221 delete event;
222 }
223 mEventList.clear();
224 Calendar::Ptr cal = mCalendarStorage->calendar();
225 if (!cal)
226 return;
227
228 const KCalendarCore::Event::List kcalevents = cal->rawEvents();
229 for (const KCalendarCore::Event::Ptr& kcalevent : kcalevents)
230 {
231 if (kcalevent->alarms().isEmpty())
232 continue; // ignore events without alarms
233
234 auto event = new KAEvent(kcalevent);
235 if (!event->isValid())
236 {
237 qCWarning(KALARM_LOG) << "DisplayCalendar::updateKAEvents: Ignoring unusable event" << kcalevent->uid();
238 delete event;
239 continue; // ignore events without usable alarms
240 }
241 mEventList += event;
242 mEventMap[kcalevent->uid()] = event;
243 }
244 }
245
246 /******************************************************************************
247 * Add the specified event to the calendar.
248 * Reply = true if 'evnt' was written to the calendar. 'evnt' is updated.
249 * = false if an error occurred, in which case 'evnt' is unchanged.
250 */
addEvent(KAEvent & evnt)251 bool DisplayCalendar::addEvent(KAEvent& evnt)
252 {
253 if (!mOpen)
254 return false;
255 qCDebug(KALARM_LOG) << "DisplayCalendar::addEvent:" << evnt.id();
256 // Check that the event type is valid for the calendar
257 const CalEvent::Type type = evnt.category();
258 if (type != CalEvent::DISPLAYING)
259 return false;
260
261 KCalendarCore::Event::Ptr kcalEvent(new KCalendarCore::Event);
262 auto event = new KAEvent(evnt);
263 QString id = event->id();
264 if (id.isEmpty())
265 id = kcalEvent->uid();
266 id = CalEvent::uid(id, type); // include the alarm type tag in the ID
267 kcalEvent->setUid(id);
268 event->setEventId(id);
269 event->updateKCalEvent(kcalEvent, KAEvent::UID_IGNORE);
270
271 bool ok = false;
272 bool remove = false;
273 if (!mEventMap.contains(event->id()))
274 {
275 mEventList += event;
276 mEventMap[event->id()] = event;
277 ok = mCalendarStorage->calendar()->addEvent(kcalEvent);
278 remove = !ok;
279 }
280 if (!ok)
281 {
282 if (remove)
283 {
284 // Adding to mCalendar failed, so undo DisplayCalendar::addEvent()
285 mEventMap.remove(event->id());
286 int i = mEventList.indexOf(event);
287 if (i >= 0)
288 mEventList.remove(i);
289 }
290 delete event;
291 return false;
292 }
293 evnt = *event;
294 if (remove)
295 delete event;
296 return true;
297 }
298
299 /******************************************************************************
300 * Delete the specified event from the calendar, if it exists.
301 * The calendar is then optionally saved.
302 */
deleteEvent(const QString & eventID,bool saveit)303 bool DisplayCalendar::deleteEvent(const QString& eventID, bool saveit)
304 {
305 if (mOpen)
306 {
307 KCalendarCore::Event::Ptr kcalEvent;
308 if (mCalendarStorage)
309 kcalEvent = mCalendarStorage->calendar()->event(eventID); // display calendar
310
311 // Make a copy of the ID QString, since the supplied reference might be
312 // destructed when the event is deleted below.
313 const QString id = eventID;
314
315 auto it = mEventMap.find(id);
316 if (it != mEventMap.end())
317 {
318 KAEvent* ev = it.value();
319 mEventMap.erase(it);
320 int i = mEventList.indexOf(ev);
321 if (i >= 0)
322 mEventList.remove(i);
323 delete ev;
324 }
325
326 CalEvent::Type status = CalEvent::EMPTY;
327 if (kcalEvent)
328 {
329 status = CalEvent::status(kcalEvent);
330 mCalendarStorage->calendar()->deleteEvent(kcalEvent);
331 }
332
333 if (status != CalEvent::EMPTY)
334 {
335 if (saveit)
336 return saveCal();
337 return true;
338 }
339 }
340 return false;
341 }
342
343 /******************************************************************************
344 * Return the event with the specified ID.
345 * This method is for the display calendar only.
346 */
kcalEvent(const QString & uniqueID)347 KCalendarCore::Event::Ptr DisplayCalendar::kcalEvent(const QString& uniqueID)
348 {
349 if (!mCalendarStorage)
350 return KCalendarCore::Event::Ptr();
351 return mCalendarStorage->calendar()->event(uniqueID);
352 }
353
354 /******************************************************************************
355 * Return all events in the calendar which contain usable alarms.
356 * This method is for the display calendar only.
357 * Optionally the event type can be filtered, using an OR of event types.
358 */
kcalEvents(CalEvent::Type type)359 KCalendarCore::Event::List DisplayCalendar::kcalEvents(CalEvent::Type type)
360 {
361 KCalendarCore::Event::List list;
362 if (!mCalendarStorage)
363 return list;
364 list = mCalendarStorage->calendar()->rawEvents();
365 for (int i = 0; i < list.count(); )
366 {
367 KCalendarCore::Event::Ptr event = list.at(i);
368 if (event->alarms().isEmpty()
369 || (type != CalEvent::EMPTY && !(type & CalEvent::status(event)))
370 || !KAEvent(event).isValid())
371 list.remove(i);
372 else
373 ++i;
374 }
375 return list;
376 }
377
378 /******************************************************************************
379 * Called when the user changes the start-of-day time.
380 * Adjust the start times of all date-only alarms' recurrences.
381 */
adjustStartOfDay()382 void DisplayCalendar::adjustStartOfDay()
383 {
384 if (!isValid())
385 return;
386 KAEvent::adjustStartOfDay(mEventList);
387 }
388
389 // vim: et sw=4:
390