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: