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

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