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

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