File indexing completed on 2024-05-05 12:10:38

0001 /*
0002   This file is part of the kcalcore library.
0003 
0004   SPDX-FileCopyrightText: 1998 Preston Brown <pbrown@kde.org>
0005   SPDX-FileCopyrightText: 2000-2004 Cornelius Schumacher <schumacher@kde.org>
0006   SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
0007   SPDX-FileCopyrightText: 2006 David Jarvie <djarvie@kde.org>
0008   SPDX-FileCopyrightText: 2021 Boris Shmarin <b.shmarin@omp.ru>
0009 
0010   SPDX-License-Identifier: LGPL-2.0-or-later
0011 */
0012 /**
0013   @file
0014   This file is part of the API for handling calendar data and
0015   defines the Calendar class.
0016 
0017   @brief
0018   Represents the main calendar class.
0019 
0020   @author Preston Brown \<pbrown@kde.org\>
0021   @author Cornelius Schumacher \<schumacher@kde.org\>
0022   @author Reinhold Kainhofer \<reinhold@kainhofer.com\>
0023   @author David Jarvie \<djarvie@kde.org\>
0024 */
0025 #include "calendar.h"
0026 #include "calendar_p.h"
0027 #include "calfilter.h"
0028 #include "icaltimezones_p.h"
0029 #include "sorting.h"
0030 #include "visitor.h"
0031 
0032 #include "kcalendarcore_debug.h"
0033 
0034 
0035 extern "C" {
0036 #include <libical/icaltimezone.h>
0037 }
0038 
0039 #include <algorithm>
0040 #include <set>
0041 
0042 using namespace KCalendarCore;
0043 
0044 /**
0045   Make a QHash::value that returns a QVector.
0046 */
0047 template<typename K, typename V>
0048 QVector<V> values(const QMultiHash<K, V> &c)
0049 {
0050     QVector<V> v;
0051     v.reserve(c.size());
0052     for (typename QMultiHash<K, V>::const_iterator it = c.begin(), end = c.end(); it != end; ++it) {
0053         v.push_back(it.value());
0054     }
0055     return v;
0056 }
0057 
0058 template<typename K, typename V>
0059 QVector<V> values(const QMultiHash<K, V> &c, const K &x)
0060 {
0061     QVector<V> v;
0062     typename QMultiHash<K, V>::const_iterator it = c.find(x);
0063     while (it != c.end() && it.key() == x) {
0064         v.push_back(it.value());
0065         ++it;
0066     }
0067     return v;
0068 }
0069 
0070 /**
0071   Template for a class that implements a visitor for adding an Incidence
0072   to a resource supporting addEvent(), addTodo() and addJournal() calls.
0073 */
0074 template<class T>
0075 class AddVisitor : public Visitor
0076 {
0077 public:
0078     AddVisitor(T *r)
0079         : mResource(r)
0080     {
0081     }
0082 
0083     bool visit(const Event::Ptr &e) override
0084     {
0085         return mResource->addEvent(e);
0086     }
0087     bool visit(const Todo::Ptr &t) override
0088     {
0089         return mResource->addTodo(t);
0090     }
0091     bool visit(const Journal::Ptr &j) override
0092     {
0093         return mResource->addJournal(j);
0094     }
0095     bool visit(const FreeBusy::Ptr &) override
0096     {
0097         return false;
0098     }
0099 
0100 private:
0101     T *mResource;
0102 };
0103 
0104 /**
0105   Template for a class that implements a visitor for deleting an Incidence
0106   from a resource supporting deleteEvent(), deleteTodo() and deleteJournal()
0107   calls.
0108 */
0109 template<class T>
0110 class DeleteVisitor : public Visitor
0111 {
0112 public:
0113     DeleteVisitor(T *r)
0114         : mResource(r)
0115     {
0116     }
0117 
0118     bool visit(const Event::Ptr &e) override
0119     {
0120         mResource->deleteEvent(e);
0121         return true;
0122     }
0123     bool visit(const Todo::Ptr &t) override
0124     {
0125         mResource->deleteTodo(t);
0126         return true;
0127     }
0128     bool visit(const Journal::Ptr &j) override
0129     {
0130         mResource->deleteJournal(j);
0131         return true;
0132     }
0133     bool visit(const FreeBusy::Ptr &) override
0134     {
0135         return false;
0136     }
0137 
0138 private:
0139     T *mResource;
0140 };
0141 //@endcond
0142 
0143 Calendar::Calendar(const QTimeZone &timeZone)
0144     : d(new KCalendarCore::Calendar::Private)
0145 {
0146     if (timeZone.isValid()) {
0147         d->mTimeZone = timeZone;
0148     } else {
0149         d->mTimeZone = QTimeZone::systemTimeZone();
0150     }
0151 }
0152 
0153 Calendar::Calendar(const QByteArray &timeZoneId)
0154     : d(new KCalendarCore::Calendar::Private)
0155 {
0156     setTimeZoneId(timeZoneId);
0157 }
0158 
0159 Calendar::~Calendar()
0160 {
0161     delete d;
0162 }
0163 
0164 Person Calendar::owner() const
0165 {
0166     return d->mOwner;
0167 }
0168 
0169 void Calendar::setOwner(const Person &owner)
0170 {
0171     if (owner != d->mOwner) {
0172         d->mOwner = owner;
0173         setModified(true);
0174         Q_EMIT ownerChanged();
0175     }
0176 }
0177 
0178 void Calendar::setTimeZone(const QTimeZone &timeZone)
0179 {
0180     if (timeZone.isValid()) {
0181         d->mTimeZone = timeZone;
0182     } else {
0183         d->mTimeZone = QTimeZone::systemTimeZone();
0184     }
0185 
0186     doSetTimeZone(d->mTimeZone);
0187 }
0188 
0189 QTimeZone Calendar::timeZone() const
0190 {
0191     return d->mTimeZone;
0192 }
0193 
0194 void Calendar::setTimeZoneId(const QByteArray &timeZoneId)
0195 {
0196     d->mTimeZone = d->timeZoneIdSpec(timeZoneId);
0197 
0198     doSetTimeZone(d->mTimeZone); // NOLINT false clang-analyzer-optin.cplusplus.VirtualCall
0199 }
0200 
0201 //@cond PRIVATE
0202 QTimeZone Calendar::Private::timeZoneIdSpec(const QByteArray &timeZoneId)
0203 {
0204     if (timeZoneId == QByteArrayLiteral("UTC")) {
0205         return QTimeZone::utc();
0206     }
0207     auto tz = QTimeZone(timeZoneId);
0208     if (tz.isValid()) {
0209         return tz;
0210     }
0211     return QTimeZone::systemTimeZone();
0212 }
0213 //@endcond
0214 
0215 QByteArray Calendar::timeZoneId() const
0216 {
0217     return d->mTimeZone.id();
0218 }
0219 
0220 void Calendar::shiftTimes(const QTimeZone &oldZone, const QTimeZone &newZone)
0221 {
0222     setTimeZone(newZone);
0223 
0224     int i;
0225     int end;
0226     Event::List ev = events();
0227     for (i = 0, end = ev.count(); i < end; ++i) {
0228         ev[i]->shiftTimes(oldZone, newZone);
0229     }
0230 
0231     Todo::List to = todos();
0232     for (i = 0, end = to.count(); i < end; ++i) {
0233         to[i]->shiftTimes(oldZone, newZone);
0234     }
0235 
0236     Journal::List jo = journals();
0237     for (i = 0, end = jo.count(); i < end; ++i) {
0238         jo[i]->shiftTimes(oldZone, newZone);
0239     }
0240 }
0241 
0242 void Calendar::setFilter(CalFilter *filter)
0243 {
0244     if (filter) {
0245         d->mFilter = filter;
0246     } else {
0247         d->mFilter = d->mDefaultFilter;
0248     }
0249     Q_EMIT filterChanged();
0250 }
0251 
0252 CalFilter *Calendar::filter() const
0253 {
0254     return d->mFilter;
0255 }
0256 
0257 QStringList Calendar::categories() const
0258 {
0259     const Incidence::List rawInc = rawIncidences();
0260     QStringList uniqueCategories;
0261     QStringList thisCats;
0262     // @TODO: For now just iterate over all incidences. In the future,
0263     // the list of categories should be built when reading the file.
0264     for (const Incidence::Ptr &inc : rawInc) {
0265         thisCats = inc->categories();
0266         for (const auto &cat : std::as_const(thisCats)) {
0267             if (!uniqueCategories.contains(cat)) {
0268                 uniqueCategories.append(cat);
0269             }
0270         }
0271     }
0272     return uniqueCategories;
0273 }
0274 
0275 Incidence::List Calendar::incidences(const QDate &date) const
0276 {
0277     return mergeIncidenceList(events(date), todos(date), journals(date));
0278 }
0279 
0280 Incidence::List Calendar::incidences() const
0281 {
0282     return mergeIncidenceList(events(), todos(), journals());
0283 }
0284 
0285 Incidence::List Calendar::rawIncidences() const
0286 {
0287     return mergeIncidenceList(rawEvents(), rawTodos(), rawJournals());
0288 }
0289 
0290 Incidence::List Calendar::instances(const Incidence::Ptr &incidence) const
0291 {
0292     if (incidence) {
0293         Event::List elist;
0294         Todo::List tlist;
0295         Journal::List jlist;
0296 
0297         if (incidence->type() == Incidence::TypeEvent) {
0298             elist = eventInstances(incidence);
0299         } else if (incidence->type() == Incidence::TypeTodo) {
0300             tlist = todoInstances(incidence);
0301         } else if (incidence->type() == Incidence::TypeJournal) {
0302             jlist = journalInstances(incidence);
0303         }
0304         return mergeIncidenceList(elist, tlist, jlist);
0305     } else {
0306         return Incidence::List();
0307     }
0308 }
0309 
0310 Incidence::List Calendar::duplicates(const Incidence::Ptr &incidence)
0311 {
0312     if (!incidence) {
0313         return {};
0314     }
0315 
0316     Incidence::List list;
0317     const Incidence::List vals = values(d->mNotebookIncidences);
0318     std::copy_if(vals.cbegin(), vals.cend(), std::back_inserter(list), [&](const Incidence::Ptr &in) {
0319         return (incidence->dtStart() == in->dtStart() || (!incidence->dtStart().isValid() && !in->dtStart().isValid()))
0320             && incidence->summary() == in->summary();
0321     });
0322     return list;
0323 }
0324 
0325 bool Calendar::addNotebook(const QString &notebook, bool isVisible)
0326 {
0327     if (d->mNotebooks.contains(notebook)) {
0328         return false;
0329     } else {
0330         d->mNotebooks.insert(notebook, isVisible);
0331         return true;
0332     }
0333 }
0334 
0335 bool Calendar::updateNotebook(const QString &notebook, bool isVisible)
0336 {
0337     if (!d->mNotebooks.contains(notebook)) {
0338         return false;
0339     } else {
0340         d->mNotebooks.insert(notebook, isVisible);
0341         for (auto noteIt = d->mNotebookIncidences.cbegin(); noteIt != d->mNotebookIncidences.cend(); ++noteIt) {
0342             const Incidence::Ptr &incidence = noteIt.value();
0343             auto visibleIt = d->mIncidenceVisibility.find(incidence);
0344             if (visibleIt != d->mIncidenceVisibility.end()) {
0345                 *visibleIt = isVisible;
0346             }
0347         }
0348         return true;
0349     }
0350 }
0351 
0352 bool Calendar::deleteNotebook(const QString &notebook)
0353 {
0354     if (!d->mNotebooks.contains(notebook)) {
0355         return false;
0356     } else {
0357         return d->mNotebooks.remove(notebook);
0358     }
0359 }
0360 
0361 bool Calendar::setDefaultNotebook(const QString &notebook)
0362 {
0363     if (!d->mNotebooks.contains(notebook)) {
0364         return false;
0365     } else {
0366         d->mDefaultNotebook = notebook;
0367         return true;
0368     }
0369 }
0370 
0371 QString Calendar::defaultNotebook() const
0372 {
0373     return d->mDefaultNotebook;
0374 }
0375 
0376 bool Calendar::hasValidNotebook(const QString &notebook) const
0377 {
0378     return d->mNotebooks.contains(notebook);
0379 }
0380 
0381 bool Calendar::isVisible(const Incidence::Ptr &incidence) const
0382 {
0383     if (d->mIncidenceVisibility.contains(incidence)) {
0384         return d->mIncidenceVisibility[incidence];
0385     }
0386     const QString nuid = notebook(incidence);
0387     bool rv;
0388     if (d->mNotebooks.contains(nuid)) {
0389         rv = d->mNotebooks.value(nuid);
0390     } else {
0391         // NOTE returns true also for nonexisting notebooks for compatibility
0392         rv = true;
0393     }
0394     d->mIncidenceVisibility[incidence] = rv;
0395     return rv;
0396 }
0397 
0398 bool Calendar::isVisible(const QString &notebook) const
0399 {
0400     QHash<QString, bool>::ConstIterator it = d->mNotebooks.constFind(notebook);
0401     return (it != d->mNotebooks.constEnd()) ? *it : true;
0402 }
0403 
0404 void Calendar::clearNotebookAssociations()
0405 {
0406     d->mNotebookIncidences.clear();
0407     d->mUidToNotebook.clear();
0408     d->mIncidenceVisibility.clear();
0409 }
0410 
0411 bool Calendar::setNotebook(const Incidence::Ptr &inc, const QString &notebook)
0412 {
0413     if (!inc) {
0414         return false;
0415     }
0416 
0417     if (!notebook.isEmpty() && !incidence(inc->uid(), inc->recurrenceId())) {
0418         qCWarning(KCALCORE_LOG) << "cannot set notebook until incidence has been added";
0419         return false;
0420     }
0421 
0422     if (d->mUidToNotebook.contains(inc->uid())) {
0423         QString old = d->mUidToNotebook.value(inc->uid());
0424         if (!old.isEmpty() && notebook != old) {
0425             if (inc->hasRecurrenceId()) {
0426                 qCWarning(KCALCORE_LOG) << "cannot set notebook for child incidences";
0427                 return false;
0428             }
0429             // Move all possible children also.
0430             const Incidence::List list = instances(inc);
0431             for (const auto &incidence : list) {
0432                 d->mNotebookIncidences.remove(old, incidence);
0433                 d->mNotebookIncidences.insert(notebook, incidence);
0434             }
0435             notifyIncidenceChanged(inc); // for removing from old notebook
0436             // do not remove from mUidToNotebook to keep deleted incidences
0437             d->mNotebookIncidences.remove(old, inc);
0438         }
0439     }
0440     if (!notebook.isEmpty()) {
0441         d->mUidToNotebook.insert(inc->uid(), notebook);
0442         d->mNotebookIncidences.insert(notebook, inc);
0443         qCDebug(KCALCORE_LOG) << "setting notebook" << notebook << "for" << inc->uid();
0444         notifyIncidenceChanged(inc); // for inserting into new notebook
0445         const Incidence::List list = instances(inc);
0446         for (const auto &incidence : list) {
0447             notifyIncidenceChanged(incidence);
0448         }
0449     }
0450 
0451     return true;
0452 }
0453 
0454 QString Calendar::notebook(const Incidence::Ptr &incidence) const
0455 {
0456     if (incidence) {
0457         return d->mUidToNotebook.value(incidence->uid());
0458     } else {
0459         return QString();
0460     }
0461 }
0462 
0463 QString Calendar::notebook(const QString &uid) const
0464 {
0465     return d->mUidToNotebook.value(uid);
0466 }
0467 
0468 QStringList Calendar::notebooks() const
0469 {
0470     return d->mNotebookIncidences.uniqueKeys();
0471 }
0472 
0473 Incidence::List Calendar::incidences(const QString &notebook) const
0474 {
0475     if (notebook.isEmpty()) {
0476         return values(d->mNotebookIncidences);
0477     } else {
0478         return values(d->mNotebookIncidences, notebook);
0479     }
0480 }
0481 
0482 #if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 95)
0483 /** static */
0484 Event::List Calendar::sortEvents(const Event::List &eventList, EventSortField sortField, SortDirection sortDirection)
0485 {
0486     Event::List eventListSorted(eventList);
0487     return sortEvents(std::move(eventListSorted), sortField, sortDirection);
0488 }
0489 #endif
0490 
0491 Event::List Calendar::sortEvents(Event::List &&eventList, EventSortField sortField, SortDirection sortDirection)
0492 {
0493     switch (sortField) {
0494     case EventSortUnsorted:
0495         break;
0496 
0497     case EventSortStartDate:
0498         if (sortDirection == SortDirectionAscending) {
0499             std::sort(eventList.begin(), eventList.end(), Events::startDateLessThan);
0500         } else {
0501             std::sort(eventList.begin(), eventList.end(), Events::startDateMoreThan);
0502         }
0503         break;
0504 
0505     case EventSortEndDate:
0506         if (sortDirection == SortDirectionAscending) {
0507             std::sort(eventList.begin(), eventList.end(), Events::endDateLessThan);
0508         } else {
0509             std::sort(eventList.begin(), eventList.end(), Events::endDateMoreThan);
0510         }
0511         break;
0512 
0513     case EventSortSummary:
0514         if (sortDirection == SortDirectionAscending) {
0515             std::sort(eventList.begin(), eventList.end(), Events::summaryLessThan);
0516         } else {
0517             std::sort(eventList.begin(), eventList.end(), Events::summaryMoreThan);
0518         }
0519         break;
0520     }
0521 
0522     return eventList;
0523 }
0524 
0525 Event::List Calendar::events(const QDate &date, const QTimeZone &timeZone, EventSortField sortField, SortDirection sortDirection) const
0526 {
0527     Event::List el = rawEventsForDate(date, timeZone, sortField, sortDirection);
0528     d->mFilter->apply(&el);
0529     return el;
0530 }
0531 
0532 Event::List Calendar::events(const QDateTime &dt) const
0533 {
0534     Event::List el = rawEventsForDate(dt.date(), dt.timeZone());
0535     d->mFilter->apply(&el);
0536     return el;
0537 }
0538 
0539 Event::List Calendar::events(const QDate &start, const QDate &end, const QTimeZone &timeZone, bool inclusive) const
0540 {
0541     Event::List el = rawEvents(start, end, timeZone, inclusive);
0542     d->mFilter->apply(&el);
0543     return el;
0544 }
0545 
0546 Event::List Calendar::events(EventSortField sortField, SortDirection sortDirection) const
0547 {
0548     Event::List el = rawEvents(sortField, sortDirection);
0549     d->mFilter->apply(&el);
0550     return el;
0551 }
0552 
0553 bool Calendar::addIncidence(const Incidence::Ptr &incidence)
0554 {
0555     if (!incidence) {
0556         return false;
0557     }
0558 
0559     AddVisitor<Calendar> v(this);
0560     return incidence->accept(v, incidence);
0561 }
0562 
0563 bool Calendar::deleteIncidence(const Incidence::Ptr &incidence)
0564 {
0565     if (!incidence) {
0566         return false;
0567     }
0568 
0569     if (beginChange(incidence)) {
0570         DeleteVisitor<Calendar> v(this);
0571         const bool result = incidence->accept(v, incidence);
0572         endChange(incidence);
0573         return result;
0574     } else {
0575         return false;
0576     }
0577 }
0578 
0579 Incidence::Ptr Calendar::createException(const Incidence::Ptr &incidence, const QDateTime &recurrenceId, bool thisAndFuture)
0580 {
0581     Q_ASSERT(recurrenceId.isValid());
0582     if (!incidence || !incidence->recurs() || !recurrenceId.isValid()) {
0583         return Incidence::Ptr();
0584     }
0585 
0586     Incidence::Ptr newInc(incidence->clone());
0587     const QDateTime current = QDateTime::currentDateTimeUtc();
0588     newInc->setCreated(current);
0589     newInc->setLastModified(current);
0590     newInc->setRevision(0);
0591     // Recurring exceptions are not support for now
0592     newInc->clearRecurrence();
0593 
0594     newInc->setRecurrenceId(recurrenceId);
0595     newInc->setThisAndFuture(thisAndFuture);
0596     newInc->setDtStart(recurrenceId);
0597 
0598     // Calculate and set the new end of the incidence
0599     QDateTime end = incidence->dateTime(IncidenceBase::RoleEnd);
0600 
0601     if (end.isValid()) {
0602         if (incidence->allDay()) {
0603             qint64 offset = incidence->dtStart().daysTo(recurrenceId);
0604             end = end.addDays(offset);
0605         } else {
0606             qint64 offset = incidence->dtStart().secsTo(recurrenceId);
0607             end = end.addSecs(offset);
0608         }
0609         newInc->setDateTime(end, IncidenceBase::RoleEnd);
0610     }
0611     return newInc;
0612 }
0613 
0614 Incidence::Ptr Calendar::incidence(const QString &uid, const QDateTime &recurrenceId) const
0615 {
0616     Incidence::Ptr i = event(uid, recurrenceId);
0617     if (i) {
0618         return i;
0619     }
0620 
0621     i = todo(uid, recurrenceId);
0622     if (i) {
0623         return i;
0624     }
0625 
0626     i = journal(uid, recurrenceId);
0627     return i;
0628 }
0629 
0630 Incidence::Ptr Calendar::deleted(const QString &uid, const QDateTime &recurrenceId) const
0631 {
0632     Incidence::Ptr i = deletedEvent(uid, recurrenceId);
0633     if (i) {
0634         return i;
0635     }
0636 
0637     i = deletedTodo(uid, recurrenceId);
0638     if (i) {
0639         return i;
0640     }
0641 
0642     i = deletedJournal(uid, recurrenceId);
0643     return i;
0644 }
0645 
0646 Incidence::List Calendar::incidencesFromSchedulingID(const QString &sid) const
0647 {
0648     Incidence::List result;
0649     const Incidence::List incidences = rawIncidences();
0650     std::copy_if(incidences.cbegin(), incidences.cend(), std::back_inserter(result), [&sid](const Incidence::Ptr &in) {
0651         return in->schedulingID() == sid;
0652     });
0653     return result;
0654 }
0655 
0656 Incidence::Ptr Calendar::incidenceFromSchedulingID(const QString &uid) const
0657 {
0658     const Incidence::List incidences = rawIncidences();
0659     const auto itEnd = incidences.cend();
0660     auto it = std::find_if(incidences.cbegin(), itEnd, [&uid](const Incidence::Ptr &in) {
0661         return in->schedulingID() == uid;
0662     });
0663 
0664     return it != itEnd ? *it : Incidence::Ptr();
0665 }
0666 
0667 #if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 95)
0668 /** static */
0669 Todo::List Calendar::sortTodos(const Todo::List &todoList, TodoSortField sortField, SortDirection sortDirection)
0670 {
0671     Todo::List todoListSorted(todoList);
0672     return sortTodos(std::move(todoListSorted), sortField, sortDirection);
0673 }
0674 #endif
0675 
0676 Todo::List Calendar::sortTodos(Todo::List &&todoList, TodoSortField sortField, SortDirection sortDirection)
0677 {
0678     // Note that To-dos may not have Start DateTimes nor due DateTimes.
0679     switch (sortField) {
0680     case TodoSortUnsorted:
0681         break;
0682 
0683     case TodoSortStartDate:
0684         if (sortDirection == SortDirectionAscending) {
0685             std::sort(todoList.begin(), todoList.end(), Todos::startDateLessThan);
0686         } else {
0687             std::sort(todoList.begin(), todoList.end(), Todos::startDateMoreThan);
0688         }
0689         break;
0690 
0691     case TodoSortDueDate:
0692         if (sortDirection == SortDirectionAscending) {
0693             std::sort(todoList.begin(), todoList.end(), Todos::dueDateLessThan);
0694         } else {
0695             std::sort(todoList.begin(), todoList.end(), Todos::dueDateMoreThan);
0696         }
0697         break;
0698 
0699     case TodoSortPriority:
0700         if (sortDirection == SortDirectionAscending) {
0701             std::sort(todoList.begin(), todoList.end(), Todos::priorityLessThan);
0702         } else {
0703             std::sort(todoList.begin(), todoList.end(), Todos::priorityMoreThan);
0704         }
0705         break;
0706 
0707     case TodoSortPercentComplete:
0708         if (sortDirection == SortDirectionAscending) {
0709             std::sort(todoList.begin(), todoList.end(), Todos::percentLessThan);
0710         } else {
0711             std::sort(todoList.begin(), todoList.end(), Todos::percentMoreThan);
0712         }
0713         break;
0714 
0715     case TodoSortSummary:
0716         if (sortDirection == SortDirectionAscending) {
0717             std::sort(todoList.begin(), todoList.end(), Todos::summaryLessThan);
0718         } else {
0719             std::sort(todoList.begin(), todoList.end(), Todos::summaryMoreThan);
0720         }
0721         break;
0722 
0723     case TodoSortCreated:
0724         if (sortDirection == SortDirectionAscending) {
0725             std::sort(todoList.begin(), todoList.end(), Todos::createdLessThan);
0726         } else {
0727             std::sort(todoList.begin(), todoList.end(), Todos::createdMoreThan);
0728         }
0729         break;
0730 
0731     case TodoSortCategories:
0732         if (sortDirection == SortDirectionAscending) {
0733             std::sort(todoList.begin(), todoList.end(), Incidences::categoriesLessThan);
0734         } else {
0735             std::sort(todoList.begin(), todoList.end(), Incidences::categoriesMoreThan);
0736         }
0737         break;
0738     }
0739 
0740     return todoList;
0741 }
0742 
0743 Todo::List Calendar::todos(TodoSortField sortField, SortDirection sortDirection) const
0744 {
0745     Todo::List tl = rawTodos(sortField, sortDirection);
0746     d->mFilter->apply(&tl);
0747     return tl;
0748 }
0749 
0750 Todo::List Calendar::todos(const QDate &date) const
0751 {
0752     Todo::List el = rawTodosForDate(date);
0753     d->mFilter->apply(&el);
0754     return el;
0755 }
0756 
0757 Todo::List Calendar::todos(const QDate &start, const QDate &end, const QTimeZone &timeZone, bool inclusive) const
0758 {
0759     Todo::List tl = rawTodos(start, end, timeZone, inclusive);
0760     d->mFilter->apply(&tl);
0761     return tl;
0762 }
0763 
0764 #if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 95)
0765 /** static */
0766 Journal::List Calendar::sortJournals(const Journal::List &journalList, JournalSortField sortField, SortDirection sortDirection)
0767 {
0768     Journal::List journalListSorted = journalList;
0769     return sortJournals(std::move(journalListSorted), sortField, sortDirection);
0770 }
0771 #endif
0772 
0773 Journal::List Calendar::sortJournals(Journal::List &&journalList, JournalSortField sortField, SortDirection sortDirection)
0774 {
0775     switch (sortField) {
0776     case JournalSortUnsorted:
0777         break;
0778 
0779     case JournalSortDate:
0780         if (sortDirection == SortDirectionAscending) {
0781             std::sort(journalList.begin(), journalList.end(), Journals::dateLessThan);
0782         } else {
0783             std::sort(journalList.begin(), journalList.end(), Journals::dateMoreThan);
0784         }
0785         break;
0786 
0787     case JournalSortSummary:
0788         if (sortDirection == SortDirectionAscending) {
0789             std::sort(journalList.begin(), journalList.end(), Journals::summaryLessThan);
0790         } else {
0791             std::sort(journalList.begin(), journalList.end(), Journals::summaryMoreThan);
0792         }
0793         break;
0794     }
0795 
0796     return journalList;
0797 }
0798 
0799 Journal::List Calendar::journals(JournalSortField sortField, SortDirection sortDirection) const
0800 {
0801     Journal::List jl = rawJournals(sortField, sortDirection);
0802     d->mFilter->apply(&jl);
0803     return jl;
0804 }
0805 
0806 Journal::List Calendar::journals(const QDate &date) const
0807 {
0808     Journal::List el = rawJournalsForDate(date);
0809     d->mFilter->apply(&el);
0810     return el;
0811 }
0812 
0813 // When this is called, the to-dos have already been added to the calendar.
0814 // This method is only about linking related to-dos.
0815 void Calendar::setupRelations(const Incidence::Ptr &forincidence)
0816 {
0817     if (!forincidence) {
0818         return;
0819     }
0820 
0821     const QString uid = forincidence->uid();
0822 
0823     // First, go over the list of orphans and see if this is their parent
0824     Incidence::List l = values(d->mOrphans, uid);
0825     d->mOrphans.remove(uid);
0826     if (!l.isEmpty()) {
0827         Incidence::List &relations = d->mIncidenceRelations[uid];
0828         relations.reserve(relations.count() + l.count());
0829         for (int i = 0, end = l.count(); i < end; ++i) {
0830             relations.append(l[i]);
0831             d->mOrphanUids.remove(l[i]->uid());
0832         }
0833     }
0834 
0835     // Now see about this incidences parent
0836     if (!forincidence->relatedTo().isEmpty()) {
0837         // Incidence has a uid it is related to but is not registered to it yet.
0838         // Try to find it
0839         Incidence::Ptr parent = incidence(forincidence->relatedTo());
0840         if (parent) {
0841             // Found it
0842 
0843             // look for hierarchy loops
0844             if (isAncestorOf(forincidence, parent)) {
0845                 forincidence->setRelatedTo(QString());
0846                 qCWarning(KCALCORE_LOG) << "hierarchy loop between " << forincidence->uid() << " and " << parent->uid();
0847             } else {
0848                 d->mIncidenceRelations[parent->uid()].append(forincidence);
0849             }
0850         } else {
0851             // Not found, put this in the mOrphans list
0852             // Note that the mOrphans dict might contain multiple entries with the
0853             // same key! which are multiple children that wait for the parent
0854             // incidence to be inserted.
0855             d->mOrphans.insert(forincidence->relatedTo(), forincidence);
0856             d->mOrphanUids.insert(forincidence->uid(), forincidence);
0857         }
0858     }
0859 }
0860 
0861 // If a to-do with sub-to-dos is deleted, move it's sub-to-dos to the orphan list
0862 void Calendar::removeRelations(const Incidence::Ptr &incidence)
0863 {
0864     if (!incidence) {
0865         qCDebug(KCALCORE_LOG) << "Warning: incidence is 0";
0866         return;
0867     }
0868 
0869     const QString uid = incidence->uid();
0870 
0871     for (const Incidence::Ptr &i : std::as_const(d->mIncidenceRelations[uid])) {
0872         if (!d->mOrphanUids.contains(i->uid())) {
0873             d->mOrphans.insert(uid, i);
0874             d->mOrphanUids.insert(i->uid(), i);
0875             i->setRelatedTo(uid);
0876         }
0877     }
0878 
0879     const QString parentUid = incidence->relatedTo();
0880 
0881     // If this incidence is related to something else, tell that about it
0882     if (!parentUid.isEmpty()) {
0883         Incidence::List &relations = d->mIncidenceRelations[parentUid];
0884         relations.erase(std::remove(relations.begin(), relations.end(), incidence), relations.end());
0885     }
0886 
0887     // Remove this one from the orphans list
0888     if (d->mOrphanUids.remove(uid)) {
0889         // This incidence is located in the orphans list - it should be removed
0890         // Since the mOrphans dict might contain the same key (with different
0891         // child incidence pointers!) multiple times, take care that we remove
0892         // the correct one. So we need to remove all items with the given
0893         // parent UID, and re-add those that are not for this item. Also, there
0894         // might be other entries with different UID that point to this
0895         // incidence (this might happen when the relatedTo of the item is
0896         // changed before its parent is inserted. This might happen with
0897         // groupware servers....). Remove them, too
0898         QStringList relatedToUids;
0899 
0900         // First, create a list of all keys in the mOrphans list which point
0901         // to the removed item
0902         relatedToUids << incidence->relatedTo();
0903         for (auto it = d->mOrphans.cbegin(); it != d->mOrphans.cend(); ++it) {
0904             if (it.value()->uid() == uid) {
0905                 relatedToUids << it.key();
0906             }
0907         }
0908 
0909         // now go through all uids that have one entry that point to the incidence
0910         for (const auto &relUid : std::as_const(relatedToUids)) {
0911             // Remove all to get access to the remaining entries
0912             Incidence::List lst = values(d->mOrphans, relUid);
0913             d->mOrphans.remove(relUid);
0914             lst.erase(std::remove(lst.begin(), lst.end(), incidence), lst.end());
0915 
0916             // Re-add those that point to a different orphan incidence
0917             for (const auto &in : std::as_const(lst)) {
0918                 d->mOrphans.insert(relUid, in);
0919             }
0920         }
0921     }
0922 
0923     // Make sure the deleted incidence doesn't relate to a non-deleted incidence,
0924     // since that would cause trouble in MemoryCalendar::close(), as the deleted
0925     // incidences are destroyed after the non-deleted incidences. The destructor
0926     // of the deleted incidences would then try to access the already destroyed
0927     // non-deleted incidence, which would segfault.
0928     //
0929     // So in short: Make sure dead incidences don't point to alive incidences
0930     // via the relation.
0931     //
0932     // This crash is tested in MemoryCalendarTest::testRelationsCrash().
0933     //  incidence->setRelatedTo( Incidence::Ptr() );
0934 }
0935 
0936 bool Calendar::isAncestorOf(const Incidence::Ptr &ancestor, const Incidence::Ptr &incidence) const
0937 {
0938     if (!incidence || incidence->relatedTo().isEmpty()) {
0939         return false;
0940     } else if (incidence->relatedTo() == ancestor->uid()) {
0941         return true;
0942     } else {
0943         return isAncestorOf(ancestor, this->incidence(incidence->relatedTo()));
0944     }
0945 }
0946 
0947 Incidence::List Calendar::relations(const QString &uid) const
0948 {
0949     return d->mIncidenceRelations[uid];
0950 }
0951 
0952 Calendar::CalendarObserver::~CalendarObserver()
0953 {
0954 }
0955 
0956 void Calendar::CalendarObserver::calendarModified(bool modified, Calendar *calendar)
0957 {
0958     Q_UNUSED(modified);
0959     Q_UNUSED(calendar);
0960 }
0961 
0962 void Calendar::CalendarObserver::calendarIncidenceAdded(const Incidence::Ptr &incidence)
0963 {
0964     Q_UNUSED(incidence);
0965 }
0966 
0967 void Calendar::CalendarObserver::calendarIncidenceChanged(const Incidence::Ptr &incidence)
0968 {
0969     Q_UNUSED(incidence);
0970 }
0971 
0972 void Calendar::CalendarObserver::calendarIncidenceAboutToBeDeleted(const Incidence::Ptr &incidence)
0973 {
0974     Q_UNUSED(incidence);
0975 }
0976 
0977 void Calendar::CalendarObserver::calendarIncidenceDeleted(const Incidence::Ptr &incidence, const Calendar *calendar)
0978 {
0979     Q_UNUSED(incidence);
0980     Q_UNUSED(calendar);
0981 }
0982 
0983 void Calendar::CalendarObserver::calendarIncidenceAdditionCanceled(const Incidence::Ptr &incidence)
0984 {
0985     Q_UNUSED(incidence);
0986 }
0987 
0988 void Calendar::registerObserver(CalendarObserver *observer)
0989 {
0990     if (!observer) {
0991         return;
0992     }
0993 
0994     if (!d->mObservers.contains(observer)) {
0995         d->mObservers.append(observer);
0996     } else {
0997         d->mNewObserver = true;
0998     }
0999 }
1000 
1001 void Calendar::unregisterObserver(CalendarObserver *observer)
1002 {
1003     if (!observer) {
1004         return;
1005     } else {
1006         d->mObservers.removeAll(observer);
1007     }
1008 }
1009 
1010 bool Calendar::isSaving() const
1011 {
1012     return false;
1013 }
1014 
1015 void Calendar::setModified(bool modified)
1016 {
1017     if (modified != d->mModified || d->mNewObserver) {
1018         d->mNewObserver = false;
1019         for (CalendarObserver *observer : std::as_const(d->mObservers)) {
1020             observer->calendarModified(modified, this);
1021         }
1022         d->mModified = modified;
1023     }
1024 }
1025 
1026 bool Calendar::isModified() const
1027 {
1028     return d->mModified;
1029 }
1030 
1031 bool Calendar::save()
1032 {
1033     return true;
1034 }
1035 
1036 bool Calendar::reload()
1037 {
1038     return true;
1039 }
1040 
1041 void Calendar::incidenceUpdated(const QString &uid, const QDateTime &recurrenceId)
1042 {
1043     Incidence::Ptr inc = incidence(uid, recurrenceId);
1044 
1045     if (!inc) {
1046         return;
1047     }
1048 
1049     inc->setLastModified(QDateTime::currentDateTimeUtc());
1050     // we should probably update the revision number here,
1051     // or internally in the Event itself when certain things change.
1052     // need to verify with ical documentation.
1053 
1054     notifyIncidenceChanged(inc);
1055 
1056     setModified(true);
1057 }
1058 
1059 void Calendar::doSetTimeZone(const QTimeZone &timeZone)
1060 {
1061     Q_UNUSED(timeZone);
1062 }
1063 
1064 void Calendar::notifyIncidenceAdded(const Incidence::Ptr &incidence)
1065 {
1066     if (!incidence) {
1067         return;
1068     }
1069 
1070     if (!d->mObserversEnabled) {
1071         return;
1072     }
1073 
1074     for (CalendarObserver *observer : std::as_const(d->mObservers)) {
1075         observer->calendarIncidenceAdded(incidence);
1076     }
1077 
1078     for (auto role : {IncidenceBase::RoleStartTimeZone, IncidenceBase::RoleEndTimeZone}) {
1079         const auto dt = incidence->dateTime(role);
1080         if (dt.isValid() && dt.timeZone() != QTimeZone::utc()) {
1081             if (!d->mTimeZones.contains(dt.timeZone())) {
1082                 d->mTimeZones.push_back(dt.timeZone());
1083             }
1084         }
1085     }
1086 }
1087 
1088 void Calendar::notifyIncidenceChanged(const Incidence::Ptr &incidence)
1089 {
1090     if (!incidence) {
1091         return;
1092     }
1093 
1094     if (!d->mObserversEnabled) {
1095         return;
1096     }
1097 
1098     for (CalendarObserver *observer : std::as_const(d->mObservers)) {
1099         observer->calendarIncidenceChanged(incidence);
1100     }
1101 }
1102 
1103 void Calendar::notifyIncidenceAboutToBeDeleted(const Incidence::Ptr &incidence)
1104 {
1105     if (!incidence) {
1106         return;
1107     }
1108 
1109     if (!d->mObserversEnabled) {
1110         return;
1111     }
1112 
1113     for (CalendarObserver *observer : std::as_const(d->mObservers)) {
1114         observer->calendarIncidenceAboutToBeDeleted(incidence);
1115     }
1116 }
1117 
1118 void Calendar::notifyIncidenceDeleted(const Incidence::Ptr &incidence)
1119 {
1120     if (!incidence) {
1121         return;
1122     }
1123 
1124     if (!d->mObserversEnabled) {
1125         return;
1126     }
1127 
1128     for (CalendarObserver *observer : std::as_const(d->mObservers)) {
1129         observer->calendarIncidenceDeleted(incidence, this);
1130     }
1131 }
1132 
1133 void Calendar::notifyIncidenceAdditionCanceled(const Incidence::Ptr &incidence)
1134 {
1135     if (!incidence) {
1136         return;
1137     }
1138 
1139     if (!d->mObserversEnabled) {
1140         return;
1141     }
1142 
1143     for (CalendarObserver *observer : std::as_const(d->mObservers)) {
1144         observer->calendarIncidenceAdditionCanceled(incidence);
1145     }
1146 }
1147 
1148 void Calendar::customPropertyUpdated()
1149 {
1150     setModified(true);
1151 }
1152 
1153 void Calendar::setProductId(const QString &id)
1154 {
1155     d->mProductId = id;
1156 }
1157 
1158 QString Calendar::productId() const
1159 {
1160     return d->mProductId;
1161 }
1162 
1163 /** static */
1164 Incidence::List Calendar::mergeIncidenceList(const Event::List &events, const Todo::List &todos, const Journal::List &journals)
1165 {
1166     Incidence::List incidences;
1167     incidences.reserve(events.count() + todos.count() + journals.count());
1168 
1169     int i;
1170     int end;
1171     for (i = 0, end = events.count(); i < end; ++i) {
1172         incidences.append(events[i]);
1173     }
1174 
1175     for (i = 0, end = todos.count(); i < end; ++i) {
1176         incidences.append(todos[i]);
1177     }
1178 
1179     for (i = 0, end = journals.count(); i < end; ++i) {
1180         incidences.append(journals[i]);
1181     }
1182 
1183     return incidences;
1184 }
1185 
1186 bool Calendar::beginChange(const Incidence::Ptr &incidence)
1187 {
1188     Q_UNUSED(incidence);
1189     return true;
1190 }
1191 
1192 bool Calendar::endChange(const Incidence::Ptr &incidence)
1193 {
1194     Q_UNUSED(incidence);
1195     return true;
1196 }
1197 
1198 void Calendar::setObserversEnabled(bool enabled)
1199 {
1200     d->mObserversEnabled = enabled;
1201 }
1202 
1203 void Calendar::appendAlarms(Alarm::List &alarms, const Incidence::Ptr &incidence, const QDateTime &from, const QDateTime &to) const
1204 {
1205     QDateTime preTime = from.addSecs(-1);
1206 
1207     Alarm::List alarmlist = incidence->alarms();
1208     for (int i = 0, iend = alarmlist.count(); i < iend; ++i) {
1209         if (alarmlist[i]->enabled()) {
1210             QDateTime dt = alarmlist[i]->nextRepetition(preTime);
1211             if (dt.isValid() && dt <= to) {
1212                 qCDebug(KCALCORE_LOG) << incidence->summary() << "':" << dt.toString();
1213                 alarms.append(alarmlist[i]);
1214             }
1215         }
1216     }
1217 }
1218 
1219 void Calendar::appendRecurringAlarms(Alarm::List &alarms, const Incidence::Ptr &incidence, const QDateTime &from, const QDateTime &to) const
1220 {
1221     QDateTime dt;
1222     bool endOffsetValid = false;
1223     Duration endOffset(0);
1224     Duration period(from, to);
1225 
1226     Alarm::List alarmlist = incidence->alarms();
1227     for (int i = 0, iend = alarmlist.count(); i < iend; ++i) {
1228         Alarm::Ptr a = alarmlist[i];
1229         if (a->enabled()) {
1230             if (a->hasTime()) {
1231                 // The alarm time is defined as an absolute date/time
1232                 dt = a->nextRepetition(from.addSecs(-1));
1233                 if (!dt.isValid() || dt > to) {
1234                     continue;
1235                 }
1236             } else {
1237                 // Alarm time is defined by an offset from the event start or end time.
1238                 // Find the offset from the event start time, which is also used as the
1239                 // offset from the recurrence time.
1240                 Duration offset(0);
1241                 if (a->hasStartOffset()) {
1242                     offset = a->startOffset();
1243                 } else if (a->hasEndOffset()) {
1244                     offset = a->endOffset();
1245                     if (!endOffsetValid) {
1246                         endOffset = Duration(incidence->dtStart(), incidence->dateTime(Incidence::RoleAlarmEndOffset));
1247                         endOffsetValid = true;
1248                     }
1249                 }
1250 
1251                 // Find the incidence's earliest alarm
1252                 QDateTime alarmStart = offset.end(a->hasEndOffset() ? incidence->dateTime(Incidence::RoleAlarmEndOffset) : incidence->dtStart());
1253                 if (alarmStart > to) {
1254                     continue;
1255                 }
1256                 QDateTime baseStart = incidence->dtStart();
1257                 if (from > alarmStart) {
1258                     alarmStart = from; // don't look earlier than the earliest alarm
1259                     baseStart = (-offset).end((-endOffset).end(alarmStart));
1260                 }
1261 
1262                 // Adjust the 'alarmStart' date/time and find the next recurrence at or after it.
1263                 // Treat the two offsets separately in case one is daily and the other not.
1264                 dt = incidence->recurrence()->getNextDateTime(baseStart.addSecs(-1));
1265                 if (!dt.isValid() || (dt = endOffset.end(offset.end(dt))) > to) { // adjust 'dt' to get the alarm time
1266                     // The next recurrence is too late.
1267                     if (!a->repeatCount()) {
1268                         continue;
1269                     }
1270 
1271                     // The alarm has repetitions, so check whether repetitions of previous
1272                     // recurrences fall within the time period.
1273                     bool found = false;
1274                     Duration alarmDuration = a->duration();
1275                     for (QDateTime base = baseStart; (dt = incidence->recurrence()->getPreviousDateTime(base)).isValid(); base = dt) {
1276                         if (a->duration().end(dt) < base) {
1277                             break; // this recurrence's last repetition is too early, so give up
1278                         }
1279 
1280                         // The last repetition of this recurrence is at or after 'alarmStart' time.
1281                         // Check if a repetition occurs between 'alarmStart' and 'to'.
1282                         int snooze = a->snoozeTime().value(); // in seconds or days
1283                         if (a->snoozeTime().isDaily()) {
1284                             Duration toFromDuration(dt, base);
1285                             int toFrom = toFromDuration.asDays();
1286                             if (a->snoozeTime().end(from) <= to || (toFromDuration.isDaily() && toFrom % snooze == 0)
1287                                 || (toFrom / snooze + 1) * snooze <= toFrom + period.asDays()) {
1288                                 found = true;
1289 #ifndef NDEBUG
1290                                 // for debug output
1291                                 dt = offset.end(dt).addDays(((toFrom - 1) / snooze + 1) * snooze);
1292 #endif
1293                                 break;
1294                             }
1295                         } else {
1296                             int toFrom = dt.secsTo(base);
1297                             if (period.asSeconds() >= snooze || toFrom % snooze == 0 || (toFrom / snooze + 1) * snooze <= toFrom + period.asSeconds()) {
1298                                 found = true;
1299 #ifndef NDEBUG
1300                                 // for debug output
1301                                 dt = offset.end(dt).addSecs(((toFrom - 1) / snooze + 1) * snooze);
1302 #endif
1303                                 break;
1304                             }
1305                         }
1306                     }
1307                     if (!found) {
1308                         continue;
1309                     }
1310                 }
1311             }
1312             qCDebug(KCALCORE_LOG) << incidence->summary() << "':" << dt.toString();
1313             alarms.append(a);
1314         }
1315     }
1316 }
1317 
1318 void Calendar::startBatchAdding()
1319 {
1320     d->batchAddingInProgress = true;
1321 }
1322 
1323 void Calendar::endBatchAdding()
1324 {
1325     d->batchAddingInProgress = false;
1326 }
1327 
1328 bool Calendar::batchAdding() const
1329 {
1330     return d->batchAddingInProgress;
1331 }
1332 
1333 void Calendar::setDeletionTracking(bool enable)
1334 {
1335     d->mDeletionTracking = enable;
1336 }
1337 
1338 bool Calendar::deletionTracking() const
1339 {
1340     return d->mDeletionTracking;
1341 }
1342 
1343 Alarm::List Calendar::alarmsTo(const QDateTime &to) const
1344 {
1345     return alarms(QDateTime(QDate(1900, 1, 1), QTime(0, 0, 0)), to);
1346 }
1347 
1348 void Calendar::virtual_hook(int id, void *data)
1349 {
1350     Q_UNUSED(id);
1351     Q_UNUSED(data);
1352     Q_ASSERT(false);
1353 }
1354 
1355 QString Calendar::id() const
1356 {
1357     return d->mId;
1358 }
1359 
1360 void Calendar::setId(const QString &id)
1361 {
1362     if (d->mId != id) {
1363         d->mId = id;
1364         Q_EMIT idChanged();
1365     }
1366 }
1367 
1368 QString Calendar::name() const
1369 {
1370     return d->mName;
1371 }
1372 
1373 void Calendar::setName(const QString &name)
1374 {
1375     if (d->mName != name) {
1376         d->mName = name;
1377         Q_EMIT nameChanged();
1378     }
1379 }
1380 
1381 QIcon Calendar::icon() const
1382 {
1383     return d->mIcon;
1384 }
1385 
1386 void Calendar::setIcon(const QIcon &icon)
1387 {
1388     d->mIcon = icon;
1389     Q_EMIT iconChanged();
1390 }
1391 
1392 AccessMode Calendar::accessMode() const
1393 {
1394     return d->mAccessMode;
1395 }
1396 
1397 void Calendar::setAccessMode(const AccessMode mode)
1398 {
1399     if (d->mAccessMode != mode) {
1400         d->mAccessMode = mode;
1401         Q_EMIT accessModeChanged();
1402     }
1403 }
1404 
1405 bool Calendar::isLoading() const
1406 {
1407     return d->mIsLoading;
1408 }
1409 
1410 void Calendar::setIsLoading(bool isLoading)
1411 {
1412     if (d->mIsLoading == isLoading) {
1413         return;
1414     }
1415 
1416     d->mIsLoading = isLoading;
1417     Q_EMIT isLoadingChanged();
1418 }
1419 
1420 #include "moc_calendar.cpp"