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

0001 /*
0002  * SPDX-FileCopyrightText: 2020 Dimitris Kardarakos <dimkard@posteo.net>
0003  *
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  */
0006 
0007 #include "calalarmclient.h"
0008 #include "alarmnotification.h"
0009 #include "alarmsmodel.h"
0010 #include "notificationhandler.h"
0011 #include "calindacadaptor.h"
0012 #include "solidwakeupbackend.h"
0013 #include "wakeupmanager.h"
0014 #include <KSharedConfig>
0015 #include <KConfigGroup>
0016 #include <QDebug>
0017 #include <QVariantMap>
0018 #include <QDateTime>
0019 #include <KLocalizedString>
0020 
0021 using namespace KCalendarCore;
0022 
0023 CalAlarmClient::CalAlarmClient(QObject *parent)
0024     : QObject(parent), m_alarms_model {new AlarmsModel(this)}, m_notification_handler {new NotificationHandler(this)}, m_wakeup_manager {new WakeupManager(this)}
0025 {
0026     new CalindacAdaptor(this);
0027 
0028     QDBusConnection::sessionBus().registerObject(QStringLiteral("/calindac"), this);
0029 
0030     KConfigGroup generalGroup(KSharedConfig::openConfig(), QStringLiteral("General"));
0031     m_check_interval = generalGroup.readEntry("CheckInterval", 45);
0032     m_suspend_seconds = generalGroup.readEntry("SuspendSeconds", 60);
0033     m_last_check = generalGroup.readEntry("CalendarsLastChecked", QDateTime());
0034 
0035     qDebug() << "CalAlarmClient:lastChecked:" << m_last_check.toString(QStringLiteral("dd.MM.yyyy hh:mm:ss"));
0036 
0037     restoreSuspendedFromConfig();
0038     saveCheckInterval();
0039     saveSuspendSeconds();
0040     checkAlarms();
0041 
0042     connect(m_notification_handler, &NotificationHandler::scheduleAlarmCheck, this, &CalAlarmClient::scheduleAlarmCheck);
0043     connect(&m_check_timer, &QTimer::timeout, this, &CalAlarmClient::checkAlarms);
0044     connect(m_wakeup_manager, &WakeupManager::wakeupAlarmClient, this, &CalAlarmClient::wakeupCallback);
0045     connect(m_wakeup_manager, &WakeupManager::activeChanged, this, &CalAlarmClient::setupShceduler);
0046     setupShceduler((m_wakeup_manager != nullptr) && (m_wakeup_manager->active()));
0047 }
0048 
0049 CalAlarmClient::~CalAlarmClient() = default;
0050 
0051 QStringList CalAlarmClient::calendarFileList() const
0052 {
0053     auto filesList { QStringList() };
0054     KConfigGroup calindoriCfgGeneral(KSharedConfig::openConfig(QStringLiteral("calindorirc")), QStringLiteral("general"));
0055     auto iCalendars = calindoriCfgGeneral.readEntry("calendars", QString());
0056     auto eCalendars = calindoriCfgGeneral.readEntry("externalCalendars", QString());
0057 
0058     auto calendarsList = iCalendars.isEmpty() ? QStringList() : iCalendars.split(QStringLiteral(";"));
0059     if (!(eCalendars.isEmpty())) {
0060         calendarsList.append(eCalendars.split(QStringLiteral(";")));
0061     }
0062 
0063     for (const auto &c : qAsConst(calendarsList)) {
0064         QString fileName = KSharedConfig::openConfig(QStringLiteral("calindorirc"))->group(c).readEntry("file");
0065 
0066         if (!(fileName.isNull())) {
0067             filesList.append(fileName);
0068         }
0069     }
0070 
0071     return filesList;
0072 }
0073 
0074 void CalAlarmClient::checkAlarms()
0075 {
0076     KConfigGroup cfg(KSharedConfig::openConfig(), QStringLiteral("General"));
0077 
0078     if (!cfg.readEntry("Enabled", true)) {
0079         return;
0080     }
0081 
0082     auto checkFrom = m_last_check.addSecs(1);
0083     m_last_check = QDateTime::currentDateTime();
0084 
0085     qDebug() << "\ncheckAlarms:Check:" << checkFrom.toString() << " -" << m_last_check.toString();
0086 
0087     FilterPeriod fPeriod { .from = checkFrom, .to = m_last_check };
0088     m_alarms_model->setCalendarFiles(calendarFileList());
0089     m_alarms_model->setPeriod(fPeriod);
0090     m_notification_handler->setPeriod(fPeriod);
0091 
0092     auto alarms = m_alarms_model->alarms();
0093     qDebug() << "checkAlarms:Alarms Found: " << alarms.count();
0094 
0095     for (const auto &alarm : qAsConst(alarms)) {
0096         m_notification_handler->addActiveNotification(alarm->parentUid(), QStringLiteral("%1\n%2").arg(alarm->time().toString(QStringLiteral("hh:mm")), alarm->text()));
0097     }
0098     m_notification_handler->sendNotifications();
0099     saveLastCheckTime();
0100     flushSuspendedToConfig();
0101 }
0102 
0103 void CalAlarmClient::saveLastCheckTime()
0104 {
0105     KConfigGroup generalGroup(KSharedConfig::openConfig(), QStringLiteral("General"));
0106     generalGroup.writeEntry("CalendarsLastChecked", m_last_check);
0107     KSharedConfig::openConfig()->sync();
0108 }
0109 
0110 void CalAlarmClient::saveCheckInterval()
0111 {
0112     KConfigGroup generalGroup(KSharedConfig::openConfig(), QStringLiteral("General"));
0113     generalGroup.writeEntry("CheckInterval", m_check_interval);
0114     KSharedConfig::openConfig()->sync();
0115 }
0116 
0117 void CalAlarmClient::saveSuspendSeconds()
0118 {
0119     KConfigGroup generalGroup(KSharedConfig::openConfig(), QStringLiteral("General"));
0120     generalGroup.writeEntry("SuspendSeconds", m_suspend_seconds);
0121     KSharedConfig::openConfig()->sync();
0122 }
0123 
0124 void CalAlarmClient::quit()
0125 {
0126     flushSuspendedToConfig();
0127     saveLastCheckTime();
0128     qDebug("\nquit");
0129     qApp->quit();
0130 }
0131 
0132 void CalAlarmClient::forceAlarmCheck()
0133 {
0134     checkAlarms();
0135     saveLastCheckTime();
0136 }
0137 
0138 QString CalAlarmClient::dumpLastCheck() const
0139 {
0140     KConfigGroup cfg(KSharedConfig::openConfig(), QStringLiteral("General"));
0141     const QDateTime lastChecked = cfg.readEntry("CalendarsLastChecked", QDateTime());
0142 
0143     return QStringLiteral("Last Check: %1").arg(lastChecked.toString());
0144 }
0145 
0146 QStringList CalAlarmClient::dumpAlarms() const
0147 {
0148     const auto start = QDateTime(QDate::currentDate(), QTime(0, 0), Qt::LocalTime);
0149     const auto end = start.date().endOfDay();
0150 
0151     AlarmsModel model {};
0152     model.setCalendarFiles(calendarFileList());
0153     model.setPeriod({.from =  start, .to = end});
0154 
0155     auto lst = QStringList();
0156     const auto alarms = model.alarms();
0157 
0158     for (const auto &alarm : qAsConst(alarms)) {
0159         lst << QStringLiteral("%1: \"%2\"").arg(alarm->time().toString(QStringLiteral("hh:mm")), alarm->parentUid());
0160     }
0161 
0162     return lst;
0163 }
0164 
0165 void CalAlarmClient::restoreSuspendedFromConfig()
0166 {
0167     qDebug() << "\nrestoreSuspendedFromConfig:Restore suspended alarms from config";
0168     KConfigGroup suspendedGroup(KSharedConfig::openConfig(), QStringLiteral("Suspended"));
0169     const auto suspendedAlarms = suspendedGroup.groupList();
0170 
0171     for (const auto &s : suspendedAlarms) {
0172         KConfigGroup suspendedAlarm(&suspendedGroup, s);
0173         QString uid = suspendedAlarm.readEntry("UID");
0174         QString txt = alarmText(uid);
0175         QDateTime remindAt = QDateTime::fromString(suspendedAlarm.readEntry("RemindAt"), QStringLiteral("yyyy,M,d,HH,m,s"));
0176         qDebug() << "restoreSuspendedFromConfig:Restoring alarm" << uid << "," << txt << "," << remindAt.toString();
0177 
0178         if (!(uid.isEmpty() && remindAt.isValid() && !(txt.isEmpty()))) {
0179             m_notification_handler->addSuspendedNotification(uid, txt, remindAt);
0180         }
0181     }
0182 }
0183 
0184 QString CalAlarmClient::alarmText(const QString &uid) const
0185 {
0186     AlarmsModel model {};
0187     model.setCalendarFiles(calendarFileList());
0188     model.setPeriod({.from = QDateTime(), .to = QDateTime::currentDateTime()});
0189     const auto alarms = model.alarms();
0190 
0191     for (const auto &alarm : qAsConst(alarms)) {
0192         if (alarm->parentUid() == uid) {
0193             return alarm->text();
0194         }
0195     }
0196 
0197     return QString();
0198 }
0199 
0200 void CalAlarmClient::flushSuspendedToConfig()
0201 {
0202     KConfigGroup suspendedGroup(KSharedConfig::openConfig(), QStringLiteral("Suspended"));
0203     suspendedGroup.deleteGroup();
0204 
0205     const auto suspendedNotifications = m_notification_handler->suspendedNotifications();
0206 
0207     if (suspendedNotifications.isEmpty()) {
0208         qDebug() << "flushSuspendedToConfig:No suspended notification exists, nothing to write to config";
0209         KSharedConfig::openConfig()->sync();
0210 
0211         return;
0212     }
0213 
0214     for (const auto &s : suspendedNotifications) {
0215         qDebug() << "flushSuspendedToConfig:Flushing suspended alarm" << s->uid() << " to config";
0216         KConfigGroup notificationGroup(&suspendedGroup, s->uid());
0217         notificationGroup.writeEntry("UID", s->uid());
0218         notificationGroup.writeEntry("RemindAt", s->remindAt());
0219     }
0220     KSharedConfig::openConfig()->sync();
0221 }
0222 
0223 void CalAlarmClient::scheduleAlarmCheck()
0224 {
0225     if ((m_wakeup_manager == nullptr) || !(m_wakeup_manager->active())) {
0226         qDebug() << "Wakeup manager is not active, alarms are handled by a timer";
0227         return;
0228     }
0229 
0230     // Look for alarms over the next days
0231     AlarmsModel model {};
0232     model.setCalendarFiles(calendarFileList());
0233     model.setPeriod({.from = m_last_check.addSecs(1), .to = (m_last_check.addDays(15)).date().startOfDay()});
0234 
0235     // Next schedule time: the trigger time of the first alarm or scheduled notification
0236     QDateTime scheduleWakeupAt {};
0237     auto firstAlarmTime = model.firstAlarmTime();
0238     auto firstSuspendedTime = m_notification_handler->firstSuspended();
0239 
0240     if (firstAlarmTime.isValid() && firstSuspendedTime.isValid()) {
0241         scheduleWakeupAt = (firstAlarmTime < firstSuspendedTime) ? firstAlarmTime : firstSuspendedTime;
0242     } else {
0243         scheduleWakeupAt = firstAlarmTime.isValid() ? firstAlarmTime : firstSuspendedTime;
0244     }
0245 
0246     if (scheduleWakeupAt.isValid()) {
0247         qDebug() << "scheduleAlarmCheck:" << "Shecdule next alarm check at" << scheduleWakeupAt.addSecs(1).toString(QStringLiteral("dd.MM.yyyy hh:mm:ss"));
0248         m_wakeup_manager->scheduleWakeup(scheduleWakeupAt.addSecs(1));
0249     } else { // If no alarms/suspended notifications exist, do not schedule anything and remove any scheduled wakeup call.
0250         qDebug() << "scheduleAlarmCheck:" << "Cancel scheduled wake up";
0251         m_wakeup_manager->removeWakeup();
0252     }
0253 }
0254 
0255 void CalAlarmClient::wakeupCallback()
0256 {
0257     qDebug() << "CalAlarmClient wakeupCallback";
0258 
0259     checkAlarms();
0260     scheduleAlarmCheck();
0261 }
0262 
0263 void CalAlarmClient::setupShceduler(const bool wakeupManagerActive)
0264 {
0265     if (wakeupManagerActive && m_wakeup_manager->hasWakeupFeatures()) {
0266         qDebug() << "setupShceduler: wake up manager offers an active backend with wakeup features";
0267         if (m_check_timer.isActive()) {
0268             m_check_timer.stop();
0269         }
0270         scheduleAlarmCheck();
0271     } else {
0272         qDebug() << "setupShceduler: No wakeup backend, alarms will be checked by a timer";
0273         if (!m_check_timer.isActive()) {
0274             m_check_timer.start(1000 * m_check_interval);
0275         }
0276     }
0277 }