diff --git a/CMakeLists.txt b/CMakeLists.txt index 86b918c..e9e1b06 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,138 +1,140 @@ cmake_minimum_required(VERSION 3.5) set(KF5_VERSION "5.64.0") # handled by release scripts set(KF5_DEP_VERSION "5.63.0") # handled by release scripts project(KPackage VERSION ${KF5_VERSION}) # ECM setup include(FeatureSummary) find_package(ECM 5.63.0 NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://projects.kde.org/projects/kdesupport/extra-cmake-modules") feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) -include(GenerateExportHeader) +include(ECMGenerateExportHeader) include(ECMGenerateHeaders) include(CMakePackageConfigHelpers) include(ECMSetupVersion) include(ECMMarkNonGuiExecutable) include(ECMAddQch) include(ECMQtDeclareLoggingCategory) include(ECMSetupQtPluginMacroNames) +set(EXCLUDE_DEPRECATED_BEFORE_AND_AT 0 CACHE STRING "Control the range of deprecated API excluded from the build [default=0].") + option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF) add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)") ecm_setup_version(PROJECT VARIABLE_PREFIX PACKAGE VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kpackage_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5PackageConfigVersion.cmake" SOVERSION 5) ################# Enable C++0x (still too early for -std=c++11) features for clang and gcc ################# if(UNIX) #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -std=c++0x") add_definitions("-Wall -std=c++0x") endif() ################# now find all used packages ################# set (REQUIRED_QT_VERSION 5.11.0) find_package(Qt5 ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE COMPONENTS Core DBus) find_package(KF5Archive ${KF5_DEP_VERSION} REQUIRED) find_package(KF5I18n ${KF5_DEP_VERSION} REQUIRED) find_package(KF5CoreAddons ${KF5_DEP_VERSION} REQUIRED) find_package(KF5DocTools ${KF5_DEP_VERSION}) set_package_properties(KF5DocTools PROPERTIES DESCRIPTION "Tools to generate documentation" TYPE OPTIONAL ) ######################################################################### ecm_setup_qtplugin_macro_names( JSON_ARG2 "K_EXPORT_KPACKAGE_PACKAGE_WITH_JSON" CONFIG_CODE_VARIABLE PACKAGE_SETUP_AUTOMOC_VARIABLES ) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050d00) include(KF5PackageMacros.cmake) # make package_version.h available include_directories(${CMAKE_CURRENT_BINARY_DIR}) ################# list the subdirectories ################# if (KF5DocTools_FOUND) add_subdirectory(docs) endif() add_definitions(-DTRANSLATION_DOMAIN=\"libkpackage5\") add_definitions(-DQT_NO_FOREACH) if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ki18n_install(po) if (KF5DocTools_FOUND) kdoctools_install(po) endif() endif() add_subdirectory(src) if (BUILD_TESTING) add_subdirectory(autotests) endif() ################ create PackageConfig.cmake and install it #################### # create a Config.cmake and a ConfigVersion.cmake file and install them set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5Package") if (BUILD_QCH) ecm_install_qch_export( TARGETS KF5Package_QCH FILE KF5PackageQchTargets.cmake DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KF5PackageQchTargets.cmake\")") endif() configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5PackageConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5PackageConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} PATH_VARS KF5_INCLUDE_INSTALL_DIR CMAKE_INSTALL_PREFIX ) install(FILES qrc.cmake "${CMAKE_CURRENT_BINARY_DIR}/KF5PackageConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5PackageConfigVersion.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/KF5PackageMacros.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5PackageTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5PackageTargets.cmake NAMESPACE KF5:: COMPONENT Devel) install(EXPORT KF5PackageToolsTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5PackageToolsTargets.cmake NAMESPACE KF5:: COMPONENT Devel) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kpackage_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) install(FILES kpackage.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/autotests/plasmoidpackagetest.cpp b/autotests/plasmoidpackagetest.cpp index d216bc0..b07dd21 100644 --- a/autotests/plasmoidpackagetest.cpp +++ b/autotests/plasmoidpackagetest.cpp @@ -1,446 +1,444 @@ /****************************************************************************** * Copyright 2007 by Bertjan Broeksema * * * * 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 "plasmoidpackagetest.h" #include "../src/kpackage/config-package.h" #include "../src/kpackage/private/versionparser.cpp" #include #include #include #include #include #include #include "packageloader.h" #include "plasmoidstructure.h" void PlasmoidPackageTest::initTestCase() { QStandardPaths::setTestModeEnabled(true); } void PlasmoidPackageTest::init() { qDebug() << "PlasmoidPackage::init()"; m_package = QStringLiteral("Package"); m_packageRoot = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/packageRoot"; m_defaultPackage = KPackage::Package(new KPackage::PlasmoidPackage); cleanup(); // to prevent previous runs from interfering with this one } void PlasmoidPackageTest::cleanup() { qDebug() << "cleaning up"; // Clean things up. QDir(m_packageRoot).removeRecursively(); } void PlasmoidPackageTest::createTestPackage(const QString &packageName, const QString &version) { qDebug() << "Create test package" << m_packageRoot; QDir pRoot(m_packageRoot); // Create the root and package dir. if (!pRoot.exists()) { QVERIFY(QDir().mkpath(m_packageRoot)); } // Create the package dir QVERIFY(QDir().mkpath(m_packageRoot + "/" + packageName)); qDebug() << "Created" << (m_packageRoot + "/" + packageName); // Create the metadata.desktop file QFile file(m_packageRoot + "/" + packageName + "/metadata.desktop"); QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); QTextStream out(&file); out << "[Desktop Entry]\n"; out << "Name=" << packageName << "\n"; out << "X-KDE-PluginInfo-Name=" << packageName << "\n"; out << "X-KDE-PluginInfo-Version=" << version << "\n"; file.flush(); file.close(); qDebug() << "OUT: " << packageName; // Create the ui dir. QVERIFY(QDir().mkpath(m_packageRoot + "/" + packageName + "/contents/ui")); // Create the main file. file.setFileName(m_packageRoot + "/" + packageName + "/contents/ui/main.qml"); QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); out << "THIS IS A PLASMOID SCRIPT....."; file.flush(); file.close(); qDebug() << "THIS IS A PLASMOID SCRIPT THING"; // Now we have a minimal plasmoid package which is valid. Let's add some // files to it for test purposes. // Create the images dir. QVERIFY(QDir().mkpath(m_packageRoot + "/" + packageName + "/contents/images")); file.setFileName(m_packageRoot + "/" + packageName + "/contents/images/image-1.svg"); QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); out << "This is a test image"; file.flush(); file.close(); file.setFileName(m_packageRoot + "/" + packageName + "/contents/images/image-2.svg"); QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); out.setDevice(&file); out << "This is another test image"; file.flush(); file.close(); // Create the scripts dir. QVERIFY(QDir().mkpath(m_packageRoot + "/" + packageName + "/contents/code")); // Create 2 js files file.setFileName(m_packageRoot + "/" + packageName + "/contents/code/script.js"); QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); out << "THIS IS A SCRIPT....."; file.flush(); file.close(); } void PlasmoidPackageTest::isValid() { KPackage::Package *p = new KPackage::Package(m_defaultPackage); p->setPath(m_packageRoot + '/' + m_package); #ifndef NDEBUG qDebug() << "package path is" << p->path(); #endif // A PlasmoidPackage is valid when: // - The package root exists. // - The package root consists an file named "ui/main.qml" QVERIFY(!p->isValid()); // Create the root and package dir. QVERIFY(QDir().mkpath(m_packageRoot)); QVERIFY(QDir().mkpath(m_packageRoot + "/" + m_package)); // Should still be invalid. delete p; p = new KPackage::Package(m_defaultPackage); p->setPath(m_packageRoot + '/' + m_package); QVERIFY(!p->isValid()); // Create the metadata.desktop file. QFile file(m_packageRoot + "/" + m_package + "/metadata.desktop"); QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); QTextStream out(&file); out << "[Desktop Entry]\n"; out << "Name=test\n"; out << "Description=Just a test desktop file"; file.flush(); file.close(); // Create the ui dir. QVERIFY(QDir().mkpath(m_packageRoot + "/" + m_package + "/contents/ui")); // No main file yet so should still be invalid. delete p; p = new KPackage::Package(m_defaultPackage); p->setPath(m_packageRoot + '/' + m_package); QVERIFY(!p->isValid()); // Create the main file. file.setFileName(m_packageRoot + "/" + m_package + "/contents/ui/main.qml"); QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); out.setDevice(&file); out << "THIS IS A PLASMOID SCRIPT.....\n"; file.flush(); file.close(); file.setPermissions(QFile::ReadUser | QFile::WriteUser); // Main file exists so should be valid now. delete p; p = new KPackage::Package(m_defaultPackage); p->setPath(m_packageRoot + '/' + m_package); QVERIFY(p->isValid()); - // Ignore deprecation warning, we are actually testing this here. -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#if KPACKAGE_ENABLE_DEPRECATED_SINCE(5, 21) QCOMPARE(p->contentsHash(), QStringLiteral("a41160c6a763ea505c95bee12a7fc87952a61cf1")); -#pragma GCC diagnostic pop +#endif QCOMPARE(p->cryptographicHash(QCryptographicHash::Sha1), QByteArrayLiteral("a41160c6a763ea505c95bee12a7fc87952a61cf1")); delete p; } void PlasmoidPackageTest::filePath() { return; // Package::filePath() returns // - {package_root}/{package_name}/path/to/file if the file exists // - QString() otherwise. KPackage::Package *p = new KPackage::Package(m_defaultPackage); p->setPath(m_packageRoot + '/' + m_package); QCOMPARE(p->filePath("scripts", QStringLiteral("main")), QString()); QVERIFY(QDir().mkpath(m_packageRoot + "/" + m_package + "/contents/ui/main.qml")); QFile file(m_packageRoot + "/" + m_package + "/contents/ui/main.qml"); QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); QTextStream out(&file); out << "THIS IS A PLASMOID SCRIPT....."; file.flush(); file.close(); // The package is valid by now so a path for code/main should get returned. delete p; p = new KPackage::Package(m_defaultPackage); p->setPath(m_packageRoot + '/' + m_package); const QString path = QFileInfo(m_packageRoot + "/" + m_package + "/contents/ui/main.qml").canonicalFilePath(); // Two ways to get the same info. // 1. Give the file type which refers to a class of files (a directory) in // the package structure and the file name. // 2. Give the file type which refers to a file in the package structure. // // NOTE: scripts, main and mainscript are defined in packages.cpp and are // specific for a PlasmoidPackage. QCOMPARE(p->filePath("scripts", QStringLiteral("main")), path); QCOMPARE(p->filePath("mainscript"), path); delete p; } void PlasmoidPackageTest::entryList() { // Create a package named @p packageName which is valid and has some images. createTestPackage(m_package, QStringLiteral("1.1")); // Create a package object and verify that it is valid. KPackage::Package *p = new KPackage::Package(m_defaultPackage); p->setPath(m_packageRoot + '/' + m_package); QVERIFY(p->isValid()); // Now we have a valid package that should contain the following files in // given filetypes: // fileTye - Files // scripts - {"script.js"} // images - {"image-1.svg", "image-2.svg"} QStringList files = p->entryList("scripts"); QCOMPARE(files.size(), 1); QVERIFY(files.contains(QStringLiteral("script.js"))); files = p->entryList("images"); QCOMPARE(files.size(), 2); QVERIFY(files.contains(QStringLiteral("image-1.svg"))); QVERIFY(files.contains(QStringLiteral("image-2.svg"))); delete p; } void PlasmoidPackageTest::createAndInstallPackage() { qDebug() << " "; qDebug() << " CreateAndInstall "; createTestPackage(QStringLiteral("plasmoid_to_package"), QStringLiteral("1.1")); const QString packagePath = m_packageRoot + '/' + "testpackage.plasmoid"; KZip creator(packagePath); QVERIFY(creator.open(QIODevice::WriteOnly)); creator.addLocalDirectory(m_packageRoot + '/' + "plasmoid_to_package", QStringLiteral(".")); creator.close(); QDir rootDir(m_packageRoot + "/plasmoid_to_package"); rootDir.removeRecursively(); QVERIFY2(QFile::exists(packagePath), qPrintable(packagePath)); KZip package(packagePath); QVERIFY(package.open(QIODevice::ReadOnly)); const KArchiveDirectory *dir = package.directory(); QVERIFY(dir);// QVERIFY(dir->entry(QStringLiteral("metadata.desktop"))); const KArchiveEntry *contentsEntry = dir->entry(QStringLiteral("contents")); QVERIFY(contentsEntry); QVERIFY(contentsEntry->isDirectory()); const KArchiveDirectory *contents = static_cast(contentsEntry); QVERIFY(contents->entry(QStringLiteral("ui"))); QVERIFY(contents->entry(QStringLiteral("images"))); m_defaultPackageStructure = new KPackage::PackageStructure(this); KPackage::Package *p = new KPackage::Package(m_defaultPackageStructure); qDebug() << "Installing " << packagePath; //const QString packageRoot = "plasma/plasmoids/"; //const QString servicePrefix = "plasma-applet-"; KJob *job = p->install(packagePath, m_packageRoot); connect(job, SIGNAL(finished(KJob*)), SLOT(packageInstalled(KJob*))); QSignalSpy spy(job, SIGNAL(finished(KJob*))); QVERIFY(spy.wait(1000)); //is the package instance usable (ie proper path) after the install job has been completed? QCOMPARE(p->path(), QString(QDir(m_packageRoot % "/plasmoid_to_package").canonicalPath() + QLatin1Char('/'))); cleanupPackage(QStringLiteral("plasmoid_to_package")); //QVERIFY(p->isValid()); delete p; } void PlasmoidPackageTest::noCrashOnAsyncInstall() { createTestPackage(QStringLiteral("plasmoid_to_package"), QStringLiteral("1.1")); const QString packagePath = m_packageRoot + '/' + "testpackage.plasmoid"; KZip creator(packagePath); QVERIFY(creator.open(QIODevice::WriteOnly)); creator.addLocalDirectory(m_packageRoot + '/' + "plasmoid_to_package", QStringLiteral(".")); creator.close(); QDir rootDir(m_packageRoot + "/plasmoid_to_package"); rootDir.removeRecursively(); KJob *job; //scope the package so it will get deleted before the install operation finishes //package is explicitlyshared internally and designed to be used on the stack //see #370718 { KPackage::Package package(new KPackage::PackageStructure(this)); job = package.install(packagePath, m_packageRoot); } connect(job, SIGNAL(finished(KJob*)), SLOT(packageInstalled(KJob*))); QSignalSpy spy(job, SIGNAL(finished(KJob*))); QVERIFY(spy.wait(1000)); cleanupPackage(QStringLiteral("plasmoid_to_package")); } void PlasmoidPackageTest::createAndUpdatePackage() { //does the version number parsing work? QVERIFY(KPackage::isVersionNewer(QStringLiteral("1.1"), QStringLiteral("1.1.1"))); QVERIFY(!KPackage::isVersionNewer(QStringLiteral("1.1.1"), QStringLiteral("1.1"))); QVERIFY(KPackage::isVersionNewer(QStringLiteral("1.1.1"), QStringLiteral("1.1.2"))); QVERIFY(KPackage::isVersionNewer(QStringLiteral("1.1.2"), QStringLiteral("2.1"))); QVERIFY(KPackage::isVersionNewer(QStringLiteral("0.1.2"), QStringLiteral("2"))); QVERIFY(!KPackage::isVersionNewer(QStringLiteral("1"), QStringLiteral("0.1.2"))); qDebug() << " "; qDebug() << " CreateAndUpdate "; createTestPackage(QStringLiteral("plasmoid_to_package"), QStringLiteral("1.1")); const QString packagePath = m_packageRoot + '/' + "testpackage.plasmoid"; KZip creator(packagePath); QVERIFY(creator.open(QIODevice::WriteOnly)); creator.addLocalDirectory(m_packageRoot + '/' + "plasmoid_to_package", QStringLiteral(".")); creator.close(); QDir rootDir(m_packageRoot + "/plasmoid_to_package"); rootDir.removeRecursively(); QVERIFY(QFile::exists(packagePath)); KZip package(packagePath); QVERIFY(package.open(QIODevice::ReadOnly)); const KArchiveDirectory *dir = package.directory(); QVERIFY(dir);// QVERIFY(dir->entry(QStringLiteral("metadata.desktop"))); const KArchiveEntry *contentsEntry = dir->entry(QStringLiteral("contents")); QVERIFY(contentsEntry); QVERIFY(contentsEntry->isDirectory()); const KArchiveDirectory *contents = static_cast(contentsEntry); QVERIFY(contents->entry(QStringLiteral("ui"))); QVERIFY(contents->entry(QStringLiteral("images"))); m_defaultPackageStructure = new KPackage::PackageStructure(this); KPackage::Package *p = new KPackage::Package(m_defaultPackageStructure); qDebug() << "Installing " << packagePath; //const QString packageRoot = "plasma/plasmoids/"; //const QString servicePrefix = "plasma-applet-"; KJob *job = p->update(packagePath, m_packageRoot); connect(job, SIGNAL(finished(KJob*)), SLOT(packageInstalled(KJob*))); QSignalSpy spy(job, SIGNAL(finished(KJob*))); QVERIFY(spy.wait(1000)); //same version, should fail job = p->update(packagePath, m_packageRoot); QSignalSpy spyFail(job, SIGNAL(finished(KJob*))); QVERIFY(spyFail.wait(1000)); QVERIFY(job->error() == KPackage::Package::JobError::NewerVersionAlreadyInstalledError); qDebug()<errorText(); //create a new package with higher version createTestPackage(QStringLiteral("plasmoid_to_package"), QStringLiteral("1.2")); KZip creator2(packagePath); QVERIFY(creator2.open(QIODevice::WriteOnly)); creator2.addLocalDirectory(m_packageRoot + '/' + "plasmoid_to_package", QStringLiteral(".")); creator2.close(); QDir rootDir2(m_packageRoot + "/plasmoid_to_package"); rootDir2.removeRecursively(); KJob *job2 = p->update(packagePath, m_packageRoot); connect(job2, SIGNAL(finished(KJob*)), SLOT(packageInstalled(KJob*))); QSignalSpy spy2(job2, SIGNAL(finished(KJob*))); QVERIFY(spy2.wait(1000)); cleanupPackage(QStringLiteral("plasmoid_to_package")); //QVERIFY(p->isValid()); delete p; } void PlasmoidPackageTest::uncompressPackageWithSubFolder() { KPackage::PackageStructure *structure = new KPackage::PackageStructure; KPackage::Package package(structure); package.setPath(QFINDTESTDATA("data/customcontent.tar.gz")); //if metadata is correctly found, servicetypes should be ("SimpleContent", "CustomContent") QCOMPARE(package.metadata().serviceTypes(), QStringList({"SimpleContent", "CustomContent"})); } void PlasmoidPackageTest::cleanupPackage(const QString &packageName) { KPackage::Package *p = new KPackage::Package(m_defaultPackageStructure); KJob *jj = p->uninstall(packageName, m_packageRoot); connect(jj, SIGNAL(finished(KJob*)), SLOT(packageUninstalled(KJob*))); QSignalSpy spy(jj, &KJob::finished); QVERIFY(spy.wait(1000)); } void PlasmoidPackageTest::packageInstalled(KJob *j) { QVERIFY2(j->error() == KJob::NoError, qPrintable(j->errorText())); } void PlasmoidPackageTest::packageUninstalled(KJob *j) { QVERIFY2(j->error() == KJob::NoError, qPrintable(j->errorText())); } QTEST_MAIN(PlasmoidPackageTest) diff --git a/src/kpackage/CMakeLists.txt b/src/kpackage/CMakeLists.txt index 05cebf0..a205db8 100644 --- a/src/kpackage/CMakeLists.txt +++ b/src/kpackage/CMakeLists.txt @@ -1,113 +1,119 @@ # This option should be removed, or moved down as far as possible. # That means porting the existing frameworks to the CMake automoc # feature. Porting is mostly removing explicit moc includes, and # leaving the ones which are truly needed (ie, if you remove # them, the build fails). set(CMAKE_AUTOMOC_RELAXED_MODE ON) set(CMAKE_AUTORCC ON) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-package.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-package.h) add_definitions(-DKDE_DEFAULT_DEBUG_AREA=1209) ########### next target ############### set(Package_LIB_SRCS package.cpp packagestructure.cpp packageloader.cpp private/packages.cpp private/packagejob.cpp private/packagejobthread.cpp private/versionparser.cpp version.cpp kpackage.qrc ) ecm_qt_declare_logging_category(Package_LIB_SRCS HEADER kpackage_debug.h IDENTIFIER KPACKAGE_LOG CATEGORY_NAME kf5.kpackage) add_library(KF5Package ${Package_LIB_SRCS}) add_library(KF5::Package ALIAS KF5Package) +ecm_generate_export_header(KF5Package + EXPORT_FILE_NAME kpackage/package_export.h + BASE_NAME KPackage + # GROUP_BASE_NAME KF <- enable once all of KF modules use ecm_generate_export_header + VERSION ${KF5_VERSION} + DEPRECATED_BASE_VERSION 0 + DEPRECATION_VERSIONS 5.21 + EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT} +) target_link_libraries(KF5Package PUBLIC #KF5::Service # For kplugininfo.h and kservice.h KF5::CoreAddons PRIVATE Qt5::DBus KF5::Archive KF5::I18n ) target_include_directories(KF5Package PUBLIC "$" ) target_include_directories(KF5Package INTERFACE "$" ) set_target_properties(KF5Package PROPERTIES VERSION ${PACKAGE_VERSION_STRING} SOVERSION ${PACKAGE_SOVERSION} EXPORT_NAME Package ) ########### install files ############### -generate_export_header(KF5Package - BASE_NAME Package - EXPORT_FILE_NAME kpackage/package_export.h) ecm_generate_headers(Package_CamelCase_HEADERS HEADER_NAMES Package PackageStructure PackageLoader REQUIRED_HEADERS Package_HEADERS PREFIX KPackage ) set(Package_HEADERS ${Package_HEADERS} version.h ) install(FILES ${Package_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/kpackage/package_export.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KPackage/kpackage COMPONENT Devel) install(FILES ${Package_CamelCase_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KPackage/KPackage COMPONENT Devel) install(FILES data/kservicetypes5/kpackage-packagestructure.desktop data/kservicetypes5/kpackage-generic.desktop data/kservicetypes5/kpackage-genericqml.desktop DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR}) install(TARGETS KF5Package EXPORT KF5PackageTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) if(BUILD_QCH) ecm_add_qch( KF5Package_QCH NAME KPackage BASE_NAME KF5Package VERSION ${KF5_VERSION} ORG_DOMAIN org.kde SOURCES # using only public headers, to cover only public API ${Package_HEADERS} MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md" LINK_QCHS KF5CoreAddons_QCH BLANK_MACROS - PACKAGE_EXPORT + KPACKAGE_EXPORT TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} COMPONENT Devel ) endif() diff --git a/src/kpackage/package.cpp b/src/kpackage/package.cpp index d0c2158..2e805a1 100644 --- a/src/kpackage/package.cpp +++ b/src/kpackage/package.cpp @@ -1,1048 +1,1048 @@ /****************************************************************************** * Copyright 2007 by Aaron Seigo * * Copyright 2010 by Marco Martin * * Copyright 2010 by Kevin Ottens * * Copyright 2009 by Rob Scheepmaker * * * * 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 "package.h" #include #include #include #include "kpackage_debug.h" #include #include #include #include #include "config-package.h" #include #include #include "packagestructure.h" #include "packageloader.h" #include "private/package_p.h" //#include "private/packages_p.h" #include "private/packagejob_p.h" #include "private/packageloader_p.h" namespace KPackage { Package::Package(PackageStructure *structure) : d(new PackagePrivate()) { d->structure = structure; if (d->structure) { d->structure.data()->initPackage(this); auto desc = i18n("Desktop file that describes this package."); addFileDefinition("metadata", QStringLiteral("metadata.json"), desc); addFileDefinition("metadata", QStringLiteral("metadata.desktop"), desc); } } Package::Package(const Package &other) : d(other.d) { } Package::~Package() { //guard against deletion on application shutdown if (PackageDeletionNotifier::self()) { Q_EMIT PackageDeletionNotifier::self()->packageDeleted(this); } } Package &Package::operator=(const Package &rhs) { if (&rhs != this) { d = rhs.d; } return *this; } bool Package::hasValidStructure() const { return d->structure; } bool Package::isValid() const { if (!d->structure) { return false; } //Minimal packages with no metadata *are* supposed to be possible //so if !metadata().isValid() go ahead if (metadata().isValid() && metadata().value(QStringLiteral("isHidden"), QStringLiteral("false")) == QLatin1String("true")) { return false; } if (d->checkedValid) { return d->valid; } const QString rootPath = d->tempRoot.isEmpty() ? d->path : d->tempRoot; if(rootPath.isEmpty()) { return false; } d->valid = true; //search for the file in all prefixes and in all possible paths for each prefix //even if it's a big nested loop, usually there is one prefix and one location //so shouldn't cause too much disk access QHashIterator it(d->contents); while (it.hasNext()) { it.next(); if (!it.value().required) { continue; } const QString foundPath = filePath(it.key(), {}); if (foundPath.isEmpty()) { //qCWarning(KPACKAGE_LOG) << "Could not find required" << (it.value().directory ? "directory" : "file") << it.key() << "for package" << path() << "should be" << it.value().paths; d->valid = false; break; } } return d->valid; } QString Package::name(const QByteArray &key) const { QHash::const_iterator it = d->contents.constFind(key); if (it == d->contents.constEnd()) { return QString(); } return it.value().name; } bool Package::isRequired(const QByteArray &key) const { QHash::const_iterator it = d->contents.constFind(key); if (it == d->contents.constEnd()) { return false; } return it.value().required; } QStringList Package::mimeTypes(const QByteArray &key) const { QHash::const_iterator it = d->contents.constFind(key); if (it == d->contents.constEnd()) { return QStringList(); } if (it.value().mimeTypes.isEmpty()) { return d->mimeTypes; } return it.value().mimeTypes; } QString Package::defaultPackageRoot() const { return d->defaultPackageRoot; } void Package::setDefaultPackageRoot(const QString &packageRoot) { d.detach(); d->defaultPackageRoot = packageRoot; if (!d->defaultPackageRoot.isEmpty() && !d->defaultPackageRoot.endsWith(QLatin1Char('/'))) { d->defaultPackageRoot.append(QLatin1Char('/')); } } void Package::setFallbackPackage(const KPackage::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); } KPackage::Package Package::fallbackPackage() const { if (d->fallbackPackage) { return (*d->fallbackPackage); } else { return Package(); } } bool Package::allowExternalPaths() const { return d->externalPaths; } void Package::setAllowExternalPaths(bool allow) { d.detach(); d->externalPaths = allow; } KPluginMetaData Package::metadata() const { //qCDebug(KPACKAGE_LOG) << "metadata: " << d->path << filePath("metadata"); if (!d->metadata && !d->path.isEmpty()) { const QString metadataPath = filePath("metadata"); if (!metadataPath.isEmpty()) { d->createPackageMetadata(metadataPath); } else { // d->path might still be a file, if its path has a trailing /, // the fileInfo lookup will fail, so remove it. QString p = d->path; if (p.endsWith(QLatin1Char('/'))) { p.chop(1); } QFileInfo fileInfo(p); if (fileInfo.isDir()) { d->createPackageMetadata(d->path); } else if (fileInfo.exists()) { d->path = fileInfo.canonicalFilePath(); d->tempRoot = d->unpack(p); } } } if (!d->metadata) { d->metadata = new KPluginMetaData(); } return *d->metadata; } QString PackagePrivate::unpack(const QString &filePath) { KArchive *archive = nullptr; QMimeDatabase db; QMimeType mimeType = db.mimeTypeForFile(filePath); if (mimeType.inherits(QStringLiteral("application/zip"))) { archive = new KZip(filePath); } else if (mimeType.inherits(QStringLiteral("application/x-compressed-tar")) || mimeType.inherits(QStringLiteral("application/x-gzip")) || mimeType.inherits(QStringLiteral("application/x-tar")) || mimeType.inherits(QStringLiteral("application/x-bzip-compressed-tar")) || mimeType.inherits(QStringLiteral("application/x-xz")) || mimeType.inherits(QStringLiteral("application/x-lzma"))) { archive = new KTar(filePath); } else { //qCWarning(KPACKAGE_LOG) << "Could not open package file, unsupported archive format:" << filePath << mimeType.name(); } QString tempRoot; if (archive && archive->open(QIODevice::ReadOnly)) { const KArchiveDirectory *source = archive->directory(); QTemporaryDir tempdir; tempdir.setAutoRemove(false); tempRoot = tempdir.path() + QLatin1Char('/'); source->copyTo(tempRoot); if (!QFile::exists(tempdir.path() + QLatin1String("/metadata.json")) && !QFile::exists(tempdir.path() + QLatin1String("/metadata.desktop"))) { // search metadata.desktop, the zip file might have the package contents in a subdirectory QDir unpackedPath(tempdir.path()); const auto entries = unpackedPath.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); for (const auto &pack : entries) { if (QFile::exists(pack.filePath() + QLatin1String("/metadata.json")) || QFile::exists(pack.filePath() + QLatin1String("/metadata.desktop"))) { tempRoot = pack.filePath() + QLatin1Char('/'); } } } createPackageMetadata(tempRoot); } else { //qCWarning(KPACKAGE_LOG) << "Could not open package file:" << path; } delete archive; return tempRoot; } bool PackagePrivate::isInsidePackageDir(const QString &canonicalPath) const { // make sure that the target file is actually inside the package dir to prevent // path traversal using symlinks or "../" path segments // make sure we got passed a valid path Q_ASSERT(QFileInfo::exists(canonicalPath)); Q_ASSERT(QFileInfo(canonicalPath).canonicalFilePath() == canonicalPath); // make sure that the base path is also canonical // this was not the case until 5.8, making this check fail e.g. if /home is a symlink // which in turn would make plasmashell not find the .qml files //installed package if (tempRoot.isEmpty()) { Q_ASSERT(QDir(path).exists()); Q_ASSERT(path == QStringLiteral("/") || QDir(path).canonicalPath() + QLatin1Char('/') == path); if (canonicalPath.startsWith(path)) { return true; } //temporary compressed package } else { Q_ASSERT(QDir(tempRoot).exists()); Q_ASSERT(tempRoot == QStringLiteral("/") || QDir(tempRoot).canonicalPath() + QLatin1Char('/') == tempRoot); if (canonicalPath.startsWith(tempRoot)) { return true; } } qCWarning(KPACKAGE_LOG) << "Path traversal attempt detected:" << canonicalPath << "is not inside" << path; return false; } QString Package::filePath(const QByteArray &fileType, const QString &filename) const { if (!d->valid) { QString result = d->fallbackFilePath(fileType, filename); if (result.isEmpty()) { // qCDebug(KPACKAGE_LOG) << fileType << "file with name" << filename // << "was not found in package with path" << d->path; } return result; } const QString discoveryKey(QString::fromUtf8(fileType) + filename); const auto path = d->discoveries.value(discoveryKey); if (!path.isEmpty()) { return path; } QStringList paths; if (!fileType.isEmpty()) { const auto contents = d->contents.constFind(fileType); if (contents == d->contents.constEnd()) { //qCDebug(KPACKAGE_LOG) << "package does not contain" << fileType << filename; return d->fallbackFilePath(fileType, filename); } paths = contents->paths; if (paths.isEmpty()) { //qCDebug(KPACKAGE_LOG) << "no matching path came of it, while looking for" << fileType << filename; d->discoveries.insert(discoveryKey, QString()); return d->fallbackFilePath(fileType, filename); } } else { //when filetype is empty paths is always empty, so try with an empty string paths << QString(); } //Nested loop, but in the medium case resolves to just one iteration // qCDebug(KPACKAGE_LOG) << "prefixes:" << d->contentsPrefixPaths.count() << d->contentsPrefixPaths; for (const QString &contentsPrefix : qAsConst(d->contentsPrefixPaths)) { QString prefix; //We are an installed package if (d->tempRoot.isEmpty()) { prefix = fileType == "metadata" ? d->path : (d->path + contentsPrefix); //We are a compressed package temporarily uncompressed in /tmp } else { prefix = fileType == "metadata" ? d->tempRoot : (d->tempRoot + contentsPrefix); } for (const QString &path : qAsConst(paths)) { QString file = prefix + path; if (!filename.isEmpty()) { file.append(QLatin1Char('/') + filename); } QFileInfo fi(file); if (fi.exists()) { if (d->externalPaths) { //qCDebug(KPACKAGE_LOG) << "found" << file; d->discoveries.insert(discoveryKey, file); return file; } // ensure that we don't return files outside of our base path // due to symlink or ../ games if (d->isInsidePackageDir(fi.canonicalFilePath())) { //qCDebug(KPACKAGE_LOG) << "found" << file; d->discoveries.insert(discoveryKey, file); return file; } } } } //qCDebug(KPACKAGE_LOG) << fileType << filename << "does not exist in" << prefixes << "at root" << d->path; return d->fallbackFilePath(fileType, filename); } QUrl Package::fileUrl(const QByteArray &fileType, const QString &filename) const { QString path = filePath(fileType, filename); //construct a qrc:/ url or a file:/ url, the only two protocols supported if (path.startsWith(QStringLiteral(":"))) { return QUrl(QStringLiteral("qrc") + path); } else { return QUrl::fromLocalFile(path); } } QStringList Package::entryList(const QByteArray &key) const { if (!d->valid) { return QStringList(); } QHash::const_iterator it = d->contents.constFind(key); if (it == d->contents.constEnd()) { //qCDebug(KPACKAGE_LOG) << "couldn't find" << key; return QStringList(); } //qCDebug(KPACKAGE_LOG) << "going to list" << key; QStringList list; for (const QString &prefix : qAsConst(d->contentsPrefixPaths)) { //qCDebug(KPACKAGE_LOG) << " looking in" << prefix; const QStringList paths = it.value().paths; for (const QString &path : paths) { //qCDebug(KPACKAGE_LOG) << " looking in" << path; if (it.value().directory) { //qCDebug(KPACKAGE_LOG) << "it's a directory, so trying out" << d->path + prefix + path; QDir dir(d->path + prefix + path); if (d->externalPaths) { list += dir.entryList(QDir::Files | QDir::Readable); } else { // ensure that we don't return files outside of our base path // due to symlink or ../ games QString canonicalized = dir.canonicalPath(); if (canonicalized.startsWith(d->path)) { list += dir.entryList(QDir::Files | QDir::Readable); } } } else { const QString fullPath = d->path + prefix + path; //qCDebug(KPACKAGE_LOG) << "it's a file at" << fullPath << QFile::exists(fullPath); if (!QFile::exists(fullPath)) { continue; } if (d->externalPaths) { list += fullPath; } else { QDir dir(fullPath); QString canonicalized = dir.canonicalPath() + QDir::separator(); //qCDebug(KPACKAGE_LOG) << "testing that" << canonicalized << "is in" << d->path; if (canonicalized.startsWith(d->path)) { list += fullPath; } } } } } return list; } void Package::setPath(const QString &path) { // if the path is already what we have, don't bother if (path == d->path) { return; } // our dptr is shared, and it is almost certainly going to change. // hold onto the old pointer just in case it does not, however! QExplicitlySharedDataPointer oldD(d); d.detach(); // without structure we're doomed if (!d->structure) { d->path.clear(); d->discoveries.clear(); d->valid = false; d->checkedValid = true; return; } // empty path => nothing to do if (path.isEmpty()) { d->path.clear(); d->discoveries.clear(); d->valid = false; d->structure.data()->pathChanged(this); return; } // now we look for all possible paths, including resolving // relative paths against the system search paths QStringList paths; if (QDir::isRelativePath(path)) { QString p; if (d->defaultPackageRoot.isEmpty()) { p = path % QLatin1Char('/'); } else { p = d->defaultPackageRoot % path % QLatin1Char('/'); } if (QDir::isRelativePath(p)) { //FIXME: can searching of the qrc be better? paths << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, p, QStandardPaths::LocateDirectory); } else { const QDir dir(p); if (QFile::exists(dir.canonicalPath())) { paths << p; } } //qCDebug(KPACKAGE_LOG) << "paths:" << p << paths << d->defaultPackageRoot; } else { const QDir dir(path); if (QFile::exists(dir.canonicalPath())) { paths << path; } } QFileInfo fileInfo(path); if (fileInfo.isFile() && d->tempRoot.isEmpty()) { d->path = fileInfo.canonicalFilePath(); d->tempRoot = d->unpack(path); } // now we search each path found, caching our previous path to know if // anything actually really changed const QString previousPath = d->path; for (const QString &p : qAsConst(paths)) { d->checkedValid = false; QDir dir(p); Q_ASSERT(QFile::exists(dir.canonicalPath())); //if it has a contents.rcc, give priority to it if (dir.exists(QStringLiteral("contents.rcc"))) { d->rccPath = dir.absoluteFilePath(QStringLiteral("contents.rcc")); QResource::registerResource(d->rccPath); //we need just the plugin name here, never the absolute path dir = QDir(QStringLiteral(":/") + defaultPackageRoot() + path.midRef(path.lastIndexOf(QLatin1Char('/')))); } d->path = dir.canonicalPath(); // canonicalPath() does not include a trailing slash (unless it is the root dir) if (!d->path.endsWith(QLatin1Char('/'))) { d->path.append(QLatin1Char('/')); } const QString fallbackPath = metadata().value(QStringLiteral("X-Plasma-RootPath")); if (!fallbackPath.isEmpty()) { const KPackage::Package fp = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Applet"), fallbackPath); setFallbackPackage(fp); } // we need to tell the structure we're changing paths ... d->structure.data()->pathChanged(this); // ... and then testing the results for validity if (isValid()) { break; } } // if nothing did change, then we go back to the old dptr if (d->path == previousPath) { d = oldD; return; } // .. but something did change, so we get rid of our discovery cache d->discoveries.clear(); delete d->metadata; d->metadata = nullptr; // uh-oh, but we didn't end up with anything valid, so we sadly reset ourselves // to futility. if (!d->valid) { d->path.clear(); d->structure.data()->pathChanged(this); } } const QString Package::path() const { return d->path; } QStringList Package::contentsPrefixPaths() const { return d->contentsPrefixPaths; } void Package::setContentsPrefixPaths(const QStringList &prefixPaths) { d.detach(); d->contentsPrefixPaths = prefixPaths; if (d->contentsPrefixPaths.isEmpty()) { d->contentsPrefixPaths << QString(); } else { // the code assumes that the prefixes have a trailing slash // so let's make that true here QMutableStringListIterator it(d->contentsPrefixPaths); while (it.hasNext()) { it.next(); if (!it.value().endsWith(QLatin1Char('/'))) { it.setValue(it.value() % QLatin1Char('/')); } } } } -#ifndef PACKAGE_NO_DEPRECATED +#if KPACKAGE_BUILD_DEPRECATED_SINCE(5, 21) QString Package::contentsHash() const { return QString::fromLocal8Bit(cryptographicHash(QCryptographicHash::Sha1)); } #endif QByteArray Package::cryptographicHash(QCryptographicHash::Algorithm algorithm) const { if (!d->valid) { qCWarning(KPACKAGE_LOG) << "can not create hash due to Package being invalid"; return QByteArray(); } QCryptographicHash hash(algorithm); const QString metadataPath = QFile::exists(d->path + QLatin1String("metadata.json")) ? d->path + QLatin1String("metadata.json") : QFile::exists(d->path + QLatin1String("metadata.desktop")) ? d->path + QLatin1String("metadata.desktop") : QString(); if (!metadataPath.isEmpty()) { QFile f(metadataPath); if (f.open(QIODevice::ReadOnly)) { while (!f.atEnd()) { hash.addData(f.read(1024)); } } else { qCWarning(KPACKAGE_LOG) << "could not add" << f.fileName() << "to the hash; file could not be opened for reading."; } } else { qCWarning(KPACKAGE_LOG) << "no metadata at" << metadataPath; } for (const QString &prefix : qAsConst(d->contentsPrefixPaths)) { const QString basePath = d->path + prefix; QDir dir(basePath); if (!dir.exists()) { return QByteArray(); } d->updateHash(basePath, QString(), dir, hash); } return hash.result().toHex(); } void Package::addDirectoryDefinition(const QByteArray &key, const QString &path, const QString &name) { const auto contentsIt = d->contents.constFind(key); ContentStructure s; if (contentsIt != d->contents.constEnd()) { if (contentsIt->paths.contains(path) && contentsIt->directory == true && contentsIt->name == name) { return; } s = *contentsIt; } d.detach(); if (!name.isEmpty()) { s.name = name; } s.paths.append(path); s.directory = true; d->contents[key] = s; } void Package::addFileDefinition(const QByteArray &key, const QString &path, const QString &name) { const auto contentsIt = d->contents.constFind(key); ContentStructure s; if (contentsIt != d->contents.constEnd()) { if (contentsIt->paths.contains(path) && contentsIt->directory == true && contentsIt->name == name) { return; } s = *contentsIt; } d.detach(); if (!name.isEmpty()) { s.name = name; } s.paths.append(path); s.directory = false; d->contents[key] = s; } void Package::removeDefinition(const QByteArray &key) { if (d->contents.contains(key)) { d.detach(); d->contents.remove(key); } if (d->discoveries.contains(QString::fromLatin1(key))) { d.detach(); d->discoveries.remove(QString::fromLatin1(key)); } } void Package::setRequired(const QByteArray &key, bool required) { QHash::iterator it = d->contents.find(key); if (it == d->contents.end()) { return; } d.detach(); // have to find the item again after detaching: d->contents is a different object now it = d->contents.find(key); it.value().required = required; } void Package::setDefaultMimeTypes(const QStringList &mimeTypes) { d.detach(); d->mimeTypes = mimeTypes; } void Package::setMimeTypes(const QByteArray &key, const QStringList &mimeTypes) { QHash::iterator it = d->contents.find(key); if (it == d->contents.end()) { return; } d.detach(); // have to find the item again after detaching: d->contents is a different object now it = d->contents.find(key); it.value().mimeTypes = mimeTypes; } QList Package::directories() const { QList dirs; QHash::const_iterator it = d->contents.constBegin(); while (it != d->contents.constEnd()) { if (it.value().directory) { dirs << it.key(); } ++it; } return dirs; } QList Package::requiredDirectories() const { QList dirs; QHash::const_iterator it = d->contents.constBegin(); while (it != d->contents.constEnd()) { if (it.value().directory && it.value().required) { dirs << it.key(); } ++it; } return dirs; } QList Package::files() const { QList files; QHash::const_iterator it = d->contents.constBegin(); while (it != d->contents.constEnd()) { if (!it.value().directory) { files << it.key(); } ++it; } return files; } QList Package::requiredFiles() const { QList files; QHash::const_iterator it = d->contents.constBegin(); while (it != d->contents.constEnd()) { if (!it.value().directory && it.value().required) { files << it.key(); } ++it; } return files; } KJob *Package::install(const QString &sourcePackage, const QString &packageRoot) { const QString src = sourcePackage; QString dest = packageRoot.isEmpty() ? defaultPackageRoot() : packageRoot; KPackage::PackageLoader::self()->d->maxCacheAge = -1; //use absolute paths if passed, otherwise go under share if (!QDir::isAbsolutePath(dest)) { dest = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + dest; } if (!d->structure) { return nullptr; } //qCDebug(KPACKAGE_LOG) << "Source: " << src; //qCDebug(KPACKAGE_LOG) << "PackageRoot: " << dest; KJob *j = d->structure.data()->install(this, src, dest); return j; } KJob *Package::update(const QString &sourcePackage, const QString &packageRoot) { const QString src = sourcePackage; QString dest = packageRoot.isEmpty() ? defaultPackageRoot() : packageRoot; KPackage::PackageLoader::self()->d->maxCacheAge = -1; //use absolute paths if passed, otherwise go under share if (!QDir::isAbsolutePath(dest)) { dest = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + dest; } if (!d->structure) { return nullptr; } //qCDebug(KPACKAGE_LOG) << "Source: " << src; //qCDebug(KPACKAGE_LOG) << "PackageRoot: " << dest; KJob *j = d->structure.data()->update(this, src, dest); return j; } KJob *Package::uninstall(const QString &packageName, const QString &packageRoot) { KPackage::PackageLoader::self()->d->maxCacheAge = -1; d->createPackageMetadata(packageRoot + QLatin1Char('/') + packageName); if (!d->structure) { return nullptr; } return d->structure.data()->uninstall(this, packageRoot); } PackagePrivate::PackagePrivate() : QSharedData(), fallbackPackage(nullptr), metadata(nullptr), externalPaths(false), valid(false), checkedValid(false) { contentsPrefixPaths << QStringLiteral("contents/"); } PackagePrivate::PackagePrivate(const PackagePrivate &other) : QSharedData() { *this = other; metadata = nullptr; } PackagePrivate::~PackagePrivate() { if (!rccPath.isEmpty()) { //qresource register/unregisterpath is refcounted if we call it two times //on the same path, the resource will actually be unregistered only when //unregister is called 2 times QResource::unregisterResource(rccPath); } if (!tempRoot.isEmpty()) { QDir dir(tempRoot); dir.removeRecursively(); } delete metadata; delete fallbackPackage; } PackagePrivate &PackagePrivate::operator=(const PackagePrivate &rhs) { if (&rhs == this) { return *this; } structure = rhs.structure; if (rhs.fallbackPackage) { fallbackPackage = new Package(*rhs.fallbackPackage); } else { fallbackPackage = nullptr; } path = rhs.path; contentsPrefixPaths = rhs.contentsPrefixPaths; contents = rhs.contents; mimeTypes = rhs.mimeTypes; defaultPackageRoot = rhs.defaultPackageRoot; metadata = nullptr; externalPaths = rhs.externalPaths; valid = rhs.valid; return *this; } void PackagePrivate::updateHash(const QString &basePath, const QString &subPath, const QDir &dir, QCryptographicHash &hash) { // hash is calculated as a function of: // * files ordered alphabetically by name, with each file's: // * path relative to the content root // * file data // * directories ordered alphabetically by name, with each dir's: // * path relative to the content root // * file listing (recursing) // symlinks (in both the file and dir case) are handled by adding // the name of the symlink itself and the abs path of what it points to const QDir::SortFlags sorting = QDir::Name | QDir::IgnoreCase; const QDir::Filters filters = QDir::Hidden | QDir::System | QDir::NoDotAndDotDot; const auto lstEntries = dir.entryList(QDir::Files | filters, sorting); for (const QString &file : lstEntries) { if (!subPath.isEmpty()) { hash.addData(subPath.toUtf8()); } hash.addData(file.toUtf8()); QFileInfo info(dir.path() + QLatin1Char('/') + file); if (info.isSymLink()) { hash.addData(info.symLinkTarget().toUtf8()); } else { QFile f(info.filePath()); if (f.open(QIODevice::ReadOnly)) { while (!f.atEnd()) { hash.addData(f.read(1024)); } } else { qCWarning(KPACKAGE_LOG) << "could not add" << f.fileName() << "to the hash; file could not be opened for reading. " << "permissions fail?" << info.permissions() << info.isFile(); } } } const auto lstEntries2 = dir.entryList(QDir::Dirs | filters, sorting); for (const QString &subDirPath : lstEntries2) { const QString relativePath = subPath + subDirPath + QLatin1Char('/'); hash.addData(relativePath.toUtf8()); QDir subDir(dir.path()); subDir.cd(subDirPath); if (subDir.path() != subDir.canonicalPath()) { hash.addData(subDir.canonicalPath().toUtf8()); } else { updateHash(basePath, relativePath, subDir, hash); } } } void PackagePrivate::createPackageMetadata(const QString &path) { const bool isDir = QFileInfo(path).isDir(); delete metadata; if (isDir && QFile::exists(path + QStringLiteral("/metadata.json"))) { metadata = new KPluginMetaData(path + QStringLiteral("/metadata.json")); } else if (isDir && QFile::exists(path + QStringLiteral("/metadata.desktop"))) { auto md = KPluginMetaData::fromDesktopFile(path + QStringLiteral("/metadata.desktop"), {QStringLiteral(":/kservicetypes5/kpackage-generic.desktop")}); metadata = new KPluginMetaData(md); } else { if (isDir) { qCWarning(KPACKAGE_LOG) << "No metadata file in the package, expected it at:" << path; } else if (path.endsWith(QLatin1String(".desktop"))) { auto md = KPluginMetaData::fromDesktopFile(path, {QStringLiteral(":/kservicetypes5/kpackage-generic.desktop")}); metadata = new KPluginMetaData(md); } else { metadata = new KPluginMetaData(path); } } } QString PackagePrivate::fallbackFilePath(const QByteArray &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 KPackage::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 KPackage::Package *slowPackage = const_cast(&package); KPackage::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())) { qCWarning(KPACKAGE_LOG) << "Warning: the fallback chain of " << package.metadata().pluginId() << "contains a cyclical dependency."; return true; } fastPackage = fastPackage->d->fallbackPackage->d->fallbackPackage; slowPackage = slowPackage->d->fallbackPackage; } return false; } Q_GLOBAL_STATIC(PackageDeletionNotifier, s_packageDeletionNotifier) PackageDeletionNotifier* PackageDeletionNotifier::self() { return s_packageDeletionNotifier; } } // Namespace #include "private/moc_package_p.cpp" diff --git a/src/kpackage/package.h b/src/kpackage/package.h index cd89af0..08c0b00 100644 --- a/src/kpackage/package.h +++ b/src/kpackage/package.h @@ -1,406 +1,407 @@ /****************************************************************************** * Copyright 2007-2011 by Aaron Seigo * * * * 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 KPACKAGE_PACKAGE_H #define KPACKAGE_PACKAGE_H #include #include #include #include #include #include #include namespace KPackage { /** * @class Package kpackage/package.h * * @short object representing an installed package * * Package defines what is in a package and provides easy access to the contents. * * To define a package, one might write the following code: * @code Package package; package.addDirectoryDefinition("images", "pics/", i18n("Images")); QStringList mimeTypes; mimeTypes << "image/svg" << "image/png" << "image/jpeg"; package.setMimeTypes("images", mimeTypes); package.addDirectoryDefinition("scripts", "code/", i18n("Executable Scripts")); mimeTypes.clear(); mimeTypes << "text/\*"; package.setMimeTypes("scripts", mimeTypes); package.addFileDefinition("mainscript", "code/main.js", i18n("Main Script File")); package.setRequired("mainscript", true); @endcode * One may also choose to create a subclass of PackageStructure and include the setup * in the constructor. * * Either way, Package creates a self-documenting contract between the packager and * the application without exposing package internals such as actual on-disk structure * of the package or requiring that all contents be explicitly known ahead of time. * * 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 install and uninstall methods * * subclasses can be compiled as plugins for easy re-use **/ //TODO: write documentation on USING a package class PackagePrivate; class PackageStructure; -class PACKAGE_EXPORT Package +class KPACKAGE_EXPORT Package { public: /** * Error codes for the install/update/remove jobs * @since 5.17 */ enum JobError { RootCreationError = KJob::UserDefinedError + 1, /**< Cannot create package root directory */ PackageFileNotFoundError, /**< The package file does not exist */ UnsupportedArchiveFormatError, /**< The archive format of the package is not supported */ PackageOpenError, /**< Can't open the package file for reading */ MetadataFileMissingError, /**< The package doesn't have a metadata.desktop file */ PluginNameMissingError, /**< The metadata.desktop file doesn't specify a plugin name */ PluginNameInvalidError, /**< The plugin name contains characters different from letters, digits, dots and underscores */ UpdatePackageTypeMismatchError, /**< A package with this plugin name was already installed, but has a different type in the metadata.desktop file */ OldVersionRemovalError, /**< Failed to remove the old version of the package during an upgrade */ NewerVersionAlreadyInstalledError, /**< We tried to update, but the same version or a newer one is already installed */ PackageAlreadyInstalledError, /**< The package is already installed and a normal install (not update) was performed */ PackageMoveError, /**< Failure to move a package from the system temporary folder to its final destination */ PackageCopyError, /**< Failure to copy a package folder from somewhere in the filesystem to its final destination */ PackageUninstallError /**< Failure to uninstall a package */ }; /** * Default constructor * * @param structure if a null pointer is passed in, this will creates an empty (invalid) Package; * otherwise the structure is allowed to set up the Package's initial layout * @since 4.6 */ explicit Package(PackageStructure *structure = nullptr); /** * Copy constructor * @since 4.6 */ Package(const Package &other); virtual ~Package(); /** * Assignment operator * @since 4.6 */ Package &operator=(const Package &rhs); /** * @return true if this package has a valid PackageStructure associatedw it with it. * A package may not be valid, but have a valid structure. Useful when dealing with * Package objects in a semi-initialized state (e.g. before calling setPath()) * @since 5.1 */ bool hasValidStructure() const; /** * @return true if all the required components exist **/ bool isValid() const; /** * Sets the path to the root of this package * @param path an absolute path, or a relative path to the default package root * @since 4.3 */ void setPath(const QString &path); /** * @return the path to the root of this particular package */ const QString path() const; /** * 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") * * Example: finding a specific image in the images directory: * filePath("images", "myimage.png") * * @param key the key of the file type to look for, * @param filename optional name of the file to locate within the package * @return path to the file on disk. QString() if not found. **/ QString filePath(const QByteArray &key, const QString &filename = QString()) const; /** * Get the url to a given file based on the key and an optional filename, is the file:// or qrc:// format * Example: finding the main script in a scripting package: * filePath("mainscript") * * Example: finding a specific image in the images directory: * filePath("images", "myimage.png") * * @param key the key of the file type to look for, * @param filename optional name of the file to locate within the package * @return path to the file on disk. QString() if not found. * @since 5.41 **/ QUrl fileUrl(const QByteArray &key, const QString &filename = QString()) const; /** * Get the list of files of a given type. * * @param fileType the type of file to look for, as defined in the * package structure. * @return list of files by name, suitable for passing to filePath **/ QStringList entryList(const QByteArray &key) const; /** * @return user visible name for the given entry **/ QString name(const QByteArray &key) const; /** * @return true if the item at path exists and is required **/ bool isRequired(const QByteArray &key) const; /** * @return the mimeTypes associated with the path, if any **/ QStringList mimeTypes(const QByteArray &key) const; /** * @return the prefix paths inserted between the base path and content entries, in order of priority. * When searching for a file, all paths will be tried in order. * @since 4.6 */ QStringList contentsPrefixPaths() const; /** * @return preferred package root. This defaults to kpackage/generic/ */ QString defaultPackageRoot() const; /** * @return true if paths/symlinks outside the package itself should be followed. * By default this is set to false for security reasons. */ bool allowExternalPaths() const; /** * @return the package metadata object. */ KPluginMetaData metadata() const; +#if KPACKAGE_ENABLE_DEPRECATED_SINCE(5, 21) /** * @return a SHA1 hash digest of the contents of the package in hexadecimal form * @since 4.4 * @deprecated Since 5.21 use cryptographicHash */ -#ifndef PACKAGE_NO_DEPRECATED - PACKAGE_DEPRECATED QString contentsHash() const; + KPACKAGE_DEPRECATED_VERSION(5, 21, "Use Package::cryptographicHash(QCryptographicHash::Algorithm)") + QString contentsHash() const; #endif /** * @return a hash digest of the contents of the package in hexadecimal form * @since 5.21 */ QByteArray cryptographicHash(QCryptographicHash::Algorithm algorithm) const; /** * Adds a directory to the structure of the package. It is added as * a not-required element with no associated mimeTypes. * * Starting in 4.6, if an entry with the given key * already exists, the path is added to it as a search alternative. * * @param key used as an internal label for this directory * @param path the path within the package for this directory * @param name the user visible (translated) name for the directory **/ void addDirectoryDefinition(const QByteArray &key, const QString &path, const QString &name); /** * Adds a file to the structure of the package. It is added as * a not-required element with no associated mimeTypes. * * Starting in 4.6, if an entry with the given key * already exists, the path is added to it as a search alternative. * * @param key used as an internal label for this file * @param path the path within the package for this file * @param name the user visible (translated) name for the file **/ void addFileDefinition(const QByteArray &key, const QString &path, const QString &name); /** * Removes a definition from the structure of the package. * @since 4.6 * @param key the internal label of the file or directory to remove */ void removeDefinition(const QByteArray &key); /** * Sets whether or not a given part of the structure is required or not. * The path must already have been added using addDirectoryDefinition * or addFileDefinition. * * @param key the entry within the package * @param required true if this entry is required, false if not */ void setRequired(const QByteArray &key, bool required); /** * Defines the default mimeTypes for any definitions that do not have * associated mimeTypes. Handy for packages with only one or predominantly * one file type. * * @param mimeTypes a list of mimeTypes **/ void setDefaultMimeTypes(const QStringList &mimeTypes); /** * Define mimeTypes for a given part of the structure * The path must already have been added using addDirectoryDefinition * or addFileDefinition. * * @param key the entry within the package * @param mimeTypes a list of mimeTypes **/ void setMimeTypes(const QByteArray &key, const QStringList &mimeTypes); /** * Sets the prefixes that all the contents in this package should * appear under. This defaults to "contents/" and is added automatically * between the base path and the entries as defined by the package * structure. Multiple entries can be added. * In this case each file request will be searched in all prefixes in order, * and the first found will be returned. * * @param prefix paths the directory prefix to use * @since 4.6 */ void setContentsPrefixPaths(const QStringList &prefixPaths); /** * Sets whether or not external paths/symlinks can be followed by a package * @param allow true if paths/symlinks outside of the package should be followed, * false if they should be rejected. */ void setAllowExternalPaths(bool allow); /** * Sets preferred package root. */ 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 KPackage::Package &package); /** * @return The fallback package root path */ KPackage::Package fallbackPackage() const; // Content structure description methods /** * @return all directories registered as part of this Package's structure */ QList directories() const; /** * @return all directories registered as part of this Package's required structure */ QList requiredDirectories() const; /** * @return all files registered as part of this Package's structure */ QList files() const; /** * @return all files registered as part of this Package's required structure */ QList requiredFiles() const; /** * Installs a package matching this package structure. By default installs a * native KPackage::Package. * After the KJob will emitted finished(), if the install went well * the Package instance will be guaranteed to have loaded the package just * installed, and be valid and usable. * * @return KJob to track installation progress and result **/ KJob *install(const QString &sourcePackage, const QString &packageRoot = QString()); /** * Updates a package matching this package structure. By default installs a * native KPackage::Package. If an older version is already installed, * it removes the old one. If the installed one is newer, * an error will occur. * After the KJob will emitted finished(), if the install went well * the Package instance will be guaranteed to have loaded the package just * updated, and be valid and usable. * * @return KJob to track installation progress and result * @since 5.17 **/ KJob *update(const QString &sourcePackage, const QString &packageRoot = QString()); /** * Uninstalls a package matching this package structure. * * @return KJob to track removal progress and result */ KJob *uninstall(const QString &packageName, const QString &packageRoot); private: QExplicitlySharedDataPointer d; friend class PackagePrivate; }; } Q_DECLARE_METATYPE(KPackage::Package) #endif diff --git a/src/kpackage/packageloader.h b/src/kpackage/packageloader.h index 4f111e8..3bb5188 100644 --- a/src/kpackage/packageloader.h +++ b/src/kpackage/packageloader.h @@ -1,143 +1,143 @@ /* * Copyright 2010 by Ryan Rix * * This program 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, or * (at your option) any later version. * * This program 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 General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KPACKAGE_LOADER_H #define KPACKAGE_LOADER_H #include #include namespace KPackage { class PackageLoaderPrivate; /** * @class PackageLoader kpackage/packageloader.h * * This is an abstract base class which defines an interface to which the package * loading logic can communicate with a parent application. The plugin loader * must be set before any plugins are loaded, otherwise (for safety reasons), the * default PackageLoader implementation will be used. The reimplemented version should * not do more than simply returning a loaded plugin. It should not init() it, and it should not * hang on to it. * * @author Ryan Rix **/ -class PACKAGE_EXPORT PackageLoader +class KPACKAGE_EXPORT PackageLoader { public: /** * Load a Package plugin. * * @param packageFormat the format of the package to load * @param packagePath the package name: the path of the package relative to the * packageFormat root path. If not specified it will have to be set manually * with Package::setPath() by the caller. * * @return a Package object matching name, or an invalid package on failure **/ Package loadPackage(const QString &packageFormat, const QString &packagePath = QString()); /** * List all available packages of a certain type * * @param packageFormat the format of the packages to list * @param packageRoot the root folder where the packages are installed. * If not specified the default from the packageformat will be taken. * * @return metadata for all the matching packages */ QList listPackages(const QString &packageFormat, const QString &packageRoot = QString()); /** * List package of a certain type that match a certain filter function * * @param packageFormat the format of the packages to list * @param packageRoot the root folder where the packages are installed. * If not specified the default from the packageformat will be taken. * @param filter a filter function that will be called on each package: * will return true for the matching ones * * @return metadata for all the matching packages * @since 5.10 */ QList findPackages(const QString &packageFormat, const QString &packageRoot = QString(), std::function filter = std::function()); /** * Loads a PackageStructure for a given format. The structure can then be used as * paramenter for a Package instance constructor * @param packageFormat the package format, such as "KPackage/GenericQML" * @return the structure instance */ KPackage::PackageStructure *loadPackageStructure(const QString &packageFormat); /** * Adds a new known package structure that can be used by the functions to load packages such * as loadPackage, findPackages etc * @param packageFormat the package format, such as "KPackage/GenericQML" * @param structure the package structure we want to be able to load packages from * @since 5.10 */ void addKnownPackageStructure(const QString &packageFormat, KPackage::PackageStructure *structure); /** * Set the plugin loader which will be queried for all loads. * * @param loader A subclass of PackageLoader which will be supplied * by the application **/ static void setPackageLoader(PackageLoader *loader); /** * Return the active plugin loader **/ static PackageLoader *self(); protected: /** * A re-implementable method that allows subclasses to override * the default behaviour of loadPackage. If the service requested is not recognized, * then the implementation should return an empty and invalid Package(). * This method is called * by loadPackage prior to attempting to load a Package using the standard * plugin mechanisms. * * @param packageFormat the format of the package to load * * @return a Package instance with the proper PackageStructure **/ virtual Package internalLoadPackage(const QString &packageFormat); PackageLoader(); virtual ~PackageLoader(); private: friend class Package; PackageLoaderPrivate *const d; Q_DISABLE_COPY(PackageLoader) }; } Q_DECLARE_METATYPE(KPackage::PackageLoader *) #endif diff --git a/src/kpackage/packagestructure.h b/src/kpackage/packagestructure.h index 7eb7829..2edf8e4 100644 --- a/src/kpackage/packagestructure.h +++ b/src/kpackage/packagestructure.h @@ -1,139 +1,139 @@ /****************************************************************************** * Copyright 2011 by Aaron Seigo * * * * 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 KPACKAGE_PACKAGESTRUCTURE_H #define KPACKAGE_PACKAGESTRUCTURE_H #include #include #include #include #include namespace KPackage { class PackageStructurePrivate; /** * @class PackageStructure kpackage/packagestructure.h * * This class is used to define the filesystem structure of a package type. * A PackageStructure is implemented as a dynamically loaded plugin, in the reimplementation * of initPackage the allowed fines and directories in the package are set into the package, * for instance: * * @code * package->addFileDefinition("mainscript", QStringLiteral("ui/main.qml"), i18n("Main Script File")); * package->setDefaultPackageRoot(QStringLiteral("plasma/wallpapers/")); * package->addDirectoryDefinition("images", QStringLiteral("images"), i18n("Images")); * package->addDirectoryDefinition("theme", QStringLiteral("theme"), i18n("Themed Images")); * QStringList mimetypes; * mimetypes << QStringLiteral("image/svg+xml") << QStringLiteral("image/png") << QStringLiteral("image/jpeg"); * package->setMimeTypes("images", mimetypes); * @endcode * * @author Aaron Seigo */ -class PACKAGE_EXPORT PackageStructure : public QObject +class KPACKAGE_EXPORT PackageStructure : public QObject { Q_OBJECT public: explicit PackageStructure(QObject *parent = nullptr, const QVariantList &args = QVariantList()); ~PackageStructure(); /** * Called when a the PackageStructure should initialize a Package with the initial * structure. This allows setting paths before setPath is called. * * Note: one special value is "metadata" which can be set to the location of KPluginMetaData * compatible .desktop file within the package. If not defined, it is assumed that this file * exists under the top level directory of the package. * * @param package the Package to set up. The object is empty of all definition when * first passed in. */ virtual void initPackage(Package *package); /** * Called whenever the path changes so that subclasses may take * package specific actions. */ virtual void pathChanged(Package *package); /** * Installs a package matching this package structure. By default installs a * native KPackage::Package. * * @param package the instance of Package that is being used for the install; useful for * accessing file paths * @param archivePath path to the package archive file * @param packageRoot path to the directory where the package should be * installed to * @return KJob* to track the installation status **/ virtual KJob *install(Package *package, const QString &archivePath, const QString &packageRoot); /** * Updates a package matching this package structure. By default installs a * native KPackage::Package. If an older version is already installed, * it removes the old one. If the installed one is newer, * an error will occur. * * @param package the instance of Package that is being used for the install; useful for * accessing file paths * @param archivePath path to the package archive file * @param packageRoot path to the directory where the package should be * installed to * @return KJob* to track the installation status * @since 5.17 **/ virtual KJob *update(Package *package, const QString &archivePath, const QString &packageRoot); /** * Uninstalls a package matching this package structure. * * @param package the instance of Package that is being used for the install; useful for * 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 KJob* to track the installation status */ virtual KJob *uninstall(Package *package, const QString &packageRoot); private: PackageStructurePrivate *d; }; } // KPackage namespace /** * Register a Package class when it is contained in a loadable module */ #define K_EXPORT_KPACKAGE_PACKAGE_WITH_JSON(classname, jsonFile) \ K_PLUGIN_FACTORY_WITH_JSON(factory, jsonFile, registerPlugin();) \ K_EXPORT_PLUGIN_VERSION(PACKAGE_VERSION) #endif diff --git a/src/kpackage/version.h b/src/kpackage/version.h index ca1c427..4264458 100644 --- a/src/kpackage/version.h +++ b/src/kpackage/version.h @@ -1,74 +1,74 @@ /* * Copyright 2008 by Aaron Seigo * * This program 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, or * (at your option) any later version. * * This program 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 General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KPACKAGEVERSION_H #define KPACKAGEVERSION_H /** @file kpackage/version.h */ #include #include #define PACKAGE_MAKE_VERSION(a,b,c) (((a) << 16) | ((b) << 8) | (c)) /** * Compile-time macro for checking the kpackage version. Not useful for * detecting the version of kpackage at runtime. */ #define PACKAGE_IS_VERSION(a,b,c) (PACKAGE_VERSION >= PACKAGE_MAKE_VERSION(a,b,c)) /** * Namespace for everything in kpackage */ namespace KPackage { /** * The runtime version of libkpackage */ -PACKAGE_EXPORT unsigned int version(); +KPACKAGE_EXPORT unsigned int version(); /** * The runtime major version of libkpackage */ -PACKAGE_EXPORT unsigned int versionMajor(); +KPACKAGE_EXPORT unsigned int versionMajor(); /** * The runtime major version of libkpackage */ -PACKAGE_EXPORT unsigned int versionMinor(); +KPACKAGE_EXPORT unsigned int versionMinor(); /** * The runtime major version of libkpackage */ -PACKAGE_EXPORT unsigned int versionRelease(); +KPACKAGE_EXPORT unsigned int versionRelease(); /** * The runtime version string of libkpackage */ -PACKAGE_EXPORT const char *versionString(); +KPACKAGE_EXPORT const char *versionString(); /** * Verifies that a plugin is compatible with plasma */ -PACKAGE_EXPORT bool isPluginVersionCompatible(unsigned int version); +KPACKAGE_EXPORT bool isPluginVersionCompatible(unsigned int version); } // Plasma namespace #endif // multiple inclusion guard