1/*
2 This file is part of the KDE project
3
4 SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
5 SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
6 SPDX-FileCopyrightText: 2006 Kevin Ottens <ervin@kde.org>
7
8 SPDX-License-Identifier: LGPL-2.0-or-later
9*/
10
11#include "kjob.h"
12#include "kjob_p.h"
13
14#include "kcoreaddons_debug.h"
15#include "kjobuidelegate.h"
16
17#include <QElapsedTimer>
18#include <QEventLoop>
19#include <QTimer>
20
21KJobPrivate::KJobPrivate()
22{
23}
24
25KJobPrivate::~KJobPrivate()
26{
27}
28
29KJob::KJob(QObject *parent)
30 : QObject(parent)
31 , d_ptr(new KJobPrivate)
32{
33 d_ptr->q_ptr = this;
34}
35
36KJob::KJob(KJobPrivate &dd, QObject *parent)
37 : QObject(parent)
38 , d_ptr(&dd)
39{
40 d_ptr->q_ptr = this;
41}
42
43KJob::~KJob()
44{
45 if (!d_ptr->isFinished) {
46 d_ptr->isFinished = true;
47 Q_EMIT finished(job: this, QPrivateSignal());
48 }
49
50 delete d_ptr->speedTimer;
51 delete d_ptr->uiDelegate;
52}
53
54void KJob::setUiDelegate(KJobUiDelegate *delegate)
55{
56 Q_D(KJob);
57 if (!delegate) {
58 delete d->uiDelegate;
59 d->uiDelegate = nullptr;
60 return;
61 }
62
63 if (delegate->setJob(this)) {
64 delete d->uiDelegate;
65 d->uiDelegate = delegate;
66 d->uiDelegate->connectJob(job: this);
67 }
68}
69
70qint64 KJob::elapsedTime() const
71{
72 Q_D(const KJob);
73 return d->accumulatedElapsedTime + (d->elapsedTimer ? d->elapsedTimer->elapsed() : 0);
74}
75
76void KJob::startElapsedTimer()
77{
78 Q_D(KJob);
79 if (!d->elapsedTimer) {
80 d->elapsedTimer = std::make_unique<QElapsedTimer>();
81 }
82 d->elapsedTimer->start();
83 d->accumulatedElapsedTime = 0;
84}
85
86KJobUiDelegate *KJob::uiDelegate() const
87{
88 return d_func()->uiDelegate;
89}
90
91KJob::Capabilities KJob::capabilities() const
92{
93 return d_func()->capabilities;
94}
95
96bool KJob::isSuspended() const
97{
98 return d_func()->suspended;
99}
100
101void KJob::finishJob(bool emitResult)
102{
103 Q_D(KJob);
104 Q_ASSERT(!d->isFinished);
105 d->isFinished = true;
106
107 if (d->eventLoop) {
108 d->eventLoop->quit();
109 }
110
111 // If we are displaying a progress dialog, remove it first.
112 Q_EMIT finished(job: this, QPrivateSignal());
113
114 if (emitResult) {
115 Q_EMIT result(job: this, QPrivateSignal());
116 }
117
118 if (isAutoDelete()) {
119 deleteLater();
120 }
121}
122
123bool KJob::kill(KillVerbosity verbosity)
124{
125 Q_D(KJob);
126 if (d->isFinished) {
127 return true;
128 }
129
130 if (doKill()) {
131 // A subclass can (but should not) call emitResult() or kill()
132 // from doKill() and thus set isFinished to true.
133 if (!d->isFinished) {
134 setError(KilledJobError);
135 finishJob(emitResult: verbosity != Quietly);
136 }
137 return true;
138 } else {
139 return false;
140 }
141}
142
143bool KJob::suspend()
144{
145 Q_D(KJob);
146 if (!d->suspended) {
147 if (doSuspend()) {
148 d->suspended = true;
149 if (d->elapsedTimer) {
150 d->accumulatedElapsedTime += d->elapsedTimer->elapsed();
151 }
152 d->elapsedTimer.reset();
153 Q_EMIT suspended(job: this, QPrivateSignal());
154
155 return true;
156 }
157 }
158
159 return false;
160}
161
162bool KJob::resume()
163{
164 Q_D(KJob);
165 if (d->suspended) {
166 if (doResume()) {
167 d->suspended = false;
168
169 // If the timer was never started previously, the reported time is wrong, so we rather keep it at zero.
170 if (d->accumulatedElapsedTime > 0) {
171 d->elapsedTimer = std::make_unique<QElapsedTimer>();
172 d->elapsedTimer->start();
173 }
174
175 Q_EMIT resumed(job: this, QPrivateSignal());
176
177 return true;
178 }
179 }
180
181 return false;
182}
183
184bool KJob::doKill()
185{
186 return false;
187}
188
189bool KJob::doSuspend()
190{
191 return false;
192}
193
194bool KJob::doResume()
195{
196 return false;
197}
198
199void KJob::setCapabilities(KJob::Capabilities capabilities)
200{
201 Q_D(KJob);
202 d->capabilities = capabilities;
203}
204
205bool KJob::exec()
206{
207 Q_D(KJob);
208 // Usually this job would delete itself, via deleteLater() just after
209 // emitting result() (unless configured otherwise). Since we use an event
210 // loop below, that event loop will process the deletion event and we'll
211 // have been deleted when exec() returns. This crashes, so temporarily
212 // suspend autodeletion and manually do it afterwards.
213 const bool wasAutoDelete = isAutoDelete();
214 setAutoDelete(false);
215
216 Q_ASSERT(!d->eventLoop);
217
218 QEventLoop loop(this);
219 d->eventLoop = &loop;
220
221 start();
222 if (!d->isFinished) {
223 d->m_startedWithExec = true;
224 d->eventLoop->exec(flags: QEventLoop::ExcludeUserInputEvents);
225 }
226 d->eventLoop = nullptr;
227
228 if (wasAutoDelete) {
229 deleteLater();
230 }
231 return (d->error == NoError);
232}
233
234int KJob::error() const
235{
236 return d_func()->error;
237}
238
239QString KJob::errorText() const
240{
241 return d_func()->errorText;
242}
243
244QString KJob::errorString() const
245{
246 return d_func()->errorText;
247}
248
249qulonglong KJob::processedAmount(Unit unit) const
250{
251 if (unit >= UnitsCount) {
252 qCWarning(KCOREADDONS_DEBUG) << "KJob::processedAmount() was called on an invalid Unit" << unit;
253 return 0;
254 }
255
256 return d_func()->m_jobAmounts[unit].processedAmount;
257}
258
259qulonglong KJob::totalAmount(Unit unit) const
260{
261 if (unit >= UnitsCount) {
262 qCWarning(KCOREADDONS_DEBUG) << "KJob::totalAmount() was called on an invalid Unit" << unit;
263 return 0;
264 }
265
266 return d_func()->m_jobAmounts[unit].totalAmount;
267}
268
269unsigned long KJob::percent() const
270{
271 return d_func()->percentage;
272}
273
274bool KJob::isFinished() const
275{
276 return d_func()->isFinished;
277}
278
279void KJob::setError(int errorCode)
280{
281 Q_D(KJob);
282 d->error = errorCode;
283}
284
285void KJob::setErrorText(const QString &errorText)
286{
287 Q_D(KJob);
288 d->errorText = errorText;
289}
290
291void KJob::setProcessedAmount(Unit unit, qulonglong amount)
292{
293 if (unit >= UnitsCount) {
294 qCWarning(KCOREADDONS_DEBUG) << "KJob::setProcessedAmount() was called on an invalid Unit" << unit;
295 return;
296 }
297
298 Q_D(KJob);
299
300 auto &[processed, total] = d->m_jobAmounts[unit];
301
302 const bool should_emit = (processed != amount);
303
304 processed = amount;
305
306 if (should_emit) {
307 Q_EMIT processedAmountChanged(job: this, unit, amount, QPrivateSignal{});
308 if (unit == d->progressUnit) {
309 Q_EMIT processedSize(job: this, size: amount);
310 emitPercent(processedAmount: processed, totalAmount: total);
311 }
312 }
313}
314
315void KJob::setTotalAmount(Unit unit, qulonglong amount)
316{
317 if (unit >= UnitsCount) {
318 qCWarning(KCOREADDONS_DEBUG) << "KJob::setTotalAmount() was called on an invalid Unit" << unit;
319 return;
320 }
321
322 Q_D(KJob);
323
324 auto &[processed, total] = d->m_jobAmounts[unit];
325
326 const bool should_emit = (total != amount);
327
328 total = amount;
329
330 if (should_emit) {
331 Q_EMIT totalAmountChanged(job: this, unit, amount, QPrivateSignal{});
332 if (unit == d->progressUnit) {
333 Q_EMIT totalSize(job: this, size: amount);
334 emitPercent(processedAmount: processed, totalAmount: total);
335 }
336 }
337}
338
339void KJob::setProgressUnit(Unit unit)
340{
341 Q_D(KJob);
342 d->progressUnit = unit;
343}
344
345void KJob::setPercent(unsigned long percentage)
346{
347 Q_D(KJob);
348 if (d->percentage != percentage) {
349 d->percentage = percentage;
350 Q_EMIT percentChanged(job: this, percent: percentage, QPrivateSignal{});
351 }
352}
353
354void KJob::emitResult()
355{
356 if (!d_func()->isFinished) {
357 finishJob(emitResult: true);
358 }
359}
360
361void KJob::emitPercent(qulonglong processedAmount, qulonglong totalAmount)
362{
363 if (totalAmount != 0) {
364 setPercent(100.0 * processedAmount / totalAmount);
365 }
366}
367
368void KJob::emitSpeed(unsigned long value)
369{
370 Q_D(KJob);
371 if (!d->speedTimer) {
372 d->speedTimer = new QTimer(this);
373 connect(sender: d->speedTimer, signal: &QTimer::timeout, context: this, slot: [d]() {
374 d->speedTimeout();
375 });
376 }
377
378 Q_EMIT speed(job: this, speed: value);
379 d->speedTimer->start(msec: 5000); // 5 seconds interval should be enough
380}
381
382void KJobPrivate::speedTimeout()
383{
384 Q_Q(KJob);
385 // send 0 and stop the timer
386 // timer will be restarted only when we receive another speed event
387 Q_EMIT q->speed(job: q, speed: 0);
388 speedTimer->stop();
389}
390
391bool KJob::isAutoDelete() const
392{
393 Q_D(const KJob);
394 return d->isAutoDelete;
395}
396
397void KJob::setAutoDelete(bool autodelete)
398{
399 Q_D(KJob);
400 d->isAutoDelete = autodelete;
401}
402
403void KJob::setFinishedNotificationHidden(bool hide)
404{
405 Q_D(KJob);
406 d->m_hideFinishedNotification = hide;
407}
408
409bool KJob::isFinishedNotificationHidden() const
410{
411 Q_D(const KJob);
412 return d->m_hideFinishedNotification;
413}
414
415bool KJob::isStartedWithExec() const
416{
417 Q_D(const KJob);
418 return d->m_startedWithExec;
419}
420
421#include "moc_kjob.cpp"
422

source code of kcoreaddons/src/lib/jobs/kjob.cpp