1 | /****************************************************************************** |
2 | * Copyright 2007 by Aaron Seigo <aseigo@kde.org> * |
3 | * Copyright 2007 by Riccardo Iaconelli <riccardo@kde.org> * |
4 | * * |
5 | * This library is free software; you can redistribute it and/or * |
6 | * modify it under the terms of the GNU Library General Public * |
7 | * License as published by the Free Software Foundation; either * |
8 | * version 2 of the License, or (at your option) any later version. * |
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 "package.h" |
22 | #include "config-plasma.h" |
23 | |
24 | #include <QDir> |
25 | #include <QFile> |
26 | #include <QRegExp> |
27 | #include <QtNetwork/QHostInfo> |
28 | |
29 | #ifdef QCA2_FOUND |
30 | #include <QtCrypto> |
31 | #endif |
32 | |
33 | #include <karchive.h> |
34 | #include <kcomponentdata.h> |
35 | #include <kdesktopfile.h> |
36 | #include <kmimetype.h> |
37 | #include <kplugininfo.h> |
38 | #include <kstandarddirs.h> |
39 | #include <ktar.h> |
40 | #include <ktempdir.h> |
41 | #include <ktemporaryfile.h> |
42 | #include <kzip.h> |
43 | #include <kdebug.h> |
44 | |
45 | #include "authorizationmanager.h" |
46 | #include "packagemetadata.h" |
47 | #include "private/authorizationmanager_p.h" |
48 | #include "private/package_p.h" |
49 | #include "private/plasmoidservice_p.h" |
50 | #include "private/service_p.h" |
51 | |
52 | namespace Plasma |
53 | { |
54 | |
55 | bool copyFolder(QString sourcePath, QString targetPath) |
56 | { |
57 | QDir source(sourcePath); |
58 | if(!source.exists()) |
59 | return false; |
60 | |
61 | QDir target(targetPath); |
62 | if(!target.exists()) { |
63 | QString targetName = target.dirName(); |
64 | target.cdUp(); |
65 | target.mkdir(targetName); |
66 | target = QDir(targetPath); |
67 | } |
68 | |
69 | foreach (const QString &fileName, source.entryList(QDir::Files)) { |
70 | QString sourceFilePath = sourcePath + QDir::separator() + fileName; |
71 | QString targetFilePath = targetPath + QDir::separator() + fileName; |
72 | |
73 | if (!QFile::copy(sourceFilePath, targetFilePath)) { |
74 | return false; |
75 | } |
76 | } |
77 | |
78 | foreach (const QString &subFolderName, source.entryList(QDir::AllDirs | QDir::NoDotAndDotDot)) { |
79 | QString sourceSubFolderPath = sourcePath + QDir::separator() + subFolderName; |
80 | QString targetSubFolderPath = targetPath + QDir::separator() + subFolderName; |
81 | |
82 | if (!copyFolder(sourceSubFolderPath, targetSubFolderPath)) { |
83 | return false; |
84 | } |
85 | } |
86 | |
87 | return true; |
88 | } |
89 | |
90 | bool removeFolder(QString folderPath) |
91 | { |
92 | QDir folder(folderPath); |
93 | if(!folder.exists()) |
94 | return false; |
95 | |
96 | foreach (const QString &fileName, folder.entryList(QDir::Files)) { |
97 | if (!QFile::remove(folderPath + QDir::separator() + fileName)) { |
98 | return false; |
99 | } |
100 | } |
101 | |
102 | foreach (const QString &subFolderName, folder.entryList(QDir::AllDirs | QDir::NoDotAndDotDot)) { |
103 | if (!removeFolder(folderPath + QDir::separator() + subFolderName)) { |
104 | return false; |
105 | } |
106 | } |
107 | |
108 | QString folderName = folder.dirName(); |
109 | folder.cdUp(); |
110 | return folder.rmdir(folderName); |
111 | } |
112 | |
113 | Package::Package() |
114 | : d(new PackagePrivate(PackageStructure::Ptr(0), QString())) |
115 | { |
116 | } |
117 | |
118 | Package::Package(const QString &packageRoot, const QString &package, |
119 | PackageStructure::Ptr structure) |
120 | : d(new PackagePrivate(structure, packageRoot, package)) |
121 | { |
122 | } |
123 | |
124 | Package::Package(const QString &packagePath, PackageStructure::Ptr structure) |
125 | : d(new PackagePrivate(structure, packagePath)) |
126 | { |
127 | } |
128 | |
129 | Package::Package(const Package &other) |
130 | : d(new PackagePrivate(*other.d)) |
131 | { |
132 | } |
133 | |
134 | Package::~Package() |
135 | { |
136 | delete d; |
137 | } |
138 | |
139 | Package &Package::operator=(const Package &rhs) |
140 | { |
141 | if (&rhs != this) { |
142 | *d = *rhs.d; |
143 | } |
144 | |
145 | return *this; |
146 | } |
147 | |
148 | bool Package::isValid() const |
149 | { |
150 | return d->isValid(); |
151 | } |
152 | |
153 | bool PackagePrivate::isValid() |
154 | { |
155 | if (!valid || !structure) { |
156 | return false; |
157 | } |
158 | |
159 | //search for the file in all prefixes and in all possible paths for each prefix |
160 | //even if it's a big nested loop, usually there is one prefix and one location |
161 | //so shouldn't cause too much disk access |
162 | QStringList prefixes = structure->contentsPrefixPaths(); |
163 | if (prefixes.isEmpty()) { |
164 | prefixes << QString(); |
165 | } |
166 | |
167 | foreach (const char *dir, structure->requiredDirectories()) { |
168 | bool failed = true; |
169 | foreach (const QString &path, structure->searchPath(dir)) { |
170 | foreach (const QString &prefix, prefixes) { |
171 | if (QFile::exists(structure->path() + prefix + path)) { |
172 | failed = false; |
173 | break; |
174 | } |
175 | } |
176 | if (!failed) { |
177 | break; |
178 | } |
179 | } |
180 | |
181 | if (failed) { |
182 | kWarning() << "Could not find required directory" << dir; |
183 | valid = false; |
184 | return false; |
185 | } |
186 | } |
187 | |
188 | foreach (const char *file, structure->requiredFiles()) { |
189 | bool failed = true; |
190 | foreach (const QString &path, structure->searchPath(file)) { |
191 | foreach (const QString &prefix, prefixes) { |
192 | if (QFile::exists(structure->path() + prefix + path)) { |
193 | failed = false; |
194 | break; |
195 | } |
196 | } |
197 | if (!failed) { |
198 | break; |
199 | } |
200 | } |
201 | |
202 | if (failed) { |
203 | kWarning() << "Could not find required file" << file; |
204 | valid = false; |
205 | return false; |
206 | } |
207 | } |
208 | |
209 | valid = true; |
210 | return true; |
211 | } |
212 | |
213 | QString Package::filePath(const char *fileType, const QString &filename) const |
214 | { |
215 | if (!d->valid) { |
216 | //kDebug() << "package is not valid"; |
217 | return QString(); |
218 | } |
219 | |
220 | QStringList paths; |
221 | |
222 | if (qstrlen(fileType) != 0) { |
223 | paths = d->structure->searchPath(fileType); |
224 | |
225 | if (paths.isEmpty()) { |
226 | //kDebug() << "no matching path came of it, while looking for" << fileType << filename; |
227 | return QString(); |
228 | } |
229 | } else { |
230 | //when filetype is empty paths is always empty, so try with an empty string |
231 | paths << QString(); |
232 | } |
233 | |
234 | //Nested loop, but in the medium case resolves to just one iteration |
235 | QStringList prefixes = d->structure->contentsPrefixPaths(); |
236 | if (prefixes.isEmpty()) { |
237 | prefixes << QString(); |
238 | } |
239 | |
240 | //kDebug() << "prefixes:" << prefixes.count() << prefixes; |
241 | foreach (const QString &contentsPrefix, prefixes) { |
242 | const QString prefix(d->structure->path() + contentsPrefix); |
243 | |
244 | foreach (const QString &path, paths) { |
245 | QString file = prefix + path; |
246 | |
247 | if (!filename.isEmpty()) { |
248 | file.append("/" ).append(filename); |
249 | } |
250 | |
251 | //kDebug() << "testing" << file << QFile::exists("/bin/ls") << QFile::exists(file); |
252 | if (QFile::exists(file)) { |
253 | if (d->structure->allowExternalPaths()) { |
254 | //kDebug() << "found" << file; |
255 | return file; |
256 | } |
257 | |
258 | // ensure that we don't return files outside of our base path |
259 | // due to symlink or ../ games |
260 | QDir dir(file); |
261 | QString canonicalized = dir.canonicalPath() + QDir::separator(); |
262 | |
263 | //kDebug() << "testing that" << canonicalized << "is in" << d->structure->path(); |
264 | if (canonicalized.startsWith(d->structure->path())) { |
265 | //kDebug() << "found" << file; |
266 | return file; |
267 | } |
268 | } |
269 | } |
270 | } |
271 | |
272 | //kDebug() << fileType << filename << "does not exist in" << prefixes << "at root" << d->structure->path(); |
273 | return QString(); |
274 | } |
275 | |
276 | QString Package::filePath(const char *fileType) const |
277 | { |
278 | return filePath(fileType, QString()); |
279 | } |
280 | |
281 | QStringList Package::entryList(const char *fileType) const |
282 | { |
283 | if (!d->valid) { |
284 | return QStringList(); |
285 | } |
286 | |
287 | return d->structure->entryList(fileType); |
288 | } |
289 | |
290 | PackageMetadata Package::metadata() const |
291 | { |
292 | if (d->structure) { |
293 | return d->structure->metadata(); |
294 | } |
295 | |
296 | return PackageMetadata(); |
297 | } |
298 | |
299 | void Package::setPath(const QString &path) |
300 | { |
301 | d->setPathFromStructure(path); |
302 | } |
303 | |
304 | const QString Package::path() const |
305 | { |
306 | return d->structure ? d->structure->path() : QString(); |
307 | } |
308 | |
309 | const PackageStructure::Ptr Package::structure() const |
310 | { |
311 | return d->structure; |
312 | } |
313 | |
314 | #ifdef QCA2_FOUND |
315 | void PackagePrivate::updateHash(const QString &basePath, const QString &subPath, const QDir &dir, QCA::Hash &hash) |
316 | { |
317 | // hash is calculated as a function of: |
318 | // * files ordered alphabetically by name, with each file's: |
319 | // * path relative to the content root |
320 | // * file data |
321 | // * directories ordered alphabetically by name, with each dir's: |
322 | // * path relative to the content root |
323 | // * file listing (recursing) |
324 | // symlinks (in both the file and dir case) are handled by adding |
325 | // the name of the symlink itself and the abs path of what it points to |
326 | |
327 | const QDir::SortFlags sorting = QDir::Name | QDir::IgnoreCase; |
328 | const QDir::Filters filters = QDir::Hidden | QDir::System | QDir::NoDotAndDotDot; |
329 | foreach (const QString &file, dir.entryList(QDir::Files | filters, sorting)) { |
330 | if (!subPath.isEmpty()) { |
331 | hash.update(subPath.toUtf8()); |
332 | } |
333 | |
334 | hash.update(file.toUtf8()); |
335 | |
336 | QFileInfo info(dir.path() + '/' + file); |
337 | if (info.isSymLink()) { |
338 | hash.update(info.symLinkTarget().toUtf8()); |
339 | } else { |
340 | QFile f(info.filePath()); |
341 | if (f.open(QIODevice::ReadOnly)) { |
342 | while (!f.atEnd()) { |
343 | hash.update(f.read(1024)); |
344 | } |
345 | } else { |
346 | kWarning() << "could not add" << f.fileName() << "to the hash; file could not be opened for reading. " |
347 | << "permissions fail?" << info.permissions() << info.isFile(); |
348 | } |
349 | } |
350 | } |
351 | |
352 | foreach (const QString &subDirPath, dir.entryList(QDir::Dirs | filters, sorting)) { |
353 | const QString relativePath = subPath + subDirPath + '/'; |
354 | hash.update(relativePath.toUtf8()); |
355 | |
356 | QDir subDir(dir.path()); |
357 | subDir.cd(subDirPath); |
358 | |
359 | if (subDir.path() != subDir.canonicalPath()) { |
360 | hash.update(subDir.canonicalPath().toUtf8()); |
361 | } else { |
362 | updateHash(basePath, relativePath, subDir, hash); |
363 | } |
364 | } |
365 | } |
366 | #endif |
367 | |
368 | QString Package::contentsHash() const |
369 | { |
370 | #ifdef QCA2_FOUND |
371 | if (!d->valid) { |
372 | kWarning() << "can not create hash due to Package being invalid" ; |
373 | return QString(); |
374 | } |
375 | |
376 | //FIXME: the initializer should go somewhere global to be shared between all plasma uses? |
377 | QCA::Initializer init; |
378 | if (!QCA::isSupported("sha1" )) { |
379 | kWarning() << "can not create hash for" << path() << "due to no SHA1 support in QCA2" ; |
380 | return QString(); |
381 | } |
382 | |
383 | QCA::Hash hash("sha1" ); |
384 | QString metadataPath = d->structure->path() + "metadata.desktop" ; |
385 | if (QFile::exists(metadataPath)) { |
386 | QFile f(metadataPath); |
387 | if (f.open(QIODevice::ReadOnly)) { |
388 | while (!f.atEnd()) { |
389 | hash.update(f.read(1024)); |
390 | } |
391 | } else { |
392 | kWarning() << "could not add" << f.fileName() << "to the hash; file could not be opened for reading." ; |
393 | } |
394 | } else { |
395 | kWarning() << "no metadata at" << metadataPath; |
396 | } |
397 | |
398 | QStringList prefixes = d->structure->contentsPrefixPaths(); |
399 | if (prefixes.isEmpty()) { |
400 | prefixes << QString(); |
401 | } |
402 | |
403 | foreach (QString prefix, prefixes) { |
404 | const QString basePath = d->structure->path() + prefix; |
405 | QDir dir(basePath); |
406 | |
407 | if (!dir.exists()) { |
408 | return QString(); |
409 | } |
410 | |
411 | d->updateHash(basePath, QString(), dir, hash); |
412 | } |
413 | return QCA::arrayToHex(hash.final().toByteArray()); |
414 | #else |
415 | // no QCA2! |
416 | kWarning() << "can not create hash for" << path() << "due to no cryptographic support (QCA2)" ; |
417 | return QString(); |
418 | #endif |
419 | } |
420 | |
421 | //TODO: provide a version of this that allows one to ask for certain types of packages, etc? |
422 | // should we be using KService here instead/as well? |
423 | QStringList Package::listInstalled(const QString &packageRoot) // static |
424 | { |
425 | QDir dir(packageRoot); |
426 | |
427 | if (!dir.exists()) { |
428 | return QStringList(); |
429 | } |
430 | |
431 | QStringList packages; |
432 | |
433 | foreach (const QString &sdir, dir.entryList(QDir::AllDirs | QDir::Readable)) { |
434 | QString metadata = packageRoot + '/' + sdir + "/metadata.desktop" ; |
435 | if (QFile::exists(metadata)) { |
436 | PackageMetadata m(metadata); |
437 | packages << m.pluginName(); |
438 | } |
439 | } |
440 | |
441 | return packages; |
442 | } |
443 | |
444 | QStringList Package::listInstalledPaths(const QString &packageRoot) // static |
445 | { |
446 | QDir dir(packageRoot); |
447 | |
448 | if (!dir.exists()) { |
449 | return QStringList(); |
450 | } |
451 | |
452 | QStringList packages; |
453 | |
454 | foreach (const QString &sdir, dir.entryList(QDir::AllDirs | QDir::Readable)) { |
455 | QString metadata = packageRoot + '/' + sdir + "/metadata.desktop" ; |
456 | if (QFile::exists(metadata)) { |
457 | packages << sdir; |
458 | } |
459 | } |
460 | |
461 | return packages; |
462 | } |
463 | |
464 | bool Package::installPackage(const QString &package, |
465 | const QString &packageRoot, |
466 | const QString &servicePrefix) // static |
467 | { |
468 | //TODO: report *what* failed if something does fail |
469 | QDir root(packageRoot); |
470 | |
471 | if (!root.exists()) { |
472 | KStandardDirs::makeDir(packageRoot); |
473 | if (!root.exists()) { |
474 | kWarning() << "Could not create package root directory:" << packageRoot; |
475 | return false; |
476 | } |
477 | } |
478 | |
479 | QFileInfo fileInfo(package); |
480 | if (!fileInfo.exists()) { |
481 | kWarning() << "No such file:" << package; |
482 | return false; |
483 | } |
484 | |
485 | QString path; |
486 | KTempDir tempdir; |
487 | bool archivedPackage = false; |
488 | |
489 | if (fileInfo.isDir()) { |
490 | // we have a directory, so let's just install what is in there |
491 | path = package; |
492 | |
493 | // make sure we end in a slash! |
494 | if (path[path.size() - 1] != '/') { |
495 | path.append('/'); |
496 | } |
497 | } else { |
498 | KArchive *archive = 0; |
499 | KMimeType::Ptr mimetype = KMimeType::findByPath(package); |
500 | |
501 | if (mimetype->is("application/zip" )) { |
502 | archive = new KZip(package); |
503 | } else if (mimetype->is("application/x-compressed-tar" ) || |
504 | mimetype->is("application/x-tar" )|| mimetype->is("application/x-bzip-compressed-tar" ) || |
505 | mimetype->is("application/x-xz-compressed-tar" ) || mimetype->is("application/x-lzma-compressed-tar" )) { |
506 | archive = new KTar(package); |
507 | } else { |
508 | kWarning() << "Could not open package file, unsupported archive format:" << package << mimetype->name(); |
509 | return false; |
510 | } |
511 | |
512 | if (!archive->open(QIODevice::ReadOnly)) { |
513 | kWarning() << "Could not open package file:" << package; |
514 | delete archive; |
515 | return false; |
516 | } |
517 | |
518 | archivedPackage = true; |
519 | path = tempdir.name(); |
520 | |
521 | const KArchiveDirectory *source = archive->directory(); |
522 | source->copyTo(path); |
523 | |
524 | QStringList entries = source->entries(); |
525 | if (entries.count() == 1) { |
526 | const KArchiveEntry *entry = source->entry(entries[0]); |
527 | if (entry->isDirectory()) { |
528 | path.append(entry->name()).append("/" ); |
529 | } |
530 | } |
531 | delete archive; |
532 | } |
533 | |
534 | QString metadataPath = path + "metadata.desktop" ; |
535 | if (!QFile::exists(metadataPath)) { |
536 | kWarning() << "No metadata file in package" << package << metadataPath; |
537 | return false; |
538 | } |
539 | |
540 | PackageMetadata meta(metadataPath); |
541 | QString targetName = meta.pluginName(); |
542 | |
543 | if (targetName.isEmpty()) { |
544 | kWarning() << "Package plugin name not specified" ; |
545 | return false; |
546 | } |
547 | |
548 | // Ensure that package names are safe so package uninstall can't inject |
549 | // bad characters into the paths used for removal. |
550 | QRegExp validatePluginName("^[\\w-\\.]+$" ); // Only allow letters, numbers, underscore and period. |
551 | if (!validatePluginName.exactMatch(targetName)) { |
552 | kWarning() << "Package plugin name " << targetName << "contains invalid characters" ; |
553 | return false; |
554 | } |
555 | |
556 | targetName = packageRoot + '/' + targetName; |
557 | if (QFile::exists(targetName)) { |
558 | kWarning() << targetName << "already exists" ; |
559 | return false; |
560 | } |
561 | |
562 | if (archivedPackage) { |
563 | // it's in a temp dir, so just move it over. |
564 | const bool ok = copyFolder(path, targetName); |
565 | removeFolder(path); |
566 | if (!ok) { |
567 | kWarning() << "Could not move package to destination:" << targetName; |
568 | return false; |
569 | } |
570 | } else { |
571 | kDebug() << "************************** 12" ; |
572 | // it's a directory containing the stuff, so copy the contents rather |
573 | // than move them |
574 | const bool ok = copyFolder(path, targetName); |
575 | kDebug() << "************************** 13" ; |
576 | if (!ok) { |
577 | kWarning() << "Could not copy package to destination:" << targetName; |
578 | return false; |
579 | } |
580 | } |
581 | |
582 | if (archivedPackage) { |
583 | // no need to remove the temp dir (which has been successfully moved if it's an archive) |
584 | tempdir.setAutoRemove(false); |
585 | } |
586 | |
587 | if (!servicePrefix.isEmpty()) { |
588 | // and now we register it as a service =) |
589 | kDebug() << "************************** 1" ; |
590 | QString metaPath = targetName + "/metadata.desktop" ; |
591 | kDebug() << "************************** 2" ; |
592 | KDesktopFile df(metaPath); |
593 | KConfigGroup cg = df.desktopGroup(); |
594 | kDebug() << "************************** 3" ; |
595 | |
596 | // Q: should not installing it as a service disqualify it? |
597 | // Q: i don't think so since KServiceTypeTrader may not be |
598 | // used by the installing app in any case, and the |
599 | // package is properly installed - aseigo |
600 | |
601 | //TODO: reduce code duplication with registerPackage below |
602 | |
603 | QString serviceName = servicePrefix + meta.pluginName(); |
604 | |
605 | QString service = KStandardDirs::locateLocal("services" , serviceName + ".desktop" ); |
606 | kDebug() << "************************** 4" ; |
607 | const bool ok = QFile::copy(metaPath, service); |
608 | kDebug() << "************************** 5" ; |
609 | if (ok) { |
610 | // the icon in the installed file needs to point to the icon in the |
611 | // installation dir! |
612 | QString iconPath = targetName + '/' + cg.readEntry("Icon" ); |
613 | QFile icon(iconPath); |
614 | if (icon.exists()) { |
615 | KDesktopFile df(service); |
616 | KConfigGroup cg = df.desktopGroup(); |
617 | cg.writeEntry("Icon" , iconPath); |
618 | } |
619 | } else { |
620 | kWarning() << "Could not register package as service (this is not necessarily fatal):" << serviceName; |
621 | } |
622 | kDebug() << "************************** 7" ; |
623 | } |
624 | |
625 | return true; |
626 | } |
627 | |
628 | bool Package::uninstallPackage(const QString &pluginName, |
629 | const QString &packageRoot, |
630 | const QString &servicePrefix) // static |
631 | { |
632 | // We need to remove the package directory and its metadata file. |
633 | QString targetName = pluginName; |
634 | targetName = packageRoot + '/' + targetName; |
635 | |
636 | if (!QFile::exists(targetName)) { |
637 | kWarning() << targetName << "does not exist" ; |
638 | return false; |
639 | } |
640 | |
641 | QString serviceName = servicePrefix + pluginName; |
642 | |
643 | QString service = KStandardDirs::locateLocal("services" , serviceName + ".desktop" ); |
644 | kDebug() << "Removing service file " << service; |
645 | bool ok = QFile::remove(service); |
646 | |
647 | if (!ok) { |
648 | kWarning() << "Unable to remove " << service; |
649 | } |
650 | |
651 | ok = removeFolder(targetName); |
652 | const QString errorString("unknown" ); |
653 | if (!ok) { |
654 | kWarning() << "Could not delete package from:" << targetName << " : " << errorString; |
655 | return false; |
656 | } |
657 | |
658 | return true; |
659 | } |
660 | |
661 | bool Package::registerPackage(const PackageMetadata &data, const QString &iconPath) |
662 | { |
663 | QString serviceName("plasma-applet-" + data.pluginName()); |
664 | QString service = KStandardDirs::locateLocal("services" , serviceName + ".desktop" ); |
665 | |
666 | if (data.pluginName().isEmpty()) { |
667 | return false; |
668 | } |
669 | |
670 | data.write(service); |
671 | |
672 | KDesktopFile config(service); |
673 | KConfigGroup cg = config.desktopGroup(); |
674 | const QString type = data.type().isEmpty() ? "Service" : data.type(); |
675 | cg.writeEntry("Type" , type); |
676 | const QString serviceTypes = data.serviceType().isNull() ? "Plasma/Applet,Plasma/Containment" : data.serviceType(); |
677 | cg.writeEntry("X-KDE-ServiceTypes" , serviceTypes); |
678 | cg.writeEntry("X-KDE-PluginInfo-EnabledByDefault" , true); |
679 | |
680 | QFile icon(iconPath); |
681 | if (icon.exists()) { |
682 | //FIXME: the '/' search will break on non-UNIX. do we care? |
683 | QString installedIcon("plasma_applet_" + data.pluginName() + |
684 | iconPath.right(iconPath.length() - iconPath.lastIndexOf("/" ))); |
685 | cg.writeEntry("Icon" , installedIcon); |
686 | installedIcon = KStandardDirs::locateLocal("icon" , installedIcon); |
687 | QFile::copy(iconPath, installedIcon); |
688 | } |
689 | |
690 | return true; |
691 | } |
692 | |
693 | bool Package::createPackage(const PackageMetadata &metadata, |
694 | const QString &source, |
695 | const QString &destination, |
696 | const QString &icon) // static |
697 | { |
698 | Q_UNUSED(icon) |
699 | if (!metadata.isValid()) { |
700 | kWarning() << "Metadata file is not complete" ; |
701 | return false; |
702 | } |
703 | |
704 | // write metadata in a temporary file |
705 | KTemporaryFile metadataFile; |
706 | if (!metadataFile.open()) { |
707 | return false; |
708 | } |
709 | metadata.write(metadataFile.fileName()); |
710 | |
711 | // put everything into a zip archive |
712 | KZip creation(destination); |
713 | creation.setCompression(KZip::NoCompression); |
714 | if (!creation.open(QIODevice::WriteOnly)) { |
715 | return false; |
716 | } |
717 | |
718 | creation.addLocalFile(metadataFile.fileName(), "metadata.desktop" ); |
719 | creation.addLocalDirectory(source, "contents" ); |
720 | creation.close(); |
721 | return true; |
722 | } |
723 | |
724 | PackagePrivate::PackagePrivate(const PackageStructure::Ptr st, const QString &p) |
725 | : structure(st), |
726 | service(0) |
727 | { |
728 | setPathFromStructure(p); |
729 | } |
730 | |
731 | PackagePrivate::PackagePrivate(const PackageStructure::Ptr st, const QString &packageRoot, const QString &path) |
732 | : structure(st), |
733 | service(0) |
734 | { |
735 | setPathFromStructure(packageRoot.isEmpty() ? path : packageRoot % "/" % path); |
736 | } |
737 | |
738 | PackagePrivate::PackagePrivate(const PackagePrivate &other) |
739 | : structure(other.structure), |
740 | service(other.service), |
741 | valid(other.valid) |
742 | { |
743 | } |
744 | |
745 | PackagePrivate::~PackagePrivate() |
746 | { |
747 | } |
748 | |
749 | PackagePrivate &PackagePrivate::operator=(const PackagePrivate &rhs) |
750 | { |
751 | structure = rhs.structure; |
752 | service = rhs.service; |
753 | valid = rhs.valid; |
754 | return *this; |
755 | } |
756 | |
757 | void PackagePrivate::setPathFromStructure(const QString &path) |
758 | { |
759 | if (!structure) { |
760 | valid = false; |
761 | return; |
762 | } |
763 | |
764 | QStringList paths; |
765 | if (path.isEmpty()) { |
766 | paths << structure->defaultPackageRoot(); |
767 | } else if (QDir::isRelativePath(path)) { |
768 | QString p = structure->defaultPackageRoot() % "/" % path % "/" ; |
769 | |
770 | if (QDir::isRelativePath(p)) { |
771 | paths = KGlobal::dirs()->findDirs("data" , p); |
772 | } else { |
773 | paths << p; |
774 | } |
775 | } else { |
776 | paths << path; |
777 | } |
778 | |
779 | foreach (const QString &p, paths) { |
780 | structure->setPath(p); |
781 | // reset valid, otherwise isValid() short-circuits |
782 | valid = true; |
783 | if (isValid()) { |
784 | return; |
785 | } |
786 | } |
787 | |
788 | valid = false; |
789 | structure->setPath(QString()); |
790 | } |
791 | |
792 | void PackagePrivate::publish(AnnouncementMethods methods) |
793 | { |
794 | if (!structure) { |
795 | return; |
796 | } |
797 | |
798 | if (!service) { |
799 | service = new PlasmoidService(structure->path()); |
800 | } |
801 | |
802 | QString resourceName = |
803 | i18nc("%1 is the name of a plasmoid, %2 the name of the machine that plasmoid is published on" , |
804 | "%1 on %2" , structure->metadata().name(), QHostInfo::localHostName()); |
805 | kDebug() << "publishing package under name " << resourceName; |
806 | service->d->publish(methods, resourceName, structure->metadata()); |
807 | } |
808 | |
809 | void PackagePrivate::unpublish() |
810 | { |
811 | if (service) { |
812 | service->d->unpublish(); |
813 | } |
814 | } |
815 | |
816 | bool PackagePrivate::isPublished() const |
817 | { |
818 | if (service) { |
819 | return service->d->isPublished(); |
820 | } else { |
821 | return false; |
822 | } |
823 | } |
824 | |
825 | } // Namespace |
826 | |