SVN_SILENT made messages (after extraction)
[kdepim.git] / kalarm / alarmcalendar.cpp
blob5a2ecea3440d523357a90048cc59ed8766685d78
1 /*
2 * alarmcalendar.cpp - KAlarm calendar file access
3 * Program: kalarm
4 * Copyright © 2001-2014 by David Jarvie <djarvie@kde.org>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "kalarm.h"
22 #include "alarmcalendar.h"
24 #include "collectionmodel.h"
25 #include "filedialog.h"
26 #include "functions.h"
27 #include "kalarmapp.h"
28 #include "mainwindow.h"
29 #include "messagebox.h"
30 #include "preferences.h"
32 #include <KCalCore/MemoryCalendar>
33 #include <KCalCore/ICalFormat>
35 #include <KTimeZone>
36 #include <KLocalizedString>
37 #include <KIO/StoredTransferJob>
38 #include <KJobWidgets>
39 #include <kfileitem.h>
40 #include <KSharedConfig>
41 #include <QTemporaryFile>
42 #include <QStandardPaths>
43 #include "kalarm_debug.h"
45 using namespace Akonadi;
46 using namespace KCalCore;
47 using namespace KAlarmCal;
49 static KACalendar::Compat fix(const KCalCore::FileStorage::Ptr&);
51 static const QString displayCalendarName = QStringLiteral("displaying.ics");
52 static const Collection::Id DISPLAY_COL_ID = -1; // collection ID used for displaying calendar
54 AlarmCalendar* AlarmCalendar::mResourcesCalendar = Q_NULLPTR;
55 AlarmCalendar* AlarmCalendar::mDisplayCalendar = Q_NULLPTR;
58 /******************************************************************************
59 * Initialise the alarm calendars, and ensure that their file names are different.
60 * There are 2 calendars:
61 * 1) A resources calendar containing the active alarms, archived alarms and
62 * alarm templates;
63 * 2) A user-specific one which contains details of alarms which are currently
64 * being displayed to that user and which have not yet been acknowledged;
65 * Reply = true if success, false if calendar name error.
67 bool AlarmCalendar::initialiseCalendars()
69 QDir dir;
70 dir.mkpath(QStandardPaths::writableLocation(QStandardPaths::DataLocation));
71 QString displayCal = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') + displayCalendarName;
72 AkonadiModel::instance();
73 CollectionControlModel::setAskDestinationPolicy(Preferences::askResource());
74 Preferences::setBackend(Preferences::Akonadi);
75 Preferences::self()->save();
76 mResourcesCalendar = new AlarmCalendar();
77 mDisplayCalendar = new AlarmCalendar(displayCal, CalEvent::DISPLAYING);
78 KACalendar::setProductId(KALARM_NAME, KALARM_VERSION);
79 CalFormat::setApplication(QStringLiteral(KALARM_NAME), QString::fromLatin1(KACalendar::icalProductId()));
80 return true;
83 /******************************************************************************
84 * Terminate access to all calendars.
86 void AlarmCalendar::terminateCalendars()
88 delete mResourcesCalendar;
89 mResourcesCalendar = Q_NULLPTR;
90 delete mDisplayCalendar;
91 mDisplayCalendar = Q_NULLPTR;
94 /******************************************************************************
95 * Return the display calendar, opening it first if necessary.
97 AlarmCalendar* AlarmCalendar::displayCalendarOpen()
99 if (mDisplayCalendar->open())
100 return mDisplayCalendar;
101 qCCritical(KALARM_LOG) << "Open error";
102 return Q_NULLPTR;
105 /******************************************************************************
106 * Find and return the event with the specified ID.
107 * The calendar searched is determined by the calendar identifier in the ID.
109 KAEvent* AlarmCalendar::getEvent(const EventId& eventId)
111 if (eventId.eventId().isEmpty())
112 return Q_NULLPTR;
113 return mResourcesCalendar->event(eventId);
116 /******************************************************************************
117 * Constructor for the resources calendar.
119 AlarmCalendar::AlarmCalendar()
121 mCalType(RESOURCES),
122 mEventType(CalEvent::EMPTY),
123 mOpen(false),
124 mUpdateCount(0),
125 mUpdateSave(false),
126 mHaveDisabledAlarms(false)
128 AkonadiModel* model = AkonadiModel::instance();
129 connect(model, &AkonadiModel::eventsAdded, this, &AlarmCalendar::slotEventsAdded);
130 connect(model, &AkonadiModel::eventsToBeRemoved, this, &AlarmCalendar::slotEventsToBeRemoved);
131 connect(model, &AkonadiModel::eventChanged, this, &AlarmCalendar::slotEventChanged);
132 connect(model, &AkonadiModel::collectionStatusChanged, this, &AlarmCalendar::slotCollectionStatusChanged);
133 Preferences::connect(SIGNAL(askResourceChanged(bool)), this, SLOT(setAskResource(bool)));
136 /******************************************************************************
137 * Constructor for a calendar file.
139 AlarmCalendar::AlarmCalendar(const QString& path, CalEvent::Type type)
141 mEventType(type),
142 mOpen(false),
143 mUpdateCount(0),
144 mUpdateSave(false),
145 mHaveDisabledAlarms(false)
147 switch (type)
149 case CalEvent::ACTIVE:
150 case CalEvent::ARCHIVED:
151 case CalEvent::TEMPLATE:
152 case CalEvent::DISPLAYING:
153 break;
154 default:
155 Q_ASSERT(false); // invalid event type for a calendar
156 break;
158 mUrl = QUrl::fromUserInput(path, QString(), QUrl::AssumeLocalFile);
159 QString icalPath = path;
160 icalPath.replace(QStringLiteral("\\.vcs$"), QStringLiteral(".ics"));
161 mICalUrl = QUrl::fromUserInput(icalPath, QString(), QUrl::AssumeLocalFile);
162 mCalType = (path == icalPath) ? LOCAL_ICAL : LOCAL_VCAL; // is the calendar in ICal or VCal format?
165 AlarmCalendar::~AlarmCalendar()
167 close();
170 /******************************************************************************
171 * Check whether the calendar is open.
173 bool AlarmCalendar::isOpen()
175 return mOpen;
178 /******************************************************************************
179 * Open the calendar if not already open, and load it into memory.
181 bool AlarmCalendar::open()
183 if (isOpen())
184 return true;
185 if (mCalType == RESOURCES)
187 mOpen = true;
189 else
191 if (!mUrl.isValid())
192 return false;
194 qCDebug(KALARM_LOG) << mUrl.toDisplayString();
195 if (!mCalendarStorage)
197 MemoryCalendar::Ptr calendar(new MemoryCalendar(Preferences::timeZone(true)));
198 mCalendarStorage = FileStorage::Ptr(new FileStorage(calendar));
201 // Check for file's existence, assuming that it does exist when uncertain,
202 // to avoid overwriting it.
203 auto statJob = KIO::stat(mUrl.url(), KIO::StatJob::SourceSide, 2);
204 KJobWidgets::setWindow(statJob, MainWindow::mainMainWindow());
205 if (!statJob->exec() || load() == 0)
207 // The calendar file doesn't yet exist, or it's zero length, so create a new one
208 bool created = false;
209 if (mICalUrl.isLocalFile())
210 created = saveCal(mICalUrl.toLocalFile());
211 else
213 QTemporaryFile tmpFile;
214 tmpFile.setAutoRemove(false);
215 tmpFile.open();
216 created = saveCal(tmpFile.fileName());
218 if (created)
219 load();
222 if (!mOpen)
224 mCalendarStorage->calendar().clear();
225 mCalendarStorage.clear();
227 return isOpen();
230 /******************************************************************************
231 * Load the calendar into memory.
232 * Reply = 1 if success
233 * = 0 if zero-length file exists.
234 * = -1 if failure to load calendar file
235 * = -2 if instance uninitialised.
237 int AlarmCalendar::load()
239 if (mCalType == RESOURCES)
242 else
244 if (!mCalendarStorage)
245 return -2;
247 QString filename;
248 qCDebug(KALARM_LOG) << mUrl.toDisplayString();
249 if (!mUrl.isLocalFile()) {
250 auto getJob = KIO::storedGet(mUrl.url());
251 KJobWidgets::setWindow(getJob, MainWindow::mainMainWindow());
252 if (!getJob->exec())
254 qCCritical(KALARM_LOG) << "Download failure";
255 KAMessageBox::error(MainWindow::mainMainWindow(),
256 xi18nc("@info", "Cannot download calendar: <filename>%1</filename>", mUrl.toDisplayString()));
257 return -1;
259 QTemporaryFile tmpFile;
260 tmpFile.setAutoRemove(false);
261 tmpFile.write(getJob->data());
262 qCDebug(KALARM_LOG) << "--- Downloaded to" << tmpFile.fileName();
263 filename = tmpFile.fileName();
264 } else {
265 filename = mUrl.toLocalFile();
267 mCalendarStorage->calendar()->setTimeSpec(Preferences::timeZone(true));
268 mCalendarStorage->setFileName(filename);
269 if (!mCalendarStorage->load())
271 // Check if the file is zero length
272 if (mUrl.isLocalFile()) {
273 auto statJob = KIO::stat(KIO::upUrl(mUrl));
274 KJobWidgets::setWindow(statJob, MainWindow::mainMainWindow());
275 statJob->exec();
276 KFileItem fi(statJob->statResult(), mUrl);
277 if (!fi.size())
278 return 0; // file is zero length
281 qCCritical(KALARM_LOG) << "Error loading calendar file '" << filename <<"'";
282 KAMessageBox::error(MainWindow::mainMainWindow(),
283 xi18nc("@info", "<para>Error loading calendar:</para><para><filename>%1</filename></para><para>Please fix or delete the file.</para>", mUrl.toDisplayString()));
284 // load() could have partially populated the calendar, so clear it out
285 mCalendarStorage->calendar()->close();
286 mCalendarStorage->calendar().clear();
287 mCalendarStorage.clear();
288 mOpen = false;
289 return -1;
291 if (!mLocalFile.isEmpty())
292 if (mLocalFile.startsWith(QDir::tempPath())) {
293 QFile::remove(mLocalFile);
295 mLocalFile = filename;
296 fix(mCalendarStorage); // convert events to current KAlarm format for when calendar is saved
297 updateDisplayKAEvents();
299 mOpen = true;
300 return 1;
303 /******************************************************************************
304 * Reload the calendar file into memory.
306 bool AlarmCalendar::reload()
308 if (mCalType == RESOURCES)
309 return true;
310 if (!mCalendarStorage)
311 return false;
313 qCDebug(KALARM_LOG) << mUrl.toDisplayString();
314 close();
315 return open();
319 /******************************************************************************
320 * Save the calendar from memory to file.
321 * If a filename is specified, create a new calendar file.
323 bool AlarmCalendar::saveCal(const QString& newFile)
325 if (mCalType == RESOURCES)
326 return true;
327 if (!mCalendarStorage)
328 return false;
330 if (!mOpen && newFile.isNull())
331 return false;
333 qCDebug(KALARM_LOG) << "\"" << newFile << "\"," << mEventType;
334 QString saveFilename = newFile.isNull() ? mLocalFile : newFile;
335 if (mCalType == LOCAL_VCAL && newFile.isNull() && mUrl.isLocalFile())
336 saveFilename = mICalUrl.toLocalFile();
337 mCalendarStorage->setFileName(saveFilename);
338 mCalendarStorage->setSaveFormat(new ICalFormat);
339 if (!mCalendarStorage->save())
341 qCCritical(KALARM_LOG) << "Saving" << saveFilename << "failed.";
342 KAMessageBox::error(MainWindow::mainMainWindow(),
343 xi18nc("@info", "Failed to save calendar to <filename>%1</filename>", mICalUrl.toDisplayString()));
344 return false;
347 if (!mICalUrl.isLocalFile())
349 QFile file(saveFilename);
350 file.open(QIODevice::ReadOnly);
351 auto putJob = KIO::storedPut(&file, mICalUrl.url(), -1);
352 KJobWidgets::setWindow(putJob, MainWindow::mainMainWindow());
353 if (!putJob->exec())
355 qCCritical(KALARM_LOG) << saveFilename << "upload failed.";
356 KAMessageBox::error(MainWindow::mainMainWindow(),
357 xi18nc("@info", "Cannot upload calendar to <filename>%1</filename>", mICalUrl.toDisplayString()));
358 return false;
362 if (mCalType == LOCAL_VCAL)
364 // The file was in vCalendar format, but has now been saved in iCalendar format.
365 mUrl = mICalUrl;
366 mCalType = LOCAL_ICAL;
368 Q_EMIT calendarSaved(this);
371 mUpdateSave = false;
372 return true;
375 /******************************************************************************
376 * Delete any temporary file at program exit.
378 void AlarmCalendar::close()
380 if (mCalType != RESOURCES)
382 if (!mLocalFile.isEmpty())
384 if (mLocalFile.startsWith(QDir::tempPath())) { // removes it only if it IS a temporary file
385 QFile::remove(mLocalFile);
387 mLocalFile = QStringLiteral("");
390 // Flag as closed now to prevent removeKAEvents() doing silly things
391 // when it's called again
392 mOpen = false;
393 if (mCalendarStorage)
395 mCalendarStorage->calendar()->close();
396 mCalendarStorage->calendar().clear();
397 mCalendarStorage.clear();
399 // Resource map should be empty, but just in case...
400 while (!mResourceMap.isEmpty())
401 removeKAEvents(mResourceMap.begin().key(), true, CalEvent::ACTIVE | CalEvent::ARCHIVED | CalEvent::TEMPLATE | CalEvent::DISPLAYING);
405 /******************************************************************************
406 * Update whether to prompt for the resource to store new alarms in.
408 void AlarmCalendar::setAskResource(bool ask)
410 CollectionControlModel::setAskDestinationPolicy(ask);
414 void AlarmCalendar::updateDisplayKAEvents()
416 if (mCalType == RESOURCES)
417 return;
418 qCDebug(KALARM_LOG);
419 const Collection::Id key = DISPLAY_COL_ID;
420 KAEvent::List& events = mResourceMap[key];
421 int i, end;
422 for (i = 0, end = events.count(); i < end; ++i)
424 KAEvent* event = events[i];
425 mEventMap.remove(EventId(key, event->id()));
426 delete event;
428 events.clear();
429 mEarliestAlarm[key] = Q_NULLPTR;
430 Calendar::Ptr cal = mCalendarStorage->calendar();
431 if (!cal)
432 return;
434 Event::List kcalevents = cal->rawEvents();
435 for (i = 0, end = kcalevents.count(); i < end; ++i)
437 Event::Ptr kcalevent = kcalevents[i];
438 if (kcalevent->alarms().isEmpty())
439 continue; // ignore events without alarms
441 KAEvent* event = new KAEvent(kcalevent);
442 if (!event->isValid())
444 qCWarning(KALARM_LOG) << "Ignoring unusable event" << kcalevent->uid();
445 delete event;
446 continue; // ignore events without usable alarms
448 event->setCollectionId(key);
449 events += event;
450 mEventMap[EventId(key, kcalevent->uid())] = event;
455 /******************************************************************************
456 * Delete a calendar and all its KAEvent instances of specified alarm types from
457 * the lists.
458 * Called after the calendar is deleted or alarm types have been disabled, or
459 * the AlarmCalendar is closed.
461 void AlarmCalendar::removeKAEvents(Collection::Id key, bool closing, CalEvent::Types types)
463 bool removed = false;
464 ResourceMap::Iterator rit = mResourceMap.find(key);
465 if (rit != mResourceMap.end())
467 bool empty = true;
468 KAEvent::List& events = rit.value();
469 for (int i = 0, end = events.count(); i < end; ++i)
471 KAEvent* event = events[i];
472 bool remove = (event->collectionId() != key);
473 if (remove)
475 if (key != DISPLAY_COL_ID)
476 qCCritical(KALARM_LOG) << "Event" << event->id() << ", collection" << event->collectionId() << "Indexed under collection" << key;
478 else
479 remove = event->category() & types;
480 if (remove)
482 mEventMap.remove(EventId(key, event->id()));
483 delete event;
484 removed = true;
486 else
487 empty = false;
489 if (empty)
490 mResourceMap.erase(rit);
492 if (removed)
494 mEarliestAlarm.remove(key);
495 // Emit signal only if we're not in the process of closing the calendar
496 if (!closing && mOpen)
498 Q_EMIT earliestAlarmChanged();
499 if (mHaveDisabledAlarms)
500 checkForDisabledAlarms();
505 /******************************************************************************
506 * Called when the enabled or read-only status of a collection has changed.
507 * If the collection is now disabled, remove its events from the calendar.
509 void AlarmCalendar::slotCollectionStatusChanged(const Collection& collection, AkonadiModel::Change change, const QVariant& value, bool inserted)
511 if (!inserted && change == AkonadiModel::Enabled)
513 // For each alarm type which has been disabled, remove the collection's
514 // events from the map, but not from AkonadiModel.
515 CalEvent::Types enabled = static_cast<CalEvent::Types>(value.toInt());
516 CalEvent::Types disabled = ~enabled & (CalEvent::ACTIVE | CalEvent::ARCHIVED | CalEvent::TEMPLATE);
517 removeKAEvents(collection.id(), false, disabled);
521 /******************************************************************************
522 * Called when events have been added to AkonadiModel.
523 * Add corresponding KAEvent instances to those held by AlarmCalendar.
525 void AlarmCalendar::slotEventsAdded(const AkonadiModel::EventList& events)
527 for (int i = 0, count = events.count(); i < count; ++i)
528 slotEventChanged(events[i]);
531 /******************************************************************************
532 * Called when an event has been changed in AkonadiModel.
533 * Change the corresponding KAEvent instance held by AlarmCalendar.
535 void AlarmCalendar::slotEventChanged(const AkonadiModel::Event& event)
537 if (!event.isConsistent())
539 qCCritical(KALARM_LOG) << "Inconsistent AkonadiModel::Event: event:" << event.event.collectionId() << ", collection" << event.collection.id();
540 return;
543 bool added = true;
544 bool updated = false;
545 KAEventMap::Iterator it = mEventMap.find(event.eventId());
546 if (it != mEventMap.end())
548 // The event ID already exists - remove the existing event first
549 KAEvent* storedEvent = it.value();
550 if (event.event.category() == storedEvent->category())
552 // The existing event is the same type - update it in place
553 *storedEvent = event.event;
554 addNewEvent(event.collection, storedEvent, true);
555 updated = true;
557 else
558 delete storedEvent;
559 added = false;
561 if (!updated)
562 addNewEvent(event.collection, new KAEvent(event.event));
564 bool enabled = event.event.enabled();
565 checkForDisabledAlarms(!enabled, enabled);
566 if (added && enabled && event.event.category() == CalEvent::ACTIVE
567 && event.event.repeatAtLogin())
568 Q_EMIT atLoginEventAdded(event.event);
571 /******************************************************************************
572 * Called when events are about to be removed from AkonadiModel.
573 * Remove the corresponding KAEvent instances held by AlarmCalendar.
575 void AlarmCalendar::slotEventsToBeRemoved(const AkonadiModel::EventList& events)
577 for (int i = 0, count = events.count(); i < count; ++i)
579 if (!events[i].isConsistent())
580 qCCritical(KALARM_LOG) << "Inconsistent AkonadiModel::Event: event:" << events[i].event.collectionId() << ", collection" << events[i].collection.id();
581 else if (mEventMap.contains(events[i].eventId()))
582 deleteEventInternal(events[i].event, events[i].collection, false);
586 /******************************************************************************
587 * Import alarms from an external calendar and merge them into KAlarm's calendar.
588 * The alarms are given new unique event IDs.
589 * Parameters: parent = parent widget for error message boxes
590 * Reply = true if all alarms in the calendar were successfully imported
591 * = false if any alarms failed to be imported.
593 bool AlarmCalendar::importAlarms(QWidget* parent, Collection* collection)
595 qCDebug(KALARM_LOG);
596 QUrl url = KFileDialog::getOpenUrl(QUrl(QStringLiteral("filedialog:///importalarms")),
597 QStringLiteral("*.vcs *.ics|%1").arg(i18nc("@info", "Calendar Files")), parent);
598 if (url.isEmpty())
600 qCCritical(KALARM_LOG) << "Empty URL";
601 return false;
603 if (!url.isValid())
605 qCDebug(KALARM_LOG) << "Invalid URL";
606 return false;
608 qCDebug(KALARM_LOG) << url.toDisplayString();
610 bool success = true;
611 QString filename;
612 bool local = url.isLocalFile();
613 if (local)
615 filename = url.toLocalFile();
616 if (!QFile::exists(filename))
618 qCDebug(KALARM_LOG) << "File '" << url.toDisplayString() <<"' not found";
619 KAMessageBox::error(parent, xi18nc("@info", "Could not load calendar <filename>%1</filename>.", url.toDisplayString()));
620 return false;
623 else
625 auto getJob = KIO::storedGet(url.url());
626 KJobWidgets::setWindow(getJob, MainWindow::mainMainWindow());
627 if (!getJob->exec())
629 qCCritical(KALARM_LOG) << "Download failure";
630 KAMessageBox::error(parent, xi18nc("@info", "Cannot download calendar: <filename>%1</filename>", url.toDisplayString()));
631 return false;
633 QTemporaryFile tmpFile;
634 tmpFile.setAutoRemove(false);
635 tmpFile.write(getJob->data());
636 tmpFile.seek(0);
637 filename = tmpFile.fileName();
638 qCDebug(KALARM_LOG) << "--- Downloaded to" << filename;
641 // Read the calendar and add its alarms to the current calendars
642 MemoryCalendar::Ptr cal(new MemoryCalendar(Preferences::timeZone(true)));
643 FileStorage::Ptr calStorage(new FileStorage(cal, filename));
644 success = calStorage->load();
645 if (!success)
647 qCDebug(KALARM_LOG) << "Error loading calendar '" << filename <<"'";
648 KAMessageBox::error(parent, xi18nc("@info", "Could not load calendar <filename>%1</filename>.", url.toDisplayString()));
650 else
652 KACalendar::Compat caltype = fix(calStorage);
653 CalEvent::Types wantedTypes = collection && collection->isValid() ? CalEvent::types(collection->contentMimeTypes()) : CalEvent::EMPTY;
654 Collection activeColl, archiveColl, templateColl;
655 Event::List events = cal->rawEvents();
656 for (int i = 0, end = events.count(); i < end; ++i)
658 Event::Ptr event = events[i];
659 if (event->alarms().isEmpty() || !KAEvent(event).isValid())
660 continue; // ignore events without alarms, or usable alarms
661 CalEvent::Type type = CalEvent::status(event);
662 if (type == CalEvent::TEMPLATE)
664 // If we know the event was not created by KAlarm, don't treat it as a template
665 if (caltype == KACalendar::Incompatible)
666 type = CalEvent::ACTIVE;
668 Collection* coll;
669 if (collection && collection->isValid())
671 if (!(type & wantedTypes))
672 continue;
673 coll = collection;
675 else
677 switch (type)
679 case CalEvent::ACTIVE: coll = &activeColl; break;
680 case CalEvent::ARCHIVED: coll = &archiveColl; break;
681 case CalEvent::TEMPLATE: coll = &templateColl; break;
682 default: continue;
684 if (!coll->isValid())
685 *coll = CollectionControlModel::destination(type);
688 Event::Ptr newev(new Event(*event));
690 // If there is a display alarm without display text, use the event
691 // summary text instead.
692 if (type == CalEvent::ACTIVE && !newev->summary().isEmpty())
694 const Alarm::List& alarms = newev->alarms();
695 for (int ai = 0, aend = alarms.count(); ai < aend; ++ai)
697 Alarm::Ptr alarm = alarms[ai];
698 if (alarm->type() == Alarm::Display && alarm->text().isEmpty())
699 alarm->setText(newev->summary());
701 newev->setSummary(QString()); // KAlarm only uses summary for template names
704 // Give the event a new ID and add it to the calendars
705 newev->setUid(CalEvent::uid(CalFormat::createUniqueId(), type));
706 KAEvent* newEvent = new KAEvent(newev);
707 if (!AkonadiModel::instance()->addEvent(*newEvent, *coll))
708 success = false;
712 if (!local)
713 QFile::remove(filename);
714 return success;
717 /******************************************************************************
718 * Export all selected alarms to an external calendar.
719 * The alarms are given new unique event IDs.
720 * Parameters: parent = parent widget for error message boxes
721 * Reply = true if all alarms in the calendar were successfully exported
722 * = false if any alarms failed to be exported.
724 bool AlarmCalendar::exportAlarms(const KAEvent::List& events, QWidget* parent)
726 bool append;
727 QString file = FileDialog::getSaveFileName(QUrl(QStringLiteral("kfiledialog:///exportalarms")),
728 QStringLiteral("*.ics|%1").arg(i18nc("@info", "Calendar Files")),
729 parent, i18nc("@title:window", "Choose Export Calendar"),
730 &append);
731 if (file.isEmpty())
732 return false;
733 QUrl url;
734 url.setPath(file);
735 if (!url.isValid())
737 qCDebug(KALARM_LOG) << "Invalid URL";
738 return false;
740 qCDebug(KALARM_LOG) << url.toDisplayString();
742 MemoryCalendar::Ptr calendar(new MemoryCalendar(Preferences::timeZone(true)));
743 FileStorage::Ptr calStorage(new FileStorage(calendar, file));
744 if (append && !calStorage->load())
746 KIO::UDSEntry uds;
747 auto statJob = KIO::stat(url.url(), KIO::StatJob::SourceSide, 2);
748 KJobWidgets::setWindow(statJob, parent);
749 statJob->exec();
750 KFileItem fi(statJob->statResult(), url);
751 if (fi.size())
753 qCCritical(KALARM_LOG) << "Error loading calendar file" << file << "for append";
754 KAMessageBox::error(MainWindow::mainMainWindow(),
755 xi18nc("@info", "Error loading calendar to append to:<nl/><filename>%1</filename>", url.toDisplayString()));
756 return false;
759 KACalendar::setKAlarmVersion(calendar);
761 // Add the alarms to the calendar
762 bool success = true;
763 bool exported = false;
764 for (int i = 0, end = events.count(); i < end; ++i)
766 const KAEvent* event = events[i];
767 Event::Ptr kcalEvent(new Event);
768 CalEvent::Type type = event->category();
769 QString id = CalEvent::uid(kcalEvent->uid(), type);
770 kcalEvent->setUid(id);
771 event->updateKCalEvent(kcalEvent, KAEvent::UID_IGNORE);
772 if (calendar->addEvent(kcalEvent))
773 exported = true;
774 else
775 success = false;
778 if (exported)
780 // One or more alarms have been exported to the calendar.
781 // Save the calendar to file.
782 QTemporaryFile* tempFile = Q_NULLPTR;
783 bool local = url.isLocalFile();
784 if (!local)
786 tempFile = new QTemporaryFile;
787 file = tempFile->fileName();
789 calStorage->setFileName(file);
790 calStorage->setSaveFormat(new ICalFormat);
791 if (!calStorage->save())
793 qCCritical(KALARM_LOG) << file << ": failed";
794 KAMessageBox::error(MainWindow::mainMainWindow(),
795 xi18nc("@info", "Failed to save new calendar to:<nl/><filename>%1</filename>", url.toDisplayString()));
796 success = false;
798 else if (!local)
800 QFile qFile(file);
801 qFile.open(QIODevice::ReadOnly);
802 auto uploadJob = KIO::storedPut(&qFile, url.url(), -1);
803 KJobWidgets::setWindow(uploadJob, parent);
804 if (!uploadJob->exec())
806 qCCritical(KALARM_LOG) << file << ": upload failed";
807 KAMessageBox::error(MainWindow::mainMainWindow(),
808 xi18nc("@info", "Cannot upload new calendar to:<nl/><filename>%1</filename>", url.toDisplayString()));
809 success = false;
812 delete tempFile;
814 calendar->close();
815 return success;
818 /******************************************************************************
819 * Flag the start of a group of calendar update calls.
820 * The purpose is to avoid multiple calendar saves during a group of operations.
822 void AlarmCalendar::startUpdate()
824 ++mUpdateCount;
827 /******************************************************************************
828 * Flag the end of a group of calendar update calls.
829 * The calendar is saved if appropriate.
831 bool AlarmCalendar::endUpdate()
833 if (mUpdateCount > 0)
834 --mUpdateCount;
835 if (!mUpdateCount)
837 if (mUpdateSave)
838 return saveCal();
840 return true;
843 /******************************************************************************
844 * Save the calendar, or flag it for saving if in a group of calendar update calls.
845 * Note that this method has no effect for Akonadi calendars.
847 bool AlarmCalendar::save()
849 if (mUpdateCount)
851 mUpdateSave = true;
852 return true;
854 else
855 return saveCal();
858 /******************************************************************************
859 * This method must only be called from the main KAlarm queue processing loop,
860 * to prevent asynchronous calendar operations interfering with one another.
862 * Purge a list of archived events from the calendar.
864 void AlarmCalendar::purgeEvents(const KAEvent::List& events)
866 for (int i = 0, end = events.count(); i < end; ++i)
868 deleteEventInternal(*events[i]);
870 if (mHaveDisabledAlarms)
871 checkForDisabledAlarms();
872 saveCal();
875 /******************************************************************************
876 * Add the specified event to the calendar.
877 * If it is an active event and 'useEventID' is false, a new event ID is
878 * created. In all other cases, the event ID is taken from 'event' (if non-null).
879 * 'event' is updated with the actual event ID.
880 * The event is added to 'resource' if specified; otherwise the default resource
881 * is used or the user is prompted, depending on policy. If 'noPrompt' is true,
882 * the user will not be prompted so that if no default resource is defined, the
883 * function will fail.
884 * Reply = true if 'event' was written to the calendar, in which case (not
885 * Akonadi) ownership of 'event' is taken by the calendar. 'event'
886 * is updated.
887 * = false if an error occurred, in which case 'event' is unchanged.
889 bool AlarmCalendar::addEvent(KAEvent& evnt, QWidget* promptParent, bool useEventID, Collection* collection, bool noPrompt, bool* cancelled)
891 if (cancelled)
892 *cancelled = false;
893 if (!mOpen)
894 return false;
895 // Check that the event type is valid for the calendar
896 qCDebug(KALARM_LOG) << evnt.id();
897 CalEvent::Type type = evnt.category();
898 if (type != mEventType)
900 switch (type)
902 case CalEvent::ACTIVE:
903 case CalEvent::ARCHIVED:
904 case CalEvent::TEMPLATE:
905 if (mEventType == CalEvent::EMPTY)
906 break;
907 // fall through to default
908 default:
909 return false;
913 Collection::Id key = (collection && collection->isValid()) ? collection->id() : -1;
914 Event::Ptr kcalEvent((mCalType == RESOURCES) ? (Event*)Q_NULLPTR : new Event);
915 KAEvent* event = new KAEvent(evnt);
916 QString id = event->id();
917 if (type == CalEvent::ACTIVE)
919 if (id.isEmpty())
920 useEventID = false;
921 else if (!useEventID)
922 id.clear();
924 else
925 useEventID = true;
926 if (id.isEmpty())
927 id = (mCalType == RESOURCES) ? CalFormat::createUniqueId() : kcalEvent->uid();
928 if (useEventID)
930 id = CalEvent::uid(id, type);
931 if (kcalEvent)
932 kcalEvent->setUid(id);
934 event->setEventId(id);
935 bool ok = false;
936 bool remove = false;
937 if (mCalType == RESOURCES)
939 Collection col;
940 if (collection && CollectionControlModel::isEnabled(*collection, type))
941 col = *collection;
942 else
943 col = CollectionControlModel::destination(type, promptParent, noPrompt, cancelled);
944 if (col.isValid())
946 // Don't add event to mEventMap yet - its Akonadi item id is not yet known.
947 // It will be added once it is inserted into AkonadiModel.
948 ok = AkonadiModel::instance()->addEvent(*event, col);
949 remove = ok; // if success, delete the local event instance on exit
950 if (ok && type == CalEvent::ACTIVE && !event->enabled())
951 checkForDisabledAlarms(true, false);
954 else
956 // It's the display calendar
957 event->updateKCalEvent(kcalEvent, KAEvent::UID_IGNORE);
958 key = DISPLAY_COL_ID;
959 if (!mEventMap.contains(EventId(key, event->id())))
961 addNewEvent(Collection(), event);
962 ok = mCalendarStorage->calendar()->addEvent(kcalEvent);
963 remove = !ok;
966 if (!ok)
968 if (remove)
970 // Adding to mCalendar failed, so undo AlarmCalendar::addEvent()
971 mEventMap.remove(EventId(key, event->id()));
972 KAEvent::List& events = mResourceMap[key];
973 int i = events.indexOf(event);
974 if (i >= 0)
975 events.remove(i);
976 if (mEarliestAlarm[key] == event)
977 findEarliestAlarm(key);
979 delete event;
980 return false;
982 evnt = *event;
983 if (remove)
984 delete event;
985 return true;
989 /******************************************************************************
990 * Internal method to add an already checked event to the calendar.
991 * mEventMap takes ownership of the KAEvent.
992 * If 'replace' is true, an existing event is being updated (NOTE: its category()
993 * must remain the same).
995 void AlarmCalendar::addNewEvent(const Collection& collection, KAEvent* event, bool replace)
997 Collection::Id key = collection.isValid() ? collection.id() : -1;
998 event->setCollectionId(key);
999 if (!replace)
1001 mResourceMap[key] += event;
1002 mEventMap[EventId(key, event->id())] = event;
1004 if (collection.isValid() && (AkonadiModel::types(collection) & CalEvent::ACTIVE)
1005 && event->category() == CalEvent::ACTIVE)
1007 // Update the earliest alarm to trigger
1008 KAEvent* earliest = mEarliestAlarm.value(key, (KAEvent*)Q_NULLPTR);
1009 if (replace && earliest == event)
1010 findEarliestAlarm(key);
1011 else
1013 KDateTime dt = event->nextTrigger(KAEvent::ALL_TRIGGER).effectiveKDateTime();
1014 if (dt.isValid()
1015 && (!earliest || dt < earliest->nextTrigger(KAEvent::ALL_TRIGGER)))
1017 mEarliestAlarm[key] = event;
1018 Q_EMIT earliestAlarmChanged();
1024 /******************************************************************************
1025 * Modify the specified event in the calendar with its new contents.
1026 * The new event must have a different event ID from the old one.
1027 * It is assumed to be of the same event type as the old one (active, etc.)
1028 * Reply = true if 'newEvent' was written to the calendar, in which case (not
1029 * Akonadi) ownership of 'newEvent' is taken by the calendar.
1030 * 'newEvent' is updated.
1031 * = false if an error occurred, in which case 'newEvent' is unchanged.
1033 bool AlarmCalendar::modifyEvent(const EventId& oldEventId, KAEvent& newEvent)
1035 EventId newId(oldEventId.collectionId(), newEvent.id());
1036 qCDebug(KALARM_LOG) << oldEventId << "->" << newId;
1037 bool noNewId = newId.isEmpty();
1038 if (!noNewId && oldEventId == newId)
1040 qCCritical(KALARM_LOG) << "Same IDs";
1041 return false;
1043 if (!mOpen)
1044 return false;
1045 if (mCalType == RESOURCES)
1047 // Set the event's ID and Akonadi ID, and update the old
1048 // event in Akonadi.
1049 KAEvent* storedEvent = event(oldEventId);
1050 if (!storedEvent)
1052 qCCritical(KALARM_LOG) << "Old event not found";
1053 return false;
1055 if (noNewId)
1056 newEvent.setEventId(CalFormat::createUniqueId());
1057 Collection c = AkonadiModel::instance()->collectionById(oldEventId.collectionId());
1058 if (!c.isValid())
1059 return false;
1060 // Don't add new event to mEventMap yet - its Akonadi item id is not yet known
1061 if (!AkonadiModel::instance()->addEvent(newEvent, c))
1062 return false;
1063 // Note: deleteEventInternal() will delete storedEvent before using the
1064 // event parameter, so need to pass a copy as the parameter.
1065 deleteEventInternal(KAEvent(*storedEvent), c);
1066 if (mHaveDisabledAlarms)
1067 checkForDisabledAlarms();
1069 else
1071 // This functionality isn't needed for the display calendar.
1072 // The calendar would take ownership of newEvent.
1073 return false;
1075 return true;
1078 /******************************************************************************
1079 * Update the specified event in the calendar with its new contents.
1080 * The event retains the same ID. The event must be in the resource calendar.
1081 * Reply = event which has been updated
1082 * = 0 if error.
1084 KAEvent* AlarmCalendar::updateEvent(const KAEvent& evnt)
1086 return updateEvent(&evnt);
1088 KAEvent* AlarmCalendar::updateEvent(const KAEvent* evnt)
1090 if (!mOpen || mCalType != RESOURCES)
1091 return Q_NULLPTR;
1092 KAEvent* kaevnt = event(EventId(*evnt));
1093 if (kaevnt)
1095 KAEvent newEvnt(*evnt);
1096 newEvnt.setItemId(evnt->itemId());
1097 if (AkonadiModel::instance()->updateEvent(newEvnt))
1099 *kaevnt = newEvnt;
1100 return kaevnt;
1103 qCDebug(KALARM_LOG) << "error";
1104 return Q_NULLPTR;
1108 /******************************************************************************
1109 * Delete the specified event from the resource calendar, if it exists.
1110 * The calendar is then optionally saved.
1112 bool AlarmCalendar::deleteEvent(const KAEvent& event, bool saveit)
1114 if (mOpen && mCalType == RESOURCES)
1116 CalEvent::Type status = deleteEventInternal(event);
1117 if (mHaveDisabledAlarms)
1118 checkForDisabledAlarms();
1119 if (status != CalEvent::EMPTY)
1121 if (saveit)
1122 return save();
1123 return true;
1126 return false;
1129 /******************************************************************************
1130 * Delete the specified event from the calendar, if it exists.
1131 * The calendar is then optionally saved.
1133 bool AlarmCalendar::deleteDisplayEvent(const QString& eventID, bool saveit)
1135 if (mOpen && mCalType != RESOURCES)
1137 CalEvent::Type status = deleteEventInternal(eventID);
1138 if (mHaveDisabledAlarms)
1139 checkForDisabledAlarms();
1140 if (status != CalEvent::EMPTY)
1142 if (saveit)
1143 return save();
1144 return true;
1147 return false;
1150 /******************************************************************************
1151 * Internal method to delete the specified event from the calendar and lists.
1152 * Reply = event status, if it was found in the resource calendar/collection or
1153 * local calendar
1154 * = CalEvent::EMPTY otherwise.
1156 CalEvent::Type AlarmCalendar::deleteEventInternal(const KAEvent& event, bool deleteFromAkonadi)
1158 Collection collection = AkonadiModel::instance()->collectionById(event.collectionId());
1159 if (!collection.isValid())
1160 return CalEvent::EMPTY;
1161 return deleteEventInternal(event.id(), event, collection, deleteFromAkonadi);
1164 CalEvent::Type AlarmCalendar::deleteEventInternal(const KAEvent& event, const Collection& collection, bool deleteFromAkonadi)
1166 if (!collection.isValid())
1167 return CalEvent::EMPTY;
1168 if (event.collectionId() != collection.id())
1170 qCCritical(KALARM_LOG) << "Event" << event.id() << ": collection" << event.collectionId() << "differs from 'collection'" << collection.id();
1171 return CalEvent::EMPTY;
1173 return deleteEventInternal(event.id(), event, collection, deleteFromAkonadi);
1176 CalEvent::Type AlarmCalendar::deleteEventInternal(const QString& eventID, const KAEvent& event, const Collection& collection, bool deleteFromAkonadi)
1178 // Make a copy of the KAEvent and the ID QString, since the supplied
1179 // references might be destructed when the event is deleted below.
1180 const QString id = eventID;
1181 const KAEvent paramEvent = event;
1183 Event::Ptr kcalEvent;
1184 if (mCalendarStorage)
1185 kcalEvent = mCalendarStorage->calendar()->event(id);
1186 Collection::Id key = collection.isValid() ? collection.id() : -1;
1187 KAEventMap::Iterator it = mEventMap.find(EventId(key, id));
1188 if (it != mEventMap.end())
1190 KAEvent* ev = it.value();
1191 mEventMap.erase(it);
1192 KAEvent::List& events = mResourceMap[key];
1193 int i = events.indexOf(ev);
1194 if (i >= 0)
1195 events.remove(i);
1196 delete ev;
1197 if (mEarliestAlarm[key] == ev)
1198 findEarliestAlarm(collection);
1200 else
1202 for (EarliestMap::Iterator eit = mEarliestAlarm.begin(); eit != mEarliestAlarm.end(); ++eit)
1204 KAEvent* ev = eit.value();
1205 if (ev && ev->id() == id)
1207 findEarliestAlarm(eit.key());
1208 break;
1212 CalEvent::Type status = CalEvent::EMPTY;
1213 if (kcalEvent)
1215 status = CalEvent::status(kcalEvent);
1216 mCalendarStorage->calendar()->deleteEvent(kcalEvent);
1218 else if (deleteFromAkonadi)
1220 // It's an Akonadi event
1221 CalEvent::Type s = paramEvent.category();
1222 if (AkonadiModel::instance()->deleteEvent(paramEvent))
1223 status = s;
1225 return status;
1229 /******************************************************************************
1230 * Return the event with the specified ID.
1231 * If 'checkDuplicates' is true, and the collection ID is invalid, if there is
1232 * a unique event with the given ID, it will be returned.
1234 KAEvent* AlarmCalendar::event(const EventId& uniqueID, bool checkDuplicates)
1236 if (!isValid())
1237 return Q_NULLPTR;
1238 const QString eventId = uniqueID.eventId();
1239 if (uniqueID.collectionId() == -1 && checkDuplicates)
1241 // The collection isn't known, but use the event ID if it is
1242 // unique among all collections.
1243 KAEvent::List list = events(eventId);
1244 if (list.count() > 1)
1246 qCWarning(KALARM_LOG) << "Multiple events found with ID" << eventId;
1247 return Q_NULLPTR;
1249 if (list.isEmpty())
1250 return Q_NULLPTR;
1251 return list[0];
1253 KAEventMap::ConstIterator it = mEventMap.constFind(uniqueID);
1254 if (it == mEventMap.constEnd())
1255 return Q_NULLPTR;
1256 return it.value();
1259 /******************************************************************************
1260 * Return the event with the specified ID.
1261 * For the Akonadi version, this method is for the display calendar only.
1263 Event::Ptr AlarmCalendar::kcalEvent(const QString& uniqueID)
1265 Q_ASSERT(mCalType != RESOURCES); // only allowed for display calendar
1266 if (!mCalendarStorage)
1267 return Event::Ptr();
1268 return mCalendarStorage->calendar()->event(uniqueID);
1271 /******************************************************************************
1272 * Find the alarm template with the specified name.
1273 * Reply = 0 if not found.
1275 KAEvent* AlarmCalendar::templateEvent(const QString& templateName)
1277 if (templateName.isEmpty())
1278 return Q_NULLPTR;
1279 KAEvent::List eventlist = events(CalEvent::TEMPLATE);
1280 for (int i = 0, end = eventlist.count(); i < end; ++i)
1282 if (eventlist[i]->templateName() == templateName)
1283 return eventlist[i];
1285 return Q_NULLPTR;
1288 /******************************************************************************
1289 * Return all events with the specified ID, from all calendars.
1291 KAEvent::List AlarmCalendar::events(const QString& uniqueId) const
1293 KAEvent::List list;
1294 if (mCalType == RESOURCES && isValid())
1296 for (ResourceMap::ConstIterator rit = mResourceMap.constBegin(); rit != mResourceMap.constEnd(); ++rit)
1298 const Collection::Id id = rit.key();
1299 KAEventMap::ConstIterator it = mEventMap.constFind(EventId(id, uniqueId));
1300 if (it != mEventMap.constEnd())
1301 list += it.value();
1304 return list;
1307 /******************************************************************************
1308 * Return all events in the calendar which contain alarms.
1309 * Optionally the event type can be filtered, using an OR of event types.
1311 KAEvent::List AlarmCalendar::events(const Collection& collection, CalEvent::Types type) const
1313 KAEvent::List list;
1314 if (mCalType != RESOURCES && (!mCalendarStorage || collection.isValid()))
1315 return list;
1316 if (collection.isValid())
1318 Collection::Id key = collection.isValid() ? collection.id() : -1;
1319 ResourceMap::ConstIterator rit = mResourceMap.constFind(key);
1320 if (rit == mResourceMap.constEnd())
1321 return list;
1322 const KAEvent::List events = rit.value();
1323 if (type == CalEvent::EMPTY)
1324 return events;
1325 for (int i = 0, end = events.count(); i < end; ++i)
1326 if (type & events[i]->category())
1327 list += events[i];
1329 else
1331 for (ResourceMap::ConstIterator rit = mResourceMap.constBegin(); rit != mResourceMap.constEnd(); ++rit)
1333 const KAEvent::List events = rit.value();
1334 if (type == CalEvent::EMPTY)
1335 list += events;
1336 else
1338 for (int i = 0, end = events.count(); i < end; ++i)
1339 if (type & events[i]->category())
1340 list += events[i];
1344 return list;
1347 /******************************************************************************
1348 * Return all events in the calendar which contain usable alarms.
1349 * For the Akonadi version, this method is for the display calendar only.
1350 * Optionally the event type can be filtered, using an OR of event types.
1352 Event::List AlarmCalendar::kcalEvents(CalEvent::Type type)
1354 Event::List list;
1355 Q_ASSERT(mCalType != RESOURCES); // only allowed for display calendar
1356 if (!mCalendarStorage)
1357 return list;
1358 list = mCalendarStorage->calendar()->rawEvents();
1359 for (int i = 0; i < list.count(); )
1361 Event::Ptr event = list[i];
1362 if (event->alarms().isEmpty()
1363 || (type != CalEvent::EMPTY && !(type & CalEvent::status(event)))
1364 || !KAEvent(event).isValid())
1365 list.remove(i);
1366 else
1367 ++i;
1369 return list;
1373 /******************************************************************************
1374 * Return whether an event is read-only.
1375 * Display calendar events are always returned as read-only.
1377 bool AlarmCalendar::eventReadOnly(Item::Id id) const
1379 if (mCalType != RESOURCES)
1380 return true;
1381 AkonadiModel* model = AkonadiModel::instance();
1382 Collection collection = model->collectionForItem(id);
1383 KAEvent event = model->event(id);
1384 if (!CollectionControlModel::isWritableEnabled(collection, event.category()))
1385 return true;
1386 return !event.isValid() || event.isReadOnly();
1387 // || compatibility(event) != KACalendar::Current;
1390 /******************************************************************************
1391 * Return the collection containing a specified event.
1393 Collection AlarmCalendar::collectionForEvent(Item::Id itemId) const
1395 if (mCalType != RESOURCES)
1396 return Collection();
1397 return AkonadiModel::instance()->collectionForItem(itemId);
1400 /******************************************************************************
1401 * Called when an alarm's enabled status has changed.
1403 void AlarmCalendar::disabledChanged(const KAEvent* event)
1405 if (event->category() == CalEvent::ACTIVE)
1407 bool status = event->enabled();
1408 checkForDisabledAlarms(!status, status);
1412 /******************************************************************************
1413 * Check whether there are any individual disabled alarms, following an alarm
1414 * creation or modification. Must only be called for an ACTIVE alarm.
1416 void AlarmCalendar::checkForDisabledAlarms(bool oldEnabled, bool newEnabled)
1418 if (mCalType == RESOURCES && newEnabled != oldEnabled)
1420 if (newEnabled && mHaveDisabledAlarms)
1421 checkForDisabledAlarms();
1422 else if (!newEnabled && !mHaveDisabledAlarms)
1424 mHaveDisabledAlarms = true;
1425 Q_EMIT haveDisabledAlarmsChanged(true);
1430 /******************************************************************************
1431 * Check whether there are any individual disabled alarms.
1433 void AlarmCalendar::checkForDisabledAlarms()
1435 if (mCalType != RESOURCES)
1436 return;
1437 bool disabled = false;
1438 KAEvent::List eventlist = events(CalEvent::ACTIVE);
1439 for (int i = 0, end = eventlist.count(); i < end; ++i)
1441 if (!eventlist[i]->enabled())
1443 disabled = true;
1444 break;
1447 if (disabled != mHaveDisabledAlarms)
1449 mHaveDisabledAlarms = disabled;
1450 Q_EMIT haveDisabledAlarmsChanged(disabled);
1454 /******************************************************************************
1455 * Return a list of all active at-login alarms.
1457 KAEvent::List AlarmCalendar::atLoginAlarms() const
1459 KAEvent::List atlogins;
1460 if (mCalType != RESOURCES)
1461 return atlogins;
1462 AkonadiModel* model = AkonadiModel::instance();
1463 if (!mCalendarStorage || mCalType != RESOURCES)
1464 return atlogins;
1465 for (ResourceMap::ConstIterator rit = mResourceMap.constBegin(); rit != mResourceMap.constEnd(); ++rit)
1467 const Collection::Id id = rit.key();
1468 if (id < 0
1469 || !(AkonadiModel::types(model->collectionById(id)) & CalEvent::ACTIVE))
1470 continue;
1471 const KAEvent::List& events = rit.value();
1472 for (int i = 0, end = events.count(); i < end; ++i)
1474 KAEvent* event = events[i];
1475 if (event->category() == CalEvent::ACTIVE && event->repeatAtLogin())
1476 atlogins += event;
1479 return atlogins;
1482 /******************************************************************************
1483 * Find and note the active alarm with the earliest trigger time for a calendar.
1485 void AlarmCalendar::findEarliestAlarm(const Collection& collection)
1487 if (mCalType != RESOURCES)
1488 return;
1489 if (!collection.isValid()
1490 || !(AkonadiModel::types(collection) & CalEvent::ACTIVE))
1491 return;
1492 findEarliestAlarm(collection.id());
1495 void AlarmCalendar::findEarliestAlarm(Collection::Id key)
1497 EarliestMap::Iterator eit = mEarliestAlarm.find(key);
1498 if (eit != mEarliestAlarm.end())
1499 eit.value() = Q_NULLPTR;
1500 if (mCalType != RESOURCES
1501 || key < 0)
1502 return;
1503 ResourceMap::ConstIterator rit = mResourceMap.constFind(key);
1504 if (rit == mResourceMap.constEnd())
1505 return;
1506 const KAEvent::List& events = rit.value();
1507 KAEvent* earliest = Q_NULLPTR;
1508 KDateTime earliestTime;
1509 for (int i = 0, end = events.count(); i < end; ++i)
1511 KAEvent* event = events[i];
1512 if (event->category() != CalEvent::ACTIVE
1513 || mPendingAlarms.contains(event->id()))
1514 continue;
1515 KDateTime dt = event->nextTrigger(KAEvent::ALL_TRIGGER).effectiveKDateTime();
1516 if (dt.isValid() && (!earliest || dt < earliestTime))
1518 earliestTime = dt;
1519 earliest = event;
1522 mEarliestAlarm[key] = earliest;
1523 Q_EMIT earliestAlarmChanged();
1526 /******************************************************************************
1527 * Return the active alarm with the earliest trigger time.
1528 * Reply = 0 if none.
1530 KAEvent* AlarmCalendar::earliestAlarm() const
1532 KAEvent* earliest = Q_NULLPTR;
1533 KDateTime earliestTime;
1534 for (EarliestMap::ConstIterator eit = mEarliestAlarm.constBegin(); eit != mEarliestAlarm.constEnd(); ++eit)
1536 KAEvent* event = eit.value();
1537 if (!event)
1538 continue;
1539 KDateTime dt = event->nextTrigger(KAEvent::ALL_TRIGGER).effectiveKDateTime();
1540 if (dt.isValid() && (!earliest || dt < earliestTime))
1542 earliestTime = dt;
1543 earliest = event;
1546 return earliest;
1549 /******************************************************************************
1550 * Note that an alarm which has triggered is now being processed. While pending,
1551 * it will be ignored for the purposes of finding the earliest trigger time.
1553 void AlarmCalendar::setAlarmPending(KAEvent* event, bool pending)
1555 QString id = event->id();
1556 bool wasPending = mPendingAlarms.contains(id);
1557 qCDebug(KALARM_LOG) << id << "," << pending << "(was" << wasPending << ")";
1558 if (pending)
1560 if (wasPending)
1561 return;
1562 mPendingAlarms.append(id);
1564 else
1566 if (!wasPending)
1567 return;
1568 mPendingAlarms.removeAll(id);
1570 // Now update the earliest alarm to trigger for its calendar
1571 findEarliestAlarm(AkonadiModel::instance()->collection(*event));
1574 /******************************************************************************
1575 * Called when the user changes the start-of-day time.
1576 * Adjust the start times of all date-only alarms' recurrences.
1578 void AlarmCalendar::adjustStartOfDay()
1580 if (!isValid())
1581 return;
1582 for (ResourceMap::ConstIterator rit = mResourceMap.constBegin(); rit != mResourceMap.constEnd(); ++rit)
1583 KAEvent::adjustStartOfDay(rit.value());
1586 /******************************************************************************
1587 * Find the version of KAlarm which wrote the calendar file, and do any
1588 * necessary conversions to the current format.
1590 KACalendar::Compat fix(const FileStorage::Ptr& fileStorage)
1592 QString versionString;
1593 int version = KACalendar::updateVersion(fileStorage, versionString);
1594 if (version == KACalendar::IncompatibleFormat)
1595 return KACalendar::Incompatible; // calendar was created by another program, or an unknown version of KAlarm
1596 return KACalendar::Current;
1600 // vim: et sw=4: