1/*
2 * Copyright 2008 Aaron Seigo <aseigo@kde.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Library General Public License as
6 * published by the Free Software Foundation; either version 2, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20#include "service.h"
21#include "servicejob.h"
22#include "private/authorizationmanager_p.h"
23#include "private/service_p.h"
24#include "private/serviceprovider_p.h"
25
26#include "config-plasma.h"
27
28#include <QFile>
29#include <QGraphicsWidget>
30#include <QTimer>
31
32#include <kdebug.h>
33#include <kservice.h>
34#include <kservicetypetrader.h>
35#include <ksharedconfig.h>
36#include <kstandarddirs.h>
37#include <dnssd/publicservice.h>
38#include <dnssd/servicebrowser.h>
39
40#include "configloader.h"
41#include "version.h"
42#include "private/configloader_p.h"
43#include "private/remoteservice_p.h"
44#include "private/remoteservicejob_p.h"
45#include "pluginloader.h"
46
47namespace Plasma
48{
49
50Service::Service(QObject *parent)
51 : QObject(parent),
52 d(new ServicePrivate(this))
53{
54}
55
56Service::Service(QObject *parent, const QVariantList &args)
57 : QObject(parent),
58 d(new ServicePrivate(this))
59{
60 Q_UNUSED(args)
61}
62
63Service::~Service()
64{
65 d->unpublish();
66 delete d;
67}
68
69Service *Service::load(const QString &name, QObject *parent)
70{
71 QVariantList args;
72 return load(name, args, parent);
73}
74
75Service *Service::load(const QString &name, const QVariantList &args, QObject *parent)
76{
77 return PluginLoader::pluginLoader()->loadService(name, args, parent);
78}
79
80Service *Service::access(const KUrl &url, QObject *parent)
81{
82 return new RemoteService(parent, url);
83}
84
85void ServicePrivate::jobFinished(KJob *job)
86{
87 emit q->finished(static_cast<ServiceJob*>(job));
88}
89
90void ServicePrivate::associatedWidgetDestroyed(QObject *obj)
91{
92 associatedWidgets.remove(static_cast<QWidget*>(obj));
93}
94
95void ServicePrivate::associatedGraphicsWidgetDestroyed(QObject *obj)
96{
97 associatedGraphicsWidgets.remove(static_cast<QGraphicsObject*>(obj));
98}
99
100void ServicePrivate::publish(AnnouncementMethods methods, const QString &name, const PackageMetadata &metadata)
101{
102#ifdef ENABLE_REMOTE_WIDGETS
103 if (!serviceProvider) {
104 AuthorizationManager::self()->d->prepareForServicePublication();
105
106 serviceProvider = new ServiceProvider(name, q);
107
108 if (methods.testFlag(ZeroconfAnnouncement) &&
109 (DNSSD::ServiceBrowser::isAvailable() == DNSSD::ServiceBrowser::Working)) {
110 //TODO: dynamically pick a free port number.
111 publicService = new DNSSD::PublicService(name, "_plasma._tcp", 4000);
112
113 QMap<QString, QByteArray> textData;
114 textData["name"] = name.toUtf8();
115 textData["plasmoidname"] = metadata.name().toUtf8();
116 textData["description"] = metadata.description().toUtf8();
117 textData["icon"] = metadata.icon().toUtf8();
118 publicService->setTextData(textData);
119 kDebug() << "about to publish";
120
121 publicService->publishAsync();
122 } else if (methods.testFlag(ZeroconfAnnouncement) &&
123 (DNSSD::ServiceBrowser::isAvailable() != DNSSD::ServiceBrowser::Working)) {
124 kDebug() << "sorry, but your zeroconf daemon doesn't seem to be running.";
125 }
126 } else {
127 kDebug() << "already published!";
128 }
129#else
130 kWarning() << "libplasma is compiled without support for remote widgets. not publishing.";
131#endif
132}
133
134void ServicePrivate::unpublish()
135{
136 delete serviceProvider;
137 serviceProvider = 0;
138
139 delete publicService;
140 publicService = 0;
141}
142
143bool ServicePrivate::isPublished() const
144{
145 if (serviceProvider) {
146 return true;
147 } else {
148 return false;
149 }
150}
151
152KConfigGroup ServicePrivate::dummyGroup()
153{
154 if (!dummyConfig) {
155 dummyConfig = new KConfig(QString(), KConfig::SimpleConfig);
156 }
157
158 return KConfigGroup(dummyConfig, "DummyGroup");
159}
160
161void Service::setDestination(const QString &destination)
162{
163 d->destination = destination;
164}
165
166QString Service::destination() const
167{
168 return d->destination;
169}
170
171QStringList Service::operationNames() const
172{
173 if (!d->config) {
174 kDebug() << "No valid operations scheme has been registered";
175 return QStringList();
176 }
177
178 return d->config->groupList();
179}
180
181KConfigGroup Service::operationDescription(const QString &operationName)
182{
183 if (!d->config) {
184 kDebug() << "No valid operations scheme has been registered";
185 return d->dummyGroup();
186 }
187
188 d->config->writeConfig();
189 KConfigGroup params(d->config->config(), operationName);
190 //kDebug() << "operation" << operationName
191 // << "requested, has keys" << params.keyList() << "from"
192 // << d->config->config()->name();
193 return params;
194}
195
196QMap<QString, QVariant> Service::parametersFromDescription(const KConfigGroup &description)
197{
198 QMap<QString, QVariant> params;
199
200 if (!d->config || !description.isValid()) {
201 return params;
202 }
203
204 const QString op = description.name();
205 foreach (const QString &key, description.keyList()) {
206 KConfigSkeletonItem *item = d->config->findItem(op, key);
207 if (item) {
208 params.insert(key, description.readEntry(key, item->property()));
209 }
210 }
211
212 return params;
213}
214
215ServiceJob *Service::startOperationCall(const KConfigGroup &description, QObject *parent)
216{
217 // TODO: nested groups?
218 ServiceJob *job = 0;
219 const QString op = description.isValid() ? description.name() : QString();
220
221 RemoteService *rs = qobject_cast<RemoteService *>(this);
222 if (!op.isEmpty() && rs && !rs->isReady()) {
223 // if we have an operation, but a non-ready remote service, just let it through
224 kDebug() << "Remote service is not ready; queueing operation";
225 QMap<QString, QVariant> params;
226 job = createJob(op, params);
227 RemoteServiceJob *rsj = qobject_cast<RemoteServiceJob *>(job);
228 if (rsj) {
229 rsj->setDelayedDescription(description);
230 }
231 } else if (!d->config) {
232 kDebug() << "No valid operations scheme has been registered";
233 } else if (!op.isEmpty() && d->config->hasGroup(op)) {
234 if (d->disabledOperations.contains(op)) {
235 kDebug() << "Operation" << op << "is disabled";
236 } else {
237 QMap<QString, QVariant> params = parametersFromDescription(description);
238 job = createJob(op, params);
239 }
240 } else {
241 kDebug() << op << "is not a valid group; valid groups are:" << d->config->groupList();
242 }
243
244 if (!job) {
245 job = new NullServiceJob(destination(), op, this);
246 }
247
248 job->setParent(parent ? parent : this);
249 connect(job, SIGNAL(finished(KJob*)), this, SLOT(jobFinished(KJob*)));
250 QTimer::singleShot(0, job, SLOT(autoStart()));
251 return job;
252}
253
254void Service::associateWidget(QWidget *widget, const QString &operation)
255{
256 if (!widget) {
257 return;
258 }
259
260 disassociateWidget(widget);
261 d->associatedWidgets.insert(widget, operation);
262 connect(widget, SIGNAL(destroyed(QObject*)), this, SLOT(associatedWidgetDestroyed(QObject*)));
263
264 widget->setEnabled(!d->disabledOperations.contains(operation));
265}
266
267void Service::disassociateWidget(QWidget *widget)
268{
269 if (!widget) {
270 return;
271 }
272
273 disconnect(widget, SIGNAL(destroyed(QObject*)),
274 this, SLOT(associatedWidgetDestroyed(QObject*)));
275 d->associatedWidgets.remove(widget);
276}
277
278void Service::associateWidget(QGraphicsWidget *widget, const QString &operation)
279{
280 associateItem(widget, operation);
281}
282
283void Service::disassociateWidget(QGraphicsWidget *widget)
284{
285 disassociateItem(widget);
286}
287
288void Service::associateItem(QGraphicsObject *widget, const QString &operation)
289{
290 if (!widget) {
291 return;
292 }
293
294 disassociateItem(widget);
295 d->associatedGraphicsWidgets.insert(widget, operation);
296 connect(widget, SIGNAL(destroyed(QObject*)),
297 this, SLOT(associatedGraphicsWidgetDestroyed(QObject*)));
298
299 widget->setEnabled(!d->disabledOperations.contains(operation));
300}
301
302void Service::disassociateItem(QGraphicsObject *widget)
303{
304 if (!widget) {
305 return;
306 }
307
308 disconnect(widget, SIGNAL(destroyed(QObject*)),
309 this, SLOT(associatedGraphicsWidgetDestroyed(QObject*)));
310 d->associatedGraphicsWidgets.remove(widget);
311}
312
313QString Service::name() const
314{
315 return d->name;
316}
317
318void Service::setName(const QString &name)
319{
320 d->name = name;
321
322 // now reset the config, which may be based on our name
323 delete d->config;
324 d->config = 0;
325
326 delete d->dummyConfig;
327 d->dummyConfig = 0;
328
329 registerOperationsScheme();
330
331 emit serviceReady(this);
332}
333
334void Service::setOperationEnabled(const QString &operation, bool enable)
335{
336 if (!d->config || !d->config->hasGroup(operation)) {
337 return;
338 }
339
340 if (enable) {
341 d->disabledOperations.remove(operation);
342 } else {
343 d->disabledOperations.insert(operation);
344 }
345
346 {
347 QHashIterator<QWidget *, QString> it(d->associatedWidgets);
348 while (it.hasNext()) {
349 it.next();
350 if (it.value() == operation) {
351 it.key()->setEnabled(enable);
352 }
353 }
354 }
355
356 {
357 QHashIterator<QGraphicsObject *, QString> it(d->associatedGraphicsWidgets);
358 while (it.hasNext()) {
359 it.next();
360 if (it.value() == operation) {
361 it.key()->setEnabled(enable);
362 }
363 }
364 }
365}
366
367bool Service::isOperationEnabled(const QString &operation) const
368{
369 return d->config && d->config->hasGroup(operation) && !d->disabledOperations.contains(operation);
370}
371
372void Service::setOperationsScheme(QIODevice *xml)
373{
374 delete d->config;
375
376 delete d->dummyConfig;
377 d->dummyConfig = 0;
378
379 KSharedConfigPtr c = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig);
380 d->config = new ConfigLoader(c, xml, this);
381 d->config->d->setWriteDefaults(true);
382
383 emit operationsChanged();
384
385 {
386 QHashIterator<QWidget *, QString> it(d->associatedWidgets);
387 while (it.hasNext()) {
388 it.next();
389 it.key()->setEnabled(d->config->hasGroup(it.value()));
390 }
391 }
392
393 {
394 QHashIterator<QGraphicsObject *, QString> it(d->associatedGraphicsWidgets);
395 while (it.hasNext()) {
396 it.next();
397 it.key()->setEnabled(d->config->hasGroup(it.value()));
398 }
399 }
400}
401
402void Service::registerOperationsScheme()
403{
404 if (d->config) {
405 // we've already done our job. let's go home.
406 return;
407 }
408
409 if (d->name.isEmpty()) {
410 kDebug() << "No name found";
411 return;
412 }
413
414 const QString path = KStandardDirs::locate("data", "plasma/services/" + d->name + ".operations");
415
416 if (path.isEmpty()) {
417 kDebug() << "Cannot find operations description:" << d->name << ".operations";
418 return;
419 }
420
421 QFile file(path);
422 setOperationsScheme(&file);
423}
424
425} // namespace Plasma
426
427#include "service.moc"
428
429