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 | |
20 | KJobPrivate::KJobPrivate() |
21 | { |
22 | } |
23 | |
24 | KJobPrivate::~KJobPrivate() |
25 | { |
26 | } |
27 | |
28 | KJob::KJob(QObject *parent) |
29 | : QObject(parent) |
30 | , d_ptr(new KJobPrivate) |
31 | { |
32 | d_ptr->q_ptr = this; |
33 | } |
34 | |
35 | KJob::KJob(KJobPrivate &dd, QObject *parent) |
36 | : QObject(parent) |
37 | , d_ptr(&dd) |
38 | { |
39 | d_ptr->q_ptr = this; |
40 | } |
41 | |
42 | KJob::~KJob() |
43 | { |
44 | if (!d_ptr->isFinished) { |
45 | d_ptr->isFinished = true; |
46 | Q_EMIT finished(job: this, QPrivateSignal()); |
47 | } |
48 | |
49 | delete d_ptr->speedTimer; |
50 | delete d_ptr->uiDelegate; |
51 | } |
52 | |
53 | void 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(job: this); |
66 | } |
67 | } |
68 | |
69 | KJobUiDelegate *KJob::uiDelegate() const |
70 | { |
71 | return d_func()->uiDelegate; |
72 | } |
73 | |
74 | KJob::Capabilities KJob::capabilities() const |
75 | { |
76 | return d_func()->capabilities; |
77 | } |
78 | |
79 | bool KJob::isSuspended() const |
80 | { |
81 | return d_func()->suspended; |
82 | } |
83 | |
84 | void 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(job: this, QPrivateSignal()); |
96 | |
97 | if (emitResult) { |
98 | Q_EMIT result(job: this, QPrivateSignal()); |
99 | } |
100 | |
101 | if (isAutoDelete()) { |
102 | deleteLater(); |
103 | } |
104 | } |
105 | |
106 | bool 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 | |
126 | bool KJob::suspend() |
127 | { |
128 | Q_D(KJob); |
129 | if (!d->suspended) { |
130 | if (doSuspend()) { |
131 | d->suspended = true; |
132 | Q_EMIT suspended(job: this, QPrivateSignal()); |
133 | |
134 | return true; |
135 | } |
136 | } |
137 | |
138 | return false; |
139 | } |
140 | |
141 | bool KJob::resume() |
142 | { |
143 | Q_D(KJob); |
144 | if (d->suspended) { |
145 | if (doResume()) { |
146 | d->suspended = false; |
147 | Q_EMIT resumed(job: this, QPrivateSignal()); |
148 | |
149 | return true; |
150 | } |
151 | } |
152 | |
153 | return false; |
154 | } |
155 | |
156 | bool KJob::doKill() |
157 | { |
158 | return false; |
159 | } |
160 | |
161 | bool KJob::doSuspend() |
162 | { |
163 | return false; |
164 | } |
165 | |
166 | bool KJob::doResume() |
167 | { |
168 | return false; |
169 | } |
170 | |
171 | void KJob::setCapabilities(KJob::Capabilities capabilities) |
172 | { |
173 | Q_D(KJob); |
174 | d->capabilities = capabilities; |
175 | } |
176 | |
177 | bool 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(flags: QEventLoop::ExcludeUserInputEvents); |
197 | } |
198 | d->eventLoop = nullptr; |
199 | |
200 | if (wasAutoDelete) { |
201 | deleteLater(); |
202 | } |
203 | return (d->error == NoError); |
204 | } |
205 | |
206 | int KJob::error() const |
207 | { |
208 | return d_func()->error; |
209 | } |
210 | |
211 | QString KJob::errorText() const |
212 | { |
213 | return d_func()->errorText; |
214 | } |
215 | |
216 | QString KJob::errorString() const |
217 | { |
218 | return d_func()->errorText; |
219 | } |
220 | |
221 | qulonglong 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 | |
231 | qulonglong 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 | |
241 | unsigned long KJob::percent() const |
242 | { |
243 | return d_func()->percentage; |
244 | } |
245 | |
246 | bool KJob::isFinished() const |
247 | { |
248 | return d_func()->isFinished; |
249 | } |
250 | |
251 | void KJob::setError(int errorCode) |
252 | { |
253 | Q_D(KJob); |
254 | d->error = errorCode; |
255 | } |
256 | |
257 | void KJob::setErrorText(const QString &errorText) |
258 | { |
259 | Q_D(KJob); |
260 | d->errorText = errorText; |
261 | } |
262 | |
263 | void 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(job: this, unit, amount, QPrivateSignal{}); |
280 | if (unit == d->progressUnit) { |
281 | Q_EMIT processedSize(job: this, size: amount); |
282 | emitPercent(processedAmount: processed, totalAmount: total); |
283 | } |
284 | } |
285 | } |
286 | |
287 | void 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(job: this, unit, amount, QPrivateSignal{}); |
304 | if (unit == d->progressUnit) { |
305 | Q_EMIT totalSize(job: this, size: amount); |
306 | emitPercent(processedAmount: processed, totalAmount: total); |
307 | } |
308 | } |
309 | } |
310 | |
311 | void KJob::setProgressUnit(Unit unit) |
312 | { |
313 | Q_D(KJob); |
314 | d->progressUnit = unit; |
315 | } |
316 | |
317 | void KJob::setPercent(unsigned long percentage) |
318 | { |
319 | Q_D(KJob); |
320 | if (d->percentage != percentage) { |
321 | d->percentage = percentage; |
322 | Q_EMIT percentChanged(job: this, percent: percentage, QPrivateSignal{}); |
323 | } |
324 | } |
325 | |
326 | void KJob::emitResult() |
327 | { |
328 | if (!d_func()->isFinished) { |
329 | finishJob(emitResult: true); |
330 | } |
331 | } |
332 | |
333 | void KJob::emitPercent(qulonglong processedAmount, qulonglong totalAmount) |
334 | { |
335 | if (totalAmount != 0) { |
336 | setPercent(100.0 * processedAmount / totalAmount); |
337 | } |
338 | } |
339 | |
340 | void KJob::emitSpeed(unsigned long value) |
341 | { |
342 | Q_D(KJob); |
343 | if (!d->speedTimer) { |
344 | d->speedTimer = new QTimer(this); |
345 | connect(sender: d->speedTimer, signal: &QTimer::timeout, context: this, slot: [d]() { |
346 | d->speedTimeout(); |
347 | }); |
348 | } |
349 | |
350 | Q_EMIT speed(job: this, speed: value); |
351 | d->speedTimer->start(msec: 5000); // 5 seconds interval should be enough |
352 | } |
353 | |
354 | void 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(job: q, speed: 0); |
360 | speedTimer->stop(); |
361 | } |
362 | |
363 | bool KJob::isAutoDelete() const |
364 | { |
365 | Q_D(const KJob); |
366 | return d->isAutoDelete; |
367 | } |
368 | |
369 | void KJob::setAutoDelete(bool autodelete) |
370 | { |
371 | Q_D(KJob); |
372 | d->isAutoDelete = autodelete; |
373 | } |
374 | |
375 | void KJob::setFinishedNotificationHidden(bool hide) |
376 | { |
377 | Q_D(KJob); |
378 | d->m_hideFinishedNotification = hide; |
379 | } |
380 | |
381 | bool KJob::isFinishedNotificationHidden() const |
382 | { |
383 | Q_D(const KJob); |
384 | return d->m_hideFinishedNotification; |
385 | } |
386 | |
387 | bool KJob::isStartedWithExec() const |
388 | { |
389 | Q_D(const KJob); |
390 | return d->m_startedWithExec; |
391 | } |
392 | |
393 | #include "moc_kjob.cpp" |
394 | |