File indexing completed on 2024-06-23 05:45:41

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 "kclock_algorithm.hpp"
0011 #include "utilmodel.h"
0012 
0013 #include <KConfigGroup>
0014 #include <KLocalizedString>
0015 #include <KSharedConfig>
0016 #include <KStatusNotifierItem>
0017 
0018 #include <QDBusConnection>
0019 #include <QDBusInterface>
0020 #include <QDBusReply>
0021 #include <QLocale>
0022 #include <QThread>
0023 #include <QXmlStreamReader>
0024 
0025 AlarmModel *AlarmModel::instance()
0026 {
0027     static AlarmModel *singleton = new AlarmModel();
0028     return singleton;
0029 }
0030 
0031 AlarmModel::AlarmModel(QObject *parent)
0032     : QAbstractListModel{parent}
0033     , m_interface{new org::kde::kclock::AlarmModel(QStringLiteral("org.kde.kclockd"), QStringLiteral("/Alarms"), QDBusConnection::sessionBus(), this)}
0034 {
0035     if (m_interface->isValid()) {
0036         connect(m_interface, SIGNAL(alarmAdded(QString)), this, SLOT(addAlarmInternal(QString)));
0037         connect(m_interface, SIGNAL(alarmRemoved(QString)), this, SLOT(removeAlarm(QString)));
0038     }
0039     setConnectedToDaemon(m_interface->isValid());
0040     load();
0041 
0042     // watch for kclockd
0043     m_watcher = new QDBusServiceWatcher(QStringLiteral("org.kde.kclockd"), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this);
0044     connect(m_watcher, &QDBusServiceWatcher::serviceRegistered, this, [this]() -> void {
0045         setConnectedToDaemon(true);
0046         if (m_interface->isValid()) {
0047             connect(m_interface, SIGNAL(alarmAdded(QString)), this, SLOT(addAlarmInternal(QString)));
0048             connect(m_interface, SIGNAL(alarmRemoved(QString)), this, SLOT(removeAlarm(QString)));
0049         }
0050     });
0051     connect(m_watcher, &QDBusServiceWatcher::serviceUnregistered, this, [this]() -> void {
0052         setConnectedToDaemon(false);
0053     });
0054 }
0055 
0056 void AlarmModel::load()
0057 {
0058     // load from dbus
0059     QDBusInterface *interface = new QDBusInterface(QStringLiteral("org.kde.kclockd"),
0060                                                    QStringLiteral("/Alarms"),
0061                                                    QStringLiteral("org.freedesktop.DBus.Introspectable"),
0062                                                    QDBusConnection::sessionBus(),
0063                                                    this);
0064     QDBusReply<QString> reply = interface->call(QStringLiteral("Introspect"));
0065     if (reply.isValid()) {
0066         QXmlStreamReader xml(reply.value());
0067         while (!xml.atEnd()) {
0068             xml.readNext();
0069             if (xml.name() == QStringLiteral("node") && xml.attributes().hasAttribute(QStringLiteral("name"))) {
0070                 if (xml.attributes().value(QStringLiteral("name")).toString().indexOf(QStringLiteral("org")) == -1) {
0071                     this->addAlarmInternal(xml.attributes().value(QStringLiteral("name")).toString());
0072                 }
0073             }
0074         }
0075     }
0076 
0077     interface->deleteLater();
0078 }
0079 
0080 QHash<int, QByteArray> AlarmModel::roleNames() const
0081 {
0082     return {{AlarmRole, "alarm"}};
0083 }
0084 
0085 QVariant AlarmModel::data(const QModelIndex &index, int role) const
0086 {
0087     if (!index.isValid() || index.row() >= alarmsList.count()) {
0088         return QVariant();
0089     }
0090 
0091     auto *alarm = alarmsList[index.row()];
0092     return alarm ? QVariant::fromValue(alarm) : QVariant();
0093 }
0094 
0095 int AlarmModel::rowCount(const QModelIndex &parent) const
0096 {
0097     Q_UNUSED(parent);
0098     return alarmsList.size();
0099 }
0100 
0101 Qt::ItemFlags AlarmModel::flags(const QModelIndex &index) const
0102 {
0103     Q_UNUSED(index);
0104     return Qt::ItemIsEditable;
0105 }
0106 
0107 void AlarmModel::remove(int index)
0108 {
0109     if (index < 0 || index >= this->alarmsList.size())
0110         return;
0111 
0112     m_interface->removeAlarm(alarmsList.at(index)->uuid().toString());
0113     auto ptr = alarmsList.at(index);
0114 
0115     Q_EMIT beginRemoveRows(QModelIndex(), index, index);
0116     alarmsList.removeAt(index);
0117     Q_EMIT endRemoveRows();
0118 
0119     ptr->deleteLater();
0120 }
0121 
0122 void AlarmModel::addAlarm(const QString &name, int hours, int minutes, int daysOfWeek, const QString &audioPath, int ringDuration, int snoozeDuration)
0123 {
0124     m_interface->addAlarm(name, hours, minutes, daysOfWeek, audioPath, ringDuration, snoozeDuration);
0125 }
0126 
0127 QString AlarmModel::timeToRingFormatted(int hours, int minutes, int daysOfWeek)
0128 {
0129     return UtilModel::instance()->timeToRingFormatted(UtilModel::instance()->calculateNextRingTime(hours, minutes, daysOfWeek));
0130 }
0131 
0132 void AlarmModel::addAlarmInternal(QString uuid)
0133 {
0134     auto alarm = new Alarm(uuid.remove(QRegularExpression(QStringLiteral("[{}-]"))));
0135     auto index = KClock::insert_index(alarm, alarmsList, [](Alarm *const &left, Alarm *const &right) {
0136         if (left->hours() < right->hours()) {
0137             return true;
0138         } else if (left->hours() > right->hours()) {
0139             return false;
0140         } else if (left->minutes() <= right->minutes()) {
0141             return true;
0142         } else {
0143             return false;
0144         }
0145     });
0146 
0147     Q_EMIT beginInsertRows(QModelIndex(), index, index);
0148     alarmsList.insert(index, alarm);
0149     Q_EMIT endInsertRows();
0150 }
0151 
0152 void AlarmModel::removeAlarm(const QString &uuid)
0153 {
0154     auto index = 0;
0155     for (auto alarm : alarmsList) {
0156         if (alarm->uuid().toString() == uuid) {
0157             break;
0158         }
0159         ++index;
0160     }
0161 
0162     if (index >= this->alarmsList.size())
0163         return;
0164 
0165     auto ptr = alarmsList.at(index);
0166 
0167     Q_EMIT beginRemoveRows(QModelIndex(), index, index);
0168     alarmsList.removeAt(index);
0169     Q_EMIT endRemoveRows();
0170 
0171     ptr->deleteLater();
0172 }
0173 
0174 bool AlarmModel::connectedToDaemon()
0175 {
0176     return m_connectedToDaemon;
0177 }
0178 
0179 void AlarmModel::setConnectedToDaemon(bool connectedToDaemon)
0180 {
0181     if (m_connectedToDaemon != connectedToDaemon) {
0182         m_connectedToDaemon = connectedToDaemon;
0183         Q_EMIT connectedToDaemonChanged();
0184     }
0185 }