Package install and uninstall are now non-static async

Installation and uninstallation of packages is now done in an instance
of the package. The methods return a KJob* which can be used to track
progress and result. The real work is done in a thread in order to make
it possible to not block the UI.
This commit is contained in:
Sebastian Kügler 2012-12-09 14:12:55 +01:00
parent eaa9afe7e6
commit 8b6a248033
6 changed files with 50 additions and 294 deletions

View File

@ -118,7 +118,10 @@ set(plasma_LIB_SRCS
private/dataengineservice.cpp
private/effects/halopainter.cpp
private/getsource.cpp
private/packages.cpp
private/packagejob.cpp
private/packagejobthread.cpp
private/plasmoidservice.cpp
private/remotedataengine.cpp
private/remoteservice.cpp

View File

@ -22,18 +22,13 @@
#include "package.h"
#include <QDBusInterface>
#include <QDBusPendingCall>
#include <QFile>
#include <QIODevice>
#include <QRegExp>
#include <QtNetwork/QHostInfo>
#include <qtemporarydir.h>
#include <karchive.h>
#include <kdebug.h>
#include <kdesktopfile.h>
#include <kservicetypetrader.h>
#include <kstandarddirs.h>
#include <ktar.h>
#include <kzip.h>
@ -46,70 +41,11 @@
#include "pluginloader.h"
#include "private/package_p.h"
#include "private/packages_p.h"
#include "private/packagejob_p.h"
namespace Plasma
{
bool copyFolder(QString sourcePath, QString targetPath)
{
QDir source(sourcePath);
if (!source.exists()) {
return false;
}
QDir target(targetPath);
if (!target.exists()) {
QString targetName = target.dirName();
target.cdUp();
target.mkdir(targetName);
target = QDir(targetPath);
}
foreach (const QString &fileName, source.entryList(QDir::Files)) {
QString sourceFilePath = sourcePath + QDir::separator() + fileName;
QString targetFilePath = targetPath + QDir::separator() + fileName;
if (!QFile::copy(sourceFilePath, targetFilePath)) {
return false;
}
}
foreach (const QString &subFolderName, source.entryList(QDir::AllDirs | QDir::NoDotAndDotDot)) {
QString sourceSubFolderPath = sourcePath + QDir::separator() + subFolderName;
QString targetSubFolderPath = targetPath + QDir::separator() + subFolderName;
if (!copyFolder(sourceSubFolderPath, targetSubFolderPath)) {
return false;
}
}
return true;
}
// Qt5 TODO: use QDir::removeRecursively() instead
bool removeFolder(QString folderPath)
{
QDir folder(folderPath);
if(!folder.exists())
return false;
foreach (const QString &fileName, folder.entryList(QDir::Files)) {
if (!QFile::remove(folderPath + QDir::separator() + fileName)) {
return false;
}
}
foreach (const QString &subFolderName, folder.entryList(QDir::AllDirs | QDir::NoDotAndDotDot)) {
if (!removeFolder(folderPath + QDir::separator() + subFolderName)) {
return false;
}
}
QString folderName = folder.dirName();
folder.cdUp();
return folder.rmdir(folderName);
}
Package::Package(PackageStructure *structure)
: d(new PackagePrivate())
{
@ -433,6 +369,7 @@ QStringList Package::entryList(const char *key) const
void Package::setPath(const QString &path)
{
kDebug() << "Package::setPath() " << path;
if (path == d->path) {
return;
}
@ -453,17 +390,17 @@ void Package::setPath(const QString &path)
QDir dir(path);
if (dir.isRelative()) {
QString location;
//kDebug() <<
if (!d->defaultPackageRoot.isEmpty()) {
dir.setPath(d->defaultPackageRoot);
if (dir.isRelative()) {
location = QStandardPaths::locate(QStandardPaths::GenericDataLocation, d->defaultPackageRoot + path);
location = QStandardPaths::locate(QStandardPaths::GenericDataLocation, d->defaultPackageRoot + path, QStandardPaths::LocateDirectory);
} else {
location = d->defaultPackageRoot + path;
}
}
if (location.isEmpty()) {
location = QStandardPaths::locate(QStandardPaths::GenericDataLocation, path);
location = QStandardPaths::locate(QStandardPaths::GenericDataLocation, path, QStandardPaths::LocateDirectory);
if (location.isEmpty()) {
d->path.clear();
@ -471,7 +408,6 @@ void Package::setPath(const QString &path)
return;
}
}
dir.setPath(location);
}
@ -483,7 +419,7 @@ void Package::setPath(const QString &path)
if (info.isDir() && !basePath.endsWith('/')) {
basePath.append('/');
}
//kDebug() << "basePath is" << basePath;
kDebug() << "basePath is" << basePath;
} else {
#ifndef NDEBUG
kDebug() << path << "invalid, basePath is" << basePath;
@ -706,213 +642,27 @@ QList<const char*> Package::requiredFiles() const
return files;
}
bool Package::installPackage(const QString &package, const QString &packageRoot)
KJob* Package::install(const QString &sourcePackage, const QString &packageRoot)
{
if (d->structure) {
return d->structure.data()->installPackage(this, package, packageRoot);
}
return PackagePrivate::installPackage(package, packageRoot, d->servicePrefix);
const QString src = sourcePackage;
const QString dest = packageRoot.isEmpty() ? defaultPackageRoot() : packageRoot;
//kDebug() << "Source: " << src;
//kDebug() << "PackageRoot: " << dest;
d->path = packageRoot + "plasma-applet-org.kde.microblog-qml";
KJob *j = d->structure.data()->install(this, src, dest);
//connect(j, SIGNAL(finished(bool)), SLOT(installFinished(bool)));
return j;
}
bool PackagePrivate::installPackage(const QString &package, const QString &packageRoot, const QString &servicePrefix)
KJob* Package::uninstall(const QString &packageName, const QString &packageRoot)
{
//TODO: report *what* failed if something does fail
QDir root(packageRoot);
if (!root.exists()) {
QDir().mkpath(packageRoot);
if (!root.exists()) {
kWarning() << "Could not create package root directory:" << packageRoot;
return false;
}
}
const QString pname = metadata().pluginName();
QFileInfo fileInfo(package);
if (!fileInfo.exists()) {
kWarning() << "No such file:" << package;
return false;
}
QString path;
QTemporaryDir tempdir;
bool archivedPackage = false;
if (fileInfo.isDir()) {
// we have a directory, so let's just install what is in there
path = package;
// make sure we end in a slash!
if (path[path.size() - 1] != '/') {
path.append('/');
}
} else {
KArchive *archive = 0;
QMimeDatabase db;
QMimeType mimetype = db.mimeTypeForFile(package);
if (mimetype.inherits("application/zip")) {
archive = new KZip(package);
} else if (mimetype.inherits("application/x-compressed-tar") ||
mimetype.inherits("application/x-tar")|| mimetype.inherits("application/x-bzip-compressed-tar") ||
mimetype.inherits("application/x-xz") || mimetype.inherits("application/x-lzma")) {
archive = new KTar(package);
} else {
kWarning() << "Could not open package file, unsupported archive format:" << package << mimetype.name();
return false;
}
if (!archive->open(QIODevice::ReadOnly)) {
kWarning() << "Could not open package file:" << package;
delete archive;
return false;
}
archivedPackage = true;
path = tempdir.path() + '/';
const KArchiveDirectory *source = archive->directory();
source->copyTo(path);
QStringList entries = source->entries();
if (entries.count() == 1) {
const KArchiveEntry *entry = source->entry(entries[0]);
if (entry->isDirectory()) {
path.append(entry->name()).append("/");
}
}
delete archive;
}
QString metadataPath = path + "metadata.desktop";
if (!QFile::exists(metadataPath)) {
kWarning() << "No metadata file in package" << package << metadataPath;
return false;
}
KPluginInfo meta(metadataPath);
QString targetName = meta.pluginName();
if (targetName.isEmpty()) {
kWarning() << "Package plugin name not specified";
return false;
}
// Ensure that package names are safe so package uninstall can't inject
// bad characters into the paths used for removal.
QRegExp validatePluginName("^[\\w-\\.]+$"); // Only allow letters, numbers, underscore and period.
if (!validatePluginName.exactMatch(targetName)) {
kWarning() << "Package plugin name " << targetName << "contains invalid characters";
return false;
}
targetName = packageRoot + '/' + targetName;
if (QFile::exists(targetName)) {
kWarning() << targetName << "already exists";
return false;
}
if (archivedPackage) {
// it's in a temp dir, so just move it over.
const bool ok = copyFolder(path, targetName);
removeFolder(path);
if (!ok) {
kWarning() << "Could not move package to destination:" << targetName;
return false;
}
} else {
// it's a directory containing the stuff, so copy the contents rather
// than move them
const bool ok = copyFolder(path, targetName);
if (!ok) {
kWarning() << "Could not copy package to destination:" << targetName;
return false;
}
}
if (archivedPackage) {
// no need to remove the temp dir (which has been successfully moved if it's an archive)
tempdir.setAutoRemove(false);
}
if (!servicePrefix.isEmpty()) {
// and now we register it as a service =)
QString metaPath = targetName + "/metadata.desktop";
KDesktopFile df(metaPath);
KConfigGroup cg = df.desktopGroup();
// Q: should not installing it as a service disqualify it?
// Q: i don't think so since KServiceTypeTrader may not be
// used by the installing app in any case, and the
// package is properly installed - aseigo
//TODO: reduce code duplication with registerPackage below
const QString serviceName = servicePrefix + meta.pluginName() + ".desktop";
QString service = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kde5/services/") + serviceName;
const bool ok = QFile::copy(metaPath, service);
if (ok) {
// the icon in the installed file needs to point to the icon in the
// installation dir!
QString iconPath = targetName + '/' + cg.readEntry("Icon");
QFile icon(iconPath);
if (icon.exists()) {
KDesktopFile df(service);
KConfigGroup cg = df.desktopGroup();
cg.writeEntry("Icon", iconPath);
}
} else {
kWarning() << "Could not register package as service (this is not necessarily fatal):" << serviceName;
}
}
QDBusInterface sycoca("org.kde.kded5", "/kbuildsycoca");
sycoca.asyncCall("recreate");
return true;
}
bool Package::uninstallPackage(const QString &packageName, const QString &packageRoot)
{
if (d->structure) {
return d->structure.data()->uninstallPackage(this, packageName, packageRoot);
}
return PackagePrivate::uninstallPackage(packageName, packageRoot, d->servicePrefix);
}
bool PackagePrivate::uninstallPackage(const QString &packageName, const QString &packageRoot, const QString &servicePrefix)
{
// We need to remove the package directory and its metadata file.
const QString targetName = packageRoot + '/' + packageName;
if (!QFile::exists(targetName)) {
kWarning() << targetName << "does not exist";
return false;
}
const QString serviceName = servicePrefix + packageName + ".desktop";
QString service = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kde5/services/") + serviceName;
#ifndef NDEBUG
kDebug() << "Removing service file " << service;
#endif
bool ok = QFile::remove(service);
if (!ok) {
kWarning() << "Unable to remove " << service;
}
ok = removeFolder(targetName);
if (!ok) {
kWarning() << "Could not delete package from:" << targetName;
return false;
}
QDBusInterface sycoca("org.kde.kded5", "/kbuildsycoca");
sycoca.asyncCall("recreate");
return true;
QString proot = path();
proot.replace(pname, "");
kDebug() << "Package::uninstalling ... " << packageRoot << proot << pname << packageName;
return d->structure.data()->uninstall(this, packageRoot);
}
PackagePrivate::PackagePrivate()

View File

@ -27,6 +27,8 @@
#include <plasma/plasma.h>
#include <plasma/plasma_export.h>
class KJob;
namespace Plasma
{
@ -65,7 +67,7 @@ namespace Plasma
* Subclassing PackageStructure does have provide a number of potential const benefits:
* * the package can be notified of path changes via the virtual pathChanged() method
* * the subclass may implement mechanisms to install and remove packages using the
* virtual installPackage and uninstallPackage methods
* virtual install and uninstall methods
* * subclasses can be compiled as plugins for easy re-use
**/
//TODO: write documentation on USING a package
@ -117,7 +119,7 @@ public:
const QString path() const;
/**
* Get the path to a given file based on the key and.an optional filename.
* Get the path to a given file based on the key and an optional filename.
* Example: finding the main script in a scripting package:
* filePath("mainscript")
*
@ -305,21 +307,16 @@ public:
* Installs a package matching this package structure. By default installs a
* native Plasma::Package.
*
* @param archivePath path to the package archive file
* @param packageRoot path to the directory where the package should be
* installed to
* @return true on successful installation, false otherwise
* @return KJob to track installation progress and result
**/
bool installPackage(const QString &archivePath, const QString &packageRoot);
KJob* install(const QString &sourcePackage, const QString &packageRoot = QString());
/**
* Uninstalls a package matching this package structure.
*
* @param packageName the name of the package to remove
* @param packageRoot path to the directory where the package should be installed to
* @return true on successful removal of the package, false otherwise
* @return KJob to track removal progress and result
*/
bool uninstallPackage(const QString &packageName, const QString &packageRoot);
KJob* uninstall(const QString &packageName, const QString &packageRoot);
private:
QExplicitlySharedDataPointer<PackagePrivate> d;

View File

@ -18,6 +18,8 @@
*******************************************************************************/
#include "packagestructure.h"
#include <kdebug.h>
#include <private/packagejob_p.h>
#include "private/package_p.h"
namespace Plasma
@ -44,14 +46,19 @@ void PackageStructure::pathChanged(Package *package)
Q_UNUSED(package)
}
bool PackageStructure::installPackage(Package *package, const QString &archivePath, const QString &packageRoot)
KJob* PackageStructure::install(Package *package, const QString &archivePath, const QString &packageRoot)
{
return PackagePrivate::installPackage(archivePath, packageRoot, package->servicePrefix());
PackageJob* j = new PackageJob(package->servicePrefix(), this);
j->install(archivePath, packageRoot);
return j;
}
bool PackageStructure::uninstallPackage(Package *package, const QString &packageName, const QString &packageRoot)
KJob* PackageStructure::uninstall(Package *package, const QString &packageRoot)
{
return PackagePrivate::uninstallPackage(packageName, packageRoot, package->servicePrefix());
PackageJob* j = new PackageJob(package->servicePrefix(), this);
kDebug() << "PS: " << package->path() << package->isValid();
j->uninstall(package->path());
return j;
}
}

View File

@ -38,6 +38,7 @@ class PLASMA_EXPORT PackageStructure : public QObject
Q_OBJECT
public:
explicit PackageStructure(QObject *parent = 0, const QVariantList &args = QVariantList());
~PackageStructure();
@ -70,9 +71,9 @@ public:
* @param archivePath path to the package archive file
* @param packageRoot path to the directory where the package should be
* installed to
* @return true on successful installation, false otherwise
* @return KJob* to track the installation status
**/
virtual bool installPackage(Package *package, const QString &archivePath, const QString &packageRoot);
virtual KJob* install(Package *package, const QString &archivePath, const QString &packageRoot);
/**
* Uninstalls a package matching this package structure.
@ -81,12 +82,12 @@ public:
* accessing file paths
* @param packageName the name of the package to remove
* @param packageRoot path to the directory where the package should be installed to
* @return true on successful removal of the package, false otherwise
* @return KJob* to track the installation status
*/
virtual bool uninstallPackage(Package *package, const QString &packageName, const QString &packageRoot);
virtual KJob* uninstall(Package *package, const QString &packageRoot);
private:
PackageStructurePrivate *const d;
PackageStructurePrivate* d;
};
} // Plasma namespace

View File

@ -72,8 +72,6 @@ public:
void createPackageMetadata(const QString &path);
void updateHash(const QString &basePath, const QString &subPath, const QDir &dir, QCryptographicHash &hash);
static bool installPackage(const QString &archivePath, const QString &packageRoot, const QString &servicePrefix);
static bool uninstallPackage(const QString &packageName, const QString &packageRoot, const QString &servicePrefix);
QWeakPointer<PackageStructure> structure;
QString path;