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 "jobtracker.h"
0011 #include "akonadiconsole_debug.h"
0012 #include "jobtrackeradaptor.h"
0013 #include <KLocalizedString>
0014 #include <QString>
0015 #include <QStringList>
0016 #include <akonadi/private/instance_p.h>
0017 
0018 #include <cassert>
0019 #include <chrono>
0020 
0021 using namespace std::chrono_literals;
0022 QString JobInfo::stateAsString() const
0023 {
0024     switch (state) {
0025     case Initial:
0026         return i18n("Waiting");
0027     case Running:
0028         return i18n("Running");
0029     case Ended:
0030         return i18n("Ended");
0031     case Failed:
0032         return i18n("Failed: %1", error);
0033     default:
0034         return i18n("Unknown state!");
0035     }
0036 }
0037 
0038 class JobTrackerPrivate
0039 {
0040 public:
0041     explicit JobTrackerPrivate(JobTracker *_q)
0042         : lastId(42)
0043         , timer(_q)
0044         , disabled(false)
0045         , q(_q)
0046     {
0047         timer.setSingleShot(true);
0048         timer.setInterval(200ms);
0049         QObject::connect(&timer, &QTimer::timeout, q, &JobTracker::signalUpdates);
0050     }
0051 
0052     [[nodiscard]] bool isSession(int id) const
0053     {
0054         return id < -1;
0055     }
0056 
0057     void startUpdatedSignalTimer()
0058     {
0059         if (!timer.isActive() && !disabled) {
0060             timer.start();
0061         }
0062     }
0063 
0064     QStringList sessions;
0065     QHash<QString, int> nameToId;
0066     QHash<int, QList<int>> childJobs;
0067     QHash<int, JobInfo> infoList;
0068     int lastId;
0069     QTimer timer;
0070     bool disabled;
0071     QList<QPair<int, int>> unpublishedUpdates;
0072 
0073 private:
0074     JobTracker *const q;
0075 };
0076 
0077 JobTracker::JobTracker(const char *name, QObject *parent)
0078     : QObject(parent)
0079     , d(new JobTrackerPrivate(this))
0080 {
0081     new JobTrackerAdaptor(this);
0082     const QString suffix = Akonadi::Instance::identifier().isEmpty() ? QString() : QLatin1Char('-') + Akonadi::Instance::identifier();
0083     QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.akonadiconsole") + suffix);
0084     QDBusConnection::sessionBus().registerObject(QLatin1Char('/') + QLatin1StringView(name), this, QDBusConnection::ExportAdaptors);
0085 }
0086 
0087 JobTracker::~JobTracker() = default;
0088 
0089 void JobTracker::jobCreated(const QString &session, const QString &jobName, const QString &parent, const QString &jobType, const QString &debugString)
0090 {
0091     if (d->disabled || session.isEmpty() || jobName.isEmpty()) {
0092         return;
0093     }
0094 
0095     int parentId = parent.isEmpty() ? -1 /*for now*/ : idForJob(parent);
0096 
0097     if (!parent.isEmpty() && parentId == -1) {
0098         qCWarning(AKONADICONSOLE_LOG) << "JobTracker: Job" << jobName << "arrived before its parent" << parent << " jobType=" << jobType
0099                                       << "! Fix the library!";
0100         jobCreated(session, parent, QString(), QStringLiteral("dummy job type"), QString());
0101         parentId = idForJob(parent);
0102         assert(parentId != -1);
0103     }
0104     int sessionId = idForSession(session);
0105     // check if it's a new session, if so, add it
0106     if (sessionId == -1) {
0107         Q_EMIT aboutToAdd(d->sessions.count(), -1);
0108         d->sessions.append(session);
0109         Q_EMIT added();
0110         sessionId = idForSession(session);
0111     }
0112     if (parent.isEmpty()) {
0113         parentId = sessionId;
0114     }
0115 
0116     // deal with the job
0117     const int existingId = idForJob(jobName);
0118     if (existingId != -1) {
0119         if (d->infoList.value(existingId).state == JobInfo::Running) {
0120             qCDebug(AKONADICONSOLE_LOG) << "Job was already known and still running:" << jobName << "from"
0121                                         << d->infoList.value(existingId).timestamp.secsTo(QDateTime::currentDateTime()) << "s ago";
0122         }
0123         // otherwise it just means the pointer got reused... insert duplicate
0124     }
0125 
0126     assert(parentId != -1);
0127     QList<int> &kids = d->childJobs[parentId];
0128     const int pos = kids.size();
0129 
0130     Q_EMIT aboutToAdd(pos, parentId);
0131 
0132     const int id = d->lastId++;
0133 
0134     JobInfo info;
0135     info.name = jobName;
0136     info.parent = parentId;
0137     info.state = JobInfo::Initial;
0138     info.timestamp = QDateTime::currentDateTime();
0139     info.type = jobType;
0140     info.debugString = debugString;
0141     d->infoList.insert(id, info);
0142     d->nameToId.insert(jobName, id); // this replaces any previous entry for jobName, which is exactly what we want
0143     kids << id;
0144 
0145     Q_EMIT added();
0146 }
0147 
0148 void JobTracker::jobEnded(const QString &jobName, const QString &error)
0149 {
0150     if (d->disabled) {
0151         return;
0152     }
0153     // this is called from dbus, so better be defensive
0154     const int jobId = idForJob(jobName);
0155     if (jobId == -1 || !d->infoList.contains(jobId)) {
0156         return;
0157     }
0158 
0159     JobInfo &info = d->infoList[jobId];
0160     if (error.isEmpty()) {
0161         info.state = JobInfo::Ended;
0162     } else {
0163         info.state = JobInfo::Failed;
0164         info.error = error;
0165     }
0166     info.endedTimestamp = QDateTime::currentDateTime();
0167 
0168     d->unpublishedUpdates << QPair<int, int>(d->childJobs[info.parent].size() - 1, info.parent);
0169     d->startUpdatedSignalTimer();
0170 }
0171 
0172 void JobTracker::jobStarted(const QString &jobName)
0173 {
0174     if (d->disabled) {
0175         return;
0176     }
0177     // this is called from dbus, so better be defensive
0178     const int jobId = idForJob(jobName);
0179     if (jobId == -1 || !d->infoList.contains(jobId)) {
0180         return;
0181     }
0182 
0183     JobInfo &info = d->infoList[jobId];
0184     info.state = JobInfo::Running;
0185     info.startedTimestamp = QDateTime::currentDateTime();
0186 
0187     d->unpublishedUpdates << QPair<int, int>(d->childJobs[info.parent].size() - 1, info.parent);
0188     d->startUpdatedSignalTimer();
0189 }
0190 
0191 QStringList JobTracker::sessions() const
0192 {
0193     return d->sessions;
0194 }
0195 
0196 QList<int> JobTracker::childJobs(int parentId) const
0197 {
0198     return d->childJobs.value(parentId);
0199 }
0200 
0201 int JobTracker::jobCount(int parentId) const
0202 {
0203     return d->childJobs.value(parentId).count();
0204 }
0205 
0206 int JobTracker::jobIdAt(int childPos, int parentId) const
0207 {
0208     return d->childJobs.value(parentId).at(childPos);
0209 }
0210 
0211 // only works on jobs
0212 int JobTracker::idForJob(const QString &job) const
0213 {
0214     return d->nameToId.value(job, -1);
0215 }
0216 
0217 // To find a session, we take the offset in the list of sessions
0218 // in order of appearance, add one, and make it negative. That
0219 // way we can discern session ids from job ids and use -1 for invalid
0220 int JobTracker::idForSession(const QString &session) const
0221 {
0222     return (d->sessions.indexOf(session) + 2) * -1;
0223 }
0224 
0225 QString JobTracker::sessionForId(int _id) const
0226 {
0227     const int id = (-_id) - 2;
0228     assert(d->sessions.size() > id);
0229     if (!d->sessions.isEmpty()) {
0230         return d->sessions.at(id);
0231     } else {
0232         return {};
0233     }
0234 }
0235 
0236 int JobTracker::parentId(int id) const
0237 {
0238     if (d->isSession(id)) {
0239         return -1;
0240     } else {
0241         return d->infoList.value(id).parent;
0242     }
0243 }
0244 
0245 int JobTracker::rowForJob(int id, int parentId) const
0246 {
0247     const QList<int> children = childJobs(parentId);
0248     // Simple version:
0249     // return children.indexOf(id);
0250     // But we can do faster since the vector is sorted
0251     return std::lower_bound(children.constBegin(), children.constEnd(), id) - children.constBegin();
0252 }
0253 
0254 JobInfo JobTracker::info(int id) const
0255 {
0256     assert(d->infoList.contains(id));
0257     return d->infoList.value(id);
0258 }
0259 
0260 void JobTracker::clear()
0261 {
0262     d->sessions.clear();
0263     d->nameToId.clear();
0264     d->childJobs.clear();
0265     d->infoList.clear();
0266     d->unpublishedUpdates.clear();
0267 }
0268 
0269 void JobTracker::setEnabled(bool on)
0270 {
0271     d->disabled = !on;
0272 }
0273 
0274 bool JobTracker::isEnabled() const
0275 {
0276     return !d->disabled;
0277 }
0278 
0279 void JobTracker::signalUpdates()
0280 {
0281     if (!d->unpublishedUpdates.isEmpty()) {
0282         Q_EMIT updated(d->unpublishedUpdates);
0283         d->unpublishedUpdates.clear();
0284     }
0285 }
0286 
0287 #include "moc_jobtracker.cpp"