1 /*
2 This file is part of Akonadi.
3
4 SPDX-FileCopyrightText: 2009 KDAB
5 SPDX-FileContributor: Till Adam <adam@kde.org>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8 */
9
10 #include "jobtracker.h"
11 #include "akonadiconsole_debug.h"
12 #include "jobtrackeradaptor.h"
13 #include <QString>
14 #include <QStringList>
15 #include <akonadi/private/instance_p.h>
16
17 #include <cassert>
18 #include <chrono>
19
20 using namespace std::chrono_literals;
stateAsString() const21 QString JobInfo::stateAsString() const
22 {
23 switch (state) {
24 case Initial:
25 return QStringLiteral("Waiting");
26 case Running:
27 return QStringLiteral("Running");
28 case Ended:
29 return QStringLiteral("Ended");
30 case Failed:
31 return QStringLiteral("Failed: %1").arg(error);
32 default:
33 return QStringLiteral("Unknown state!");
34 }
35 }
36
37 class JobTrackerPrivate
38 {
39 public:
JobTrackerPrivate(JobTracker * _q)40 explicit JobTrackerPrivate(JobTracker *_q)
41 : lastId(42)
42 , timer(_q)
43 , disabled(false)
44 , q(_q)
45 {
46 timer.setSingleShot(true);
47 timer.setInterval(200ms);
48 QObject::connect(&timer, &QTimer::timeout, q, &JobTracker::signalUpdates);
49 }
50
isSession(int id) const51 bool isSession(int id) const
52 {
53 return id < -1;
54 }
55
startUpdatedSignalTimer()56 void startUpdatedSignalTimer()
57 {
58 if (!timer.isActive() && !disabled) {
59 timer.start();
60 }
61 }
62
63 QStringList sessions;
64 QHash<QString, int> nameToId;
65 QHash<int, QVector<int>> childJobs;
66 QHash<int, JobInfo> infoList;
67 int lastId;
68 QTimer timer;
69 bool disabled;
70 QList<QPair<int, int>> unpublishedUpdates;
71
72 private:
73 JobTracker *const q;
74 };
75
JobTracker(const char * name,QObject * parent)76 JobTracker::JobTracker(const char *name, QObject *parent)
77 : QObject(parent)
78 , d(new JobTrackerPrivate(this))
79 {
80 new JobTrackerAdaptor(this);
81 const QString suffix = Akonadi::Instance::identifier().isEmpty() ? QString() : QLatin1Char('-') + Akonadi::Instance::identifier();
82 QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.akonadiconsole") + suffix);
83 QDBusConnection::sessionBus().registerObject(QLatin1Char('/') + QLatin1String(name), this, QDBusConnection::ExportAdaptors);
84 }
85
86 JobTracker::~JobTracker() = default;
87
jobCreated(const QString & session,const QString & jobName,const QString & parent,const QString & jobType,const QString & debugString)88 void JobTracker::jobCreated(const QString &session, const QString &jobName, const QString &parent, const QString &jobType, const QString &debugString)
89 {
90 if (d->disabled || session.isEmpty() || jobName.isEmpty()) {
91 return;
92 }
93
94 int parentId = parent.isEmpty() ? -1 /*for now*/ : idForJob(parent);
95
96 if (!parent.isEmpty() && parentId == -1) {
97 qCWarning(AKONADICONSOLE_LOG) << "JobTracker: Job" << jobName << "arrived before its parent" << parent << " jobType=" << jobType
98 << "! Fix the library!";
99 jobCreated(session, parent, QString(), QStringLiteral("dummy job type"), QString());
100 parentId = idForJob(parent);
101 assert(parentId != -1);
102 }
103 int sessionId = idForSession(session);
104 // check if it's a new session, if so, add it
105 if (sessionId == -1) {
106 Q_EMIT aboutToAdd(d->sessions.count(), -1);
107 d->sessions.append(session);
108 Q_EMIT added();
109 sessionId = idForSession(session);
110 }
111 if (parent.isEmpty()) {
112 parentId = sessionId;
113 }
114
115 // deal with the job
116 const int existingId = idForJob(jobName);
117 if (existingId != -1) {
118 if (d->infoList.value(existingId).state == JobInfo::Running) {
119 qCDebug(AKONADICONSOLE_LOG) << "Job was already known and still running:" << jobName << "from"
120 << d->infoList.value(existingId).timestamp.secsTo(QDateTime::currentDateTime()) << "s ago";
121 }
122 // otherwise it just means the pointer got reused... insert duplicate
123 }
124
125 assert(parentId != -1);
126 QVector<int> &kids = d->childJobs[parentId];
127 const int pos = kids.size();
128
129 Q_EMIT aboutToAdd(pos, parentId);
130
131 const int id = d->lastId++;
132
133 JobInfo info;
134 info.name = jobName;
135 info.parent = parentId;
136 info.state = JobInfo::Initial;
137 info.timestamp = QDateTime::currentDateTime();
138 info.type = jobType;
139 info.debugString = debugString;
140 d->infoList.insert(id, info);
141 d->nameToId.insert(jobName, id); // this replaces any previous entry for jobName, which is exactly what we want
142 kids << id;
143
144 Q_EMIT added();
145 }
146
jobEnded(const QString & jobName,const QString & error)147 void JobTracker::jobEnded(const QString &jobName, const QString &error)
148 {
149 if (d->disabled) {
150 return;
151 }
152 // this is called from dbus, so better be defensive
153 const int jobId = idForJob(jobName);
154 if (jobId == -1 || !d->infoList.contains(jobId)) {
155 return;
156 }
157
158 JobInfo &info = d->infoList[jobId];
159 if (error.isEmpty()) {
160 info.state = JobInfo::Ended;
161 } else {
162 info.state = JobInfo::Failed;
163 info.error = error;
164 }
165 info.endedTimestamp = QDateTime::currentDateTime();
166
167 d->unpublishedUpdates << QPair<int, int>(d->childJobs[info.parent].size() - 1, info.parent);
168 d->startUpdatedSignalTimer();
169 }
170
jobStarted(const QString & jobName)171 void JobTracker::jobStarted(const QString &jobName)
172 {
173 if (d->disabled) {
174 return;
175 }
176 // this is called from dbus, so better be defensive
177 const int jobId = idForJob(jobName);
178 if (jobId == -1 || !d->infoList.contains(jobId)) {
179 return;
180 }
181
182 JobInfo &info = d->infoList[jobId];
183 info.state = JobInfo::Running;
184 info.startedTimestamp = QDateTime::currentDateTime();
185
186 d->unpublishedUpdates << QPair<int, int>(d->childJobs[info.parent].size() - 1, info.parent);
187 d->startUpdatedSignalTimer();
188 }
189
sessions() const190 QStringList JobTracker::sessions() const
191 {
192 return d->sessions;
193 }
194
childJobs(int parentId) const195 QVector<int> JobTracker::childJobs(int parentId) const
196 {
197 return d->childJobs.value(parentId);
198 }
199
jobCount(int parentId) const200 int JobTracker::jobCount(int parentId) const
201 {
202 return d->childJobs.value(parentId).count();
203 }
204
jobIdAt(int childPos,int parentId) const205 int JobTracker::jobIdAt(int childPos, int parentId) const
206 {
207 return d->childJobs.value(parentId).at(childPos);
208 }
209
210 // only works on jobs
idForJob(const QString & job) const211 int JobTracker::idForJob(const QString &job) const
212 {
213 return d->nameToId.value(job, -1);
214 }
215
216 // To find a session, we take the offset in the list of sessions
217 // in order of appearance, add one, and make it negative. That
218 // way we can discern session ids from job ids and use -1 for invalid
idForSession(const QString & session) const219 int JobTracker::idForSession(const QString &session) const
220 {
221 return (d->sessions.indexOf(session) + 2) * -1;
222 }
223
sessionForId(int _id) const224 QString JobTracker::sessionForId(int _id) const
225 {
226 const int id = (-_id) - 2;
227 assert(d->sessions.size() > id);
228 if (!d->sessions.isEmpty()) {
229 return d->sessions.at(id);
230 } else {
231 return QString();
232 }
233 }
234
parentId(int id) const235 int JobTracker::parentId(int id) const
236 {
237 if (d->isSession(id)) {
238 return -1;
239 } else {
240 return d->infoList.value(id).parent;
241 }
242 }
243
rowForJob(int id,int parentId) const244 int JobTracker::rowForJob(int id, int parentId) const
245 {
246 const QVector<int> children = childJobs(parentId);
247 // Simple version:
248 // return children.indexOf(id);
249 // But we can do faster since the vector is sorted
250 return std::lower_bound(children.constBegin(), children.constEnd(), id) - children.constBegin();
251 }
252
info(int id) const253 JobInfo JobTracker::info(int id) const
254 {
255 assert(d->infoList.contains(id));
256 return d->infoList.value(id);
257 }
258
clear()259 void JobTracker::clear()
260 {
261 d->sessions.clear();
262 d->nameToId.clear();
263 d->childJobs.clear();
264 d->infoList.clear();
265 d->unpublishedUpdates.clear();
266 }
267
setEnabled(bool on)268 void JobTracker::setEnabled(bool on)
269 {
270 d->disabled = !on;
271 }
272
isEnabled() const273 bool JobTracker::isEnabled() const
274 {
275 return !d->disabled;
276 }
277
signalUpdates()278 void JobTracker::signalUpdates()
279 {
280 if (!d->unpublishedUpdates.isEmpty()) {
281 Q_EMIT updated(d->unpublishedUpdates);
282 d->unpublishedUpdates.clear();
283 }
284 }
285