diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d2f514f7..15d098720 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,419 +1,419 @@ cmake_minimum_required(VERSION 2.8.12) cmake_policy(SET CMP0043 NEW) project(gcompris-qt C CXX) # get all the redist dll needed for windows when compiling with vc set(CMAKE_INSTALL_UCRT_LIBRARIES 1) include(InstallRequiredSystemLibraries) # Set c++11 support include(CheckCXXCompilerFlag) check_cxx_compiler_flag("-std=c++11" COMPILER_SUPPORTS_CXX11) check_cxx_compiler_flag("-std=c++0x" COMPILER_SUPPORTS_CXX0X) if(COMPILER_SUPPORTS_CXX11) set(my_cxx_flags "-std=c++11") elseif(COMPILER_SUPPORTS_CXX0X) set(my_cxx_flags "-std=c++0x") else() message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.") endif() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${my_cxx_flags}") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${my_cxx_flags}") # enable qml debugging for DEBUG builds: set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DQT_QML_DEBUG") set(GCOMPRIS_MAJOR_VERSION 0) set(GCOMPRIS_MINOR_VERSION 97) set(GCOMPRIS_PATCH_VERSION 0) if("${ANDROID_ARCH}" STREQUAL "arm64") set(GCOMPRIS_PATCH_VERSION 1) endif() # Set the BUILD_DATE string(TIMESTAMP BUILD_DATE %Y%m) # cmake modules setup find_package(ECM 1.4.0 QUIET NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ${CMAKE_SOURCE_DIR}/cmake/) set(CMAKE_PREFIX_PATH "${Qt5_DIR}/lib/cmake/Qt5") # KDE po to qm tools if(ECM_FOUND) include(kdeFetchTranslation) include(ECMAddTests) include(ECMPoQmTools) if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ecm_install_po_files_as_qm(po) endif() option(BUILD_TESTING "Build and enable unit tests" OFF) include(ECMCoverageOption) endif() # add tools (cppcheck, clang-tidy...) if build on testing mode only # (slower compilation) if(BUILD_TESTING) include(CodeQualityUtils) endif() set(QT_REQUIRED_VERSION 5.6.0) if(CMAKE_SYSTEM_NAME STREQUAL Android) find_package(ECM) set(ANDROID 1) # TODO: possibly should be setup by toolchain one day set(QT_QMAKE_EXECUTABLE "${_qt5Core_install_prefix}/bin/qmake") if(ECM_VERSION VERSION_GREATER "5.55.0") set(QT_REQUIRED_VERSION 5.12.0) endif() # workaround until this fix is in released ECM if(ECM_VERSION VERSION_LESS "5.15.0") add_definitions(-DANDROID) endif() endif() # Set executable filename if(ANDROID) set(GCOMPRIS_EXECUTABLE_NAME GCompris) elseif(SAILFISHOS) set(GCOMPRIS_EXECUTABLE_NAME harbour-gcompris-qt) elseif(WIN32) set(GCOMPRIS_EXECUTABLE_NAME GCompris) else() set(GCOMPRIS_EXECUTABLE_NAME gcompris-qt) endif() set(GCOMPRIS_VERSION ${GCOMPRIS_MAJOR_VERSION}.${GCOMPRIS_MINOR_VERSION}) # An integer value that represents the version of the application # Increase it at each release math(EXPR GCOMPRIS_VERSION_CODE "${GCOMPRIS_MAJOR_VERSION}*10000 + ${GCOMPRIS_MINOR_VERSION}*100 + ${GCOMPRIS_PATCH_VERSION}") # prevent build in source directory if("${CMAKE_BINARY_DIR}" STREQUAL "${CMAKE_SOURCE_DIR}") message(SEND_ERROR "Building in the source directory is not supported.") message(FATAL_ERROR "Please remove the created \"CMakeCache.txt\" file, the \"CMakeFiles\" directory and create a build directory and call \"${CMAKE_COMMAND} \".") endif() find_package(Qt5 ${QT_REQUIRED_VERSION} REQUIRED Qml Quick Gui Multimedia Core Svg Xml XmlPatterns LinguistTools Sensors) if(ANDROID) find_package(Qt5 ${QT_REQUIRED_VERSION} REQUIRED AndroidExtras) endif() if(SAILFISHOS) find_package(Qt5 ${QT_REQUIRED_VERSION} REQUIRED Widgets) endif() ## For now we workaround CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION to clang with a command variable, so not needed # if(ANDROID AND ECM_VERSION VERSION_LESS "5.56.0" AND Qt5Core_VERSION VERSION_GREATER "5.11.99") # message(FATAL_ERROR "ECM ${ECM_VERSION} not compatible with Qt ${Qt5Core_VERSION} version for android.") # endif() if((UNIX AND NOT APPLE AND NOT SAILFISHOS AND NOT ANDROID) OR WIN32) find_package(OpenSSL REQUIRED) endif() find_package(KF5 QUIET COMPONENTS DocTools ) if(ECM_FOUND) include(KDEInstallDirs) if(ECM_VERSION VERSION_GREATER "1.6.0") add_subdirectory(images) install(FILES org.kde.gcompris.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) install(FILES org.kde.gcompris.desktop DESTINATION ${KDE_INSTALL_APPDIR}) else() message(STATUS "ECM_VERSION is ${ECM_VERSION}, icons and desktop files won't be installed.") endif() endif() # Tell CMake to run moc when necessary: set(CMAKE_AUTOMOC ON) # As moc files are generated in the binary dir, tell CMake # to always look for includes there: set(CMAKE_INCLUDE_CURRENT_DIR ON) #get_cmake_property(_variableNames VARIABLES) #foreach (_variableName ${_variableNames}) # message("${_variableName}=${${_variableName}}") #endforeach() option(WITH_DOWNLOAD "Internal download" ON) # @FIXME These permissions should be removed if download is disable # but it makes the application crash on exit (tested on Android 6) set(ANDROID_INTERNET_PERMISSION "") set(ANDROID_ACCESS_NETWORK_STATE_PERMISSION "") set(GRAPHICAL_RENDERER "auto" CACHE STRING "Policy for choosing the renderer backend [opengl|software|auto]") # Set output directory if(CMAKE_HOST_APPLE) set(_bundle_bin gcompris-qt.app/Contents/MacOS) set(_data_dest_dir bin/${_bundle_bin}/../Resources) else() set(_data_dest_dir share/${GCOMPRIS_EXECUTABLE_NAME}) endif() if(ANDROID) # Android .so output set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/android/libs/${ANDROID_ABI}/) set(GCOMPRIS_TRANSLATIONS_DIR ${CMAKE_BINARY_DIR}/${_data_dest_dir} CACHE INTERNAL "" FORCE) set(GCOMPRIS_RCC_DIR ${CMAKE_BINARY_DIR}/android/assets/${_data_dest_dir}/rcc CACHE INTERNAL "" FORCE) set(ANDROID_PACKAGE "net.gcompris.full") add_subdirectory(android) elseif(CMAKE_HOST_APPLE) # MacOSX build set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(GCOMPRIS_TRANSLATIONS_DIR ${CMAKE_BINARY_DIR}/${_data_dest_dir}/translations CACHE INTERNAL "" FORCE) set(GCOMPRIS_RCC_DIR ${CMAKE_BINARY_DIR}/${_data_dest_dir}/rcc CACHE INTERNAL "" FORCE) else() # Desktop build set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(GCOMPRIS_TRANSLATIONS_DIR ${CMAKE_BINARY_DIR}/${_data_dest_dir}/translations CACHE INTERNAL "" FORCE) set(GCOMPRIS_RCC_DIR ${CMAKE_BINARY_DIR}/${_data_dest_dir}/rcc CACHE INTERNAL "" FORCE) endif() # Always create these folders add_custom_command( OUTPUT shareFolders COMMAND cmake -E make_directory ${GCOMPRIS_TRANSLATIONS_DIR} COMMAND cmake -E make_directory ${GCOMPRIS_RCC_DIR} ) add_custom_target( createShareFolders ALL DEPENDS shareFolders ) include(cmake/rcc.cmake) # Translations handling # Simple command calling the python script add_custom_command( OUTPUT retrievePoFilesFromSvn COMMAND python3 tools/l10n-fetch-po-files.py WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) # Install translations add_custom_target(getSvnTranslations DEPENDS retrievePoFilesFromSvn COMMENT "Re-run cmake after this to be able to run BuildTranslations with the latest files" ) # Get all po files in po/. You can get them doing: python3 tools/l10n-fetch-po-files.py file(GLOB TRANSLATIONS_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "po/*.po") # Set the output dir for the translation files to /bin foreach(PoSource ${TRANSLATIONS_FILES}) # Changes the .po extension to .ts string(REPLACE ".po" ".ts" TsSource ${PoSource}) # Removes the po/ folder string(REPLACE "po/" "" TsSource ${TsSource}) # qm filename string(REPLACE ".ts" ".qm" QmOutput ${TsSource}) set(OutTsFile ${CMAKE_BINARY_DIR}/tmp/${TsSource}) add_custom_command( OUTPUT ${QmOutput} COMMAND cmake -E make_directory ${GCOMPRIS_TRANSLATIONS_DIR} COMMAND cmake -E make_directory ${CMAKE_BINARY_DIR}/tmp # Remove the obsolete translations and set po in the ts output file COMMAND msgattrib --no-obsolete ${CMAKE_CURRENT_SOURCE_DIR}/${PoSource} -o ${OutTsFile} # Convert the po into ts COMMAND Qt5::lconvert -if po -of ts -i ${OutTsFile} -o ${OutTsFile} # Convert the ts in qm removing non finished translations COMMAND Qt5::lrelease -compress -nounfinished ${OutTsFile} -qm ${GCOMPRIS_TRANSLATIONS_DIR}/${QmOutput} ) list(APPEND QM_FILES ${QmOutput}) endforeach() # Install translations if(WIN32) add_custom_target(BuildTranslations DEPENDS ${QM_FILES} COMMENT "If you don't have the .po, you need to run make getSvnTranslations first then re-run cmake" ) else() add_custom_target(BuildTranslations ALL DEPENDS ${QM_FILES} COMMENT "If you don't have the .po, you need to run make getSvnTranslations first then re-run cmake" ) endif() add_custom_command( OUTPUT doBundleTranslations COMMAND 7z a -w${CMAKE_BINARY_DIR}/share/${GCOMPRIS_EXECUTABLE_NAME} ${CMAKE_BINARY_DIR}/translations-${GCOMPRIS_VERSION}.7z ${CMAKE_BINARY_DIR}/share/${GCOMPRIS_EXECUTABLE_NAME}/translations WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) # Bundle translations add_custom_target(BundleTranslations DEPENDS doBundleTranslations COMMENT "If you want to provide a zip of the translations on a server (run make BuildTranslations first)" ) add_custom_command( OUTPUT doDlAndInstallBundledTranslations COMMAND curl -fsS -o translations-${GCOMPRIS_VERSION}.7z - http://gcompris.net/download/translations-${GCOMPRIS_VERSION}.7z + https://gcompris.net/download/translations-${GCOMPRIS_VERSION}.7z COMMAND 7z x -y -o${CMAKE_BINARY_DIR}/share/${GCOMPRIS_EXECUTABLE_NAME} translations-${GCOMPRIS_VERSION}.7z WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) # Download and install bundled translations add_custom_target(DlAndInstallBundledTranslations DEPENDS doDlAndInstallBundledTranslations COMMENT "Download the bundled translation and install them in the build dir" ) if(CMAKE_HOST_APPLE) install(DIRECTORY ${GCOMPRIS_TRANSLATIONS_DIR} DESTINATION ${_bundle_bin}) elseif(ANDROID) install(DIRECTORY ${GCOMPRIS_TRANSLATIONS_DIR} DESTINATION "share") else() install(DIRECTORY ${GCOMPRIS_TRANSLATIONS_DIR} DESTINATION ${_data_dest_dir}) endif() # Build standalone package option -> if ON, we will copy the required Qt files in the build package. # If OFF, "make install" will not copy Qt files so only GCompris files will be packaged. # By default, it is true on Windows (as we deliver NSIS package), macOS (bundled), android (apk) and false on linux (to do make install) # If you want to create a STGZ package for linux (auto-extractible), override this variable by typing : cmake -DBUILD_STANDALONE=ON if(UNIX AND NOT ANDROID AND NOT APPLE) option(BUILD_STANDALONE "Build a standalone package when typing 'make package'" OFF) else() option(BUILD_STANDALONE "Build a standalone package when typing 'make package'" ON) endif() option(WITH_KIOSK_MODE "Set the kiosk mode by default" OFF) if(WIN32) set(COMPRESSED_AUDIO "mp3" CACHE STRING "Compressed Audio format [ogg|aac|mp3]") elseif(APPLE) set(COMPRESSED_AUDIO "aac" CACHE STRING "Compressed Audio format [ogg|aac|mp3]") else() set(COMPRESSED_AUDIO "ogg" CACHE STRING "Compressed Audio format [ogg|aac|mp3]") endif() file(GLOB_RECURSE OGG_FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/ "*.ogg") foreach(OGG_FILE ${OGG_FILES}) # This should only replace the extension string(REGEX REPLACE "ogg$" "aac" AAC_FILE ${OGG_FILE}) add_custom_command( OUTPUT ${AAC_FILE} # Put the good line depending on your installation COMMAND avconv -v warning -i ${OGG_FILE} -acodec libvo_aacenc ${AAC_FILE} #COMMAND ffmpeg -v warning -i ${OGG_FILE} -acodec aac -strict -2 ${AAC_FILE} ) list(APPEND AAC_FILES ${AAC_FILE}) # This should only replace the extension string(REGEX REPLACE "ogg$" "mp3" MP3_FILE ${OGG_FILE}) add_custom_command( OUTPUT ${MP3_FILE} # Put the good line depending on your installation #COMMAND avconv -v warning -i ${OGG_FILE} -acodec mp3 ${MP3_FILE} COMMAND ffmpeg -v warning -i ${OGG_FILE} -acodec mp3 -strict -2 ${MP3_FILE} ) list(APPEND MP3_FILES ${MP3_FILE}) endforeach() add_custom_target( createAacFromOgg DEPENDS ${AAC_FILES} ) add_custom_target( createMp3FromOgg DEPENDS ${MP3_FILES} ) if(ANDROID) set(GCOMPRIS_ASSETS_DIR ${GCOMPRIS_RCC_DIR}/../../../ CACHE INTERNAL "" FORCE) else() set(GCOMPRIS_ASSETS_DIR ${GCOMPRIS_RCC_DIR} CACHE INTERNAL "" FORCE) endif() # predownload assets (voices and images) and install them in the rcc folder set(DOWNLOAD_ASSETS "" CACHE STRING "Download and packages images and voices. use a list like: words,en,fr,pt_BR,music to retrieve multiple files") add_custom_command( OUTPUT predownloadAssets COMMAND python3 tools/download-assets.py ${DOWNLOAD_ASSETS} ${COMPRESSED_AUDIO} ${GCOMPRIS_ASSETS_DIR} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) add_custom_command( OUTPUT assetsFolders COMMAND cmake -E make_directory "${GCOMPRIS_ASSETS_DIR}/data2" COMMAND cmake -E make_directory "${GCOMPRIS_ASSETS_DIR}/data2/voices-${COMPRESSED_AUDIO}" COMMAND cmake -E make_directory "${GCOMPRIS_ASSETS_DIR}/data2/words" COMMAND cmake -E make_directory "${GCOMPRIS_ASSETS_DIR}/data2/backgroundMusic" ) # Install assets add_custom_target(getAssets DEPENDS assetsFolders predownloadAssets ) add_custom_command( OUTPUT doBundleConvertedOggs COMMAND 7z a converted_ogg_to_${COMPRESSED_AUDIO}-${GCOMPRIS_VERSION}.7z '-ir!src/*${COMPRESSED_AUDIO}' WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) # Bundle oggs ready to be uploaded on a server. This ease build on system without the appropriate audio # convertion tools. add_custom_target(BundleConvertedOggs DEPENDS doBundleConvertedOggs COMMENT "Bundle the converted oggs to upload them on a server. First set COMPRESSED_AUDIO appropriately." ) add_custom_command( OUTPUT doDlAndInstallBundledConvertedOggs COMMAND curl -fsS -o converted_ogg_to_${COMPRESSED_AUDIO}-${GCOMPRIS_VERSION}.7z - http://gcompris.net/download/converted_ogg_to_${COMPRESSED_AUDIO}-${GCOMPRIS_VERSION}.7z + https://gcompris.net/download/converted_ogg_to_${COMPRESSED_AUDIO}-${GCOMPRIS_VERSION}.7z COMMAND 7z x -y converted_ogg_to_${COMPRESSED_AUDIO}-${GCOMPRIS_VERSION}.7z WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) # Download and install bundled converted oggs add_custom_target(DlAndInstallBundledConvertedOggs DEPENDS doDlAndInstallBundledConvertedOggs COMMENT "Download the bundled converted oggs and install them in the source dir" ) set(ARCHIVE_NAME ${CMAKE_PROJECT_NAME}-${GCOMPRIS_VERSION}) add_custom_target(dist COMMAND git archive --prefix=${ARCHIVE_NAME}/ HEAD | xz > ${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}.tar.xz WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) if(KF5_FOUND) add_subdirectory(docs/docbook) endif() # qml-box2d include(cmake/box2d.cmake) add_subdirectory(src) if(SAILFISHOS) # Need to be done at the end, after src add_subdirectory(platforms/sailfishOS) endif() # only enable unit tests for linux if(BUILD_TESTING) enable_testing() add_subdirectory(tests) endif() add_custom_target(binaries) add_dependencies(binaries ${GCOMPRIS_EXECUTABLE_NAME} rcc_core rcc_menu rcc_activities all_activities) diff --git a/src/activities/algebra_plus/CMakeLists.txt b/src/activities/algebra_plus/CMakeLists.txt index 49cfa1de2..84c75ddb5 100644 --- a/src/activities/algebra_plus/CMakeLists.txt +++ b/src/activities/algebra_plus/CMakeLists.txt @@ -1 +1 @@ -GCOMPRIS_ADD_RCC(activities/algebra_plus *.qml *.svg *.js resource/*/* resource/*.*) \ No newline at end of file +GCOMPRIS_ADD_RCC(activities/algebra_plus *.qml *.svg *.js resource/*/* resource/*.*) diff --git a/src/activities/wordsgame/resource/default-mk.json b/src/activities/wordsgame/resource/default-mk.json index a5bd0fdb6..3e1caae9a 100644 --- a/src/activities/wordsgame/resource/default-mk.json +++ b/src/activities/wordsgame/resource/default-mk.json @@ -1,196 +1,196 @@ { "levels": [ { "level": "1", "sublevels": "15", "words": [ "топка", "со", "шапка", "и", "или", "после", "фарма", "кола", "тркало", "птица", "пат", "негово", "нејзино", "чаша", "брз", "лет", "види", "само", "лесно", "љубов", "забава", "див", "почеток", "скок", "имам", "куче", "маче", "крава", "книга", "буква", "црвено", "зелено", "сино", "мое", "суво" ] }, { "level": "2", "sublevels": "15", "words": [ "љубов", "ученик", "препродавач", "маржа", "странство", "живот", "замок", "слобода", "планина", "ропство", "фактор", "ситуација", "коњ", "коњак", "коњаница", "височина", "знаење", "поддршка", "езеро", "компјутер", "музика", "звук", "цвет", "чудесно", "власт", "сон", "училиште", "гостин", "тежок", "игра" ] }, { "level": "3", "sublevels": "15", "words": [ "борчлија", "барабанџија", "горештина", "еќим", "солидарност", "зијан", "кадија", "марифет", "оџа", "аџилак", "раја", "саѓи", "хајка", "хармоничност", "харпун", "харфа", "џавкање", "ќар", "ќебап", "ќебе", "ќерамида", "ќелија", "тамбураш", "тутунџија", "тужба", "љубезност", "ѓеврек", "ѓердан", "печиво", "жаба", "желка", "жед", "желудник", "ориз", "оружје", "нож", "ножица", "нечистотија", "нишка", "ѕвонче", "ѕвезда", "ѕвонар", "ѕуница", "исток", "игла", "џем", "џамлии", "ѓезве", "ашов", "сеир", "студент", "тапија", "антилопа", "леопард", "фереџе", "ќор-сокак", "мрежа", "октопод", "јагненце", "кокошка", "танц", "стапка", "баждарина", "скулптура", "театар", "опера", "концерт", "лагер", "независност", "обврзник", "достоинство", "чест", "семејство", "празник", "карикатура", "ловец", "рибар", "машина", "кораб", "понекогаш", "град", "невозможно", "штрк", "чалма", "едрилица", "подморница", "исцелител", "доброволец", "придонес", "преведувач", "игралиште", "полјана", "колиба", "неразделни", "катанец", "џам", "зеленчук", "краставица", "кокошарник", "амбар", "вистина", "правда", "писмен", "шајка", "чизми", "полн", "чист" ] }], "name": "default-mk", "locale": "mk", "description": "Default Macedonian" -} \ No newline at end of file +} diff --git a/src/core/DownloadManager.h b/src/core/DownloadManager.h index bf207cf60..b6f84d639 100644 --- a/src/core/DownloadManager.h +++ b/src/core/DownloadManager.h @@ -1,392 +1,392 @@ /* GCompris - DownloadManager.h * * Copyright (C) 2014 Holger Kaelberer * * Authors: * Holger Kaelberer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, 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 General Public License * along with this program; if not, see . */ #ifndef DOWNLOADMANAGER_H #define DOWNLOADMANAGER_H #include #include #include #include #include #include #include #include #include #include #include /** * @class DownloadManager * @short A singleton class responsible for downloading, updating and * maintaining remote resources. * @ingroup infrastructure * * DownloadManager is responsible for downloading, updating and registering * additional resources (in Qt's binary .rcc format) used by GCompris. * It downloads from a upstream URL (ApplicationSettings.downloadServerUrl) to the * default local writable location. Downloads are based on common relative * resource paths, such that a URL of the form * - * http://\/\ + * https://\/\ * * will be downloaded to a local path * * /\cachePath()\>/\ * * and registered with a resource root path * * qrc:/\/ * * Internally resources are uniquely identified by their relative resource * path * * \ * (e.g. data2/voices-ogg/voices-en.rcc>) * * Downloading and verification of local files is controlled by MD5 * checksums that are expected to be stored in @c Contents files in each * upstream directory according to the syntax produced by the @c md5sum * tool. The checksums are used for checking whether a local rcc file is * up-to-date (to avoid unnecesary rcc downloads) and to verify that the * transfer was complete. Only valid rcc files (with correct checksums) * are registered. * * A resource file must reference the "/gcompris/data" prefix with * \. All data are loaded and referenced * from this prefix. It is possible to check if a data is registered with * isDataRegistered. * * @sa DownloadDialog, ApplicationSettings.downloadServerUrl, * ApplicationSettings.isAutomaticDownloadsEnabled, * ApplicationSettings.cachePath */ class DownloadManager : public QObject { Q_OBJECT private: DownloadManager(); // prohibit external creation, we are a singleton! static DownloadManager* _instance; // singleton instance /** Container for a full download job */ typedef struct DownloadJob { QUrl url; ///< url of the currently running sub-job QFile file; ///< target file for the currently running sub-job QNetworkReply *reply; ///< reply object for the currently running sub-job QList queue; ///< q of remaining sub jobs (QList for convenience) QMap contents; ///< checksum map for download verification QList knownContentsUrls; ///< store already tried upstream Contents files (for infinite loop protection) DownloadJob(const QUrl &u = QUrl()) : url(u), file(), reply(0), queue(QList()) {} } DownloadJob; QList activeJobs; ///< track active jobs to allow for parallel downloads QMutex jobsMutex; ///< not sure if we need to expect concurrent access, better lockit! static const QString contentsFilename; static const QCryptographicHash::Algorithm hashMethod = QCryptographicHash::Md5; QList registeredResources; QMutex rcMutex; ///< not sure if we need to expect concurrent access, better lockit! QNetworkAccessManager accessManager; QUrl serverUrl; /** * Get the platform-specific path storing downloaded resources. * * Uses QStandardPaths::writableLocation(QStandardPaths::CacheLocation) * which returns * - on desktop linux $HOME/.cache/KDE/gcompris-qt/ * - on other platforms check * * @return An absolute path. */ QString getSystemDownloadPath() const; /** * Get all paths that are used for storing resources. * * @returns A list of absolute paths used for storing local resources. * The caller should keep the returned list order when looking for * resources, for now the lists contains: * 1. data folder in the application path * 2. getSystemDownloadPath() * 3. QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)/gcompris-qt * which is * - $HOME/.local/share/gcompris-qt (on linux desktop) * - /storage/sdcard0/GCompris (on android) * 4. [QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation)]/gcompris-qt * which is on GNU/Linux * - $HOME/.local/share/KDE/gcompris-qt * - $HOME/.local/share/gcompris-qt * - $HOME/.local/share/applications/gcompris-qt * - /usr/local/share/KDE/gcompris-qt * - /usr/share/KDE/gcompris-qt */ QStringList getSystemResourcePaths() const; QString getResourceRootForFilename(const QString& filename) const; QString getFilenameForUrl(const QUrl& url) const; QUrl getUrlForFilename(const QString& filename) const; /** * Transforms the passed relative path to an absolute resource path of an * existing .rcc file, honouring the order of the systemResourcePaths * * @returns The absolute path of the .rcc file if it exists, QString() * otherwise */ QString getAbsoluteResourcePath(const QString& path) const; /** * Transforms the passed absolute path to a relative resource path if * possible. * * @returns The relative path if it could be generated, QString() otherwise. */ QString getRelativeResourcePath(const QString& path) const; QString tempFilenameForFilename(const QString &filename) const; QString filenameForTempFilename(const QString &tempFilename) const; bool checkDownloadRestriction() const; DownloadJob* getJobByReply(QNetworkReply *r); DownloadJob* getJobByUrl_locked(const QUrl& url) const; /** Start a new download specified by the passed DownloadJob */ bool download(DownloadJob* job); /** Parses upstream Contents file and build checksum map. */ bool parseContents(DownloadJob *job); /** Compares the checksum of the file in filename with the contents map in * the passed DownloadJob */ bool checksumMatches(DownloadJob *job, const QString& filename) const; bool registerResourceAbsolute(const QString& filename); /** Unregisters the passed resource * * Caller must lock rcMutex. */ void unregisterResource_locked(const QString& filename); bool isRegistered(const QString& filename) const; #if 0 QStringList getLocalResources(); #endif private slots: /** Handle a finished download. * * Called whenever a single download (sub-job) has finished. Responsible * for iterating over possibly remaining sub-jobs of our DownloadJob. */ void downloadFinished(); void downloadReadyRead(); void handleError(QNetworkReply::NetworkError code); public: // public interface: /** * Possible return codes of a finished download */ enum DownloadFinishedCode { Success = 0, /**< Download finished successfully */ Error = 1, /**< Download error */ NoChange = 2 /**< Local files are up-to-date, no download was needed */ }; virtual ~DownloadManager(); /** * Registers DownloadManager singleton in the QML engine. */ static QObject *downloadManagerProvider(QQmlEngine *engine, QJSEngine *scriptEngine); static DownloadManager* getInstance(); /** * Generates a relative voices resources file-path for a given @p locale. * * @param locale Locale name string of the form \_\. * * @returns A relative voices resource path. */ Q_INVOKABLE QString getVoicesResourceForLocale(const QString& locale) const; // @returns A relative background music resource path. Q_INVOKABLE QString getBackgroundMusicResources() const; /** * Checks whether the given relative resource @p path exists locally. * * @param path A relative resource path. */ Q_INVOKABLE bool haveLocalResource(const QString& path) const; /** * Whether any download is currently running. */ Q_INVOKABLE bool downloadIsRunning() const; /** * Whether the passed relative @p data is registered. * * For example, if you have a resource file which loads files under the * 'words' path like 'words/one.png'. You can call this method with 'words/one.png' * or with 'words'. * * @param data Relative resource path (file or directory). * * @sa areVoicesRegistered */ Q_INVOKABLE bool isDataRegistered(const QString& data) const; /** * Whether voices for the currently active locale are registered. * * @sa isDataRegistered */ Q_INVOKABLE bool areVoicesRegistered() const; /** * Registers a rcc resource file given by a relative resource path * * @param filename Relative resource path. */ Q_INVOKABLE bool registerResource(const QString& filename); public slots: /** * Updates a resource @p path from the upstream server unless prohibited * by the settings and registers it if possible. * * If not prohibited by the setting 'isAutomaticDownloadsEnabled' checks * for an available upstream resource specified by @p path. * If the corresponding local resource does not exist or is out of date, * the resource is downloaded and registered. * * If automatic downloads/updates are prohibited and a local copy of the * specified resource exists, it is registered. * * @param path A relative resource path. * * @returns success */ Q_INVOKABLE bool updateResource(const QString& path); /** * Download a resource specified by the relative resource @p path and * register it if possible. * * If a corresponding local resource exists, an update will only be * downloaded if it is not up-to-date according to checksum comparison. * Whenever at the end we have a valid .rcc file it will be registered. * * @param path A relative resource path. * * @returns success */ Q_INVOKABLE bool downloadResource(const QString& path); /** * Shutdown DownloadManager instance. * * Aborts all currently running downloads. */ Q_INVOKABLE void shutdown(); /** * Abort all currently running downloads. */ Q_INVOKABLE void abortDownloads(); #if 0 Q_INVOKABLE bool checkForUpdates(); // might be helpful later with other use-cases! Q_INVOKABLE void registerLocalResources(); #endif signals: /** Emitted when a download error occurs. * * @param code enum NetworkError code. * @param msg Error string. */ void error(int code, const QString& msg); /** Emitted when a download has started. * * @param resource Relative resource path of the started download. */ void downloadStarted(const QString& resource); /** Emitted during a running download. * * All values refer to the currently active sub-job. * * @param bytesReceived Downloaded bytes. * @param bytesTotal Total bytes to download. */ void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); /** Emitted when a download has finished. * * Also emitted in error cases. * * @param code DownloadFinishedCode. FIXME: when using DownloadFinishedCode * instead of int the code will not be passed to the QML layer, * use QENUMS? */ void downloadFinished(int code); /** Emitted when a resource has been registered. * * @param resource Relative resource path of the registered resource. * * @sa voicesRegistered */ void resourceRegistered(const QString& resource); /** Emitted when voices resources for current locale have been registered. * * Convenience signal and special case of resourceRegistered. * * @sa resourceRegistered */ void voicesRegistered(); /** Emitted when background music has been registered. * * Convenience signal and special case of resourceRegistered. * * @sa resourceRegistered */ void backgroundMusicRegistered(); }; #endif /* DOWNLOADMANAGER_H */