1/*****************************************************************************
2* Copyright (C) 2009 Shaun Reich <shaun.reich@kdemail.net> *
3* Copyright (C) 2006-2008 Rafael Fernández López <ereslibre@kde.org> *
4* Copyright (C) 2001 George Staikos <staikos@kde.org> *
5* Copyright (C) 2000 Matej Koss <koss@miesto.sk> *
6* Copyright (C) 2000 David Faure <faure@kde.org> *
7* *
8* This program is free software; you can redistribute it and/or *
9* modify it under the terms of the GNU General Public License as *
10* published by the Free Software Foundation; either version 2 of *
11* the License, or (at your option) any later version. *
12* *
13* This program is distributed in the hope that it will be useful, *
14* but WITHOUT ANY WARRANTY; without even the implied warranty of *
15* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16* GNU General Public License for more details. *
17* *
18* You should have received a copy of the GNU General Public License *
19* along with this program. If not, see <http://www.gnu.org/licenses/>. *
20*****************************************************************************/
21
22#include "uiserver.h"
23#include "jobviewadaptor.h"
24#include "jobview_interface.h"
25#include "requestviewcallwatcher.h"
26
27#include <klocale.h>
28#include <kdebug.h>
29
30#include <QtDBus/QDBusPendingReply>
31#include <qdbusabstractinterface.h>
32
33JobView::JobView(uint jobId, QObject *parent)
34 : QObject(parent),
35 m_capabilities(-1),
36 m_percent(-1),
37 m_totalAmount(0),
38 m_processAmount(0),
39 m_jobId(jobId),
40 m_state(Running),
41 m_isTerminated(false),
42 m_currentPendingCalls(0)
43{
44 new JobViewV2Adaptor(this);
45
46 m_objectPath.setPath(QString("/JobViewServer/JobView_%1").arg(m_jobId));
47 QDBusConnection::sessionBus().registerObject(m_objectPath.path(), this);
48}
49
50JobView::~JobView()
51{
52}
53
54void JobView::terminate(const QString &errorMessage)
55{
56 QDBusConnection::sessionBus().unregisterObject(m_objectPath.path(), QDBusConnection::UnregisterTree);
57
58 typedef QPair<QString, QDBusAbstractInterface*> iFacePair;
59 foreach(const iFacePair &pair, m_objectPaths) {
60 kDebug(7024) << "making async call of terminate for: " << pair.first;
61 pair.second->asyncCall(QLatin1String("terminate"), errorMessage);
62 }
63
64 m_error = errorMessage;
65
66 if (m_currentPendingCalls < 1) {
67 // if hit it means a job exists for *something* but can't be terminated properly
68 // because the async call to create the job didn't come back fast enough.
69 // (thus addJobContact wasn't called before this was hit).
70// Q_ASSERT(!m_objectPaths.isEmpty());
71
72 // no more calls waiting. Lets mark ourselves for deletion.
73 emit finished(this);
74 }
75
76 m_isTerminated = true;
77}
78
79void JobView::requestSuspend()
80{
81 emit suspendRequested();
82}
83
84void JobView::requestResume()
85{
86 emit resumeRequested();
87}
88
89void JobView::requestCancel()
90{
91 emit cancelRequested();
92}
93
94void JobView::setSuspended(bool suspended)
95{
96 typedef QPair<QString, QDBusAbstractInterface*> iFacePair;
97 foreach(const iFacePair &pair, m_objectPaths) {
98 pair.second->asyncCall(QLatin1String("setSuspended"), suspended);
99 }
100
101 m_state = suspended ? Suspended : Running;
102 emit changed(m_jobId);
103}
104
105uint JobView::state() const
106{
107 return m_state;
108}
109
110void JobView::setTotalAmount(qulonglong amount, const QString &unit)
111{
112 typedef QPair<QString, QDBusAbstractInterface*> iFacePair;
113 foreach(const iFacePair &pair, m_objectPaths) {
114 pair.second->asyncCall(QLatin1String("setTotalAmount"), amount, unit);
115 }
116
117 m_totalAmount = amount;
118 m_totalUnit = unit;
119
120 if (unit == "bytes") {
121 m_sizeTotal = amount ? KGlobal::locale()->formatByteSize(amount) : QString();
122
123 } else if (unit == "files") {
124 m_sizeTotal = amount ? i18np("%1 file", "%1 files", amount) : QString();
125
126 } else if (unit == "dirs") {
127 m_sizeTotal = amount ? i18np("%1 folder", "%1 folders", amount) : QString();
128
129 }
130 emit changed(m_jobId);
131}
132
133QString JobView::sizeTotal() const
134{
135 return m_sizeTotal;
136}
137
138void JobView::setProcessedAmount(qulonglong amount, const QString &unit)
139{
140 typedef QPair<QString, QDBusAbstractInterface*> iFacePair;
141 foreach(const iFacePair &pair, m_objectPaths) {
142 pair.second->asyncCall(QLatin1String("setProcessedAmount"), amount, unit);
143 }
144
145 m_processAmount = amount;
146 m_processUnit = unit;
147
148 if (unit == "bytes") {
149 m_sizeProcessed = amount ? KGlobal::locale()->formatByteSize(amount) : QString();
150
151 } else if (unit == "files") {
152 m_sizeProcessed = amount ? i18np("%1 file", "%1 files", amount) : QString();
153
154 } else if (unit == "dirs") {
155 m_sizeProcessed = amount ? i18np("%1 folder", "%1 folders", amount) : QString();
156 }
157 emit changed(m_jobId);
158}
159
160QString JobView::sizeProcessed() const
161{
162 return m_sizeProcessed;
163}
164
165void JobView::setPercent(uint value)
166{
167 typedef QPair<QString, QDBusAbstractInterface*> iFacePair;
168 foreach(const iFacePair &pair, m_objectPaths) {
169 pair.second->asyncCall(QLatin1String("setPercent"), value);
170 }
171
172 m_percent = value;
173 emit changed(m_jobId);
174}
175
176uint JobView::percent() const
177{
178 return m_percent;
179}
180
181void JobView::setSpeed(qulonglong bytesPerSecond)
182{
183 typedef QPair<QString, QDBusAbstractInterface*> iFacePair;
184 foreach(const iFacePair &pair, m_objectPaths) {
185 pair.second->asyncCall(QLatin1String("setSpeed"), bytesPerSecond);
186 }
187
188 m_speed = bytesPerSecond ? KGlobal::locale()->formatByteSize(bytesPerSecond) : QString();
189 emit changed(m_jobId);
190}
191
192QString JobView::speed() const
193{
194 return m_speed;
195}
196
197void JobView::setInfoMessage(const QString &infoMessage)
198{
199 typedef QPair<QString, QDBusAbstractInterface*> iFacePair;
200 foreach(const iFacePair &pair, m_objectPaths) {
201 pair.second->asyncCall(QLatin1String("setInfoMessage"), infoMessage);
202 }
203
204 m_infoMessage = infoMessage;
205 emit changed(m_jobId);
206}
207
208QString JobView::infoMessage() const
209{
210 return m_infoMessage;
211}
212
213bool JobView::setDescriptionField(uint number, const QString &name, const QString &value)
214{
215 typedef QPair<QString, QDBusAbstractInterface*> iFacePair;
216 foreach(const iFacePair &pair, m_objectPaths) {
217 pair.second->asyncCall(QLatin1String("setDescriptionField"), number, name, value);
218 }
219
220 if (m_descFields.contains(number)) {
221 m_descFields[number].first = name;
222 m_descFields[number].second = value;
223 } else {
224 QPair<QString, QString> tempDescField(name, value);
225 m_descFields.insert(number, tempDescField);
226 }
227 emit changed(m_jobId);
228 return true;
229}
230
231void JobView::clearDescriptionField(uint number)
232{
233 typedef QPair<QString, QDBusAbstractInterface*> iFacePair;
234 foreach(const iFacePair &pair, m_objectPaths) {
235 pair.second->asyncCall(QLatin1String("clearDescriptionField"), number);
236 }
237
238 if (m_descFields.contains(number)) {
239 m_descFields.remove(number);
240 }
241 emit changed(m_jobId);
242}
243
244void JobView::setAppName(const QString &appName)
245{
246 typedef QPair<QString, QDBusAbstractInterface*> iFacePair;
247 foreach(const iFacePair &pair, m_objectPaths) {
248 pair.second->asyncCall(QLatin1String("setAppName"), appName);
249 }
250
251 m_applicationName = appName;
252}
253
254QString JobView::appName() const
255{
256 return m_appIconName;
257}
258
259void JobView::setAppIconName(const QString &appIconName)
260{
261 typedef QPair<QString, QDBusAbstractInterface*> iFacePair;
262 foreach(const iFacePair &pair, m_objectPaths) {
263 pair.second->asyncCall(QLatin1String("setAppIconName"), appIconName);
264 }
265
266 m_appIconName = appIconName;
267}
268
269QString JobView::appIconName() const
270{
271 return m_appIconName;
272}
273
274void JobView::setCapabilities(int capabilities)
275{
276 typedef QPair<QString, QDBusAbstractInterface*> iFacePair;
277 foreach(const iFacePair &pair, m_objectPaths) {
278 pair.second->asyncCall(QLatin1String("setCapabilities"), capabilities);
279 }
280
281 m_capabilities = capabilities;
282}
283
284int JobView::capabilities() const
285{
286 return m_capabilities;
287}
288
289QString JobView::error() const
290{
291 return m_error;
292}
293
294uint JobView::jobId() const
295{
296 return m_jobId;
297}
298
299QDBusObjectPath JobView::objectPath() const
300{
301 return m_objectPath;
302}
303
304void JobView::setDestUrl(const QDBusVariant &destUrl)
305{
306 m_destUrl = destUrl.variant();
307 emit destUrlSet();
308}
309
310QVariant JobView::destUrl() const
311{
312 return m_destUrl;
313}
314
315void JobView::addJobContact(const QString& objectPath, const QString& address)
316{
317 org::kde::JobViewV2 *client =
318 new org::kde::JobViewV2(address, objectPath, QDBusConnection::sessionBus());
319
320 QPair<QString, QDBusAbstractInterface*> pair(objectPath, client);
321
322 //propagate any request signals from the client's job, up to us, then to the parent KJob
323 //otherwise e.g. the pause button on plasma's tray would be broken.
324 connect(client, SIGNAL(suspendRequested()), this, SIGNAL(suspendRequested()));
325 connect(client, SIGNAL(resumeRequested()), this, SIGNAL(resumeRequested()));
326 connect(client, SIGNAL(cancelRequested()), this, SIGNAL(cancelRequested()));
327 Q_ASSERT(!m_objectPaths.contains(address));
328 m_objectPaths.insert(address, pair);
329
330 //If the job already has any information, send it to the contact
331 if (m_capabilities > -1) {
332 client->asyncCall(QLatin1String("setCapabilities"), m_capabilities);
333 }
334
335 if (!m_applicationName.isEmpty()) {
336 client->asyncCall(QLatin1String("setAppName"), m_applicationName);
337 }
338
339 if (!m_appIconName.isEmpty()) {
340 client->asyncCall(QLatin1String("setAppIconName"), m_appIconName);
341 }
342
343 if (m_percent > -1) {
344 client->asyncCall(QLatin1String("setPercent"), m_percent);
345 }
346
347 if (!m_infoMessage.isEmpty()) {
348 client->asyncCall(QLatin1String("setInfoMessage"), m_infoMessage);
349 }
350
351 if (!m_descFields.isEmpty()) {
352 QHashIterator<uint, QPair <QString, QString > > i(m_descFields);
353 while (i.hasNext()) {
354 i.next();
355 client->asyncCall(QLatin1String("setDescriptionField"), i.key(), i.value().first, i.value().second);
356 }
357 }
358
359 if (m_state == Suspended) {
360 client->asyncCall(QLatin1String("setSuspended"), true);
361 }
362
363 if (m_processAmount > 0) {
364 client->asyncCall(QLatin1String("setProcessedAmount"), m_processAmount, m_processUnit);
365 }
366
367 if (m_totalAmount > 0) {
368 client->asyncCall(QLatin1String("setTotalAmount"), m_totalAmount, m_totalUnit);
369 }
370}
371
372QStringList JobView::jobContacts()
373{
374 QStringList output;
375 QHash<QString, QPair<QString, QDBusAbstractInterface*> >::const_iterator it = m_objectPaths.constBegin();
376 for (; it != m_objectPaths.constEnd(); ++it) {
377 //for debug purposes only
378 output.append("service name of the interface: " + it.key() + "; objectPath for the interface: " + it.value().first);
379 }
380 return output;
381}
382
383void JobView::pendingCallStarted()
384{
385 ++m_currentPendingCalls;
386}
387
388void JobView::pendingCallFinished(RequestViewCallWatcher* watcher)
389{
390 QDBusPendingReply<QDBusObjectPath> reply = *watcher;
391 QString address = watcher->service();
392
393 if (reply.isError()) { // this happens if plasma crashed meanwhile
394 kWarning() << "got error from" << address << ":" << reply.error();
395 kWarning() << "app name was" << watcher->jobView()->appName();
396 return;
397 }
398
399 // note: this is the *remote* jobview objectpath, not the kuiserver one.
400 QDBusObjectPath objectPath = reply.argumentAt<0>();
401
402 Q_ASSERT(reply.isValid());
403
404 --m_currentPendingCalls;
405
406 if (m_isTerminated) {
407
408 // do basically the same as terminate() except only for service
409 // since this one missed out.
410
411 org::kde::JobViewV2 *client = new org::kde::JobViewV2(address, objectPath.path(), QDBusConnection::sessionBus());
412
413 kDebug(7024) << "making async terminate call to objectPath: " << objectPath.path();
414 kDebug(7024) << "this was because a pending call was finished, but the job was already terminated before it returned.";
415 kDebug(7024) << "current pending calls left: " << m_currentPendingCalls;
416
417 // forcibly set the percent (should be 100). Since the job missed out on that too.
418 client->asyncCall(QLatin1String("setPercent"), m_percent);
419 client->asyncCall(QLatin1String("terminate"), m_error);
420
421 if (m_currentPendingCalls < 1) {
422 kDebug() << "no more async calls left pending..emitting finished so we can have ourselves deleted.";
423 emit finished(this);
424 }
425 } else {
426 // add this job contact because we are _not_ just terminating here.
427 // we'll need it for regular things like speed changes, etc.
428 kDebug(7024) << "adding job contact for address: " << address << " objectPath: " << objectPath.path();
429 addJobContact(objectPath.path(), address);
430 }
431}
432
433void JobView::serviceDropped(const QString &address)
434{
435 m_objectPaths.remove(address);
436 --m_currentPendingCalls;
437}
438
439#include "jobview.moc"
440