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 | |
21 | KJobPrivate::KJobPrivate() |
22 | { |
23 | } |
24 | |
25 | KJobPrivate::~KJobPrivate() |
26 | { |
27 | } |
28 | |
29 | KJob::KJob(QObject *parent) |
30 | : QObject(parent) |
31 | , d_ptr(new KJobPrivate) |
32 | { |
33 | d_ptr->q_ptr = this; |
34 | } |
35 | |
36 | KJob::KJob(KJobPrivate &dd, QObject *parent) |
37 | : QObject(parent) |
38 | , d_ptr(&dd) |
39 | { |
40 | d_ptr->q_ptr = this; |
41 | } |
42 | |
43 | KJob::~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 | |
54 | void 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 | |
70 | qint64 KJob::elapsedTime() const |
71 | { |
72 | Q_D(const KJob); |
73 | return d->accumulatedElapsedTime + (d->elapsedTimer ? d->elapsedTimer->elapsed() : 0); |
74 | } |
75 | |
76 | void 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 | |
86 | KJobUiDelegate *KJob::uiDelegate() const |
87 | { |
88 | return d_func()->uiDelegate; |
89 | } |
90 | |
91 | KJob::Capabilities KJob::capabilities() const |
92 | { |
93 | return d_func()->capabilities; |
94 | } |
95 | |
96 | bool KJob::isSuspended() const |
97 | { |
98 | return d_func()->suspended; |
99 | } |
100 | |
101 | void 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 | |
123 | bool 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 | |
143 | bool 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 | |
162 | bool 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 | |
184 | bool KJob::doKill() |
185 | { |
186 | return false; |
187 | } |
188 | |
189 | bool KJob::doSuspend() |
190 | { |
191 | return false; |
192 | } |
193 | |
194 | bool KJob::doResume() |
195 | { |
196 | return false; |
197 | } |
198 | |
199 | void KJob::setCapabilities(KJob::Capabilities capabilities) |
200 | { |
201 | Q_D(KJob); |
202 | d->capabilities = capabilities; |
203 | } |
204 | |
205 | bool 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 | |
234 | int KJob::error() const |
235 | { |
236 | return d_func()->error; |
237 | } |
238 | |
239 | QString KJob::errorText() const |
240 | { |
241 | return d_func()->errorText; |
242 | } |
243 | |
244 | QString KJob::errorString() const |
245 | { |
246 | return d_func()->errorText; |
247 | } |
248 | |
249 | qulonglong 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 | |
259 | qulonglong 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 | |
269 | unsigned long KJob::percent() const |
270 | { |
271 | return d_func()->percentage; |
272 | } |
273 | |
274 | bool KJob::isFinished() const |
275 | { |
276 | return d_func()->isFinished; |
277 | } |
278 | |
279 | void KJob::setError(int errorCode) |
280 | { |
281 | Q_D(KJob); |
282 | d->error = errorCode; |
283 | } |
284 | |
285 | void KJob::setErrorText(const QString &errorText) |
286 | { |
287 | Q_D(KJob); |
288 | d->errorText = errorText; |
289 | } |
290 | |
291 | void 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 | |
315 | void 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 | |
339 | void KJob::setProgressUnit(Unit unit) |
340 | { |
341 | Q_D(KJob); |
342 | d->progressUnit = unit; |
343 | } |
344 | |
345 | void 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 | |
354 | void KJob::emitResult() |
355 | { |
356 | if (!d_func()->isFinished) { |
357 | finishJob(emitResult: true); |
358 | } |
359 | } |
360 | |
361 | void KJob::emitPercent(qulonglong processedAmount, qulonglong totalAmount) |
362 | { |
363 | if (totalAmount != 0) { |
364 | setPercent(100.0 * processedAmount / totalAmount); |
365 | } |
366 | } |
367 | |
368 | void 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 | |
382 | void 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 | |
391 | bool KJob::isAutoDelete() const |
392 | { |
393 | Q_D(const KJob); |
394 | return d->isAutoDelete; |
395 | } |
396 | |
397 | void KJob::setAutoDelete(bool autodelete) |
398 | { |
399 | Q_D(KJob); |
400 | d->isAutoDelete = autodelete; |
401 | } |
402 | |
403 | void KJob::setFinishedNotificationHidden(bool hide) |
404 | { |
405 | Q_D(KJob); |
406 | d->m_hideFinishedNotification = hide; |
407 | } |
408 | |
409 | bool KJob::isFinishedNotificationHidden() const |
410 | { |
411 | Q_D(const KJob); |
412 | return d->m_hideFinishedNotification; |
413 | } |
414 | |
415 | bool KJob::isStartedWithExec() const |
416 | { |
417 | Q_D(const KJob); |
418 | return d->m_startedWithExec; |
419 | } |
420 | |
421 | #include "moc_kjob.cpp" |
422 | |