diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 4e64f38dc..544834c0c 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -37,6 +37,7 @@ MACRO(PLASMA_UNIT_TESTS) ENDMACRO(PLASMA_UNIT_TESTS) PLASMA_UNIT_TESTS( + fallbackpackagetest packagestructuretest packageurlinterceptortest pluginloadertest diff --git a/autotests/data/testfallbackpackage/contents/ui/main.qml b/autotests/data/testfallbackpackage/contents/ui/main.qml new file mode 100644 index 000000000..cde850a9d --- /dev/null +++ b/autotests/data/testfallbackpackage/contents/ui/main.qml @@ -0,0 +1,7 @@ +import QtQuick 2.0 + +Rectangle { + id: root + color: "darkblue" +} + diff --git a/autotests/data/testfallbackpackage/metadata.desktop b/autotests/data/testfallbackpackage/metadata.desktop new file mode 100644 index 000000000..f39f4947d --- /dev/null +++ b/autotests/data/testfallbackpackage/metadata.desktop @@ -0,0 +1,15 @@ +[Desktop Entry] +Encoding=UTF-8 +Keywords= +Name=Test Fallback Package +Type=Service + +X-KDE-ParentApp= +X-KDE-PluginInfo-Author=Marco Martin +X-KDE-PluginInfo-Category= +X-KDE-PluginInfo-Email=mart@kde.org +X-KDE-PluginInfo-License=GPLv2+ +X-KDE-PluginInfo-Name=org.kde.testfallbackpackage +X-KDE-PluginInfo-Version= +X-KDE-PluginInfo-Website= +X-Plasma-MainScript=ui/main.qml diff --git a/autotests/data/testpackage/contents/ui/otherfile.qml b/autotests/data/testpackage/contents/ui/otherfile.qml new file mode 100644 index 000000000..cde850a9d --- /dev/null +++ b/autotests/data/testpackage/contents/ui/otherfile.qml @@ -0,0 +1,7 @@ +import QtQuick 2.0 + +Rectangle { + id: root + color: "darkblue" +} + diff --git a/autotests/fallbackpackagetest.cpp b/autotests/fallbackpackagetest.cpp new file mode 100644 index 000000000..91bc6e93d --- /dev/null +++ b/autotests/fallbackpackagetest.cpp @@ -0,0 +1,71 @@ +/****************************************************************************** +* Copyright 2007 by Aaron Seigo * +* Copyright 2014 Marco Martin * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Library General Public * +* License as published by the Free Software Foundation; either * +* version 2 of the License, or (at your option) any later version. * +* * +* This library is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * +* Library General Public License for more details. * +* * +* You should have received a copy of the GNU Library General Public License * +* along with this library; see the file COPYING.LIB. If not, write to * +* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * +* Boston, MA 02110-1301, USA. * +*******************************************************************************/ + +#include "fallbackpackagetest.h" + + +#include + +#include "packagestructure.h" +#include "pluginloader.h" + +void FallbackPackageTest::initTestCase() +{ + m_fallPackagePath = QFINDTESTDATA("data/testpackage"); + m_fallbackPkg = Plasma::PluginLoader::self()->loadPackage("Plasma/Generic"); + m_fallbackPkg.setPath(m_fallPackagePath); + + m_packagePath = QFINDTESTDATA("data/testfallbackpackage"); + m_pkg = Plasma::PluginLoader::self()->loadPackage("Plasma/Generic"); + m_pkg.setPath(m_packagePath); +} + +void FallbackPackageTest::beforeFallback() +{ + QVERIFY(m_fallbackPkg.hasValidStructure()); + QVERIFY(m_pkg.hasValidStructure()); + + //m_fallbackPkg should have otherfile.qml, m_pkg shouldn't + QVERIFY(!m_fallbackPkg.filePath("ui", "otherfile.qml").isEmpty()); + QVERIFY(m_pkg.filePath("ui", "otherfile.qml").isEmpty()); +} + +void FallbackPackageTest::afterFallback() +{ + m_pkg.setFallbackPackage(m_fallbackPkg); + + //after setting the fallback, m_pkg should resolve the exact same file as m_fallbackPkg + // for otherfile.qml + QVERIFY(!m_pkg.filePath("ui", "otherfile.qml").isEmpty()); + QCOMPARE(m_fallbackPkg.filePath("ui", "otherfile.qml"), m_pkg.filePath("ui", "otherfile.qml")); + QVERIFY(m_fallbackPkg.filePath("mainscript") != m_pkg.filePath("mainscript")); +} + +void FallbackPackageTest::cycle() +{ + m_fallbackPkg.setFallbackPackage(m_pkg); + m_pkg.setFallbackPackage(m_fallbackPkg); + + //The cycle should have been detected and filePath should take a not infinite time + QTRY_COMPARE_WITH_TIMEOUT(m_fallbackPkg.filePath("ui", "otherfile.qml"), m_pkg.filePath("ui", "otherfile.qml"), 1000); +} + +QTEST_MAIN(FallbackPackageTest) + diff --git a/autotests/fallbackpackagetest.h b/autotests/fallbackpackagetest.h new file mode 100644 index 000000000..a1bdb4a3c --- /dev/null +++ b/autotests/fallbackpackagetest.h @@ -0,0 +1,46 @@ +/****************************************************************************** +* Copyright 2007 by Aaron Seigo * +* Copyright 2014 Marco Martin * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Library General Public * +* License as published by the Free Software Foundation; either * +* version 2 of the License, or (at your option) any later version. * +* * +* This library is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * +* Library General Public License for more details. * +* * +* You should have received a copy of the GNU Library General Public License * +* along with this library; see the file COPYING.LIB. If not, write to * +* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * +* Boston, MA 02110-1301, USA. * +*******************************************************************************/ + +#ifndef FALLBACKPACKAGETEST_H + +#include + +#include "plasma/package.h" + +class FallbackPackageTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void beforeFallback(); + void afterFallback(); + void cycle(); + + +private: + Plasma::Package m_pkg; + Plasma::Package m_fallbackPkg; + QString m_packagePath; + QString m_fallPackagePath; +}; + +#endif + diff --git a/src/plasma/data/servicetypes/plasma-shell.desktop b/src/plasma/data/servicetypes/plasma-shell.desktop index e2c83ba8b..ab7ac686c 100644 --- a/src/plasma/data/servicetypes/plasma-shell.desktop +++ b/src/plasma/data/servicetypes/plasma-shell.desktop @@ -56,4 +56,3 @@ Comment[sv]=Plasma skalkomponenter Comment[uk]=Компоненти оболонки Плазми Comment[x-test]=xxPlasma Shell Componentsxx Comment[zh_TW]=Plasma Shell 組件 - diff --git a/src/plasma/package.cpp b/src/plasma/package.cpp index 98f9352f3..07b3c90c5 100644 --- a/src/plasma/package.cpp +++ b/src/plasma/package.cpp @@ -49,6 +49,7 @@ Package::Package(PackageStructure *structure) : d(new PackagePrivate()) { d->structure = structure; + if (d->structure) { d->structure.data()->initPackage(this); } @@ -188,6 +189,27 @@ void Package::setDefaultPackageRoot(const QString &packageRoot) } } +void Package::setFallbackPackage(const Plasma::Package &package) +{ + if ((d->fallbackPackage && d->fallbackPackage->path() == package.path() && d->fallbackPackage->metadata() == package.metadata()) || + //can't be fallback of itself + (package.path() == path() && package.metadata() == metadata()) || + d->hasCycle(package)) { + return; + } + + d->fallbackPackage = new Package(package); +} + +Plasma::Package Package::fallbackPackage() const +{ + if (d->fallbackPackage) { + return (*d->fallbackPackage); + } else { + return Package(); + } +} + QString Package::servicePrefix() const { return d->servicePrefix; @@ -290,7 +312,7 @@ QString Package::filePath(const char *fileType, const QString &filename) const { if (!d->valid) { //qDebug() << "package is not valid"; - return QString(); + return d->fallbackFilePath(fileType, filename); } const QString discoveryKey(fileType + filename); @@ -305,7 +327,7 @@ QString Package::filePath(const char *fileType, const QString &filename) const //qDebug()<contents.keys(); if (!d->contents.contains(fileType)) { //qDebug() << "package does not contain" << fileType << filename; - return QString(); + return d->fallbackFilePath(fileType, filename); } paths = d->contents[fileType].paths; @@ -313,7 +335,7 @@ QString Package::filePath(const char *fileType, const QString &filename) const if (paths.isEmpty()) { //qDebug() << "no matching path came of it, while looking for" << fileType << filename; d->discoveries.insert(discoveryKey, QString()); - return QString(); + return d->fallbackFilePath(fileType, filename); } } else { //when filetype is empty paths is always empty, so try with an empty string @@ -356,7 +378,7 @@ QString Package::filePath(const char *fileType, const QString &filename) const } //qDebug() << fileType << filename << "does not exist in" << prefixes << "at root" << d->path; - return QString(); + return d->fallbackFilePath(fileType, filename); } QStringList Package::entryList(const char *key) const @@ -500,6 +522,7 @@ void Package::setPath(const QString &path) } } + // if nothing did change, then we go back to the old dptr if (d->path == previousPath) { d = oldD; @@ -511,6 +534,8 @@ void Package::setPath(const QString &path) delete d->metadata; d->metadata = 0; + QString fallback; + // uh-oh, but we didn't end up with anything valid, so we sadly reset ourselves // to futility. if (!d->valid) { @@ -764,6 +789,7 @@ KJob *Package::uninstall(const QString &packageName, const QString &packageRoot) PackagePrivate::PackagePrivate() : QSharedData(), servicePrefix("plasma-applet-"), + fallbackPackage(0), metadata(0), externalPaths(false), valid(false), @@ -786,6 +812,7 @@ PackagePrivate::~PackagePrivate() dir.removeRecursively(); } delete metadata; + delete fallbackPackage; } PackagePrivate &PackagePrivate::operator=(const PackagePrivate &rhs) @@ -795,6 +822,11 @@ PackagePrivate &PackagePrivate::operator=(const PackagePrivate &rhs) } structure = rhs.structure; + if (rhs.fallbackPackage) { + fallbackPackage = new Package(*rhs.fallbackPackage); + } else { + fallbackPackage = 0; + } path = rhs.path; contentsPrefixPaths = rhs.contentsPrefixPaths; servicePrefix = rhs.servicePrefix; @@ -874,4 +906,38 @@ void PackagePrivate::createPackageMetadata(const QString &path) metadata = new KPluginInfo(metadataPath); } +QString PackagePrivate::fallbackFilePath(const char *key, const QString &filename) const +{ + //don't fallback if the package isn't valid and never fallback the metadata file + if (qstrcmp(key, "metadata") != 0 && fallbackPackage && fallbackPackage->isValid()) { + return fallbackPackage->filePath(key, filename); + } else { + return QString(); + } +} + +bool PackagePrivate::hasCycle(const Plasma::Package &package) +{ + if (!package.d->fallbackPackage) { + return false; + } + + //This is the Floyd cycle detection algorithm + //http://en.wikipedia.org/wiki/Cycle_detection#Tortoise_and_hare + Plasma::Package *slowPackage = const_cast(&package); + Plasma::Package *fastPackage = const_cast(&package); + + while (fastPackage && fastPackage->d->fallbackPackage) { + //consider two packages the same if they have the same metadata + if ((fastPackage->d->fallbackPackage->metadata().isValid() && fastPackage->d->fallbackPackage->metadata() == slowPackage->metadata()) || + (fastPackage->d->fallbackPackage->d->fallbackPackage && fastPackage->d->fallbackPackage->d->fallbackPackage->metadata().isValid() && fastPackage->d->fallbackPackage->d->fallbackPackage->metadata() == slowPackage->metadata())) { + qWarning() << "Warning: the fallback chain of " << package.metadata().pluginName() << "contains a cyclical dependency."; + return true; + } + fastPackage = fastPackage->d->fallbackPackage->d->fallbackPackage; + slowPackage = slowPackage->d->fallbackPackage; + } + return false; +} + } // Namespace diff --git a/src/plasma/package.h b/src/plasma/package.h index 2c686d784..28f8b08a1 100644 --- a/src/plasma/package.h +++ b/src/plasma/package.h @@ -290,6 +290,20 @@ public: */ void setDefaultPackageRoot(const QString &packageRoot); + /** + * Sets the fallback package root path + * If a file won't be found in this package, it will search it in the package + * with the same structure identified by path + * It is intended to be used by the packageStructure + * @param path package root path @see setPath + */ + void setFallbackPackage(const Plasma::Package &package); + + /** + * @return The fallback package root path + */ + Plasma::Package fallbackPackage() const; + // Content structure description methods /** * @return all directories registered as part of this Package's structure @@ -328,6 +342,7 @@ public: private: QExplicitlySharedDataPointer d; + friend class PackagePrivate; }; } diff --git a/src/plasma/private/package_p.h b/src/plasma/private/package_p.h index d902eb11c..ddf2bdb84 100644 --- a/src/plasma/private/package_p.h +++ b/src/plasma/private/package_p.h @@ -73,6 +73,8 @@ public: void createPackageMetadata(const QString &path); QString unpack(const QString &filePath); void updateHash(const QString &basePath, const QString &subPath, const QDir &dir, QCryptographicHash &hash); + QString fallbackFilePath(const char *key, const QString &filename = QString()) const; + bool hasCycle(const Plasma::Package &package); QWeakPointer structure; QString path; @@ -82,6 +84,7 @@ public: QString servicePrefix; QHash discoveries; QHash contents; + Package *fallbackPackage; #ifndef PLASMA_NO_PACKAGE_EXTRADATA QStringList mimeTypes; #endif