File indexing completed on 2024-04-28 16:54:34

0001 /*
0002     SPDX-FileCopyrightText: 2019 Kai Uwe Broulik <kde@privat.broulik.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include "jobsmodel.h"
0008 #include "jobsmodel_p.h"
0009 
0010 #include "utils_p.h"
0011 
0012 #include <QDebug>
0013 
0014 #include <KJob>
0015 #include <KLocalizedString>
0016 
0017 #include <Plasma/DataEngine>
0018 #include <Plasma/DataEngineConsumer>
0019 #include <Plasma/ServiceJob>
0020 
0021 #include "job.h"
0022 #include "job_p.h"
0023 
0024 using namespace NotificationManager;
0025 
0026 JobsModel::JobsModel()
0027     : QAbstractListModel(nullptr)
0028     , d(new JobsModelPrivate(this))
0029 {
0030     connect(d, &JobsModelPrivate::jobViewAboutToBeAdded, this, [this](int row, Job *job) {
0031         Q_UNUSED(job);
0032         beginInsertRows(QModelIndex(), row, row);
0033     });
0034     connect(d, &JobsModelPrivate::jobViewAdded, this, [this](int row) {
0035         Q_UNUSED(row);
0036         endInsertRows();
0037     });
0038 
0039     connect(d, &JobsModelPrivate::jobViewAboutToBeRemoved, this, [this](int row) {
0040         beginRemoveRows(QModelIndex(), row, row);
0041     });
0042     connect(d, &JobsModelPrivate::jobViewRemoved, this, [this](int row) {
0043         Q_UNUSED(row);
0044         endRemoveRows();
0045     });
0046 
0047     connect(d, &JobsModelPrivate::jobViewChanged, this, [this](int row, Job *job, const QVector<int> &roles) {
0048         Q_UNUSED(job);
0049         const QModelIndex idx = index(row, 0);
0050         Q_EMIT dataChanged(idx, idx, roles);
0051     });
0052 
0053     connect(d, &JobsModelPrivate::serviceOwnershipLost, this, &JobsModel::serviceOwnershipLost);
0054 }
0055 
0056 JobsModel::~JobsModel() = default;
0057 
0058 JobsModel::Ptr JobsModel::createJobsModel()
0059 {
0060     static QWeakPointer<JobsModel> s_instance;
0061     if (!s_instance) {
0062         QSharedPointer<JobsModel> ptr(new JobsModel());
0063         s_instance = ptr.toWeakRef();
0064         return ptr;
0065     }
0066     return s_instance.toStrongRef();
0067 }
0068 
0069 bool JobsModel::init()
0070 {
0071     return d->init();
0072 }
0073 
0074 bool JobsModel::isValid() const
0075 {
0076     return d->m_valid;
0077 }
0078 
0079 QVariant JobsModel::data(const QModelIndex &index, int role) const
0080 {
0081     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid)) {
0082         return QVariant();
0083     }
0084 
0085     Job *job = d->m_jobViews.at(index.row());
0086 
0087     switch (role) {
0088     case Notifications::IdRole:
0089         return job->id();
0090     case Notifications::TypeRole:
0091         return Notifications::JobType;
0092     // basically when it started
0093     case Notifications::CreatedRole:
0094         if (job->created().isValid()) {
0095             return job->created();
0096         }
0097         break;
0098     // basically when it finished
0099     case Notifications::UpdatedRole:
0100         if (job->updated().isValid()) {
0101             return job->updated();
0102         }
0103         break;
0104     case Notifications::SummaryRole:
0105         return job->summary();
0106     case Notifications::BodyRole:
0107         return job->text();
0108     case Qt::AccessibleDescriptionRole:
0109         return i18nc("@info %1 notification body %2 job name", "%1 from %2", job->text(), job->applicationName());
0110     case Notifications::DesktopEntryRole:
0111         return job->desktopEntry();
0112     case Notifications::ApplicationNameRole:
0113         return job->applicationName();
0114     case Notifications::ApplicationIconNameRole:
0115         return job->applicationIconName();
0116 
0117     case Notifications::JobStateRole:
0118         return job->state();
0119     case Notifications::PercentageRole:
0120         return job->percentage();
0121     case Notifications::JobErrorRole:
0122         return job->error();
0123     case Notifications::SuspendableRole:
0124         return job->suspendable();
0125     case Notifications::KillableRole:
0126         return job->killable();
0127     case Notifications::JobDetailsRole:
0128         return QVariant::fromValue(job);
0129 
0130     // successfully finished jobs timeout like a regular notifiation
0131     // whereas running or error'd jobs are persistent
0132     case Notifications::TimeoutRole:
0133         return job->state() == Notifications::JobStateStopped && !job->error() ? -1 : 0;
0134     case Notifications::ClosableRole:
0135         return job->state() == Notifications::JobStateStopped;
0136 
0137     case Notifications::ConfigurableRole:
0138         return false;
0139     case Notifications::ExpiredRole:
0140         return job->expired();
0141     case Notifications::DismissedRole:
0142         return job->dismissed();
0143 
0144     // A job is usually either a long lasting operation you're aware about
0145     // or a quick job you don't care about.
0146     // When it's running, it's there, when it failed, it's persistent.
0147     // There's hardly a reason why it should show up as "unread".
0148     case Notifications::ReadRole:
0149         return true;
0150     }
0151 
0152     return QVariant();
0153 }
0154 
0155 bool JobsModel::setData(const QModelIndex &index, const QVariant &value, int role)
0156 {
0157     if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid)) {
0158         return false;
0159     }
0160 
0161     Job *job = d->m_jobViews.at(index.row());
0162 
0163     switch (role) {
0164     case Notifications::DismissedRole:
0165         if (value.toBool() != job->dismissed()) {
0166             job->setDismissed(value.toBool());
0167             return true;
0168         }
0169         break;
0170     }
0171 
0172     return false;
0173 }
0174 
0175 int JobsModel::rowCount(const QModelIndex &parent) const
0176 {
0177     if (parent.isValid()) {
0178         return 0;
0179     }
0180 
0181     return d->m_jobViews.count();
0182 }
0183 
0184 QHash<int, QByteArray> JobsModel::roleNames() const
0185 {
0186     return Utils::roleNames();
0187 }
0188 
0189 void JobsModel::close(const QModelIndex &idx)
0190 {
0191     if (checkIndex(idx, QAbstractItemModel::CheckIndexOption::IndexIsValid)) {
0192         d->removeAt(idx.row());
0193     }
0194 }
0195 
0196 void JobsModel::expire(const QModelIndex &idx)
0197 {
0198     if (checkIndex(idx, QAbstractItemModel::CheckIndexOption::IndexIsValid)) {
0199         d->m_jobViews.at(idx.row())->setExpired(true);
0200     }
0201 }
0202 
0203 void JobsModel::suspend(const QModelIndex &idx)
0204 {
0205     if (checkIndex(idx, QAbstractItemModel::CheckIndexOption::IndexIsValid)) {
0206         d->m_jobViews.at(idx.row())->suspend();
0207     }
0208 }
0209 
0210 void JobsModel::resume(const QModelIndex &idx)
0211 {
0212     if (checkIndex(idx, QAbstractItemModel::CheckIndexOption::IndexIsValid)) {
0213         d->m_jobViews.at(idx.row())->resume();
0214     }
0215 }
0216 
0217 void JobsModel::kill(const QModelIndex &idx)
0218 {
0219     if (checkIndex(idx, QAbstractItemModel::CheckIndexOption::IndexIsValid)) {
0220         d->m_jobViews.at(idx.row())->kill();
0221     }
0222 }
0223 
0224 void JobsModel::clear(Notifications::ClearFlags flags)
0225 {
0226     if (d->m_jobViews.isEmpty()) {
0227         return;
0228     }
0229 
0230     for (int i = d->m_jobViews.count() - 1; i >= 0; --i) {
0231         Job *job = d->m_jobViews.at(i);
0232 
0233         bool clear = (flags.testFlag(Notifications::ClearExpired) && job->expired());
0234 
0235         // Compared to notifications, the number of jobs is typically small
0236         // so for simplicity we can just delete one item at a time
0237         if (clear) {
0238             d->removeAt(i);
0239         }
0240     }
0241 }