File indexing completed on 2024-11-17 05:15:20

0001 /*
0002  * Copyright 2020 Han Young <hanyoung@protonmail.com>
0003  * Copyright 2020-2021 Devin Lin <devin@kde.org>
0004  *
0005  * SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "alarmmodel.h"
0009 
0010 #include "alarmmodeladaptor.h"
0011 #include "utilities.h"
0012 
0013 #include <KConfigGroup>
0014 #include <KLocalizedString>
0015 #include <KSharedConfig>
0016 
0017 #include <QDBusConnection>
0018 #include <QDBusReply>
0019 #include <QDebug>
0020 #include <QLocale>
0021 
0022 AlarmModel *AlarmModel::instance()
0023 {
0024     static AlarmModel *singleton = new AlarmModel();
0025     return singleton;
0026 }
0027 
0028 AlarmModel::AlarmModel(QObject *parent)
0029     : QObject{parent}
0030 {
0031     // DBus
0032     new AlarmModelAdaptor(this);
0033     QDBusConnection::sessionBus().registerObject(QStringLiteral("/Alarms"), this);
0034 
0035     // load alarms from config
0036     load();
0037 
0038     // update notify icon in systemtray
0039     connect(this, &AlarmModel::nextAlarm, this, &AlarmModel::updateNotifierItem);
0040 
0041     // alarm wakeup behaviour
0042     connect(&Utilities::instance(), &Utilities::wakeup, this, &AlarmModel::wakeupCallback);
0043     connect(&Utilities::instance(), &Utilities::needsReschedule, this, &AlarmModel::scheduleAlarm);
0044 }
0045 
0046 void AlarmModel::load()
0047 {
0048     auto config = KSharedConfig::openConfig();
0049     KConfigGroup group = config->group(ALARM_CFG_GROUP);
0050     QStringList list = group.keyList();
0051 
0052     for (const QString &key : list) {
0053         QString json = group.readEntry(key, QString());
0054         if (!json.isEmpty()) {
0055             Alarm *alarm = new Alarm(json, this);
0056             m_alarmsList.append(alarm);
0057         }
0058     }
0059 }
0060 
0061 void AlarmModel::save()
0062 {
0063     std::for_each(m_alarmsList.begin(), m_alarmsList.end(), [](Alarm *alarm) {
0064         alarm->save();
0065     });
0066 }
0067 
0068 void AlarmModel::configureWakeups()
0069 {
0070     // start alarm polling
0071     scheduleAlarm();
0072 }
0073 
0074 quint64 AlarmModel::getNextAlarm()
0075 {
0076     return m_nextAlarmTime;
0077 }
0078 
0079 void AlarmModel::scheduleAlarm()
0080 {
0081     // if there are no alarms, return
0082     if (m_alarmsList.isEmpty()) {
0083         m_nextAlarmTime = 0;
0084         Q_EMIT nextAlarm(0);
0085         return;
0086     }
0087 
0088     alarmsToRing.clear();
0089 
0090     // get the next minimum time for a wakeup (next alarm ring), and add alarms that will needed to be woken up to the list
0091     quint64 minTime = std::numeric_limits<quint64>::max();
0092     for (auto *alarm : std::as_const(m_alarmsList)) {
0093         if (alarm->nextRingTime() > 0) {
0094             if (alarm->nextRingTime() == minTime) {
0095                 alarmsToRing.append(alarm);
0096             } else if (alarm->nextRingTime() < minTime) {
0097                 alarmsToRing.clear();
0098                 alarmsToRing.append(alarm);
0099                 minTime = alarm->nextRingTime();
0100             }
0101         }
0102     }
0103 
0104     int activeAlarmCount = alarmsToRing.size();
0105     while (activeAlarmCount--) {
0106         Utilities::instance().incfActiveCount();
0107     }
0108 
0109     // if there is an alarm that needs to rung
0110     if (minTime != std::numeric_limits<quint64>::max()) {
0111         qDebug() << "Scheduled alarm wakeup at" << QDateTime::fromSecsSinceEpoch(minTime).toString() << ".";
0112         m_nextAlarmTime = minTime;
0113 
0114         // if we scheduled a wakeup before, cancel it first
0115         if (m_cookie > 0) {
0116             Utilities::instance().clearWakeup(m_cookie);
0117         }
0118 
0119         m_cookie = Utilities::instance().scheduleWakeup(minTime);
0120     } else {
0121         // this doesn't explicitly cancel the alarm currently waiting in m_worker if disabled by user
0122         // because alarm->ring() will return immediately if disabled
0123         qDebug() << "No alarms scheduled.";
0124 
0125         m_nextAlarmTime = 0;
0126         Utilities::instance().clearWakeup(m_cookie);
0127         m_cookie = -1;
0128     }
0129     Q_EMIT nextAlarm(m_nextAlarmTime);
0130 }
0131 
0132 void AlarmModel::wakeupCallback(int cookie)
0133 {
0134     if (m_cookie == cookie) {
0135         for (auto alarm : std::as_const(alarmsToRing)) {
0136             alarm->ring();
0137         }
0138         scheduleAlarm();
0139     }
0140 }
0141 void AlarmModel::removeAlarm(const QString &uuid)
0142 {
0143     for (int i = 0; i < m_alarmsList.size(); i++) {
0144         if (m_alarmsList[i]->uuid() == uuid) {
0145             removeAlarm(i);
0146             break;
0147         }
0148     }
0149 }
0150 
0151 void AlarmModel::removeAlarm(int index)
0152 {
0153     if (index < 0 || index >= this->m_alarmsList.size()) {
0154         return;
0155     }
0156 
0157     Q_EMIT alarmRemoved(m_alarmsList.at(index)->uuid());
0158 
0159     Alarm *alarmPointer = m_alarmsList.at(index);
0160 
0161     // remove from list of alarms to ring
0162     for (int i = 0; i < alarmsToRing.size(); i++) {
0163         if (alarmsToRing.at(i) == alarmPointer) {
0164             alarmsToRing.removeAt(i);
0165             i--;
0166         }
0167     }
0168 
0169     // write to config
0170     auto config = KSharedConfig::openConfig();
0171     KConfigGroup group = config->group(ALARM_CFG_GROUP);
0172     group.deleteEntry(m_alarmsList.at(index)->uuid());
0173     m_alarmsList.at(index)->deleteLater(); // delete object
0174     m_alarmsList.removeAt(index);
0175 
0176     config->sync();
0177     scheduleAlarm();
0178 }
0179 
0180 void AlarmModel::addAlarm(const QString &name, int hours, int minutes, int daysOfWeek, const QString &audioPath, int ringDuration, int snoozeDuration)
0181 {
0182     Alarm *alarm = new Alarm(name, hours, minutes, daysOfWeek, audioPath, ringDuration, snoozeDuration, this);
0183     alarm->save();
0184 
0185     // insert new alarm in order by time of day
0186     int i = 0;
0187     while (i < m_alarmsList.size() && !(m_alarmsList[i]->hours() == hours && m_alarmsList[i]->minutes() >= minutes)) {
0188         ++i;
0189     }
0190     m_alarmsList.insert(i, alarm);
0191 
0192     scheduleAlarm();
0193     Q_EMIT alarmAdded(alarm->uuid());
0194 }
0195 
0196 void AlarmModel::initNotifierItem()
0197 {
0198     if (!m_item) {
0199         m_item = new KStatusNotifierItem(this);
0200         m_item->setIconByName(QStringLiteral("clock"));
0201         m_item->setStandardActionsEnabled(false);
0202         m_item->setCategory(KStatusNotifierItem::SystemServices);
0203         m_item->setStatus(KStatusNotifierItem::Passive);
0204     }
0205 }
0206 
0207 void AlarmModel::updateNotifierItem(quint64 time)
0208 {
0209     if (time == 0) {
0210         // no alarm waiting, only set notifier if we have one
0211         if (m_item) {
0212             m_item->setStatus(KStatusNotifierItem::Passive);
0213             m_item->setToolTip(QStringLiteral("clock"), QStringLiteral("KClock"), QString());
0214         }
0215     } else {
0216         auto dateTime = QDateTime::fromSecsSinceEpoch(time).toLocalTime();
0217         initNotifierItem();
0218         m_item->setStatus(KStatusNotifierItem::Active);
0219         m_item->setToolTip(QStringLiteral("clock"),
0220                            QStringLiteral("KClock"),
0221                            xi18nc("@info",
0222                                   "Alarm: <shortcut>%1 %2</shortcut>",
0223                                   QLocale::system().standaloneDayName(dateTime.date().dayOfWeek()),
0224                                   QLocale::system().toString(dateTime.time(), QLocale::ShortFormat)));
0225     }
0226 }