File indexing completed on 2024-05-12 05:14:41
0001 /* 0002 * displaycalendar.cpp - KAlarm display calendar file access 0003 * Program: kalarm 0004 * SPDX-FileCopyrightText: 2001-2023 David Jarvie <djarvie@kde.org> 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "displaycalendar.h" 0010 0011 #include "mainwindow.h" 0012 #include "preferences.h" 0013 #include "lib/messagebox.h" 0014 #include "kalarm_debug.h" 0015 0016 #include <KCalendarCore/MemoryCalendar> 0017 #include <KCalendarCore/ICalFormat> 0018 0019 #include <KLocalizedString> 0020 0021 #include <QStandardPaths> 0022 #include <QDir> 0023 0024 using namespace KCalendarCore; 0025 using namespace KAlarmCal; 0026 0027 //clazy:excludeall=non-pod-global-static 0028 0029 0030 namespace 0031 { 0032 const QString displayCalendarName = QStringLiteral("displaying.ics"); 0033 } 0034 0035 bool DisplayCalendar::mInitialised {false}; 0036 KAEvent::List DisplayCalendar::mEventList; 0037 QHash<QString, KAEvent*> DisplayCalendar::mEventMap; 0038 KCalendarCore::FileStorage::Ptr DisplayCalendar::mCalendarStorage; 0039 QString DisplayCalendar::mDisplayCalPath; 0040 QString DisplayCalendar::mDisplayICalPath; 0041 DisplayCalendar::CalType DisplayCalendar::mCalType; 0042 bool DisplayCalendar::mOpen {false}; 0043 0044 0045 /****************************************************************************** 0046 * Initialise the display alarm calendar. 0047 * It is user-specific, and contains details of alarms which are currently being 0048 * displayed to that user and which have not yet been acknowledged; 0049 */ 0050 void DisplayCalendar::initialise() 0051 { 0052 QDir dir; 0053 dir.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); 0054 mDisplayCalPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1Char('/') + displayCalendarName; 0055 mDisplayICalPath = mDisplayCalPath; 0056 mDisplayICalPath.replace(QStringLiteral("\\.vcs$"), QStringLiteral(".ics")); 0057 mCalType = (mDisplayCalPath == mDisplayICalPath) ? LOCAL_ICAL : LOCAL_VCAL; // is the calendar in ICal or VCal format? 0058 mInitialised = true; 0059 } 0060 0061 /****************************************************************************** 0062 * Terminate access to the display calendar. 0063 */ 0064 void DisplayCalendar::terminate() 0065 { 0066 close(); 0067 mInitialised = false; 0068 } 0069 0070 /****************************************************************************** 0071 * Open the calendar if not already open, and load it into memory. 0072 */ 0073 bool DisplayCalendar::open() 0074 { 0075 if (mOpen) 0076 return true; 0077 0078 // Open the display calendar. 0079 qCDebug(KALARM_LOG) << "DisplayCalendar::open:" << mDisplayCalPath; 0080 if (!mCalendarStorage) 0081 { 0082 MemoryCalendar::Ptr calendar(new MemoryCalendar(Preferences::timeSpecAsZone())); 0083 mCalendarStorage = FileStorage::Ptr(new FileStorage(calendar, mDisplayCalPath)); 0084 } 0085 0086 // Check for file's existence, assuming that it does exist when uncertain, 0087 // to avoid overwriting it. 0088 QFileInfo fi(mDisplayCalPath); 0089 if (!fi.exists() || !fi.isFile() || load() == 0) 0090 { 0091 // The calendar file doesn't yet exist, or it's zero length, so create a new one 0092 if (saveCal(mDisplayICalPath)) 0093 load(); 0094 } 0095 0096 if (!mOpen) 0097 { 0098 mCalendarStorage->calendar().reset(); 0099 mCalendarStorage.reset(); 0100 } 0101 return mOpen; 0102 } 0103 0104 /****************************************************************************** 0105 * Load the calendar into memory. 0106 * Reply = 1 if success 0107 * = 0 if zero-length file exists. 0108 * = -1 if failure to load calendar file 0109 * = -2 if instance uninitialised. 0110 */ 0111 int DisplayCalendar::load() 0112 { 0113 // Load the display calendar. 0114 if (!mCalendarStorage) 0115 return -2; 0116 0117 qCDebug(KALARM_LOG) << "DisplayCalendar::load:" << mDisplayCalPath; 0118 if (!mCalendarStorage->load()) 0119 { 0120 // Load error. Check if the file is zero length 0121 QFileInfo fi(mDisplayCalPath); 0122 if (fi.exists() && !fi.size()) 0123 return 0; // file is zero length 0124 0125 qCCritical(KALARM_LOG) << "DisplayCalendar::load: Error loading calendar file '" << mDisplayCalPath <<"'"; 0126 KAMessageBox::error(MainWindow::mainMainWindow(), 0127 xi18nc("@info", "<para>Error loading calendar:</para><para><filename>%1</filename></para><para>Please fix or delete the file.</para>", mDisplayCalPath)); 0128 // load() could have partially populated the calendar, so clear it out 0129 mCalendarStorage->calendar().reset(); 0130 mCalendarStorage.reset(); 0131 mOpen = false; 0132 return -1; 0133 } 0134 QString versionString; 0135 KACalendar::updateVersion(mCalendarStorage, versionString); // convert events to current KAlarm format for when calendar is saved 0136 updateKAEvents(); 0137 0138 mOpen = true; 0139 return 1; 0140 } 0141 0142 /****************************************************************************** 0143 * Save the calendar. 0144 */ 0145 bool DisplayCalendar::save() 0146 { 0147 return saveCal(); 0148 } 0149 0150 /****************************************************************************** 0151 * Save the calendar from memory to file. 0152 * If a filename is specified, create a new calendar file. 0153 */ 0154 bool DisplayCalendar::saveCal(const QString& newFile) 0155 { 0156 if (!mCalendarStorage) 0157 return false; 0158 if (!mOpen && newFile.isEmpty()) 0159 return false; 0160 0161 qCDebug(KALARM_LOG) << "DisplayCalendar::saveCal:" << "\"" << newFile; 0162 QString saveFilename = newFile.isEmpty() ? mDisplayCalPath : newFile; 0163 if (mCalType == LOCAL_VCAL && newFile.isNull()) 0164 saveFilename = mDisplayICalPath; 0165 mCalendarStorage->setFileName(saveFilename); 0166 mCalendarStorage->setSaveFormat(new ICalFormat); 0167 if (!mCalendarStorage->save()) 0168 { 0169 qCCritical(KALARM_LOG) << "DisplayCalendar::saveCal: Saving" << saveFilename << "failed."; 0170 KAMessageBox::error(MainWindow::mainMainWindow(), 0171 xi18nc("@info", "Failed to save calendar to <filename>%1</filename>", mDisplayICalPath)); 0172 return false; 0173 } 0174 0175 if (mCalType == LOCAL_VCAL) 0176 { 0177 // The file was in vCalendar format, but has now been saved in iCalendar format. 0178 mDisplayCalPath = mDisplayICalPath; 0179 mCalType = LOCAL_ICAL; 0180 } 0181 return true; 0182 } 0183 0184 /****************************************************************************** 0185 * Close display calendar file at program exit. 0186 */ 0187 void DisplayCalendar::close() 0188 { 0189 if (mCalendarStorage) 0190 { 0191 mCalendarStorage->calendar().reset(); 0192 mCalendarStorage.reset(); 0193 } 0194 0195 // Flag as closed now to prevent removeKAEvents() doing silly things 0196 // when it's called again 0197 mOpen = false; 0198 0199 // Events list should be empty, but just in case... 0200 for (KAEvent* event : std::as_const(mEventList)) 0201 { 0202 mEventMap.remove(event->id()); 0203 delete event; 0204 } 0205 mEventList.clear(); 0206 } 0207 0208 /****************************************************************************** 0209 * Create a KAEvent instance corresponding to each KCalendarCore::Event in the 0210 * display calendar, and store them in the event map in place of the old set. 0211 * Called after the display calendar has completed loading. 0212 */ 0213 void DisplayCalendar::updateKAEvents() 0214 { 0215 qCDebug(KALARM_LOG) << "DisplayCalendar::updateKAEvents"; 0216 for (KAEvent* event : std::as_const(mEventList)) 0217 { 0218 mEventMap.remove(event->id()); 0219 delete event; 0220 } 0221 mEventList.clear(); 0222 Calendar::Ptr cal = mCalendarStorage->calendar(); 0223 if (!cal) 0224 return; 0225 0226 const KCalendarCore::Event::List kcalevents = cal->rawEvents(); 0227 for (const KCalendarCore::Event::Ptr& kcalevent : kcalevents) 0228 { 0229 if (kcalevent->alarms().isEmpty()) 0230 continue; // ignore events without alarms 0231 0232 auto event = new KAEvent(kcalevent); 0233 if (!event->isValid()) 0234 { 0235 qCWarning(KALARM_LOG) << "DisplayCalendar::updateKAEvents: Ignoring unusable event" << kcalevent->uid(); 0236 delete event; 0237 continue; // ignore events without usable alarms 0238 } 0239 mEventList += event; 0240 mEventMap[kcalevent->uid()] = event; 0241 } 0242 } 0243 0244 /****************************************************************************** 0245 * Add the specified event to the calendar. 0246 * Reply = true if 'evnt' was written to the calendar. 'evnt' is updated. 0247 * = false if an error occurred, in which case 'evnt' is unchanged. 0248 */ 0249 bool DisplayCalendar::addEvent(KAEvent& evnt) 0250 { 0251 if (!mOpen) 0252 return false; 0253 qCDebug(KALARM_LOG) << "DisplayCalendar::addEvent:" << evnt.id(); 0254 // Check that the event type is valid for the calendar 0255 const CalEvent::Type type = evnt.category(); 0256 if (type != CalEvent::DISPLAYING) 0257 return false; 0258 0259 KCalendarCore::Event::Ptr kcalEvent(new KCalendarCore::Event); 0260 auto event = new KAEvent(evnt); 0261 QString id = event->id(); 0262 if (id.isEmpty()) 0263 id = kcalEvent->uid(); 0264 id = CalEvent::uid(id, type); // include the alarm type tag in the ID 0265 kcalEvent->setUid(id); 0266 event->setEventId(id); 0267 event->updateKCalEvent(kcalEvent, KAEvent::UidAction::Ignore); 0268 0269 bool ok = false; 0270 bool remove = false; 0271 if (!mEventMap.contains(event->id())) 0272 { 0273 mEventList += event; 0274 mEventMap[event->id()] = event; 0275 ok = mCalendarStorage->calendar()->addEvent(kcalEvent); 0276 remove = !ok; 0277 } 0278 if (!ok) 0279 { 0280 if (remove) 0281 { 0282 // Adding to mCalendar failed, so undo DisplayCalendar::addEvent() 0283 mEventMap.remove(event->id()); 0284 int i = mEventList.indexOf(event); 0285 if (i >= 0) 0286 mEventList.remove(i); 0287 } 0288 delete event; 0289 return false; 0290 } 0291 evnt = *event; 0292 if (remove) 0293 delete event; 0294 return true; 0295 } 0296 0297 /****************************************************************************** 0298 * Delete the specified event from the calendar, if it exists. 0299 * The calendar is then optionally saved. 0300 */ 0301 bool DisplayCalendar::deleteEvent(const QString& eventID, bool saveit) 0302 { 0303 if (mOpen) 0304 { 0305 KCalendarCore::Event::Ptr kcalEvent; 0306 if (mCalendarStorage) 0307 kcalEvent = mCalendarStorage->calendar()->event(eventID); // display calendar 0308 0309 // Make a copy of the ID QString, since the supplied reference might be 0310 // destructed when the event is deleted below. 0311 const QString id = eventID; 0312 0313 auto it = mEventMap.find(id); 0314 if (it != mEventMap.end()) 0315 { 0316 KAEvent* ev = it.value(); 0317 mEventMap.erase(it); 0318 int i = mEventList.indexOf(ev); 0319 if (i >= 0) 0320 mEventList.remove(i); 0321 delete ev; 0322 } 0323 0324 CalEvent::Type status = CalEvent::EMPTY; 0325 if (kcalEvent) 0326 { 0327 status = CalEvent::status(kcalEvent); 0328 mCalendarStorage->calendar()->deleteEvent(kcalEvent); 0329 } 0330 0331 if (status != CalEvent::EMPTY) 0332 { 0333 if (saveit) 0334 return saveCal(); 0335 return true; 0336 } 0337 } 0338 return false; 0339 } 0340 0341 /****************************************************************************** 0342 * Return the event with the specified ID. 0343 * This method is for the display calendar only. 0344 */ 0345 KCalendarCore::Event::Ptr DisplayCalendar::kcalEvent(const QString& uniqueID) 0346 { 0347 if (!mCalendarStorage) 0348 return {}; 0349 return mCalendarStorage->calendar()->event(uniqueID); 0350 } 0351 0352 /****************************************************************************** 0353 * Return all events in the calendar which contain usable alarms. 0354 * This method is for the display calendar only. 0355 * Optionally the event type can be filtered, using an OR of event types. 0356 */ 0357 KCalendarCore::Event::List DisplayCalendar::kcalEvents(CalEvent::Type type) 0358 { 0359 KCalendarCore::Event::List list; 0360 if (!mCalendarStorage) 0361 return list; 0362 list = mCalendarStorage->calendar()->rawEvents(); 0363 for (int i = 0; i < list.count(); ) 0364 { 0365 KCalendarCore::Event::Ptr event = list.at(i); 0366 if (event->alarms().isEmpty() 0367 || (type != CalEvent::EMPTY && !(type & CalEvent::status(event))) 0368 || !KAEvent(event).isValid()) 0369 list.remove(i); 0370 else 0371 ++i; 0372 } 0373 return list; 0374 } 0375 0376 /****************************************************************************** 0377 * Called when the user changes the start-of-day time. 0378 * Adjust the start times of all date-only alarms' recurrences. 0379 */ 0380 void DisplayCalendar::adjustStartOfDay() 0381 { 0382 if (!isValid()) 0383 return; 0384 KAEvent::adjustStartOfDay(mEventList); 0385 } 0386 0387 // vim: et sw=4: