1/*
2 * This file is part of the KDE project
3 * Copyright (C) 2009 Shaun Reich <shaun.reich@kdemail.net>
4 * Copyright (C) 2006-2008 Rafael Fernández López <ereslibre@kde.org>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License version 2 as published by the Free Software Foundation.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19*/
20
21#include "progresslistmodel.h"
22
23#include <QDBusServiceWatcher>
24
25#include <KDebug>
26
27#include "jobviewserveradaptor.h"
28#include "kuiserveradaptor.h"
29#include "jobviewserver_interface.h"
30#include "requestviewcallwatcher.h"
31#include "uiserver.h"
32#include <QtDBus/qdbusabstractinterface.h>
33
34ProgressListModel::ProgressListModel(QObject *parent)
35 : QAbstractItemModel(parent), QDBusContext(), m_jobId(1),
36 m_uiServer(0)
37{
38 m_serviceWatcher = new QDBusServiceWatcher(this);
39 m_serviceWatcher->setConnection(QDBusConnection::sessionBus());
40 m_serviceWatcher->setWatchMode(QDBusServiceWatcher::WatchForUnregistration);
41 connect(m_serviceWatcher, SIGNAL(serviceUnregistered(const QString &)), this, SLOT(serviceUnregistered(const QString &)));
42
43 // Register necessary services and D-Bus adaptors.
44 new JobViewServerAdaptor(this);
45 new KuiserverAdaptor(this);
46
47 QDBusConnection sessionBus = QDBusConnection::sessionBus();
48
49 if (!sessionBus.registerService(QLatin1String("org.kde.kuiserver"))) {
50 kDebug(7024) <<
51 "********** Error, we have failed to register service org.kde.kuiserver. Perhaps something has already taken it?";
52 }
53
54 if (!sessionBus.registerService(QLatin1String("org.kde.JobViewServer"))) {
55 kDebug(7024) <<
56 "********** Error, we have failed to register service JobViewServer. Perhaps something already has taken it?";
57 }
58
59 if (!sessionBus.registerObject(QLatin1String("/JobViewServer"), this)) {
60 kDebug(7024) <<
61 "********** Error, we have failed to register object /JobViewServer.";
62 }
63
64 /* unused
65 if (m_registeredServices.isEmpty() && !m_uiServer) {
66 m_uiServer = new UiServer(this);
67 }
68 */
69}
70
71ProgressListModel::~ProgressListModel()
72{
73 QDBusConnection sessionBus = QDBusConnection::sessionBus();
74 sessionBus.unregisterService("org.kde.JobViewServer");
75 sessionBus.unregisterService("org.kde.kuiserver");
76
77 qDeleteAll(m_jobViews);
78 qDeleteAll(m_registeredServices);
79
80 delete m_uiServer;
81}
82
83QModelIndex ProgressListModel::parent(const QModelIndex&) const
84{
85 return QModelIndex();
86}
87
88QDBusObjectPath ProgressListModel::requestView(const QString &appName, const QString &appIconName, int capabilities)
89{
90 return newJob(appName, appIconName, capabilities);
91}
92
93Qt::ItemFlags ProgressListModel::flags(const QModelIndex &index) const
94{
95 Q_UNUSED(index);
96 return Qt::ItemIsEnabled;
97}
98
99int ProgressListModel::columnCount(const QModelIndex &parent) const
100{
101 Q_UNUSED(parent);
102 return 1;
103}
104
105QVariant ProgressListModel::data(const QModelIndex &index, int role) const
106{
107 QVariant result;
108
109 if (!index.isValid()) {
110 return result;
111 }
112
113 JobView *jobView = m_jobViews.at(index.row());
114 Q_ASSERT(jobView);
115
116 switch (role) {
117 case JobView::Capabilities:
118 result = jobView->capabilities();
119 break;
120 case JobView::ApplicationName:
121 result = jobView->appName();
122 break;
123 case JobView::Icon:
124 result = jobView->appIconName();
125 break;
126 case JobView::SizeTotal:
127 result = jobView->sizeTotal();
128 break;
129 case JobView::SizeProcessed:
130 result = jobView->sizeProcessed();
131 break;
132 case JobView::TimeTotal:
133
134 break;
135 case JobView::TimeElapsed:
136
137 break;
138 case JobView::Speed:
139 result = jobView->speed();
140 break;
141 case JobView::Percent:
142 result = jobView->percent();
143 break;
144 case JobView::InfoMessage:
145 result = jobView->infoMessage();
146 break;
147 case JobView::DescFields:
148
149 break;
150 case JobView::State:
151 result = jobView->state();
152 break;
153 case JobView::JobViewRole:
154 result = QVariant::fromValue<JobView*>(jobView);
155 break;
156 default:
157 break;
158 }
159
160 return result;
161}
162
163QModelIndex ProgressListModel::index(int row, int column, const QModelIndex &parent) const
164{
165 Q_UNUSED(parent);
166
167 if (row >= m_jobViews.count() || column > 0)
168 return QModelIndex();
169
170 return createIndex(row, column);
171}
172
173QModelIndex ProgressListModel::indexForJob(JobView *jobView) const
174{
175 int index = m_jobViews.indexOf(jobView);
176
177 if (index != -1) {
178 return createIndex(index, 0, jobView);
179 } else {
180 return QModelIndex();
181 }
182}
183
184int ProgressListModel::rowCount(const QModelIndex &parent) const
185{
186 return parent.isValid() ? 0 : m_jobViews.count();
187}
188
189QDBusObjectPath ProgressListModel::newJob(const QString &appName, const QString &appIcon, int capabilities)
190{
191 // Since s_jobId is an unsigned int, if we received an overflow and go back to 0,
192 // be sure we do not assign 0 to a valid job, 0 is reserved only for
193 // reporting problems.
194 if (!m_jobId) ++m_jobId;
195 JobView *newJob = new JobView(m_jobId);
196 ++m_jobId;
197
198 QString callerService = message().service();
199 m_jobViewsOwners.insertMulti(callerService, newJob);
200 m_serviceWatcher->addWatchedService(callerService);
201
202 newJob->setAppName(appName);
203 newJob->setAppIconName(appIcon);
204 newJob->setCapabilities(capabilities);
205
206 beginInsertRows(QModelIndex(), 0, 0);
207 m_jobViews.prepend(newJob);
208 endInsertRows();
209
210 //The model will now get notified when a job changes -- so it can emit dataChanged(..)
211 connect(newJob, SIGNAL(changed(uint)), this, SLOT(jobChanged(uint)));
212 connect(newJob, SIGNAL(finished(JobView*)), this, SLOT(jobFinished(JobView*)));
213 connect(newJob, SIGNAL(destUrlSet()), this, SLOT(emitJobUrlsChanged()));
214 connect(this, SIGNAL(serviceDropped(const QString&)), newJob, SLOT(serviceDropped(const QString&)));
215
216 //Forward this new job over to existing DBus clients.
217 foreach(QDBusAbstractInterface* interface, m_registeredServices) {
218
219 newJob->pendingCallStarted();
220 QDBusPendingCall pendingCall = interface->asyncCall(QLatin1String("requestView"), appName, appIcon, capabilities);
221 RequestViewCallWatcher *watcher = new RequestViewCallWatcher(newJob, interface->service(), pendingCall, this);
222
223 connect(watcher, SIGNAL(callFinished(RequestViewCallWatcher*)),
224 newJob, SLOT(pendingCallFinished(RequestViewCallWatcher*)));
225 }
226
227 return newJob->objectPath();
228}
229
230QStringList ProgressListModel::gatherJobUrls()
231{
232 QStringList jobUrls;
233
234 foreach(JobView* jobView, m_jobViews) {
235 jobUrls.append(jobView->destUrl().toString());
236 }
237 return jobUrls;
238}
239
240void ProgressListModel::jobFinished(JobView *jobView)
241{
242 // Job finished, delete it if we are not in self-ui mode, *and* the config option to keep finished jobs is set
243 //TODO: does not check for case for the config
244 if (!m_uiServer) {
245 kDebug(7024) << "removing jobview from list, it finished";
246 m_jobViews.removeOne(jobView);
247 //job dies, dest. URL's change..
248 emit jobUrlsChanged(gatherJobUrls());
249 }
250}
251
252void ProgressListModel::jobChanged(uint jobId)
253{
254 emit dataChanged(createIndex(jobId - 1, 0), createIndex(jobId + 1, 0));
255 layoutChanged();
256}
257
258void ProgressListModel::emitJobUrlsChanged()
259{
260 emit jobUrlsChanged(gatherJobUrls());
261}
262
263void ProgressListModel::registerService(const QString &serviceName, const QString &objectPath)
264{
265 QDBusConnection sessionBus = QDBusConnection::sessionBus();
266
267 if (!serviceName.isEmpty() && !objectPath.isEmpty()) {
268 if (sessionBus.interface()->isServiceRegistered(serviceName).value() &&
269 !m_registeredServices.contains(serviceName)) {
270
271 org::kde::JobViewServer *client =
272 new org::kde::JobViewServer(serviceName, objectPath, sessionBus);
273
274 if (client->isValid()) {
275
276 delete m_uiServer;
277 m_uiServer = 0;
278
279 m_serviceWatcher->addWatchedService(serviceName);
280 m_registeredServices.insert(serviceName, client);
281
282
283 //tell this new client to create all of the same jobs that we currently have.
284 //also connect them so that when the method comes back, it will return a
285 //QDBusObjectPath value, which is where we can contact that job, within "org.kde.JobViewV2"
286 //TODO: KDE5 remember to replace current org.kde.JobView interface with the V2 one.. (it's named V2 for compat. reasons).
287
288 //TODO: this falls victim to what newJob used to be vulnerable to...async calls returning too slowly and a terminate ensuing before that.
289 // it may not be a problem (yet), though.
290 foreach(JobView* jobView, m_jobViews) {
291
292 QDBusPendingCall pendingCall = client->asyncCall(QLatin1String("requestView"), jobView->appName(), jobView->appIconName(), jobView->capabilities());
293
294 RequestViewCallWatcher *watcher = new RequestViewCallWatcher(jobView, serviceName, pendingCall, this);
295 connect(watcher, SIGNAL(callFinished(RequestViewCallWatcher*)),
296 jobView, SLOT(pendingCallFinished(RequestViewCallWatcher*)));
297 }
298 } else {
299 delete client;
300 }
301 }
302 }
303}
304
305bool ProgressListModel::requiresJobTracker()
306{
307 return m_registeredServices.isEmpty();
308}
309
310void ProgressListModel::serviceUnregistered(const QString &name)
311{
312 m_serviceWatcher->removeWatchedService(name);
313 if (m_registeredServices.contains(name)) {
314
315 emit serviceDropped(name);
316 m_registeredServices.remove(name);
317
318 /* unused (FIXME)
319 if (m_registeredServices.isEmpty()) {
320 //the last service dropped, we *need* to show our GUI
321 m_uiServer = new UiServer(this);
322 }
323 */
324 }
325
326 QList<JobView*> jobs = m_jobViewsOwners.values(name);
327 if (!jobs.isEmpty()) {
328 m_jobViewsOwners.remove(name);
329 Q_FOREACH(JobView *job, jobs) {
330 job->terminate(QString());
331 }
332 }
333}
334
335QStringList ProgressListModel::registeredJobContacts()
336{
337 QStringList output;
338 foreach (JobView* jobView, m_jobViews) {
339 output.append(jobView->jobContacts());
340 }
341 return output;
342}
343
344#include "progresslistmodel.moc"
345