File indexing completed on 2024-04-21 05:50:56

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 <QDebug>
0010 #include <QDir>
0011 #include <QFile>
0012 #include <QNetworkRequest>
0013 #include <QNetworkReply>
0014 #include <KLocalizedString>
0015 #include <KConfig>
0016 #include <KConfigGroup>
0017 
0018 class CalendarController::Private
0019 {
0020 public:
0021     Private() : config {"kongressrc"}  {};
0022     KConfig config;
0023     QNetworkAccessManager *nam = nullptr;
0024 };
0025 
0026 CalendarController::CalendarController(QObject *parent)
0027     : QObject {parent}, m_storages {QMap<QString, KCalendarCore::FileStorage::Ptr> {}}, m_calendars {QMap<QString, KCalendarCore::MemoryCalendar::Ptr>{}}, d {new Private}
0028 {
0029     loadSavedConferences();
0030 }
0031 
0032 void CalendarController::setNetworkAccessManager(QNetworkAccessManager *nam)
0033 {
0034     d->nam = nam;
0035 }
0036 
0037 QString CalendarController::calendars() const
0038 {
0039     return d->config.group("general").readEntry("calendars", QString {});
0040 }
0041 
0042 void CalendarController::removeCalendarFromConfig(const QString &calendarId)
0043 {
0044     d->config.reparseConfiguration();
0045     auto calendarsList = d->config.group("general").readEntry("calendars", QString {}).split(";");
0046     if (calendarsList.contains(calendarId)) {
0047         qDebug() << "Removing calendar " << calendarId;
0048         calendarsList.removeAll(calendarId);
0049 
0050         d->config.deleteGroup(calendarId);
0051         d->config.group("general").writeEntry("calendars", calendarsList.join(";"));
0052         d->config.sync();
0053 
0054         Q_EMIT calendarsChanged();
0055     }
0056 }
0057 
0058 QString CalendarController::calendarFile(const QString &calendarId)
0059 {
0060     if (d->config.hasGroup(calendarId) && d->config.group(calendarId).hasKey("file")) {
0061         return  d->config.group(calendarId).readEntry("file");
0062     }
0063     d->config.group(calendarId).writeEntry("file", filenameToPath(calendarId));
0064     d->config.sync();
0065 
0066     return filenameToPath(calendarId);
0067 }
0068 
0069 QString CalendarController::filenameToPath(const QString &calendarId)
0070 {
0071     auto basePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
0072     QDir baseFolder {basePath};
0073     baseFolder.mkpath(QStringLiteral("."));
0074 
0075     return basePath + "/kongress_" + calendarId + ".ics";
0076 }
0077 
0078 KCalendarCore::MemoryCalendar::Ptr CalendarController::createLocalCalendar(const QString &calendarId, const QByteArray &timeZoneId)
0079 {
0080     auto m_fullpath = calendarFile(calendarId);
0081     auto tzId = QTimeZone::availableTimeZoneIds().contains(timeZoneId) ? timeZoneId : QTimeZone::systemTimeZoneId();
0082 
0083     KCalendarCore::MemoryCalendar::Ptr calendar {new KCalendarCore::MemoryCalendar {tzId}};
0084     qDebug() << "Memory calendar " << calendarId << "(local) with timezone " << tzId << " has been created";
0085     KCalendarCore::FileStorage::Ptr storage {new KCalendarCore::FileStorage {calendar}};
0086     storage->setFileName(m_fullpath);
0087     QFile calendarFile {m_fullpath};
0088 
0089     if (!calendarFile.exists()) {
0090         qDebug() << "Creating file" << storage->save();
0091     }
0092 
0093     if (storage->load()) {
0094         m_storages[calendarId] = storage;
0095         m_calendars[calendarId] = calendar;
0096     }
0097 
0098     Q_EMIT calendarsChanged();
0099 
0100     return calendar;
0101 }
0102 
0103 void CalendarController::deleteCalendar(const QString &calendarId)
0104 {
0105     qDebug() << "Deleting calendar " << calendarId;
0106 
0107     auto fileName = m_storages[calendarId]->fileName();
0108 
0109     QFile calendarFile {fileName};
0110 
0111     if (calendarFile.exists()) {
0112         calendarFile.remove();
0113     }
0114 
0115     m_storages.remove(calendarId);
0116     m_calendars.remove(calendarId);
0117 
0118     removeCalendarFromConfig(calendarId);
0119 
0120     Q_EMIT calendarsChanged();
0121 }
0122 
0123 bool CalendarController::save(const QString &calendarId)
0124 {
0125     if (m_storages[calendarId]->save()) {
0126         Q_EMIT calendarsChanged();
0127 
0128         return true;
0129     }
0130 
0131     return false;
0132 }
0133 
0134 void CalendarController::createCalendarFromUrl(const QString &calendarId, const QUrl &url, const QByteArray &timeZoneId)
0135 {
0136     if (calendarId.isEmpty() || url.isEmpty()) {
0137         return;
0138     }
0139     qDebug() << "createCalendarFromUrl: calendarId " << calendarId << ", url" << url;
0140     Q_EMIT downlading(calendarId, true);
0141 
0142     auto filePath = calendarFile(calendarId);
0143 
0144     QNetworkRequest request {url};
0145     auto reply = d->nam->get(request);
0146 
0147     connect(reply, &QNetworkReply::finished, [this, reply, filePath, calendarId, timeZoneId]() {
0148         if (reply->error() == QNetworkReply::NoError) {
0149             if (saveToDisk(filePath, reply->readAll())) {
0150                 downloadFinished(calendarId, timeZoneId, filePath);
0151             }
0152         }
0153         reply->deleteLater();
0154         Q_EMIT downlading(calendarId, false);
0155     });
0156 
0157 }
0158 
0159 void CalendarController::downloadFinished(const QString &calendarId, const QByteArray &timeZoneId, const QString &filePath)
0160 {
0161     auto availableTimezones = QTimeZone::availableTimeZoneIds();
0162     auto tz = availableTimezones.contains(timeZoneId) ? timeZoneId : QTimeZone::systemTimeZoneId();
0163     addTzIdToConfig(calendarId, tz);
0164 
0165     KCalendarCore::MemoryCalendar::Ptr calendar {new KCalendarCore::MemoryCalendar {tz}};
0166     qDebug() << "Memory calendar " << calendarId << " (online) with timezone " << tz << " has been created";
0167     KCalendarCore::FileStorage::Ptr storage {new KCalendarCore::FileStorage {calendar}};
0168     storage->setFileName(filePath);
0169 
0170     if (storage->load()) {
0171         m_storages[calendarId] = storage;
0172         m_calendars[calendarId] = calendar;
0173     }
0174 
0175     addConferenceToConfig(calendarId);
0176 
0177     Q_EMIT calendarsChanged();
0178     Q_EMIT calendarDownloaded(calendarId);
0179 }
0180 
0181 bool CalendarController::saveToDisk(const QString &filename, const QByteArray &data)
0182 {
0183     QFile file {filename};
0184     if (!file.open(QIODevice::WriteOnly)) {
0185         qDebug() << (QString {"Could not open %1 for writing: %2"}).arg(qPrintable(filename), qPrintable(file.errorString()));
0186         return false;
0187     }
0188     file.write(data);
0189     file.close();
0190 
0191     return true;
0192 }
0193 
0194 KCalendarCore::MemoryCalendar::Ptr CalendarController::memoryCalendar(const QString &calendarId) const
0195 {
0196     if (m_calendars.contains(calendarId)) {
0197         return m_calendars[calendarId];
0198     }
0199 
0200     return nullptr;
0201 }
0202 
0203 void CalendarController::addConferenceToConfig(const QString &calendarId)
0204 {
0205 
0206     const QStringList calendarLists {"conferenceCalendars", "favoritesCalendars"};
0207 
0208     for (const auto &calendarList : calendarLists) {
0209         const auto calendarName = (calendarList == "conferenceCalendars") ? calendarId : (QString {"%1_%2"}).arg("favorites", calendarId);
0210 
0211         if (d->config.group("general").readEntry(calendarList, QString {}).isEmpty()) {
0212             d->config.group("general").writeEntry(calendarList, calendarName);
0213             d->config.sync();
0214 
0215             return;
0216         }
0217 
0218         auto calendarsList = d->config.group("general").readEntry(calendarList, QString {}).split(";");
0219         if (!calendarsList.contains(calendarName)) {
0220             calendarsList.append(calendarName);
0221             d->config.group("general").writeEntry(calendarList, calendarsList.join(";"));
0222             d->config.sync();
0223         }
0224     }
0225 }
0226 
0227 void CalendarController::loadSavedConferences()
0228 {
0229     auto onlineCalendarIds = d->config.group("general").readEntry("conferenceCalendars", QString {});
0230     if (onlineCalendarIds.isEmpty()) {
0231         return;
0232     }
0233 
0234     auto calendarsList = onlineCalendarIds.split(";");
0235 
0236     for (const auto &calendarId : calendarsList) {
0237         auto filePath = d->config.group(calendarId).readEntry("file", QString {});
0238         QFile calendarFile {filePath};
0239 
0240         if (!calendarFile.exists()) {
0241             continue;
0242         }
0243 
0244         auto tz = tzIdFromConfig(calendarId);
0245         KCalendarCore::MemoryCalendar::Ptr calendar {new KCalendarCore::MemoryCalendar {tz}};
0246         KCalendarCore::FileStorage::Ptr storage {new KCalendarCore::FileStorage {calendar}};
0247         storage->setFileName(filePath);
0248 
0249         if (storage->load()) {
0250             m_storages[calendarId] = storage;
0251             m_calendars[calendarId] = calendar;
0252 
0253             qDebug() << "Calendar " << calendarId << " loaded succesfully";
0254         }
0255     }
0256 }
0257 
0258 void CalendarController::addTzIdToConfig(const QString &calendarId, const QByteArray &timeZoneId)
0259 {
0260     if (!(d->config.hasGroup(calendarId))) {
0261         return;
0262     }
0263 
0264     d->config.group(calendarId).writeEntry("timeZoneId", timeZoneId);
0265     d->config.sync();
0266 }
0267 
0268 QByteArray CalendarController::tzIdFromConfig(const QString &calendarId) const
0269 {
0270     if (!(d->config.hasGroup(calendarId))) {
0271         return QByteArray {};
0272     }
0273 
0274     if (d->config.hasGroup(calendarId) && !(d->config.group(calendarId).hasKey("timeZoneId"))) {
0275         return QByteArray {};
0276     }
0277 
0278     return d->config.group(calendarId).readEntry("timeZoneId").toUtf8();
0279 }
0280 
0281 QVariantMap CalendarController::exportData(const QString &calendarName, LocalCalendar *sourceCalendar)
0282 {
0283     auto sourceEvents = sourceCalendar->memorycalendar()->rawEvents();
0284 
0285     auto dirPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
0286     QFile targetFile {dirPath + "/kongress_favorites_" + calendarName + ".ics"};
0287     auto fileSuffix {1};
0288     while (targetFile.exists()) {
0289         targetFile.setFileName(dirPath + "/kongress_favorites_" + calendarName + "(" + QString::number(fileSuffix++) + ").ics");
0290     }
0291     KCalendarCore::Calendar::Ptr targetCalendar {new KCalendarCore::MemoryCalendar(QTimeZone::systemTimeZoneId())};
0292     KCalendarCore::FileStorage::Ptr targetStorage {new KCalendarCore::FileStorage {targetCalendar}};
0293     targetStorage->setFileName(targetFile.fileName());
0294 
0295     for (const auto &event : std::as_const(sourceEvents)) {
0296         targetCalendar->addEvent(event);
0297     }
0298 
0299     if (!(targetStorage->save())) {
0300         return {
0301             { "success", false },
0302             { "reason", i18n("Cannot save calendar file. Export failed.") }
0303         };
0304     }
0305 
0306     return {
0307         { "success", true },
0308         { "reason", i18n("Export completed successfully") },
0309         { "targetFolder", QUrl {QStringLiteral("file://") + dirPath} }
0310     };
0311 }
0312 
0313 #include "moc_calendarcontroller.cpp"