File indexing completed on 2024-05-12 05:14:57

0001 /*
0002  *  resourcescalendar.cpp  -  KAlarm calendar resources 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 "resourcescalendar.h"
0010 
0011 #include "eventid.h"
0012 #include "kalarmapp.h"
0013 #include "resources/resources.h"
0014 #include "kalarm_debug.h"
0015 
0016 #include <KCalendarCore/CalFormat>
0017 
0018 using namespace KAlarmCal;
0019 
0020 
0021 ResourcesCalendar*             ResourcesCalendar::mInstance {nullptr};
0022 ResourcesCalendar::ResourceMap ResourcesCalendar::mResourceMap;
0023 ResourcesCalendar::EarliestMap ResourcesCalendar::mEarliestAlarm;
0024 ResourcesCalendar::EarliestMap ResourcesCalendar::mEarliestNonDispAlarm;
0025 QSet<QString>                  ResourcesCalendar::mPendingAlarms;
0026 bool                           ResourcesCalendar::mIgnoreAtLogin {false};
0027 bool                           ResourcesCalendar::mHaveDisabledAlarms {false};
0028 QHash<ResourceId, QHash<QString, KernelWakeAlarm>> ResourcesCalendar::mWakeSuspendTimers;
0029 
0030 
0031 /******************************************************************************
0032 * Initialise the resource alarm calendars, and ensure that their file names are
0033 * different. The resources calendar contains the active alarms, archived alarms
0034 * and alarm templates;
0035 */
0036 void ResourcesCalendar::initialise(const QByteArray& appName, const QByteArray& appVersion)
0037 {
0038     if (!mInstance)
0039     {
0040         KACalendar::setProductId(appName, appVersion);
0041         mInstance = new ResourcesCalendar();
0042     }
0043 }
0044 
0045 /******************************************************************************
0046 * Terminate access to the resource calendars.
0047 */
0048 void ResourcesCalendar::terminate()
0049 {
0050     delete mInstance;
0051     mInstance = nullptr;
0052 }
0053 
0054 /******************************************************************************
0055 * Constructor for the resources calendar.
0056 */
0057 ResourcesCalendar::ResourcesCalendar()
0058 {
0059     Resources* resources = Resources::instance();
0060     connect(resources, &Resources::resourceAdded, this, &ResourcesCalendar::slotResourceAdded);
0061     connect(resources, &Resources::eventsAdded, this, &ResourcesCalendar::slotEventsAdded);
0062     connect(resources, &Resources::eventsToBeRemoved, this, &ResourcesCalendar::slotEventsToBeRemoved);
0063     connect(resources, &Resources::eventUpdated, this, &ResourcesCalendar::slotEventUpdated);
0064     connect(resources, &Resources::resourcesPopulated, this, &ResourcesCalendar::slotResourcesPopulated);
0065     connect(resources, &Resources::settingsChanged, this, &ResourcesCalendar::slotResourceSettingsChanged);
0066     connect(theApp(), &KAlarmApp::alarmEnabledToggled, this, &ResourcesCalendar::slotAlarmsEnabledToggled);
0067     Preferences::connect(&Preferences::wakeFromSuspendAdvanceChanged, this, &ResourcesCalendar::slotWakeFromSuspendAdvanceChanged);
0068 
0069     // Fetch events from all resources which already exist.
0070     QList<Resource> allResources = Resources::enabledResources();
0071     for (Resource& resource : allResources)
0072         slotResourceAdded(resource);
0073 }
0074 
0075 ResourcesCalendar::~ResourcesCalendar()
0076 {
0077     // Resource map should be empty, but just in case...
0078     while (!mResourceMap.isEmpty())
0079         removeKAEvents(mResourceMap.constBegin().key(), true, CalEvent::ACTIVE | CalEvent::ARCHIVED | CalEvent::TEMPLATE | CalEvent::DISPLAYING);
0080 }
0081 
0082 /******************************************************************************
0083 * Delete a calendar and all its KAEvent instances of specified alarm types from
0084 * the lists.
0085 * Called after the calendar is deleted or alarm types have been disabled, or
0086 * the ResourcesCalendar is closed.
0087 */
0088 void ResourcesCalendar::removeKAEvents(ResourceId key, bool closing, CalEvent::Types types)
0089 {
0090     bool removed = false;
0091     ResourceMap::Iterator rit = mResourceMap.find(key);
0092     if (rit != mResourceMap.end())
0093     {
0094         Resource resource = Resources::resource(key);
0095         QSet<QString> retained;
0096         QSet<QString>& eventIds = rit.value();
0097         for (auto it = eventIds.constBegin();  it != eventIds.constEnd();  ++it)
0098         {
0099             const KAEvent event = resource.event(*it, true);
0100             bool remove = (event.resourceId() != key);
0101             if (remove)
0102                 qCCritical(KALARM_LOG) << "ResourcesCalendar::removeKAEvents: Event" << event.id() << ", resource" << event.resourceId() << "Indexed under resource" << key;
0103             else
0104                 remove = event.category() & types;
0105             if (remove)
0106                 removed = true;
0107             else
0108                 retained.insert(*it);
0109         }
0110         if (retained.isEmpty())
0111             mResourceMap.erase(rit);
0112         else
0113             eventIds.swap(retained);
0114     }
0115     if (removed)
0116     {
0117         mEarliestAlarm.remove(key);
0118         mEarliestNonDispAlarm.remove(key);
0119         // Emit signal only if we're not in the process of closing the calendar
0120         if (!closing)
0121         {
0122             Q_EMIT earliestAlarmChanged();
0123             if (mHaveDisabledAlarms)
0124                 checkForDisabledAlarms();
0125         }
0126     }
0127 }
0128 
0129 /******************************************************************************
0130 * Called when the enabled or read-only status of a resource has changed.
0131 * If the resource is now disabled, remove its events from the calendar.
0132 */
0133 void ResourcesCalendar::slotResourceSettingsChanged(Resource& resource, ResourceType::Changes change)
0134 {
0135     if (change & ResourceType::Enabled)
0136     {
0137         if (resource.isValid())
0138         {
0139             // For each alarm type which has been disabled, remove the
0140             // resource's events from the map, but not from the resource.
0141             const CalEvent::Types enabled = resource.enabledTypes();
0142             const CalEvent::Types disabled = ~enabled & (CalEvent::ACTIVE | CalEvent::ARCHIVED | CalEvent::TEMPLATE);
0143             removeKAEvents(resource.id(), false, disabled);
0144 
0145             // For each alarm type which has been enabled, add the resource's
0146             // events to the map.
0147             if (enabled != CalEvent::EMPTY)
0148                 slotEventsAdded(resource, resource.events());
0149         }
0150     }
0151 }
0152 
0153 /******************************************************************************
0154 * Called when all resources have been populated for the first time.
0155 */
0156 void ResourcesCalendar::slotResourcesPopulated()
0157 {
0158     // Now that all calendars have been processed, all repeat-at-login alarms
0159     // will have been triggered. Prevent any new or updated repeat-at-login
0160     // alarms (e.g. when they are edited by the user) triggering from now on.
0161     mIgnoreAtLogin = true;
0162 }
0163 
0164 /******************************************************************************
0165 * Called when a resource has been added.
0166 * Add its KAEvent instances to those held by ResourcesCalendar.
0167 * All events must have their resource ID set.
0168 */
0169 void ResourcesCalendar::slotResourceAdded(Resource& resource)
0170 {
0171     slotEventsAdded(resource, resource.events());
0172 }
0173 
0174 /******************************************************************************
0175 * Called when events have been added to a resource.
0176 * Record that the event is now usable by the ResourcesCalendar.
0177 * Update the earliest alarm for the resource.
0178 */
0179 void ResourcesCalendar::slotEventsAdded(Resource& resource, const QList<KAEvent>& events)
0180 {
0181     for (const KAEvent& event : events)
0182         slotEventUpdated(resource, event);
0183 }
0184 
0185 /******************************************************************************
0186 * Called when an event has been changed in a resource.
0187 * Record that the event is now usable by the ResourcesCalendar.
0188 * Update the earliest alarm for the resource.
0189 */
0190 void ResourcesCalendar::slotEventUpdated(Resource& resource, const KAEvent& event)
0191 {
0192     const ResourceId key = resource.id();
0193     const bool added = !mResourceMap[key].contains(event.id());
0194     qCDebug(KALARM_LOG) << "ResourcesCalendar::slotEventUpdated: resource" << resource.displayId() << (added ? "added" : "updated") << event.id();
0195     mResourceMap[key].insert(event.id());
0196 
0197     if ((resource.alarmTypes() & CalEvent::ACTIVE)
0198     &&  event.category() == CalEvent::ACTIVE)
0199     {
0200         // Set/clear wake from suspend timer if needed
0201         checkKernelWakeSuspend(key, event);
0202 
0203         // Update the earliest alarm to trigger
0204         const QString earliestId        = mEarliestAlarm.value(key);
0205         const QString earliestNonDispId = mEarliestNonDispAlarm.value(key);
0206         if (earliestId == event.id()  ||  earliestNonDispId == event.id())
0207             findEarliestAlarm(resource);
0208         else
0209         {
0210             const KADateTime dt = event.nextTrigger(KAEvent::Trigger::All).effectiveKDateTime();
0211             if (dt.isValid())
0212             {
0213                 bool changed = false;
0214                 DateTime next;
0215                 if (!earliestId.isEmpty())
0216                     next = resource.event(earliestId).nextTrigger(KAEvent::Trigger::All);
0217                 if (earliestId.isEmpty()  ||  dt < next)
0218                 {
0219                     mEarliestAlarm[key] = event.id();
0220                     changed = true;
0221                 }
0222                 if (!(event.actionTypes() & KAEvent::Action::Display))
0223                 {
0224                     // It is not a display event.
0225                     DateTime nextNonDisp;
0226                     if (!earliestNonDispId.isEmpty())
0227                         nextNonDisp = (earliestId == earliestNonDispId) ? next : resource.event(earliestNonDispId).nextTrigger(KAEvent::Trigger::All);
0228                     if (earliestNonDispId.isEmpty()  ||  dt < nextNonDisp)
0229                     {
0230                         mEarliestNonDispAlarm[key] = event.id();
0231                         changed = true;
0232                     }
0233                 }
0234                 if (changed)
0235                     Q_EMIT earliestAlarmChanged();
0236             }
0237         }
0238     }
0239 
0240     if (event.category() == CalEvent::ACTIVE)
0241     {
0242         bool enabled = event.enabled();
0243         checkForDisabledAlarms(!enabled, enabled);
0244         if (!mIgnoreAtLogin  &&  added  &&  enabled  &&  event.repeatAtLogin())
0245             Q_EMIT atLoginEventAdded(event);
0246     }
0247 }
0248 
0249 /******************************************************************************
0250 * Called when events are about to be removed from a resource.
0251 * Remove the corresponding KAEvent instances held by ResourcesCalendar.
0252 */
0253 void ResourcesCalendar::slotEventsToBeRemoved(Resource& resource, const QList<KAEvent>& events)
0254 {
0255     const ResourceId key = resource.id();
0256     for (const KAEvent& event : events)
0257     {
0258         if (mResourceMap.value(key).contains(event.id()))
0259             deleteEventInternal(event, resource, false);
0260     }
0261 }
0262 
0263 /******************************************************************************
0264 * Called when alarm monitoring has been enabled or disabled (for all alarms).
0265 * Arm or disarm kernel wake alarms for all events which use them.
0266 */
0267 void ResourcesCalendar::slotAlarmsEnabledToggled(bool enabled)
0268 {
0269     if (!KernelWakeAlarm::isAvailable())
0270         return;
0271 
0272     if (enabled)
0273     {
0274         // Set kernel wake timers for all events which require them.
0275         setKernelWakeSuspend();
0276     }
0277     else
0278     {
0279         // Disarm all kernel wake timers (but don't delete them).
0280         for (auto itr = mWakeSuspendTimers.begin(), endr = mWakeSuspendTimers.end();  itr != endr;  ++itr)
0281         {
0282             auto& resourceHash = itr.value();
0283             for (auto it = resourceHash.begin(), end = resourceHash.end();  it != end;  ++it)
0284                 it.value().disarm();
0285         }
0286     }
0287 }
0288 
0289 /******************************************************************************
0290 * Called when the wake-from-suspend wakeup advance interval has changed.
0291 * Revise all kernel wake alarm times.
0292 * Note that the user only has the option to change the wakeup advance if kernel
0293 * wake alarms are used, and not if the RTC wake timer is used.
0294 */
0295 void ResourcesCalendar::slotWakeFromSuspendAdvanceChanged(unsigned advance)
0296 {
0297     if (!KernelWakeAlarm::isAvailable()  ||  !theApp()->alarmsEnabled())
0298         return;
0299 
0300     qCDebug(KALARM_LOG) << "ResourcesCalendar::slotWakeFromSuspendAdvanceChanged:" << advance;
0301     setKernelWakeSuspend();
0302 }
0303 
0304 /******************************************************************************
0305 * This method must only be called from the main KAlarm queue processing loop,
0306 * to prevent asynchronous calendar operations interfering with one another.
0307 *
0308 * Purge a list of archived events from the calendar.
0309 */
0310 void ResourcesCalendar::purgeEvents(const QList<KAEvent>& events)
0311 {
0312     for (const KAEvent& event : events)
0313     {
0314         Resource resource = Resources::resource(event.resourceId());
0315         if (resource.isValid())
0316             deleteEventInternal(event.id(), event, resource, true);
0317     }
0318     if (mHaveDisabledAlarms)
0319         mInstance->checkForDisabledAlarms();
0320 }
0321 
0322 /******************************************************************************
0323 * Add the specified event to the calendar.
0324 * If it is an active event and 'useEventID' is false, a new event ID is
0325 * created. In all other cases, the event ID is taken from 'evnt' (if non-null).
0326 * 'evnt' is updated with the actual event ID.
0327 * The event is added to 'resource' if specified; otherwise the default resource
0328 * is used or the user is prompted, depending on policy, and 'resource' is
0329 * updated with the actual resource used. If 'noPrompt' is true, the user will
0330 * not be prompted so that if no default resource is defined, the function will
0331 * fail.
0332 * Reply = true if 'evnt' was written to the calendar. 'evnt' is updated.
0333 *       = false if an error occurred, in which case 'evnt' is unchanged.
0334 */
0335 bool ResourcesCalendar::addEvent(KAEvent& evnt, Resource& resource, QWidget* promptParent, AddEventOptions options, bool* cancelled)
0336 {
0337     bool useEventID = options & UseEventId;
0338     if (cancelled)
0339         *cancelled = false;
0340     qCDebug(KALARM_LOG) << "ResourcesCalendar::addEvent:" << evnt.id() << ", resource" << resource.displayId();
0341 
0342     // Check that the event type is valid for the calendar
0343     const CalEvent::Type type = evnt.category();
0344     switch (type)
0345     {
0346         case CalEvent::ACTIVE:
0347         case CalEvent::ARCHIVED:
0348         case CalEvent::TEMPLATE:
0349             break;
0350         default:
0351             return false;
0352     }
0353 
0354     KAEvent event = evnt;
0355     QString id = event.id();
0356     if (type == CalEvent::ACTIVE)
0357     {
0358         if (id.isEmpty())
0359             useEventID = false;
0360         else if (!useEventID)
0361             id.clear();
0362     }
0363     else
0364         useEventID = true;
0365     if (id.isEmpty())
0366         id = KCalendarCore::CalFormat::createUniqueId();
0367     if (useEventID)
0368         id = CalEvent::uid(id, type);   // include the alarm type tag in the ID
0369     event.setEventId(id);
0370 
0371     bool ok = false;
0372     if (!resource.isEnabled(type))
0373     {
0374         Resources::DestOptions destOptions {};
0375         if (options & NoResourcePrompt)
0376             destOptions |= Resources::NoResourcePrompt;
0377         resource = Resources::destination(type, promptParent, destOptions, cancelled);
0378         if (!resource.isValid())
0379             qCWarning(KALARM_LOG) << "ResourcesCalendar::addEvent: Error! Cannot create" << type << "(No default calendar is defined)";
0380     }
0381     if (resource.isValid())
0382     {
0383         // Don't add event to mResourceMap yet - its ID is not yet known.
0384         // It will be added after it is inserted into the data model, when
0385         // the resource signals eventsAdded().
0386         ok = resource.addEvent(event);
0387         if (ok  &&  type == CalEvent::ACTIVE  &&  !event.enabled())
0388             mInstance->checkForDisabledAlarms(true, false);
0389         event.setResourceId(resource.id());
0390     }
0391     if (ok)
0392         evnt = event;
0393     return ok;
0394 }
0395 
0396 /******************************************************************************
0397 * Modify the specified event in the calendar with its new contents.
0398 * The new event must have a different event ID from the old one; if it does not
0399 * have an ID, it will be updated with a new ID.
0400 * It is assumed to be of the same event type as the old one (active, etc.)
0401 * Reply = true if 'newEvent' was written to the calendar. 'newEvent' is updated.
0402 *       = false if an error occurred, in which case 'newEvent' is unchanged.
0403 */
0404 bool ResourcesCalendar::modifyEvent(const EventId& oldEventId, KAEvent& newEvent)
0405 {
0406     const EventId newId(oldEventId.resourceId(), newEvent.id());
0407     bool noNewId = newId.isEmpty();
0408     if (!noNewId  &&  oldEventId == newId)
0409     {
0410         qCCritical(KALARM_LOG) << "ResourcesCalendar::modifyEvent: Same IDs" << oldEventId;
0411         return false;
0412     }
0413 
0414     // Set the event's ID, and update the old event in the resources calendar.
0415     if (!mResourceMap.value(oldEventId.resourceId()).contains(oldEventId.eventId()))
0416     {
0417         qCCritical(KALARM_LOG) << "ResourcesCalendar::modifyEvent: Old event not found" << oldEventId;
0418         return false;
0419     }
0420     Resource resource = Resources::resource(oldEventId.resourceId());
0421     if (!resource.isValid())
0422     {
0423         qCCritical(KALARM_LOG) << "ResourcesCalendar::modifyEvent: Old event's resource not found" << oldEventId;
0424         return false;
0425     }
0426     const KAEvent oldEvent = resource.event(oldEventId.eventId());
0427     if (noNewId)
0428         newEvent.setEventId(KCalendarCore::CalFormat::createUniqueId());
0429     qCDebug(KALARM_LOG) << "ResourcesCalendar::modifyEvent:" << oldEventId << "->" << newEvent.id();
0430 
0431     // Don't add new event to mResourceMap yet - it will be added when the resource
0432     // signals eventsAdded().
0433     if (!resource.addEvent(newEvent))
0434         return false;
0435     deleteEventInternal(oldEvent, resource);
0436     if (mHaveDisabledAlarms)
0437         mInstance->checkForDisabledAlarms();
0438     return true;
0439 }
0440 
0441 /******************************************************************************
0442 * Update the specified event in the calendar with its new contents.
0443 * The event retains the same ID. The event must be in the resource calendar,
0444 * and must have its resourceId() set correctly.
0445 * Reply = event which has been updated
0446 *       = invalid if error.
0447 */
0448 KAEvent ResourcesCalendar::updateEvent(const KAEvent& evnt, bool saveIfReadOnly)
0449 {
0450     if (mResourceMap.value(evnt.resourceId()).contains(evnt.id()))
0451     {
0452         Resource resource = Resources::resource(evnt.resourceId());
0453         if (resource.updateEvent(evnt, saveIfReadOnly))
0454         {
0455             // Set/clear wake from suspend timer if needed
0456             checkKernelWakeSuspend(resource.id(), evnt);
0457             return evnt;
0458         }
0459     }
0460     qCDebug(KALARM_LOG) << "ResourcesCalendar::updateEvent: error" << evnt.id();
0461     return {};
0462 }
0463 
0464 
0465 /******************************************************************************
0466 * Delete the specified event from the resource calendar, if it exists.
0467 * The calendar is then optionally saved.
0468 */
0469 bool ResourcesCalendar::deleteEvent(const KAEvent& event, Resource& resource, bool saveit)
0470 {
0471     Q_UNUSED(saveit);
0472 
0473     if (!resource.isValid())
0474     {
0475         resource = Resources::resource(event.resourceId());
0476         if (!resource.isValid())
0477         {
0478             qCDebug(KALARM_LOG) << "ResourcesCalendar::deleteEvent: Resource not found for" << event.id();
0479             return false;
0480         }
0481     }
0482     else if (!resource.containsEvent(event.id()))
0483     {
0484         qCDebug(KALARM_LOG) << "ResourcesCalendar::deleteEvent: Event" << event.id() << "not in resource" << resource.displayId();
0485         return false;
0486     }
0487     qCDebug(KALARM_LOG) << "ResourcesCalendar::deleteEvent:" << event.id();
0488     const CalEvent::Type status = deleteEventInternal(event.id(), event, resource, true);
0489     if (mHaveDisabledAlarms)
0490         mInstance->checkForDisabledAlarms();
0491     return status != CalEvent::EMPTY;
0492 }
0493 
0494 /******************************************************************************
0495 * Internal method to delete the specified event from the calendar and lists.
0496 * Reply = event status, if it was found in the resource calendar/calendar
0497 *         resource or local calendar
0498 *       = CalEvent::EMPTY otherwise.
0499 */
0500 CalEvent::Type ResourcesCalendar::deleteEventInternal(const KAEvent& event, Resource& resource, bool deleteFromResource)
0501 {
0502     if (!resource.isValid())
0503         return CalEvent::EMPTY;
0504     if (event.resourceId() != resource.id())
0505     {
0506         qCCritical(KALARM_LOG) << "ResourcesCalendar::deleteEventInternal: Event" << event.id() << ": resource" << event.resourceId() << "differs from 'resource'" << resource.id();
0507         return CalEvent::EMPTY;
0508     }
0509     return deleteEventInternal(event.id(), event, resource, deleteFromResource);
0510 }
0511 
0512 CalEvent::Type ResourcesCalendar::deleteEventInternal(const QString& eventID, const KAEvent& event, Resource& resource, bool deleteFromResource)
0513 {
0514     const ResourceId key = resource.id();
0515 
0516     if (mWakeSuspendTimers.contains(key))
0517         mWakeSuspendTimers[key].remove(eventID);   // this cancels the timer
0518 
0519     mResourceMap[key].remove(eventID);
0520     if (mEarliestAlarm.value(key)        == eventID
0521     ||  mEarliestNonDispAlarm.value(key) == eventID)
0522         mInstance->findEarliestAlarm(resource);
0523 
0524     CalEvent::Type status = CalEvent::EMPTY;
0525     if (deleteFromResource)
0526     {
0527         // Delete from the resource.
0528         CalEvent::Type s = event.category();
0529         if (resource.deleteEvent(event))
0530             status = s;
0531     }
0532     return status;
0533 }
0534 
0535 /******************************************************************************
0536 * Return the event with the specified ID.
0537 * If 'findUniqueId' is true, and the resource ID is invalid, if there is a
0538 * unique event with the given ID, it will be returned.
0539 */
0540 KAEvent ResourcesCalendar::event(const EventId& uniqueID, bool findUniqueId)
0541 {
0542     const QString eventId = uniqueID.eventId();
0543     const ResourceId resourceId = uniqueID.resourceId();
0544     if (resourceId == -1  &&  findUniqueId)
0545     {
0546         // The resource isn't known, but use the event ID if it is unique among
0547         // all resources.
0548         const QList<KAEvent> list = events(eventId);
0549         if (list.count() > 1)
0550         {
0551             qCWarning(KALARM_LOG) << "ResourcesCalendar::event: Multiple events found with ID" << eventId;
0552             return {};
0553         }
0554         if (list.isEmpty())
0555             return {};
0556         return list[0];
0557     }
0558 
0559     // The resource is specified.
0560     if (!mResourceMap.value(resourceId).contains(eventId))
0561         return {};
0562     return Resources::resource(resourceId).event(eventId);
0563 }
0564 
0565 /******************************************************************************
0566 * Find the alarm template with the specified name.
0567 * Reply = 0 if not found.
0568 */
0569 KAEvent ResourcesCalendar::templateEvent(const QString& templateName)
0570 {
0571     if (templateName.isEmpty())
0572         return {};
0573     const QList<KAEvent> eventlist = events(CalEvent::TEMPLATE);
0574     for (const KAEvent& event : eventlist)
0575     {
0576         if (event.name() == templateName)
0577             return event;
0578     }
0579     return {};
0580 }
0581 
0582 /******************************************************************************
0583 * Return all events with the specified ID, from all calendars.
0584 */
0585 QList<KAEvent> ResourcesCalendar::events(const QString& uniqueId)
0586 {
0587     QList<KAEvent> list;
0588     for (ResourceMap::ConstIterator rit = mResourceMap.constBegin();  rit != mResourceMap.constEnd();  ++rit)
0589     {
0590         if (rit.value().contains(uniqueId))
0591             list += Resources::resource(rit.key()).event(uniqueId);
0592     }
0593     return list;
0594 }
0595 
0596 QList<KAEvent> ResourcesCalendar::events(const Resource& resource, CalEvent::Types type)
0597 {
0598     return events(type, resource);
0599 }
0600 
0601 QList<KAEvent> ResourcesCalendar::events(CalEvent::Types type)
0602 {
0603     Resource resource;
0604     return events(type, resource);
0605 }
0606 
0607 QList<KAEvent> ResourcesCalendar::events(CalEvent::Types type, const Resource& resource)
0608 {
0609     QList<KAEvent> list;
0610     if (resource.isValid())
0611     {
0612         const ResourceId key = resource.id();
0613         ResourceMap::ConstIterator rit = mResourceMap.constFind(key);
0614         if (rit == mResourceMap.constEnd())
0615             return list;
0616         const QList<KAEvent> events = eventsForResource(resource, rit.value());
0617         if (type == CalEvent::EMPTY)
0618             return events;
0619         for (const KAEvent& event : events)
0620             if (type & event.category())
0621                 list += event;
0622     }
0623     else
0624     {
0625         for (ResourceMap::ConstIterator rit = mResourceMap.constBegin();  rit != mResourceMap.constEnd();  ++rit)
0626         {
0627             const Resource res = Resources::resource(rit.key());
0628             const QList<KAEvent> events = eventsForResource(res, rit.value());
0629             if (type == CalEvent::EMPTY)
0630                 list += events;
0631             else
0632             {
0633                 for (const KAEvent& event : events)
0634                     if (type & event.category())
0635                         list += event;
0636             }
0637         }
0638     }
0639     return list;
0640 }
0641 
0642 /******************************************************************************
0643 * Called when an alarm's enabled status has changed.
0644 */
0645 void ResourcesCalendar::disabledChanged(const KAEvent& event)
0646 {
0647     if (event.category() == CalEvent::ACTIVE)
0648     {
0649         bool status = event.enabled();
0650         mInstance->checkForDisabledAlarms(!status, status);
0651     }
0652 }
0653 
0654 /******************************************************************************
0655 * Check whether there are any individual disabled alarms, following an alarm
0656 * creation or modification. Must only be called for an ACTIVE alarm.
0657 */
0658 void ResourcesCalendar::checkForDisabledAlarms(bool oldEnabled, bool newEnabled)
0659 {
0660     if (newEnabled != oldEnabled)
0661     {
0662         if (newEnabled  &&  mHaveDisabledAlarms)
0663             checkForDisabledAlarms();
0664         else if (!newEnabled  &&  !mHaveDisabledAlarms)
0665         {
0666             mHaveDisabledAlarms = true;
0667             Q_EMIT haveDisabledAlarmsChanged(true);
0668         }
0669     }
0670 }
0671 
0672 /******************************************************************************
0673 * Check whether there are any individual disabled alarms.
0674 */
0675 void ResourcesCalendar::checkForDisabledAlarms()
0676 {
0677     bool disabled = false;
0678     const QList<KAEvent> eventlist = events(CalEvent::ACTIVE);
0679     for (const KAEvent& event : eventlist)
0680     {
0681         if (!event.enabled())
0682         {
0683             disabled = true;
0684             break;
0685         }
0686     }
0687     if (disabled != mHaveDisabledAlarms)
0688     {
0689         mHaveDisabledAlarms = disabled;
0690         Q_EMIT haveDisabledAlarmsChanged(disabled);
0691     }
0692 }
0693 
0694 /******************************************************************************
0695 * Set kernel wake alarm timers for all events which require them.
0696 */
0697 void ResourcesCalendar::setKernelWakeSuspend()
0698 {
0699     for (auto itr = mWakeSuspendTimers.begin(), endr = mWakeSuspendTimers.end();  itr != endr;  ++itr)
0700     {
0701         const ResourceId resourceId = itr.key();
0702         Resource resource = Resources::resource(resourceId);
0703         auto& resourceHash = itr.value();
0704         for (auto it = resourceHash.begin(), end = resourceHash.end();  it != end;  ++it)
0705         {
0706             const KAEvent event = resource.event(it.key());
0707             checkKernelWakeSuspend(resourceId, event);
0708         }
0709     }
0710 }
0711 
0712 /******************************************************************************
0713 * Set or clear any kernel wake alarm associated with an event.
0714 */
0715 void ResourcesCalendar::checkKernelWakeSuspend(ResourceId key, const KAEvent& event)
0716 {
0717     if (KernelWakeAlarm::isAvailable()  &&  event.enabled()  &&  event.wakeFromSuspend())
0718     {
0719         const KADateTime dt = event.nextDateTime(KAEvent::NextWorkHoliday).kDateTime();
0720         if (!dt.isDateOnly())   // can't determine a wakeup time for date-only events
0721         {
0722             KernelWakeAlarm& kernelAlarm = mWakeSuspendTimers[key][event.id()];
0723             if (theApp()->alarmsEnabled())
0724                 kernelAlarm.arm(dt.addSecs(static_cast<int>(Preferences::wakeFromSuspendAdvance()) * -60));
0725             else
0726                 kernelAlarm.disarm();
0727         }
0728     }
0729     else
0730     {
0731         if (mWakeSuspendTimers.contains(key))
0732             mWakeSuspendTimers[key].remove(event.id());   // this cancels the timer
0733     }
0734 }
0735 
0736 /******************************************************************************
0737 * Find and note the active alarm with the earliest trigger time for a calendar,
0738 * and the non-display active alarm with the earliest trigger time.
0739 */
0740 void ResourcesCalendar::findEarliestAlarm(const Resource& resource)
0741 {
0742     ResourceId key = resource.id();
0743     if (key < 0)
0744         return;
0745     if (!(resource.alarmTypes() & CalEvent::ACTIVE))
0746         return;
0747 
0748     // Invalidate any existing earliest alarms for the resource
0749     EarliestMap::Iterator eit = mEarliestAlarm.find(key);
0750     if (eit != mEarliestAlarm.end())
0751         eit.value() = QString();
0752     eit = mEarliestNonDispAlarm.find(key);
0753     if (eit != mEarliestNonDispAlarm.end())
0754         eit.value() = QString();
0755 
0756     ResourceMap::ConstIterator rit = mResourceMap.constFind(key);
0757     if (rit == mResourceMap.constEnd())
0758         return;
0759     const QList<KAEvent> events = eventsForResource(resource, rit.value());
0760     KAEvent earliest, earliestNonDisp;
0761     KADateTime earliestTime, earliestNonDispTime;
0762     for (const KAEvent& event : events)
0763     {
0764         if (event.category() != CalEvent::ACTIVE
0765         ||  mPendingAlarms.contains(event.id()))
0766             continue;
0767         const KADateTime dt = event.nextTrigger(KAEvent::Trigger::All).effectiveKDateTime();
0768         if (dt.isValid())
0769         {
0770             if (!earliest.isValid() || dt < earliestTime)
0771             {
0772                 earliestTime = dt;
0773                 earliest = event;
0774             }
0775             if (!(event.actionTypes() & KAEvent::Action::Display))
0776             {
0777                 if (!earliestNonDisp.isValid() || dt < earliestNonDispTime)
0778                 {
0779                     earliestNonDispTime = dt;
0780                     earliestNonDisp = event;
0781                 }
0782             }
0783         }
0784     }
0785     mEarliestAlarm[key]        = earliest.id();
0786     mEarliestNonDispAlarm[key] = earliestNonDisp.id();
0787     Q_EMIT earliestAlarmChanged();
0788 }
0789 
0790 /******************************************************************************
0791 * Return the active alarm with the earliest trigger time.
0792 * Reply = invalid if none.
0793 */
0794 KAEvent ResourcesCalendar::earliestAlarm(KADateTime& nextTriggerTime, bool excludeDisplayAlarms)
0795 {
0796     KAEvent earliest;
0797     KADateTime earliestTime;
0798     const EarliestMap& earliestAlarms(excludeDisplayAlarms ? mEarliestNonDispAlarm : mEarliestAlarm);
0799     for (EarliestMap::ConstIterator eit = earliestAlarms.constBegin();  eit != earliestAlarms.constEnd();  ++eit)
0800     {
0801         const QString id = eit.value();
0802         if (id.isEmpty())
0803             continue;
0804         Resource res = Resources::resource(eit.key());
0805         const KAEvent event = res.event(id);
0806         if (!event.isValid())
0807         {
0808             // Something went wrong: mEarliestAlarm wasn't updated when it should have been!!
0809             qCCritical(KALARM_LOG) << "ResourcesCalendar::earliestAlarm: resource" << eit.key() << "does not contain" << id;
0810             mInstance->findEarliestAlarm(res);
0811             return earliestAlarm(nextTriggerTime, excludeDisplayAlarms);
0812         }
0813 //TODO: use next trigger calculated in findEarliestAlarm() (allowing for it being out of date)?
0814         const KADateTime dt = event.nextTrigger(KAEvent::Trigger::All).effectiveKDateTime();
0815         if (dt.isValid()  &&  (!earliest.isValid() || dt < earliestTime))
0816         {
0817             earliestTime = dt;
0818             earliest = event;
0819         }
0820     }
0821     nextTriggerTime = earliestTime;
0822     return earliest;
0823 }
0824 
0825 /******************************************************************************
0826 * Note that an alarm which has triggered is now being processed. While pending,
0827 * it will be ignored for the purposes of finding the earliest trigger time.
0828 */
0829 void ResourcesCalendar::setAlarmPending(const KAEvent& event, bool pending)
0830 {
0831     const QString id = event.id();
0832     bool wasPending = mPendingAlarms.contains(id);
0833     qCDebug(KALARM_LOG) << "ResourcesCalendar::setAlarmPending:" << id << "," << pending << "(was" << wasPending << ")";
0834     if (pending)
0835     {
0836         if (wasPending)
0837             return;
0838         mPendingAlarms += id;
0839     }
0840     else
0841     {
0842         if (!wasPending)
0843             return;
0844         mPendingAlarms.remove(id);
0845     }
0846     // Now update the earliest alarm to trigger for its calendar
0847     mInstance->findEarliestAlarm(Resources::resourceForEvent(event.id()));
0848 }
0849 
0850 /******************************************************************************
0851 * Get the events for a list of event IDs.
0852 */
0853 QList<KAEvent> ResourcesCalendar::eventsForResource(const Resource& resource, const QSet<QString>& eventIds)
0854 {
0855     QList<KAEvent> events;
0856     events.reserve(eventIds.count());
0857     for (const QString& eventId : eventIds)
0858         events += resource.event(eventId);
0859     return events;
0860 }
0861 
0862 #include "moc_resourcescalendar.cpp"
0863 
0864 // vim: et sw=4: