File indexing completed on 2024-05-05 05:28:19

0001 /*
0002  * SPDX-FileCopyrightText: 2020 Dimitris Kardarakos <dimkard@posteo.net>
0003  *
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  */
0006 
0007 #include "calendarcontroller.h"
0008 #include "localcalendar.h"
0009 #include <QStandardPaths>
0010 #include <QDebug>
0011 #include <QFile>
0012 #include <QStringLiteral>
0013 #include <KLocalizedString>
0014 #include <KCalendarCore/ICalFormat>
0015 #include <KCalendarCore/Calendar>
0016 #include <KCalendarCore/MemoryCalendar>
0017 #include <KCalendarCore/Attendee>
0018 #include <KCalendarCore/Person>
0019 #include "attendeesmodel.h"
0020 #include "calindoriconfig.h"
0021 
0022 CalendarController &CalendarController::instance()
0023 {
0024     static CalendarController instance;
0025     return instance;
0026 }
0027 
0028 CalendarController::CalendarController(QObject *parent) : QObject {parent}, m_events {}, m_todos {}
0029 {
0030     m_calendar = std::make_unique<LocalCalendar>();
0031     m_calendar->setName(CalindoriConfig::instance().activeCalendar());
0032 
0033     connect(&CalindoriConfig::instance(), &CalindoriConfig::activeCalendarChanged, this, [this]{
0034         m_calendar->setName(CalindoriConfig::instance().activeCalendar());
0035         Q_EMIT activeCalendarChanged();
0036     });
0037 }
0038 
0039 void CalendarController::importCalendarData(const QByteArray &data)
0040 {
0041     KCalendarCore::MemoryCalendar::Ptr importedCalendar {new KCalendarCore::MemoryCalendar {QTimeZone::systemTimeZoneId()}};
0042     KCalendarCore::ICalFormat icalFormat {};
0043     auto readResult = icalFormat.fromRawString(importedCalendar, data);
0044 
0045     if (!readResult) {
0046         qDebug() << "The file read was not a valid calendar";
0047         Q_EMIT statusMessageChanged(i18n("The url or file given does not contain valid calendar data"), MessageType::NegativeAnswer);
0048         return;
0049     }
0050 
0051     m_events = importedCalendar->rawEvents();
0052     m_todos = importedCalendar->rawTodos();
0053 
0054     if (m_events.isEmpty() && m_todos.isEmpty()) {
0055         qDebug() << "No events or tasks found.";
0056         Q_EMIT statusMessageChanged(i18n("The url or file given does not contain any event or task"), MessageType::NegativeAnswer);
0057         return;
0058     }
0059 
0060     QString confirmMsg {};
0061 
0062     if (!m_events.isEmpty() && m_todos.isEmpty()) {
0063         confirmMsg = i18np("1 event will be added", "%1 events will be added", m_events.count());
0064     } else if (m_events.isEmpty() && !m_todos.isEmpty()) {
0065         confirmMsg = i18np("1 task will be added", "%1 tasks will be added", m_todos.count());
0066     } else {
0067         auto incidenceCount = m_events.count() + m_todos.count();
0068         confirmMsg = i18n("%1 incidences will be added", incidenceCount);
0069     }
0070 
0071     Q_EMIT statusMessageChanged(confirmMsg, MessageType::Question);
0072 }
0073 
0074 void CalendarController::importFromBuffer(LocalCalendar *localCalendar)
0075 {
0076     auto calendar = localCalendar->calendar();
0077 
0078     for (const auto &event : qAsConst(m_events)) {
0079         calendar->addEvent(event);
0080     }
0081 
0082     for (const auto &todo : qAsConst(m_todos)) {
0083         calendar->addTodo(todo);
0084     }
0085 
0086     bool result = localCalendar->save();
0087 
0088     if (!m_events.isEmpty()) {
0089         Q_EMIT localCalendar->eventsChanged();
0090         m_events.clear();
0091     }
0092 
0093     if (!m_todos.isEmpty()) {
0094         Q_EMIT localCalendar->todosChanged();
0095         m_todos.clear();
0096     }
0097 
0098     sendMessage(result);
0099 }
0100 
0101 void CalendarController::importFromBuffer(const QString &targetCalendar)
0102 {
0103     auto filePath = CalindoriConfig::instance().calendarFile(targetCalendar);
0104     QFile calendarFile {filePath};
0105     if (!calendarFile.exists()) {
0106         sendMessage(false);
0107         return;
0108     }
0109 
0110     KCalendarCore::Calendar::Ptr calendar {new KCalendarCore::MemoryCalendar(QTimeZone::systemTimeZoneId())};
0111     KCalendarCore::FileStorage::Ptr storage {new KCalendarCore::FileStorage {calendar}};
0112     storage->setFileName(filePath);
0113     if (!storage->load()) {
0114         sendMessage(false);
0115         return;
0116     }
0117 
0118     for (const auto &event : qAsConst(m_events)) {
0119         calendar->addEvent(event);
0120     }
0121 
0122     for (const auto &todo : qAsConst(m_todos)) {
0123         calendar->addTodo(todo);
0124     }
0125 
0126     sendMessage(storage->save());
0127 }
0128 
0129 void CalendarController::sendMessage(const bool positive)
0130 {
0131     if (positive) {
0132         Q_EMIT statusMessageChanged(i18n("Import completed successfully"), MessageType::PositiveAnswer);
0133     } else {
0134         Q_EMIT statusMessageChanged(i18n("An error has occurred during import"), MessageType::NegativeAnswer);
0135     }
0136 }
0137 
0138 void CalendarController::abortImporting()
0139 {
0140     m_events.clear();
0141     m_todos.clear();
0142 }
0143 
0144 void CalendarController::removeEvent(LocalCalendar *localCalendar, const QVariantMap &eventData)
0145 {
0146     KCalendarCore::Calendar::Ptr calendar = localCalendar->calendar();
0147     QString uid = eventData[QStringLiteral("uid")].toString();
0148     KCalendarCore::Event::Ptr event = calendar->event(uid);
0149     calendar->deleteEvent(event);
0150     bool deleted = localCalendar->save();
0151     Q_EMIT localCalendar->eventsChanged();
0152 
0153     qDebug() << "Event " << uid << " deleted: " << deleted;
0154 }
0155 
0156 void CalendarController::upsertEvent(const QVariantMap &eventData, const QVariantList &attendeesList)
0157 {
0158     qDebug() << "\naddEdit:\tCreating event";
0159 
0160     KCalendarCore::Calendar::Ptr calendar = CalendarController::instance().activeCalendar()->calendar();
0161     QDateTime now = QDateTime::currentDateTime();
0162     QString uid = eventData[QStringLiteral("uid")].toString();
0163     QString summary = eventData[QStringLiteral("summary")].toString();
0164     bool clearPartStatus {false};
0165 
0166     QDate startDate = eventData[QStringLiteral("startDate")].toDate();
0167     int startHour = eventData[QStringLiteral("startHour")].value<int>();
0168     int startMinute = eventData[QStringLiteral("startMinute")].value<int>();
0169 
0170     QDate endDate = eventData[QStringLiteral("endDate")].toDate();
0171     int endHour = eventData[QStringLiteral("endHour")].value<int>();
0172     int endMinute = eventData[QStringLiteral("endMinute")].value<int>();
0173 
0174     QDateTime startDateTime;
0175     QDateTime endDateTime;
0176     bool allDayFlg = eventData[QStringLiteral("allDay")].toBool();
0177 
0178     if (allDayFlg) {
0179         startDateTime = startDate.startOfDay();
0180         endDateTime = endDate.startOfDay();
0181     } else {
0182         startDateTime = QDateTime(startDate, QTime(startHour, startMinute, 0, 0), QTimeZone::systemTimeZone());
0183         endDateTime = QDateTime(endDate, QTime(endHour, endMinute, 0, 0), QTimeZone::systemTimeZone());
0184     }
0185 
0186     KCalendarCore::Event::Ptr event;
0187     if (uid.isEmpty()) {
0188         event = KCalendarCore::Event::Ptr(new KCalendarCore::Event());
0189         event->setUid(summary.at(0) + now.toString(QStringLiteral("yyyyMMddhhmmsszzz")));
0190     } else {
0191         event = calendar->event(uid);
0192         event->setUid(uid);
0193         clearPartStatus = (event->dtStart() != startDateTime) || (event->dtEnd() != endDateTime) || (event->allDay() != allDayFlg);
0194     }
0195 
0196     event->setDtStart(startDateTime);
0197     event->setDtEnd(endDateTime);
0198     event->setDescription(eventData[QStringLiteral("description")].toString());
0199     event->setSummary(summary);
0200     event->setAllDay(allDayFlg);
0201     event->setLocation(eventData[QStringLiteral("location")].toString());
0202 
0203     event->clearAttendees();
0204     for (auto &a : qAsConst(attendeesList)) {
0205         auto attendee = a.value<KCalendarCore::Attendee>();
0206         if (clearPartStatus) {
0207             qDebug() << "Participants need to be informed";
0208             attendee.setRSVP(true);
0209             attendee.setStatus(KCalendarCore::Attendee::PartStat::NeedsAction);
0210         }
0211         event->addAttendee(attendee);
0212     }
0213 
0214     if (!attendeesList.isEmpty()) {
0215         event->setOrganizer(KCalendarCore::Person { CalendarController::instance().activeCalendar()->ownerName(), CalendarController::instance().activeCalendar()->ownerEmail()});
0216     }
0217 
0218     event->clearAlarms();
0219     QVariantList newAlarms = eventData[QStringLiteral("alarms")].value<QVariantList>();
0220     QVariantList::const_iterator itr = newAlarms.constBegin();
0221     while (itr != newAlarms.constEnd()) {
0222         KCalendarCore::Alarm::Ptr newAlarm = event->newAlarm();
0223         QHash<QString, QVariant> newAlarmHashMap = (*itr).value<QHash<QString, QVariant>>();
0224         int startOffsetValue = newAlarmHashMap[QStringLiteral("startOffsetValue")].value<int>();
0225         int startOffsetType = newAlarmHashMap[QStringLiteral("startOffsetType")].value<int>();
0226         int actionType = newAlarmHashMap[QStringLiteral("actionType")].value<int>();
0227 
0228         qDebug() << "addEdit:\tAdding alarm with start offset value " << startOffsetValue;
0229         newAlarm->setStartOffset(KCalendarCore::Duration(startOffsetValue, static_cast<KCalendarCore::Duration::Type>(startOffsetType)));
0230         newAlarm->setType(static_cast<KCalendarCore::Alarm::Type>(actionType));
0231         newAlarm->setEnabled(true);
0232         newAlarm->setText((event->summary()).isEmpty() ?  event->description() : event->summary());
0233         ++itr;
0234     }
0235 
0236     ushort newPeriod = static_cast<ushort>(eventData[QStringLiteral("periodType")].toInt());
0237 
0238     //Bother with recurrences only if a recurrence has been found, either existing or new
0239     if ((event->recurrenceType() != KCalendarCore::Recurrence::rNone) || (newPeriod != KCalendarCore::Recurrence::rNone)) {
0240         //WORKAROUND: When changing an event from non-recurring to recurring, duplicate events are displayed
0241         if (!uid.isEmpty()) {
0242             calendar->deleteEvent(event);
0243         }
0244 
0245         switch (newPeriod) {
0246         case KCalendarCore::Recurrence::rYearlyDay:
0247         case KCalendarCore::Recurrence::rYearlyMonth:
0248         case KCalendarCore::Recurrence::rYearlyPos:
0249             event->recurrence()->setYearly(eventData[QStringLiteral("repeatEvery")].toInt());
0250             break;
0251         case KCalendarCore::Recurrence::rMonthlyDay:
0252         case KCalendarCore::Recurrence::rMonthlyPos:
0253             event->recurrence()->setMonthly(eventData[QStringLiteral("repeatEvery")].toInt());
0254             break;
0255         case KCalendarCore::Recurrence::rWeekly:
0256             event->recurrence()->setWeekly(eventData[QStringLiteral("repeatEvery")].toInt());
0257             break;
0258         case KCalendarCore::Recurrence::rDaily:
0259             event->recurrence()->setDaily(eventData[QStringLiteral("repeatEvery")].toInt());
0260             break;
0261         default:
0262             event->recurrence()->clear();
0263         }
0264 
0265         if (newPeriod != KCalendarCore::Recurrence::rNone) {
0266             int stopAfter = eventData[QStringLiteral("stopAfter")].toInt() > 0 ? eventData[QStringLiteral("stopAfter")].toInt() : -1;
0267             event->recurrence()->setDuration(stopAfter);
0268             event->recurrence()->setAllDay(allDayFlg);
0269         }
0270 
0271         if (!uid.isEmpty()) {
0272             calendar->addEvent(event);
0273         }
0274     }
0275 
0276     event->setStatus(eventData[QStringLiteral("status")].value<KCalendarCore::Incidence::Status>());
0277 
0278     if (uid.isEmpty()) {
0279         calendar->addEvent(event);
0280     }
0281 
0282     bool merged = CalendarController::instance().activeCalendar()->save();
0283     Q_EMIT CalendarController::instance().activeCalendar()->eventsChanged();
0284 
0285     qDebug() << "Event upsert: " << merged;
0286 }
0287 
0288 QDateTime CalendarController::localSystemDateTime() const
0289 {
0290     return QDateTime::currentDateTime();
0291 }
0292 
0293 QVariantMap CalendarController::validateEvent(const QVariantMap &eventMap) const
0294 {
0295     QVariantMap result {};
0296 
0297     QDate startDate = eventMap[QStringLiteral("startDate")].toDate();
0298     bool validStartHour {false};
0299     int startHour = eventMap[QStringLiteral("startHour")].toInt(&validStartHour);
0300     bool validStartMinutes {false};
0301     int startMinute = eventMap[QStringLiteral("startMinute")].toInt(&validStartMinutes);
0302     QDate endDate = eventMap[QStringLiteral("endDate")].toDate();
0303     bool validEndHour {false};
0304     int endHour = eventMap[QStringLiteral("endHour")].toInt(&validEndHour);
0305     bool validEndMinutes {false};
0306     int endMinutes = eventMap[QStringLiteral("endMinute")].toInt(&validEndMinutes);
0307     bool allDayFlg = eventMap[QStringLiteral("allDay")].toBool();
0308 
0309     if (startDate.isValid() && validStartHour && validStartMinutes && endDate.isValid() && validEndHour && validEndMinutes) {
0310         if (allDayFlg && (endDate != startDate)) {
0311             result[QStringLiteral("success")] = false;
0312             result[QStringLiteral("reason")] = i18n("In case of all day events, start date and end date should be equal");
0313 
0314             return result;
0315         }
0316 
0317         auto startDateTime = QDateTime(startDate, QTime(startHour, startMinute, 0, 0), QTimeZone::systemTimeZone());
0318         auto endDateTime = QDateTime(endDate, QTime(endHour, endMinutes, 0, 0), QTimeZone::systemTimeZone());
0319 
0320         if (!allDayFlg && (startDateTime > endDateTime)) {
0321             result[QStringLiteral("success")] = false;
0322             result[QStringLiteral("reason")] = i18n("End date time should be equal to or greater than the start date time");
0323             return result;
0324         }
0325 
0326         auto validPeriodType {false};
0327         auto periodType = static_cast<ushort>(eventMap[QStringLiteral("periodType")].toInt(&validPeriodType));
0328         auto eventDuration = startDateTime.secsTo(endDateTime);
0329         auto validRepeatEvery {false};
0330         auto repeatEvery = static_cast<ushort>(eventMap[QStringLiteral("repeatEvery")].toInt(&validRepeatEvery));
0331 
0332         if (validPeriodType && (periodType == KCalendarCore::Recurrence::rDaily) && validRepeatEvery && (repeatEvery == 1) && eventDuration > 86400) {
0333             result[QStringLiteral("success")] = false;
0334             result[QStringLiteral("reason")] = i18n("Daily events should not span multiple days");
0335             return result;
0336         }
0337     }
0338 
0339     result[QStringLiteral("success")] = true;
0340     result[QStringLiteral("reason")] = QString();
0341 
0342     return result;
0343 
0344 }
0345 
0346 void CalendarController::upsertTodo(LocalCalendar *localCalendar, const QVariantMap &todo)
0347 {
0348     KCalendarCore::Calendar::Ptr calendar = localCalendar->calendar();
0349     KCalendarCore::Todo::Ptr vtodo;
0350     QDateTime now = QDateTime::currentDateTime();
0351     QString uid = todo[QStringLiteral("uid")].toString();
0352     QString summary = todo[QStringLiteral("summary")].toString();
0353     QDate startDate = todo[QStringLiteral("startDate")].toDate();
0354     int startHour = todo[QStringLiteral("startHour")].value<int>();
0355     int startMinute = todo[QStringLiteral("startMinute")].value<int>();
0356     bool allDayFlg = todo[QStringLiteral("allDay")].toBool();
0357 
0358     if (uid.isEmpty()) {
0359         vtodo = KCalendarCore::Todo::Ptr(new KCalendarCore::Todo());
0360         vtodo->setUid(summary.at(0) + now.toString(QStringLiteral("yyyyMMddhhmmsszzz")));
0361     } else {
0362         vtodo = calendar->todo(uid);
0363         vtodo->setUid(uid);
0364     }
0365 
0366     QDateTime startDateTime;
0367     if (allDayFlg) {
0368         startDateTime = startDate.startOfDay();
0369     } else {
0370         startDateTime = QDateTime(startDate, QTime(startHour, startMinute, 0, 0), QTimeZone::systemTimeZone());
0371     }
0372 
0373     vtodo->setDtStart(startDateTime);
0374 
0375     QDate dueDate = todo[QStringLiteral("dueDate")].toDate();
0376 
0377     bool validDueHour {false};
0378     int dueHour = todo[QStringLiteral("dueHour")].toInt(&validDueHour);
0379 
0380     bool validDueMinutes {false};
0381     int dueMinute = todo[QStringLiteral("dueMinute")].toInt(&validDueMinutes);
0382 
0383     QDateTime dueDateTime = QDateTime();
0384     if (dueDate.isValid() && validDueHour && validDueMinutes && !allDayFlg) {
0385         dueDateTime = QDateTime(dueDate, QTime(dueHour, dueMinute, 0, 0), QTimeZone::systemTimeZone());
0386     } else if (dueDate.isValid() && allDayFlg) {
0387         dueDateTime = dueDate.startOfDay();
0388     }
0389 
0390     vtodo->setDtDue(dueDateTime);
0391     vtodo->setDescription(todo[QStringLiteral("description")].toString());
0392     vtodo->setSummary(summary);
0393     vtodo->setAllDay((startDate.isValid() || dueDate.isValid()) ? allDayFlg : false);
0394     vtodo->setLocation(todo[QStringLiteral("location")].toString());
0395     vtodo->setCompleted(todo[QStringLiteral("completed")].toBool());
0396 
0397     vtodo->clearAlarms();
0398     QVariantList newAlarms = todo[QStringLiteral("alarms")].value<QVariantList>();
0399     QVariantList::const_iterator itr = newAlarms.constBegin();
0400     while (itr != newAlarms.constEnd()) {
0401         KCalendarCore::Alarm::Ptr newAlarm = vtodo->newAlarm();
0402         QHash<QString, QVariant> newAlarmHashMap = (*itr).value<QHash<QString, QVariant>>();
0403         int startOffsetValue = newAlarmHashMap[QStringLiteral("startOffsetValue")].value<int>();
0404         int startOffsetType = newAlarmHashMap[QStringLiteral("startOffsetType")].value<int>();
0405         int actionType = newAlarmHashMap[QStringLiteral("actionType")].value<int>();
0406 
0407         newAlarm->setStartOffset(KCalendarCore::Duration(startOffsetValue, static_cast<KCalendarCore::Duration::Type>(startOffsetType)));
0408         newAlarm->setType(static_cast<KCalendarCore::Alarm::Type>(actionType));
0409         newAlarm->setEnabled(true);
0410         newAlarm->setText((vtodo->summary()).isEmpty() ?  vtodo->description() : vtodo->summary());
0411         ++itr;
0412     }
0413 
0414     calendar->addTodo(vtodo);
0415     bool merged = localCalendar->save();
0416 
0417     Q_EMIT localCalendar->todosChanged();
0418 
0419     qDebug() << "Todo upsert: " << merged;
0420 }
0421 
0422 void CalendarController::removeTodo(LocalCalendar *localCalendar, const QVariantMap &todo)
0423 {
0424     KCalendarCore::Calendar::Ptr calendar = localCalendar->calendar();
0425     QString uid = todo[QStringLiteral("uid")].toString();
0426     KCalendarCore::Todo::Ptr vtodo = calendar->todo(uid);
0427 
0428     calendar->deleteTodo(vtodo);
0429     bool removed = localCalendar->save();
0430 
0431     Q_EMIT localCalendar->todosChanged();
0432     qDebug() << "Todo deleted: " << removed;
0433 }
0434 
0435 QVariantMap CalendarController::validateTodo(const QVariantMap &todo) const
0436 {
0437     QVariantMap result {};
0438 
0439     QDate startDate = todo[QStringLiteral("startDate")].toDate();
0440     bool validStartHour {false};
0441     int startHour = todo[QStringLiteral("startHour")].toInt(&validStartHour);
0442     bool validStartMinutes {false};
0443     int startMinute = todo[QStringLiteral("startMinute")].toInt(&validStartMinutes);
0444     QDate dueDate = todo[QStringLiteral("dueDate")].toDate();
0445     bool validDueHour {false};
0446     int dueHour = todo[QStringLiteral("dueHour")].toInt(&validDueHour);
0447     bool validDueMinutes {false};
0448     int dueMinute = todo[QStringLiteral("dueMinute")].toInt(&validDueMinutes);
0449     bool allDayFlg = todo[QStringLiteral("allDay")].toBool();
0450 
0451     if (startDate.isValid() && validStartHour && validStartMinutes && dueDate.isValid() && validDueHour && validDueMinutes) {
0452         if (allDayFlg && (dueDate != startDate)) {
0453             result[QStringLiteral("success")] = false;
0454             result[QStringLiteral("reason")] = i18n("In case of all day tasks, start date and due date should be equal");
0455 
0456             return result;
0457         }
0458 
0459         if (!allDayFlg && (QDateTime(startDate, QTime(startHour, startMinute, 0, 0), QTimeZone::systemTimeZone()) >  QDateTime(dueDate, QTime(dueHour, dueMinute, 0, 0), QTimeZone::systemTimeZone()))) {
0460             result[QStringLiteral("success")] = false;
0461             result[QStringLiteral("reason")] = i18n("Due date time should be equal to or greater than the start date time");
0462             return result;
0463         }
0464     }
0465 
0466     result[QStringLiteral("success")] = true;
0467     result[QStringLiteral("reason")] = QString();
0468 
0469     return result;
0470 }
0471 
0472 QString CalendarController::fileNameFromUrl(const QUrl &sourcePath)
0473 {
0474     return sourcePath.fileName();
0475 }
0476 
0477 QVariantMap CalendarController::exportData(const QString &calendarName)
0478 {
0479     auto filePath = CalindoriConfig::instance().calendarFile(calendarName);
0480     QFile calendarFile {filePath};
0481     if (!calendarFile.exists()) {
0482         return {
0483             { QStringLiteral("success"), false },
0484             { QStringLiteral("reason"), i18n("Cannot read calendar. Export failed.") }
0485         };
0486     }
0487 
0488     KCalendarCore::Calendar::Ptr calendar {new KCalendarCore::MemoryCalendar(QTimeZone::systemTimeZoneId())};
0489     KCalendarCore::FileStorage::Ptr storage {new KCalendarCore::FileStorage {calendar}};
0490     storage->setFileName(filePath);
0491     if (!storage->load()) {
0492         return {
0493             { QStringLiteral("success"), false },
0494             { QStringLiteral("reason"), i18n("Cannot load calendar. Export failed.") }
0495         };
0496     }
0497 
0498     auto dirPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
0499     QFile targetFile {dirPath + QStringLiteral("/calindori_") + calendarName + QStringLiteral(".ics")};
0500     auto fileSuffix {1};
0501     while (targetFile.exists()) {
0502         targetFile.setFileName(dirPath + QStringLiteral("/calindori_") + calendarName + QStringLiteral("(") + QString::number(fileSuffix++) + QStringLiteral(").ics"));
0503     }
0504 
0505     storage->setFileName(targetFile.fileName());
0506     if (!(storage->save())) {
0507         return {
0508             { QStringLiteral("success"), false },
0509             { QStringLiteral("reason"), i18n("Cannot save calendar file. Export failed.") }
0510         };
0511 
0512     }
0513 
0514     return {
0515         { QStringLiteral("success"), true },
0516         { QStringLiteral("reason"), i18n("Export completed successfully") },
0517         { QStringLiteral("targetFolder"), QUrl {QStringLiteral("file://") + dirPath} }
0518     };
0519 }
0520 
0521 LocalCalendar *CalendarController::activeCalendar() const
0522 {
0523     return m_calendar.get();
0524 }