File indexing completed on 2024-05-12 05:12:45

0001 /*
0002  This file is part of Akonadi.
0003 
0004  SPDX-FileCopyrightText: 2009 KDAB
0005  SPDX-FileContributor: Till Adam <adam@kde.org>
0006 
0007  SPDX-License-Identifier: GPL-2.0-or-later
0008  */
0009 
0010 #include "jobtrackermodel.h"
0011 #include "jobtracker.h"
0012 
0013 #include <KLocalizedString>
0014 
0015 #include <QColor>
0016 #include <QFont>
0017 #include <QModelIndex>
0018 #include <QPair>
0019 #include <QStringList>
0020 
0021 #include <cassert>
0022 
0023 class JobTrackerModelPrivate
0024 {
0025 public:
0026     JobTrackerModelPrivate(const char *name, JobTrackerModel *_q)
0027         : q(_q)
0028         , tracker(name)
0029     {
0030     }
0031 
0032     [[nodiscard]] int rowForParentId(int parentid) const
0033     {
0034         const int grandparentid = tracker.parentId(parentid);
0035         int row = -1;
0036         if (grandparentid == -1) {
0037             const QString session = tracker.sessionForId(parentid);
0038             if (!session.isEmpty()) {
0039                 row = tracker.sessions().indexOf(session);
0040             }
0041         } else {
0042             // offset of the parent in the list of children of the grandparent
0043             row = tracker.rowForJob(parentid, grandparentid);
0044         }
0045         return row;
0046     }
0047 
0048 private:
0049     JobTrackerModel *const q;
0050 
0051 public:
0052     JobTracker tracker;
0053 };
0054 
0055 JobTrackerModel::JobTrackerModel(const char *name, QObject *parent)
0056     : QAbstractItemModel(parent)
0057     , d(new JobTrackerModelPrivate(name, this))
0058 {
0059     connect(&d->tracker, &JobTracker::aboutToAdd, this, &JobTrackerModel::jobAboutToBeAdded);
0060     connect(&d->tracker, &JobTracker::added, this, &JobTrackerModel::jobAdded);
0061     connect(&d->tracker, &JobTracker::updated, this, &JobTrackerModel::jobsUpdated);
0062 }
0063 
0064 JobTrackerModel::~JobTrackerModel() = default;
0065 
0066 JobTracker &JobTrackerModel::jobTracker()
0067 {
0068     return d->tracker;
0069 }
0070 
0071 QModelIndex JobTrackerModel::index(int row, int column, const QModelIndex &parent) const
0072 {
0073     if (column < 0 || column >= NumColumns) {
0074         return {};
0075     }
0076     if (!parent.isValid()) { // session, at top level
0077         if (row < 0 || row >= d->tracker.sessions().size()) {
0078             return {};
0079         }
0080         return createIndex(row, column, d->tracker.idForSession(d->tracker.sessions().at(row)));
0081     }
0082     if (parent.column() != 0) {
0083         return {};
0084     }
0085     // job, i.e. non-toplevel
0086     const int jobCount = d->tracker.jobCount(parent.internalId());
0087     if (row < 0 || row >= jobCount) {
0088         return {};
0089     }
0090     return createIndex(row, column, d->tracker.jobIdAt(row, parent.internalId()));
0091 }
0092 
0093 QModelIndex JobTrackerModel::parent(const QModelIndex &idx) const
0094 {
0095     if (!idx.isValid()) {
0096         return {};
0097     }
0098 
0099     const int parentid = d->tracker.parentId(idx.internalId());
0100     if (parentid == -1) {
0101         return {}; // top level session
0102     }
0103 
0104     const int row = d->rowForParentId(parentid);
0105     if (row >= 0) {
0106         return createIndex(row, 0, parentid);
0107     } else {
0108         return {};
0109     }
0110 }
0111 
0112 int JobTrackerModel::rowCount(const QModelIndex &parent) const
0113 {
0114     if (!parent.isValid()) {
0115         return d->tracker.sessions().size();
0116     } else {
0117         if (parent.column() != 0) {
0118             return 0;
0119         }
0120         return d->tracker.jobCount(parent.internalId());
0121     }
0122 }
0123 
0124 int JobTrackerModel::columnCount(const QModelIndex &parent) const
0125 {
0126     Q_UNUSED(parent)
0127     return NumColumns;
0128 }
0129 
0130 static QString formatTimeWithMsec(const QTime &time)
0131 {
0132     return time.toString(QStringLiteral("HH:mm:ss.zzz t"));
0133 }
0134 
0135 static QString formatDurationWithMsec(qint64 msecs)
0136 {
0137     QTime time(0, 0, 0);
0138     time = time.addMSecs(msecs);
0139     return time.toString(QStringLiteral("HH:mm:ss.zzz"));
0140 }
0141 
0142 QVariant JobTrackerModel::data(const QModelIndex &idx, int role) const
0143 {
0144     // top level items are sessions
0145     if (!idx.parent().isValid()) {
0146         if (role == Qt::DisplayRole) {
0147             const QStringList sessions = d->tracker.sessions();
0148             if (idx.column() == 0 && idx.row() <= sessions.size()) {
0149                 return sessions.at(idx.row());
0150             }
0151         }
0152     } else { // not top level, so a job or subjob
0153         const int id = idx.internalId();
0154         if (role != Qt::DisplayRole && role != Qt::ForegroundRole && role != Qt::FontRole && role != Qt::ToolTipRole && role != FailedIdRole) {
0155             // Avoid the QHash lookup for all other roles
0156             return {};
0157         }
0158         const JobInfo info = d->tracker.info(id);
0159         if (role == Qt::DisplayRole) {
0160             switch (idx.column()) {
0161             case ColumnJobId:
0162                 return info.name;
0163             case ColumnCreated:
0164                 return formatTimeWithMsec(info.timestamp.time());
0165             case ColumnWaitTime:
0166                 if (info.startedTimestamp.isNull() || info.timestamp.isNull()) {
0167                     return QString();
0168                 }
0169                 return formatDurationWithMsec(info.timestamp.msecsTo(info.startedTimestamp));
0170             case ColumnJobDuration:
0171                 if (info.endedTimestamp.isNull() || info.startedTimestamp.isNull()) {
0172                     return QString();
0173                 }
0174                 return formatDurationWithMsec(info.startedTimestamp.msecsTo(info.endedTimestamp));
0175             case ColumnJobType:
0176                 return info.type;
0177             case ColumnState:
0178                 return info.stateAsString();
0179             case ColumnInfo:
0180                 return info.debugString;
0181             }
0182         } else if (role == Qt::ForegroundRole) {
0183             if (info.state == JobInfo::Failed) {
0184                 return QColor(Qt::red);
0185             }
0186         } else if (role == Qt::FontRole) {
0187             if (info.state == JobInfo::Running) {
0188                 QFont f;
0189                 f.setBold(true);
0190                 return f;
0191             }
0192         } else if (role == Qt::ToolTipRole) {
0193             if (info.state == JobInfo::Failed) {
0194                 return info.error;
0195             }
0196         } else if (role == FailedIdRole) {
0197             return info.state == JobInfo::Failed;
0198         }
0199     }
0200     return {};
0201 }
0202 
0203 QVariant JobTrackerModel::headerData(int section, Qt::Orientation orientation, int role) const
0204 {
0205     if (role == Qt::DisplayRole) {
0206         if (orientation == Qt::Horizontal) {
0207             switch (section) {
0208             case ColumnJobId:
0209                 return i18n("Job ID");
0210             case ColumnCreated:
0211                 return i18n("Created");
0212             case ColumnWaitTime:
0213                 return i18n("Wait time"); // duration  (time started - time created)
0214             case ColumnJobDuration:
0215                 return i18n("Job duration"); // duration (time ended - time started)
0216             case ColumnJobType:
0217                 return i18n("Job Type");
0218             case ColumnState:
0219                 return i18n("State");
0220             case ColumnInfo:
0221                 return i18n("Info");
0222             }
0223         }
0224     }
0225     return {};
0226 }
0227 
0228 void JobTrackerModel::resetTracker()
0229 {
0230     beginResetModel();
0231     d->tracker.clear();
0232     endResetModel();
0233 }
0234 
0235 bool JobTrackerModel::isEnabled() const
0236 {
0237     return d->tracker.isEnabled();
0238 }
0239 
0240 void JobTrackerModel::setEnabled(bool on)
0241 {
0242     d->tracker.setEnabled(on);
0243 }
0244 
0245 void JobTrackerModel::jobAboutToBeAdded(int pos, int parentId)
0246 {
0247     QModelIndex parentIdx;
0248     if (parentId != -1) {
0249         const int row = d->rowForParentId(parentId);
0250         if (row >= 0) {
0251             parentIdx = createIndex(row, 0, parentId);
0252         }
0253     }
0254     beginInsertRows(parentIdx, pos, pos);
0255 }
0256 
0257 void JobTrackerModel::jobAdded()
0258 {
0259     endInsertRows();
0260 }
0261 
0262 void JobTrackerModel::jobsUpdated(const QList<QPair<int, int>> &jobs)
0263 {
0264     // TODO group them by parent? It's likely that multiple jobs for the same
0265     // parent will come in in the same batch, isn't it?
0266     for (const auto &job : jobs) {
0267         const int pos = job.first;
0268         const int parentId = job.second;
0269         QModelIndex parentIdx;
0270         if (parentId != -1) {
0271             const int row = d->rowForParentId(parentId);
0272             if (row >= 0) {
0273                 parentIdx = createIndex(row, 0, parentId);
0274             }
0275         }
0276         Q_EMIT dataChanged(index(pos, 0, parentIdx), index(pos, 3, parentIdx));
0277     }
0278 }
0279 
0280 #include "moc_jobtrackermodel.cpp"