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"