diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ef0a40..e0d3b65 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,142 +1,159 @@ cmake_minimum_required(VERSION 3.5) set(KF5_VERSION "5.56.0") # handled by release scripts set(KF5_DEP_VERSION "5.55.0") # handled by release scripts project(KFileMetaData VERSION ${KF5_VERSION}) include(FeatureSummary) find_package(ECM 5.55.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 "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${ECM_MODULE_PATH}) include(KDEInstallDirs) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(ECMAddTests) include(GenerateExportHeader) include(ECMSetupVersion) include(ECMGenerateHeaders) include(ECMAddQch) include(ECMQtDeclareLoggingCategory) include(CheckStructHasMember) 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 KFILEMETADATA PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5FileMetaDataConfigVersion.cmake" SOVERSION 3) # Dependencies set(REQUIRED_QT_VERSION 5.10.0) +find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG COMPONENTS Gui) +set_package_properties(Qt5Gui PROPERTIES DESCRIPTION "Qt 5: Gui component" + TYPE OPTIONAL + PURPOSE "Qt5::Gui is needed to build the AppImage extractor") + find_package(Qt5 ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE COMPONENTS Xml) find_package(KF5 ${KF5_DEP_VERSION} COMPONENTS Archive) set_package_properties(KF5Archive PROPERTIES DESCRIPTION "KDE Frameworks 5: Archive Framework" URL "https://download.kde.org/stable/frameworks/" TYPE OPTIONAL PURPOSE "Archive is needed to build ODF and OOXML 2007 extractors") find_package(KF5 ${KF5_DEP_VERSION} COMPONENTS CoreAddons) set_package_properties(KF5CoreAddons PROPERTIES DESCRIPTION "KDE Frameworks 5: Core Addons Framework" URL "https://download.kde.org/stable/frameworks/" TYPE REQUIRED PURPOSE "Needed for the formatting of properties for display purposes") +find_package(KF5 ${KF5_DEP_VERSION} COMPONENTS Config) +set_package_properties(KF5Config PROPERTIES DESCRIPTION "KDE Frameworks 5: Config Framework" + URL "https://download.kde.org/stable/frameworks/" TYPE OPTIONAL + PURPOSE "Config is needed to build the AppImage extractor") + find_package(KF5 ${KF5_DEP_VERSION} REQUIRED COMPONENTS I18n) find_package(Poppler 0.12.1 COMPONENTS Qt5) set_package_properties(Poppler PROPERTIES DESCRIPTION "A PDF rendering library" URL "https://poppler.freedesktop.org/" TYPE OPTIONAL PURPOSE "Support for PDF files") find_package(Taglib 1.11.1) set_package_properties(Taglib PROPERTIES DESCRIPTION "Id3 tag reader" URL "https://taglib.org/" TYPE OPTIONAL PURPOSE "Support for music metadata") find_package(LibExiv2 0.21) set_package_properties(LibExiv2 PROPERTIES TYPE OPTIONAL PURPOSE "Support for image metadata") find_package(FFmpeg 57.48 COMPONENTS AVCODEC) find_package(FFmpeg 57.40 COMPONENTS AVFORMAT) find_package(FFmpeg 55.27 COMPONENTS AVUTIL) set_package_properties(FFmpeg PROPERTIES DESCRIPTION "Video Tag reader" URL "https://ffmpeg.org/" TYPE OPTIONAL PURPOSE "Support for video metadata") if (FFmpeg_AVFORMAT_FOUND) set(CMAKE_REQUIRED_INCLUDES ${AVCODEC_INCLUDE_DIRS}) CHECK_STRUCT_HAS_MEMBER(AVStream codecpar "stdint.h;libavformat/avformat.h" HAVE_AVSTREAM_CODECPAR LANGUAGE CXX) endif() find_package(EPub) set_package_properties(EPub PROPERTIES DESCRIPTION "Ebook epub reader" URL "https://sourceforge.net/projects/ebook-tools/" TYPE OPTIONAL PURPOSE "Support for epub metadata") find_package(CatDoc) set_package_properties(CatDoc PROPERTIES DESCRIPTION "catdoc executable" URL "https://www.wagner.pp.ru/~vitus/software/catdoc/" TYPE RUNTIME PURPOSE "Extract text from office 98 files - RUNTIME dependency") if ( CMAKE_SYSTEM_NAME MATCHES "Linux" ) find_package(Xattr) set_package_properties(Xattr PROPERTIES DESCRIPTION "library libattr " URL "https://savannah.nongnu.org/projects/attr" TYPE REQUIRED PURPOSE "Extended attribute shared library") endif() #find_package(QMobipocket) #set_package_properties(QMobipocket PROPERTIES DESCRIPTION "Mobipocket epub reader" # URL "https://projects.kde.org/projects/kde/kdegraphics/kdegraphics-mobipocket" # TYPE OPTIONAL PURPOSE "Support for mobi metadata") +find_package(libappimage 0.1.10 CONFIG) +set_package_properties(libappimage PROPERTIES DESCRIPTION "Core library of the AppImage project" + URL "https://github.com/AppImage/libappimage" + TYPE OPTIONAL + PURPOSE "Needed to build the AppImage extractor" + ) + add_definitions(-DTRANSLATION_DOMAIN=\"kfilemetadata5\") add_subdirectory(src) if (BUILD_TESTING) add_subdirectory(autotests) add_subdirectory(tests) endif() # Config files set(CMAKECONFIG_INSTALL_DIR "${CMAKECONFIG_INSTALL_PREFIX}/KF5FileMetaData") if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ki18n_install(po) endif() if (BUILD_QCH) ecm_install_qch_export( TARGETS KF5FileMetaData_QCH FILE KF5FileMetaDataQchTargets.cmake DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KF5FileMetaDataQchTargets.cmake\")") endif() include(CMakePackageConfigHelpers) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5FileMetaDataConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5FileMetaDataConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5FileMetaDataConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5FileMetaDataConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT devel ) # contains list of debug categories, for kdebugsettings install(FILES kfilemetadata.categories DESTINATION ${KDE_INSTALL_CONFDIR}) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 52908fc..b34d3b7 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,214 +1,230 @@ find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Test) find_package(PythonInterp) set_package_properties(PythonInterp PROPERTIES DESCRIPTION "Python Interpreter" URL "https://www.python.org" TYPE OPTIONAL PURPOSE "Python interpreter is needed to execute test for external extractors or writers") remove_definitions(-DQT_NO_CAST_FROM_ASCII) configure_file(indexerextractortestsconfig.h.in ${CMAKE_CURRENT_BINARY_DIR}/indexerextractortestsconfig.h @ONLY) set(KfileMetaDataAutotest_SRCS) ecm_qt_declare_logging_category(KfileMetaDataAutotest_SRCS HEADER kfilemetadata_debug.h IDENTIFIER KFILEMETADATA_LOG CATEGORY_NAME kf5.kfilemetadata) set(indexerextractor_SRCS indexerextractortests.cpp ../src/extractors/plaintextextractor.cpp ) ecm_add_test(${indexerextractor_SRCS} TEST_NAME "indexextractortest" LINK_LIBRARIES Qt5::Test KF5::FileMetaData ) # # Office # if(KF5Archive_FOUND) ecm_add_test(odfextractortest.cpp ../src/extractors/odfextractor.cpp TEST_NAME "odfextractortest" LINK_LIBRARIES Qt5::Test Qt5::Xml KF5::FileMetaData KF5::Archive ) endif() if(KF5Archive_FOUND) ecm_add_test(office2007extractortest.cpp ../src/extractors/office2007extractor.cpp TEST_NAME "officeextractortest" LINK_LIBRARIES Qt5::Test Qt5::Xml KF5::FileMetaData KF5::Archive ) endif() # # Poppler # if(Poppler_Qt5_FOUND) ecm_add_test(popplerextractortest.cpp ../src/extractors/popplerextractor.cpp TEST_NAME "popplerextractortest" LINK_LIBRARIES Qt5::Test KF5::FileMetaData Poppler::Qt5 ) endif() # # EPub # if(EPUB_FOUND) include_directories(${EPUB_INCLUDE_DIR}) ecm_add_test(epubextractortest.cpp ../src/extractors/epubextractor.cpp TEST_NAME "epubextractortest" LINK_LIBRARIES Qt5::Test KF5::FileMetaData ${EPUB_LIBRARIES} ) endif() # # Mobi # if (QMOBIPOCKET_FOUND) include_directories(${QMOBIPOCKET_INCLUDE_DIR}) ecm_add_test(mobiextractortest.cpp ../src/extractors/mobiextractor.cpp TEST_NAME "mobiextractortest" LINK_LIBRARIES Qt5::Test KF5::FileMetaData ${QMOBIPOCKET_LIBRARIES} ) endif() # # Property Info # ecm_add_test(propertyinfotest.cpp TEST_NAME "propertyinfotest" LINK_LIBRARIES Qt5::Test KF5::FileMetaData ) # # Exiv2 # if(LibExiv2_FOUND) kde_enable_exceptions() ecm_add_test(exiv2extractortest.cpp ../src/extractors/exiv2extractor.cpp TEST_NAME "exiv2extractortest" LINK_LIBRARIES Qt5::Test KF5::FileMetaData LibExiv2::LibExiv2 ) endif() # # FFMPEG # if(FFMPEG_FOUND) kde_enable_exceptions() ecm_add_test(ffmpegextractortest.cpp ../src/extractors/ffmpegextractor.cpp TEST_NAME "ffmpegextractortest" LINK_LIBRARIES Qt5::Test KF5::FileMetaData ${AVCODEC_LIBRARIES} ${AVFORMAT_LIBRARIES} ${AVUTIL_LIBRARIES} ) target_include_directories(ffmpegextractortest SYSTEM PRIVATE ${AVCODEC_INCLUDE_DIRS} ${AVFORMAT_INCLUDE_DIRS} ${AVUTIL_INCLUDE_DIRS}) endif() # # TagLib # if(TAGLIB_FOUND) kde_enable_exceptions() ecm_add_test(taglibextractortest.cpp ../src/extractors/taglibextractor.cpp TEST_NAME "taglibextractortest" LINK_LIBRARIES Qt5::Test KF5::FileMetaData ${TAGLIB_LIBRARIES} ) target_include_directories(taglibextractortest SYSTEM PRIVATE ${TAGLIB_INCLUDES}) ecm_add_test(embeddedimagedatatest.cpp TEST_NAME "embeddedimagedatatest" LINK_LIBRARIES Qt5::Test KF5::FileMetaData ${TAGLIB_LIBRARIES} ) target_include_directories(embeddedimagedatatest SYSTEM PRIVATE ${TAGLIB_INCLUDES}) endif() if(PYTHONINTERP_FOUND) configure_file(samplefiles/testexternalextractor/main.py samplefiles/testexternalextractor/main.py) configure_file(samplefiles/testexternalextractor/manifest.json samplefiles/testexternalextractor/manifest.json COPYONLY) ecm_add_test(externalextractortest.cpp ../src/externalextractor.cpp ${KfileMetaDataAutotest_SRCS} TEST_NAME "externalextractortest" LINK_LIBRARIES Qt5::Test KF5::FileMetaData KF5::I18n ) endif() # # Collection # set(extractorcollection_SRCS extractorcollectiontest.cpp ) ecm_add_test(${extractorcollection_SRCS} TEST_NAME "extractorcollectiontest" LINK_LIBRARIES Qt5::Test KF5::FileMetaData ) # # XML # set(xmlextractor_SRCS xmlextractortest.cpp ../src/extractors/xmlextractor.cpp ../src/extractors/dublincoreextractor.cpp ../src/kfilemetadata_debug.cpp ) ecm_add_test(${xmlextractor_SRCS} TEST_NAME "xmlextractortest" LINK_LIBRARIES Qt5::Test Qt5::Xml KF5::FileMetaData ) # # Postscript DSC # set(postscriptdscextractor_SRCS postscriptdscextractortest.cpp ../src/extractors/postscriptdscextractor.cpp ../src/kfilemetadata_debug.cpp ) ecm_add_test(${postscriptdscextractor_SRCS} TEST_NAME "postscriptdscextractortest" LINK_LIBRARIES Qt5::Test KF5::FileMetaData ) +# +# AppImage +# +if(libappimage_FOUND AND KF5Config_FOUND AND Qt5Gui_FOUND) + ecm_add_test(appimageextractortest.cpp ../src/extractors/appimageextractor.cpp + TEST_NAME "appimageextractortest" + LINK_LIBRARIES + Qt5::Test + KF5::FileMetaData + KF5::ConfigCore + Qt5::Xml + Qt5::Gui + libappimage + ) +endif() + ################ # Writer tests # ################ # # UserMetaData # if(CMAKE_SYSTEM_NAME MATCHES "Linux") kde_enable_exceptions() ecm_add_test(usermetadatawritertest.cpp ../src/usermetadata.cpp TEST_NAME "usermetadatawritertest" LINK_LIBRARIES Qt5::Test KF5::FileMetaData ) endif() # # TagLib # if(TAGLIB_FOUND) kde_enable_exceptions() ecm_add_test(taglibwritertest.cpp ../src/writers/taglibwriter.cpp TEST_NAME "taglibwritertest" LINK_LIBRARIES Qt5::Test KF5::FileMetaData ${TAGLIB_LIBRARIES} ) target_include_directories(taglibwritertest SYSTEM PRIVATE ${TAGLIB_INCLUDES}) endif() if(PYTHONINTERP_FOUND) configure_file(samplefiles/testexternalwriter/main.py samplefiles/testexternalwriter/main.py) configure_file(samplefiles/testexternalwriter/manifest.json samplefiles/testexternalwriter/manifest.json COPYONLY) ecm_add_test(externalwritertest.cpp ../src/externalwriter.cpp ${KfileMetaDataAutotest_SRCS} TEST_NAME "externalwritertest" LINK_LIBRARIES Qt5::Test KF5::FileMetaData KF5::I18n ) endif() diff --git a/autotests/appimageextractortest.cpp b/autotests/appimageextractortest.cpp new file mode 100644 index 0000000..65ad9b6 --- /dev/null +++ b/autotests/appimageextractortest.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2019 Friedrich W. H. Kossebau + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "appimageextractortest.h" + +// Qt +#include +#include + +#include "simpleextractionresult.h" +#include "indexerextractortestsconfig.h" +#include "extractors/appimageextractor.h" + +using namespace KFileMetaData; + +QString AppImageExtractorTest::testFilePath(const QString& fileName) const +{ + return QLatin1String(INDEXER_TESTS_SAMPLE_FILES_PATH) + QLatin1Char('/') + fileName; +} + +/* +The test.AppImage got created by: + +wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage +chmod +x linuxdeploy-x86_64.AppImage +./linuxdeploy-x86_64.AppImage --appdir AppDir --output appimage --executable=/usr/bin/echo --desktop-file=appimagetest.desktop --icon-file=/usr/share/icons/oxygen/base/16x16/apps/kde.png +mkdir AppDir/usr/share/metainfo/ +cp org.kde.kfilemetadata.appimagetest.appdata.xml AppDir/usr/share/metainfo/ +./linuxdeploy-x86_64.AppImage --appdir AppDir --output appimage +mv Test_Desktop_Name-x86_64.AppImage test.AppImage + +To edit the appdata.xml & desktop file, run ./test.AppImage --appimage-extract to get the files. +Use linuxdeploy to create the appimage then again. +*/ + +void AppImageExtractorTest::test() +{ + AppImageExtractor plugin{this}; + + SimpleExtractionResult result(testFilePath(QStringLiteral("test.AppImage")), QStringLiteral("application/vnd.appimage")); + plugin.extract(&result); + + QCOMPARE(result.types().size(), 0); + + QCOMPARE(result.properties().value(Property::Author), QVariant(QStringLiteral("Konqi"))); + QCOMPARE(result.properties().value(Property::Title), QVariant(QStringLiteral("Test Desktop Name"))); + QCOMPARE(result.properties().value(Property::Comment), QVariant(QStringLiteral("Test Desktop Comment"))); + QCOMPARE(result.properties().value(Property::Description), QVariant(QStringLiteral("Test description line 1.\nTest description line 2."))); + QCOMPARE(result.properties().value(Property::License), QVariant(QStringLiteral("GPL-2.0"))); + +} + +QTEST_GUILESS_MAIN(AppImageExtractorTest) diff --git a/autotests/appimageextractortest.h b/autotests/appimageextractortest.h new file mode 100644 index 0000000..57104b4 --- /dev/null +++ b/autotests/appimageextractortest.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2019 Friedrich W. H. Kossebau + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef APPIMAGEEXTRACTORTEST_H +#define APPIMAGEEXTRACTORTEST_H + +#include + +class AppImageExtractorTest : public QObject +{ + Q_OBJECT +private: + QString testFilePath(const QString& fileName) const; + +private Q_SLOTS: + void test(); +}; + +#endif diff --git a/autotests/samplefiles/test.AppImage b/autotests/samplefiles/test.AppImage new file mode 100755 index 0000000..4a6cc53 Binary files /dev/null and b/autotests/samplefiles/test.AppImage differ diff --git a/src/extractors/CMakeLists.txt b/src/extractors/CMakeLists.txt index 6b4859c..131429e 100644 --- a/src/extractors/CMakeLists.txt +++ b/src/extractors/CMakeLists.txt @@ -1,219 +1,235 @@ if(Poppler_Qt5_FOUND) add_library(kfilemetadata_popplerextractor MODULE popplerextractor.cpp) target_link_libraries(kfilemetadata_popplerextractor KF5::FileMetaData Poppler::Qt5 ) set_target_properties(kfilemetadata_popplerextractor PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kfilemetadata") install( TARGETS kfilemetadata_popplerextractor DESTINATION ${PLUGIN_INSTALL_DIR}/kf5/kfilemetadata) endif() if(TAGLIB_FOUND) add_library(kfilemetadata_taglibextractor MODULE taglibextractor.cpp ) target_include_directories(kfilemetadata_taglibextractor SYSTEM PRIVATE ${TAGLIB_INCLUDES}) target_link_libraries( kfilemetadata_taglibextractor KF5::FileMetaData ${TAGLIB_LIBRARIES} ) set_target_properties(kfilemetadata_taglibextractor PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kfilemetadata") install( TARGETS kfilemetadata_taglibextractor DESTINATION ${PLUGIN_INSTALL_DIR}/kf5/kfilemetadata) endif() if(LibExiv2_FOUND) add_library(kfilemetadata_exiv2extractor MODULE exiv2extractor.cpp) kde_target_enable_exceptions(kfilemetadata_exiv2extractor PRIVATE) target_link_libraries(kfilemetadata_exiv2extractor KF5::FileMetaData LibExiv2::LibExiv2 ) if(NOT ${LibExiv2_VERSION} VERSION_LESS 0.26) target_compile_definitions(kfilemetadata_exiv2extractor PRIVATE "-DHAVE_WEBP_SUPPORT") endif() set_target_properties(kfilemetadata_exiv2extractor PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kfilemetadata") install( TARGETS kfilemetadata_exiv2extractor DESTINATION ${PLUGIN_INSTALL_DIR}/kf5/kfilemetadata) endif() if(FFMPEG_FOUND) add_library(kfilemetadata_ffmpegextractor MODULE ffmpegextractor.cpp) target_include_directories(kfilemetadata_ffmpegextractor SYSTEM PRIVATE ${AVCODEC_INCLUDE_DIRS} ${AVFORMAT_INCLUDE_DIRS} ${AVUTIL_INCLUDE_DIRS}) target_link_libraries(kfilemetadata_ffmpegextractor KF5::FileMetaData ${AVCODEC_LIBRARIES} ${AVFORMAT_LIBRARIES} ${AVUTIL_LIBRARIES} ) set_target_properties(kfilemetadata_ffmpegextractor PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kfilemetadata") install( TARGETS kfilemetadata_ffmpegextractor DESTINATION ${PLUGIN_INSTALL_DIR}/kf5/kfilemetadata) endif() if(EPUB_FOUND) add_library(kfilemetadata_epubextractor MODULE epubextractor.cpp) target_include_directories(kfilemetadata_epubextractor SYSTEM PRIVATE ${EPUB_INCLUDE_DIR}) target_link_libraries(kfilemetadata_epubextractor KF5::FileMetaData ${EPUB_LIBRARIES} ) set_target_properties(kfilemetadata_epubextractor PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kfilemetadata") install( TARGETS kfilemetadata_epubextractor DESTINATION ${PLUGIN_INSTALL_DIR}/kf5/kfilemetadata) endif() # # Plain Text # add_library(kfilemetadata_plaintextextractor MODULE plaintextextractor.cpp) target_link_libraries( kfilemetadata_plaintextextractor KF5::FileMetaData ) set_target_properties(kfilemetadata_plaintextextractor PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kfilemetadata") install( TARGETS kfilemetadata_plaintextextractor DESTINATION ${PLUGIN_INSTALL_DIR}/kf5/kfilemetadata) # # PO # add_library(kfilemetadata_poextractor MODULE poextractor.cpp) target_link_libraries( kfilemetadata_poextractor KF5::FileMetaData ) set_target_properties(kfilemetadata_poextractor PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kfilemetadata") install( TARGETS kfilemetadata_poextractor DESTINATION ${PLUGIN_INSTALL_DIR}/kf5/kfilemetadata) # # XML # add_library(kfilemetadata_xmlextractor MODULE dublincoreextractor.cpp xmlextractor.cpp ../kfilemetadata_debug.cpp ) target_link_libraries( kfilemetadata_xmlextractor KF5::FileMetaData Qt5::Core Qt5::Xml ) set_target_properties(kfilemetadata_xmlextractor PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kfilemetadata") install( TARGETS kfilemetadata_xmlextractor DESTINATION ${PLUGIN_INSTALL_DIR}/kf5/kfilemetadata ) # # Postscript DSC # add_library(kfilemetadata_postscriptdscextractor MODULE postscriptdscextractor.cpp ../kfilemetadata_debug.cpp ) target_link_libraries( kfilemetadata_postscriptdscextractor KF5::FileMetaData Qt5::Core ) set_target_properties(kfilemetadata_postscriptdscextractor PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kfilemetadata") install( TARGETS kfilemetadata_postscriptdscextractor DESTINATION ${PLUGIN_INSTALL_DIR}/kf5/kfilemetadata ) # # ODF # if(KF5Archive_FOUND) add_library(kfilemetadata_odfextractor MODULE odfextractor.cpp) target_link_libraries(kfilemetadata_odfextractor KF5::FileMetaData Qt5::Core Qt5::Xml KF5::Archive ) set_target_properties(kfilemetadata_odfextractor PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kfilemetadata") install( TARGETS kfilemetadata_odfextractor DESTINATION ${PLUGIN_INSTALL_DIR}/kf5/kfilemetadata) endif() # # Office 2007 # if(KF5Archive_FOUND) add_library(kfilemetadata_office2007extractor MODULE office2007extractor.cpp) target_link_libraries(kfilemetadata_office2007extractor KF5::FileMetaData Qt5::Core Qt5::Xml KF5::Archive ) set_target_properties(kfilemetadata_office2007extractor PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kfilemetadata") install( TARGETS kfilemetadata_office2007extractor DESTINATION ${PLUGIN_INSTALL_DIR}/kf5/kfilemetadata) endif() # # Office (binary formats) # add_library(kfilemetadata_officeextractor MODULE officeextractor.cpp) target_link_libraries(kfilemetadata_officeextractor KF5::FileMetaData ) set_target_properties(kfilemetadata_officeextractor PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kfilemetadata") install( TARGETS kfilemetadata_officeextractor DESTINATION ${PLUGIN_INSTALL_DIR}/kf5/kfilemetadata) # # Mobipocket # if (QMOBIPOCKET_FOUND) add_library(kfilemetadata_mobiextractor MODULE mobiextractor.cpp) target_include_directories(kfilemetadata_mobiextractor SYSTEM PRIVATE ${QMOBIPOCKET_INCLUDE_DIR}) target_link_libraries(kfilemetadata_mobiextractor KF5::FileMetaData ${QMOBIPOCKET_LIBRARIES} ) set_target_properties(kfilemetadata_mobiextractor PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kfilemetadata") install( TARGETS kfilemetadata_mobiextractor DESTINATION ${PLUGIN_INSTALL_DIR}/kf5/kfilemetadata) endif() + +if(libappimage_FOUND AND KF5Config_FOUND AND Qt5Gui_FOUND) + add_library(kfilemetadata_appimageextractor MODULE appimageextractor.cpp ) + target_link_libraries( kfilemetadata_appimageextractor + KF5::FileMetaData + KF5::ConfigCore + Qt5::Xml + Qt5::Gui + libappimage + ) + + set_target_properties(kfilemetadata_appimageextractor PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kfilemetadata") + install( + TARGETS kfilemetadata_appimageextractor + DESTINATION ${PLUGIN_INSTALL_DIR}/kf5/kfilemetadata) +endif() diff --git a/src/extractors/appimageextractor.cpp b/src/extractors/appimageextractor.cpp new file mode 100644 index 0000000..f0a4331 --- /dev/null +++ b/src/extractors/appimageextractor.cpp @@ -0,0 +1,302 @@ +/* + Copyright (C) 2019 Friedrich W. H. Kossebau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "appimageextractor.h" + +// KF +#include +// Qt +#include +#include +#include +#include +#include +// libappimage +#include + +using namespace KFileMetaData; + + +namespace { +namespace AttributeNames { +QString xml_lang() { return QStringLiteral("xml:lang"); } +} +} + + +// helper class to extract the interesting data from the appdata file +// prefers localized strings over unlocalized, using system locale +class AppDataParser +{ +public: + AppDataParser(const char* appImageFilePath, const QString& appdataFilePath); + +public: + QString summary() const { return !m_localized.summary.isEmpty() ? m_localized.summary : m_unlocalized.summary; } + QString description() const { return !m_localized.description.isEmpty() ? m_localized.description : m_unlocalized.description; } + QString developerName() const { return !m_localized.developerName.isEmpty() ? m_localized.developerName : m_unlocalized.developerName; } + QString projectLicense() const { return m_projectLicense; } + +private: + void extractDescription(const QDomElement& e, const QString& localeName); + +private: + struct Data { + QString summary; + QString description; + QString developerName; + }; + Data m_localized; + Data m_unlocalized; + QString m_projectLicense; +}; + + +AppDataParser::AppDataParser(const char* appImageFilePath, const QString& appdataFilePath) +{ + if (appdataFilePath.isEmpty()) { + return; + } + + unsigned long size = 0L; + char* buf = nullptr; + bool ok = appimage_read_file_into_buffer_following_symlinks(appImageFilePath, + qUtf8Printable(appdataFilePath), + &buf, + &size); + + QScopedPointer cleanup(buf); + + if (!ok) { + return; + } + + QDomDocument domDocument; + if (!domDocument.setContent(QByteArray::fromRawData(buf, size))) { + return; + } + + QDomElement docElem = domDocument.documentElement(); + if (docElem.tagName() != QLatin1String("component")) { + return; + } + + const auto localeName = QLocale::system().bcp47Name(); + + QDomElement ec = docElem.firstChildElement(); + while (!ec.isNull()) { + const auto tagName = ec.tagName(); + const auto hasLangAttribute = ec.hasAttribute(AttributeNames::xml_lang()); + const auto matchingLocale = hasLangAttribute && (ec.attribute(AttributeNames::xml_lang()) == localeName); + if (matchingLocale || !hasLangAttribute) { + if (tagName == QLatin1String("summary")) { + Data& data = hasLangAttribute ? m_localized : m_unlocalized; + data.summary = ec.text(); + } else if (tagName == QLatin1String("description")) { + extractDescription(ec, localeName); + } else if (tagName == QLatin1String("developer_name")) { + Data& data = hasLangAttribute ? m_localized : m_unlocalized; + data.developerName = ec.text(); + } else if (tagName == QLatin1String("project_license")) { + m_projectLicense = ec.text(); + } + } + ec = ec.nextSiblingElement(); + } +} + +using DesriptionDomFilter = std::function; + +void stripDescriptionTextElements(QDomElement& element, const DesriptionDomFilter& stripFilter) +{ + auto childElement = element.firstChildElement(); + while (!childElement.isNull()) { + auto nextChildElement = childElement.nextSiblingElement(); + + const auto tagName = childElement.tagName(); + const bool isElementToFilter = (tagName == QLatin1String("p")) || (tagName == QLatin1String("li")); + if (isElementToFilter && stripFilter(childElement)) { + element.removeChild(childElement); + } else { + stripDescriptionTextElements(childElement, stripFilter); + } + + childElement = nextChildElement; + } +} + +void AppDataParser::extractDescription(const QDomElement& e, const QString& localeName) +{ + // create fake html from it and let QTextDocument transform it to plain text for us + QDomDocument descriptionDocument; + auto htmlElement = descriptionDocument.createElement(QStringLiteral("html")); + descriptionDocument.appendChild(htmlElement); + + // first localized... + auto clonedE = descriptionDocument.importNode(e, true).toElement(); + clonedE.setTagName(QStringLiteral("body")); + stripDescriptionTextElements(clonedE, [localeName](const QDomElement& e) { + return !e.hasAttribute(AttributeNames::xml_lang()) || + e.attribute(AttributeNames::xml_lang()) != localeName; + }); + htmlElement.appendChild(clonedE); + + QTextDocument textDocument; + textDocument.setHtml(descriptionDocument.toString(-1)); + + m_localized.description = textDocument.toPlainText().trimmed(); + + if (!m_localized.description.isEmpty()) { + // localized will be preferred, no need to calculate unlocalized one + return; + } + + // then unlocalized if still needed + htmlElement.removeChild(clonedE); // reuse descriptionDocument + clonedE = descriptionDocument.importNode(e, true).toElement(); + clonedE.setTagName(QStringLiteral("body")); + stripDescriptionTextElements(clonedE, [](const QDomElement& e) { + return e.hasAttribute(AttributeNames::xml_lang()); + }); + htmlElement.appendChild(clonedE); + + textDocument.setHtml(descriptionDocument.toString(-1)); + + m_unlocalized.description = textDocument.toPlainText().trimmed(); +} + + +// helper class to extract the interesting data from the desktop file +class DesktopFileParser +{ +public: + DesktopFileParser(const char* appImageFilePath, const QString& desktopFilePath); + +public: + QString name; + QString comment; +}; + + +DesktopFileParser::DesktopFileParser(const char* appImageFilePath, const QString& desktopFilePath) +{ + if (desktopFilePath.isEmpty()) { + return; + } + + unsigned long size = 0L; + char* buf = nullptr; + bool ok = appimage_read_file_into_buffer_following_symlinks(appImageFilePath, + qUtf8Printable(desktopFilePath), + &buf, + &size); + + QScopedPointer cleanup(buf); + + if (!ok) { + return; + } + + // create real file, KDesktopFile needs that + QTemporaryFile tmpDesktopFile; + tmpDesktopFile.open(); + tmpDesktopFile.write(buf, size); + tmpDesktopFile.close(); + + KDesktopFile desktopFile(tmpDesktopFile.fileName()); + name = desktopFile.readName(); + comment = desktopFile.readComment(); +} + + +AppImageExtractor::AppImageExtractor(QObject* parent) + : ExtractorPlugin(parent) +{ +} + +QStringList AppImageExtractor::mimetypes() const +{ + return QStringList{ + QStringLiteral("application/x-iso9660-appimage"), + QStringLiteral("application/vnd.appimage"), + }; +} + +void KFileMetaData::AppImageExtractor::extract(ExtractionResult* result) +{ + const auto appImageFilePath = result->inputUrl().toUtf8(); + const auto appImageType = appimage_get_type(appImageFilePath.constData(), false); + // not a valid appimage file? + if (appImageType <= 0) { + return; + } + + // find desktop file and appdata file + // need to scan ourselves, given there are no fixed names in the spec yet defined + // and we just can try as the other appimage tools to simply use the first file of the type found + char** filePaths = appimage_list_files(appImageFilePath.constData()); + if (!filePaths) { + return; + } + + QString desktopFilePath; + QString appdataFilePath; + for (int i = 0; filePaths[i] != nullptr; ++i) { + const auto filePath = QString::fromUtf8(filePaths[i]); + + if (filePath.startsWith(QLatin1String("usr/share/metainfo/")) && + filePath.endsWith(QLatin1String(".appdata.xml"))) { + appdataFilePath = filePath; + if (!desktopFilePath.isEmpty()) { + break; + } + } + + if (filePath.endsWith(QLatin1String(".desktop")) && !filePath.contains(QLatin1Char('/'))) { + desktopFilePath = filePath; + if (!appdataFilePath.isEmpty()) { + break; + } + } + } + + appimage_string_list_free(filePaths); + + // extract data from both files... + const AppDataParser appData(appImageFilePath.constData(), appdataFilePath); + + const DesktopFileParser desktopFileData(appImageFilePath.constData(), desktopFilePath); + + // ... and insert into the result + result->add(Property::Title, desktopFileData.name); + + if (!desktopFileData.comment.isEmpty()) { + result->add(Property::Comment, desktopFileData.comment); + } else if (!appData.summary().isEmpty()) { + result->add(Property::Comment, appData.summary()); + } + if (!appData.description().isEmpty()) { + result->add(Property::Description, appData.description()); + } + if (!appData.projectLicense().isEmpty()) { + result->add(Property::License, appData.projectLicense()); + } + if (!appData.developerName().isEmpty()) { + result->add(Property::Author, appData.developerName()); + } +} diff --git a/src/extractors/appimageextractor.h b/src/extractors/appimageextractor.h new file mode 100644 index 0000000..8eb7d16 --- /dev/null +++ b/src/extractors/appimageextractor.h @@ -0,0 +1,43 @@ +/* + Copyright (C) 2019 Friedrich W. H. Kossebau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef APPIMAGEEXTRACTOR_H +#define APPIMAGEEXTRACTOR_H + +#include "extractorplugin.h" + +namespace KFileMetaData +{ + +class AppImageExtractor : public ExtractorPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.kde.kf5.kfilemetadata.ExtractorPlugin") + Q_INTERFACES(KFileMetaData::ExtractorPlugin) + +public: + explicit AppImageExtractor(QObject* parent = nullptr); + +public: + void extract(ExtractionResult* result) override; + QStringList mimetypes() const override; +}; + +} + +#endif