diff --git a/src/core/ApplicationInfo.cpp b/src/core/ApplicationInfo.cpp index 0ec9b1a55..d7a1d93dd 100644 --- a/src/core/ApplicationInfo.cpp +++ b/src/core/ApplicationInfo.cpp @@ -1,296 +1,296 @@ /* GCompris - ApplicationInfo.cpp * * Copyright (C) 2014-2015 Bruno Coudoin * * Authors: * Bruno Coudoin * * This file was originally created from Digia example code under BSD license * and heavily modified since then. * * 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 . */ #include "ApplicationInfo.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include QQuickWindow *ApplicationInfo::m_window = nullptr; ApplicationInfo *ApplicationInfo::m_instance = nullptr; ApplicationInfo::ApplicationInfo(QObject *parent): QObject(parent) { #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(Q_OS_BLACKBERRY) || defined(SAILFISHOS) m_isMobile = true; #else m_isMobile = false; #endif #if defined(Q_OS_ANDROID) // Put android before checking linux/unix as it is also a linux m_platform = Android; #elif defined(Q_OS_MAC) m_platform = MacOSX; #elif (defined(Q_OS_LINUX) || defined(Q_OS_UNIX)) m_platform = Linux; #elif defined(Q_OS_WIN) m_platform = Windows; #elif defined(Q_OS_IOS) m_platform = Ios; #elif defined(Q_OS_BLACKBERRY) m_platform = Blackberry; #elif defined(SAILFISHOS) m_platform = SailfishOS; #else // default is Linux m_platform = Linux; #endif m_isBox2DInstalled = false; QRect rect = qApp->primaryScreen()->geometry(); m_ratio = qMin(qMax(rect.width(), rect.height())/800. , qMin(rect.width(), rect.height())/520.); // calculate a factor for font-scaling, cf. - // http://doc.qt.io/qt-5/scalability.html#calculating-scaling-ratio + // https://doc.qt.io/qt-5/scalability.html#calculating-scaling-ratio qreal refDpi = 216.; qreal refHeight = 1776.; qreal refWidth = 1080.; qreal height = qMax(rect.width(), rect.height()); qreal width = qMin(rect.width(), rect.height()); qreal dpi = qApp->primaryScreen()->logicalDotsPerInch(); m_fontRatio = qMax(qreal(1.0), qMin(height*refDpi/(dpi*refHeight), width*refDpi/(dpi*refWidth))); m_isPortraitMode = m_isMobile ? rect.height() > rect.width() : false; m_applicationWidth = m_isMobile ? rect.width() : 1120; m_useOpenGL = true; if (m_isMobile) connect(qApp->primaryScreen(), &QScreen::physicalSizeChanged, this, &ApplicationInfo::notifyPortraitMode); // @FIXME this does not work on iOS: https://bugreports.qt.io/browse/QTBUG-50624 #if not defined(Q_OS_IOS) // Get all symbol fonts to remove them QFontDatabase database; m_excludedFonts = database.families(QFontDatabase::Symbol); #endif // Get fonts from rcc const QStringList fontFilters = {"*.otf", "*.ttf"}; m_fontsFromRcc = QDir(":/gcompris/src/core/resource/fonts").entryList(fontFilters); } ApplicationInfo::~ApplicationInfo() { m_instance = nullptr; } bool ApplicationInfo::sensorIsSupported(const QString& sensorType) { return QSensor::sensorTypes().contains(sensorType.toUtf8()); } Qt::ScreenOrientation ApplicationInfo::getNativeOrientation() { return QGuiApplication::primaryScreen()->nativeOrientation(); } void ApplicationInfo::setApplicationWidth(const int newWidth) { if (newWidth != m_applicationWidth) { m_applicationWidth = newWidth; emit applicationWidthChanged(); } } QString ApplicationInfo::getResourceDataPath() { return QString("qrc:/gcompris/data"); } QString ApplicationInfo::getFilePath(const QString &file) { #if defined(Q_OS_ANDROID) return QString("assets:/share/GCompris/rcc/%1").arg(file); #elif defined(Q_OS_MACX) return QString("%1/../Resources/rcc/%2").arg(QCoreApplication::applicationDirPath(), file); #elif defined(Q_OS_IOS) return QString("%1/rcc/%2").arg(QCoreApplication::applicationDirPath(), file); #else return QString("%1/%2/rcc/%3").arg(QCoreApplication::applicationDirPath(), GCOMPRIS_DATA_FOLDER, file); #endif } QString ApplicationInfo::getAudioFilePath(const QString &file) { QString localeName = getVoicesLocale(ApplicationSettings::getInstance()->locale()); return getAudioFilePathForLocale(file, localeName); } QString ApplicationInfo::getAudioFilePathForLocale(const QString &file, const QString &localeName) { QString filename = file; filename.replace("$LOCALE", localeName); filename.replace("$CA", CompressedAudio()); if(file.startsWith('/') || file.startsWith(QLatin1String("qrc:")) || file.startsWith(':')) return filename; return getResourceDataPath() + '/' + filename; } QString ApplicationInfo::getLocaleFilePath(const QString &file) { QString localeShortName = localeShort(); QString filename = file; filename.replace("$LOCALE", localeShortName); return filename; } QStringList ApplicationInfo::getSystemExcludedFonts() { return m_excludedFonts; } QStringList ApplicationInfo::getFontsFromRcc() { return m_fontsFromRcc; } QStringList ApplicationInfo::getBackgroundMusicFromRcc() { const QStringList backgroundMusicFilters = { QString("*.%1").arg(COMPRESSED_AUDIO) }; m_backgroundMusicFromRcc = QDir(":/gcompris/data/backgroundMusic").entryList(backgroundMusicFilters); return m_backgroundMusicFromRcc; } void ApplicationInfo::notifyPortraitMode() { int width = qApp->primaryScreen()->geometry().width(); int height = qApp->primaryScreen()->geometry().height(); setIsPortraitMode(height > width); } void ApplicationInfo::setIsPortraitMode(const bool newMode) { if (m_isPortraitMode != newMode) { m_isPortraitMode = newMode; emit portraitModeChanged(); } } void ApplicationInfo::setWindow(QQuickWindow *window) { m_window = window; } void ApplicationInfo::screenshot(const QString &file) { QImage img = m_window->grabWindow(); img.save(file); } void ApplicationInfo::notifyFullscreenChanged() { if(ApplicationSettings::getInstance()->isFullscreen()) m_window->showFullScreen(); else m_window->showNormal(); } // Would be better to create a component importing Box2D 2.0 using QQmlContext and test if it exists but it does not work. void ApplicationInfo::setBox2DInstalled(const QQmlEngine &engine) { /* QQmlContext *context = new QQmlContext(engine.rootContext()); context->setContextObject(&myDataSet); QQmlComponent component(&engine); component.setData("import QtQuick 2.0\nimport Box2D 2.0\nItem { }", QUrl()); component.create(context); box2dInstalled = (component != nullptr); */ bool box2dInstalled = false; for(const QString &folder: engine.importPathList()) { if(QDir(folder).entryList().contains(QStringLiteral("Box2D.2.0"))) { if(QDir(folder+"/Box2D.2.0").entryList().contains("qmldir")) { qDebug() << "Found box2d in " << folder; box2dInstalled = true; break; } } } m_isBox2DInstalled = box2dInstalled; emit isBox2DInstalledChanged(); } // return the shortest possible locale name for the given locale, describing // a unique voices dataset QString ApplicationInfo::getVoicesLocale(const QString &locale) { QString _locale = locale; if(_locale == GC_DEFAULT_LOCALE) { _locale = QLocale::system().name(); if(_locale == "C") _locale = "en_US"; } // locales we have country-specific voices for: if (_locale.startsWith(QLatin1String("en_GB")) || _locale.startsWith(QLatin1String("en_US")) || _locale.startsWith(QLatin1String("pt_BR")) || _locale.startsWith(QLatin1String("zh_CN")) || _locale.startsWith(QLatin1String("zh_TW"))) return QLocale(_locale).name(); // short locale for all the rest: return localeShort(_locale); } QVariantList ApplicationInfo::localeSort(QVariantList list, const QString& locale) const { std::sort(list.begin(), list.end(), [&locale,this](const QVariant& a, const QVariant& b) { return (localeCompare(a.toString(), b.toString(), locale) < 0); }); return list; } QObject *ApplicationInfo::applicationInfoProvider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) /* * Connect the fullscreen change signal to applicationInfo in order to change * the QQuickWindow value */ ApplicationInfo* appInfo = getInstance(); connect(ApplicationSettings::getInstance(), &ApplicationSettings::fullscreenChanged, appInfo, &ApplicationInfo::notifyFullscreenChanged); return appInfo; } diff --git a/src/core/ApplicationInfo.h b/src/core/ApplicationInfo.h index 3439e88b7..db0c7c08c 100644 --- a/src/core/ApplicationInfo.h +++ b/src/core/ApplicationInfo.h @@ -1,445 +1,445 @@ /* GCompris - ApplicationInfo.h * * Copyright (C) 2014-2015 Bruno Coudoin * * Authors: * Bruno Coudoin * * This file was originally created from Digia example code under BSD license * and heavily modified since then. * * 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 APPLICATIONINFO_H #define APPLICATIONINFO_H #include #include "ApplicationSettings.h" #include #include #include class QQuickWindow; /** * @class ApplicationInfo * @short A general purpose singleton that exposes miscellaneous native * functions to the QML layer. * @ingroup infrastructure */ class ApplicationInfo : public QObject { Q_OBJECT /** * Width of the application viewport. */ Q_PROPERTY(int applicationWidth READ applicationWidth WRITE setApplicationWidth NOTIFY applicationWidthChanged) /** * Platform the application is currently running on. */ Q_PROPERTY(Platform platform READ platform CONSTANT) /** * Whether the application is currently running on a mobile platform. * * Mobile platforms are Android, Ios (not supported yet), * Blackberry (not supported) */ Q_PROPERTY(bool isMobile READ isMobile CONSTANT) /** * Whether the current platform supports fragment shaders. * * This flag is used in some core modules to selectively deactivate * particle effects which cause crashes on some Android devices. * * cf. https://bugreports.qt.io/browse/QTBUG-44194 * * For now always set to false, to prevent crashes. */ Q_PROPERTY(bool hasShader READ hasShader CONSTANT) /** * Whether currently in portrait mode, on mobile platforms. * * Based on viewport geometry. */ Q_PROPERTY(bool isPortraitMode READ isPortraitMode WRITE setIsPortraitMode NOTIFY portraitModeChanged) /** * Ratio factor used for scaling of sizes on high-dpi devices. * * Must be used by activities as a scaling factor to all pixel values. */ Q_PROPERTY(qreal ratio READ ratio NOTIFY ratioChanged) /** * Ratio factor used for font scaling. * * On some low-dpi Android devices with high res (e.g. Galaxy Tab 4) the * fonts in Text-like elements appear too small with respect to the other * graphics -- also if we are using font.pointSize. * * For these cases we calculate a fontRatio in ApplicationInfo that takes * dpi information into account, as proposed on - * http://doc.qt.io/qt-5/scalability.html#calculating-scaling-ratio + * https://doc.qt.io/qt-5/scalability.html#calculating-scaling-ratio * * GCText applies this factor automatically on its new fontSize property. */ Q_PROPERTY(qreal fontRatio READ fontRatio NOTIFY fontRatioChanged) /** * Short (2-letter) locale string of the currently active language. */ Q_PROPERTY(QString localeShort READ localeShort CONSTANT) /** * GCompris version string (compile time). */ Q_PROPERTY(QString GCVersion READ GCVersion CONSTANT) /** * GCompris version code (compile time). */ Q_PROPERTY(int GCVersionCode READ GCVersionCode CONSTANT) /** * Qt version string (runtime). */ Q_PROPERTY(QString QTVersion READ QTVersion CONSTANT) /** * Audio codec used for voices resources. * * This is determined at compile time (ogg for free platforms, aac on * MacOSX and IOS). */ Q_PROPERTY(QString CompressedAudio READ CompressedAudio CONSTANT) /** * Download allowed * * This is determined at compile time. If set to NO GCompris will * never download anything. */ Q_PROPERTY(bool isDownloadAllowed READ isDownloadAllowed CONSTANT) /** * Whether the application is currently using OpenGL or not. * * Use to deactivate some effects if OpenGL not used. */ Q_PROPERTY(bool useOpenGL READ useOpenGL WRITE setUseOpenGL NOTIFY useOpenGLChanged) /** * Whether Box2D is installed or not. * * Use to disable activities that use Box2D when it's not installed. */ Q_PROPERTY(bool isBox2DInstalled READ isBox2DInstalled NOTIFY isBox2DInstalledChanged) public: /** * Known host platforms. */ enum Platform { Linux, /**< Linux (except Android) */ Windows, /**< Windows */ MacOSX, /**< MacOSX */ Android, /**< Android */ Ios, /**< IOS (not supported) */ Blackberry, /**< Blackberry (not supported) */ SailfishOS /**< SailfishOS */ }; Q_ENUM(Platform) /** * Returns an absolute and platform independent path to the passed @p file * * @param file A relative filename. * @returns Absolute path to the file. */ static QString getFilePath(const QString &file); /** * Returns the short locale name for the passed @p locale. * * Handles also 'system' (GC_DEFAULT_LOCALE) correctly which resolves to * QLocale::system().name(). * * @param locale A locale string of the form \_\ * @returns A short locale string of the form \ */ static QString localeShort(const QString &locale) { QString _locale = locale; if(_locale == GC_DEFAULT_LOCALE) { _locale = QLocale::system().name(); } // Can't use left(2) because of Asturian where there are 3 chars return _locale.left(_locale.indexOf('_')); } /** * Returns a locale string that can be used in voices filenames. * * @param locale A locale string of the form \_\ * @returns A locale string as used in voices filenames. */ Q_INVOKABLE QString getVoicesLocale(const QString &locale); /** * Request GCompris to take the Audio Focus at the system level. * * On systems that support it, it will mute a running audio player. */ Q_INVOKABLE bool requestAudioFocus() const; /** * Abandon the Audio Focus. * * On systems that support it, it will let an audio player start again. */ Q_INVOKABLE void abandonAudioFocus() const; /** * Compare two strings respecting locale specific sort order. * * @param a First string to compare * @param b Second string to compare * @param locale Locale to respect for comparison in any of the forms * used in GCompris xx[_XX][.codeset]. Defaults to currently * set language from global configuration. * @returns -1, 0 or 1 if a is less than, equal to or greater than b */ Q_INVOKABLE int localeCompare(const QString& a, const QString& b, const QString& locale = "") const; /** * Sort a list of strings respecting locale specific sort order. * * This function is supposed to be called from QML/JS. As there are still * problems marshalling QStringList from C++ to QML/JS we use QVariantList * both for argument and return value. * * @param list List of strings to be sorted. * @param locale Locale to respect for sorting in any of the forms * used in GCompris xx[_XX][.codeset]. * @returns List sorted by the sort order of the passed locale. */ Q_INVOKABLE QVariantList localeSort(QVariantList list, const QString& locale = "") const; /// @cond INTERNAL_DOCS static ApplicationInfo *getInstance() { if(!m_instance) { m_instance = new ApplicationInfo(); } return m_instance; } static QObject *applicationInfoProvider(QQmlEngine *engine, QJSEngine *scriptEngine); static void setWindow(QQuickWindow *window); explicit ApplicationInfo(QObject *parent = 0); ~ApplicationInfo(); int applicationWidth() const { return m_applicationWidth; } void setApplicationWidth(const int newWidth); Platform platform() const { return m_platform; } bool isPortraitMode() const { return m_isPortraitMode; } void setIsPortraitMode(const bool newMode); bool isMobile() const { return m_isMobile; } bool hasShader() const { #if defined(Q_OS_ANDROID) return false; #else return true; #endif } qreal ratio() const { return m_ratio; } qreal fontRatio() const { return m_fontRatio; } QString localeShort() const { return localeShort( ApplicationSettings::getInstance()->locale() ); } static QString GCVersion() { return VERSION; } static int GCVersionCode() { return VERSION_CODE; } static QString QTVersion() { return qVersion(); } static QString CompressedAudio() { return COMPRESSED_AUDIO; } static bool isDownloadAllowed() { return QString(DOWNLOAD_ALLOWED) == "ON"; } bool useOpenGL() const { return m_useOpenGL; } void setUseOpenGL(bool useOpenGL) { m_useOpenGL = useOpenGL; } bool isBox2DInstalled() const { return m_isBox2DInstalled; } void setBox2DInstalled(const QQmlEngine &engine); /** * Returns the native screen orientation. * * Wraps QScreen::nativeOrientation: The native orientation of the screen * is the orientation where the logo sticker of the device appears the * right way up, or Qt::PrimaryOrientation if the platform does not support * this functionality. * * The native orientation is a property of the hardware, and does not change */ Q_INVOKABLE Qt::ScreenOrientation getNativeOrientation(); /** * Change the desired orientation of the application. * - * Android specific function, cf. http://developer.android.com/reference/android/app/Activity.html#setRequestedOrientation(int) + * Android specific function, cf. https://developer.android.com/reference/android/app/Activity.html#setRequestedOrientation(int) * * @param orientation Desired orientation of the application. For possible - * values cf. http://developer.android.com/reference/android/content/pm/ActivityInfo.html#screenOrientation . + * values cf. https://developer.android.com/reference/android/content/pm/ActivityInfo.html#screenOrientation . * Some useful values: * - -1: SCREEN_ORIENTATION_UNSPECIFIED * - 0: SCREEN_ORIENTATION_LANDSCAPE: forces landscape * - 1: SCREEN_ORIENTATION_PORTRAIT: forces portrait * - 5: SCREEN_ORIENTATION_NOSENSOR: forces native * orientation mode on each device (portrait on * smartphones, landscape on tablet) * - 14: SCREEN_ORIENTATION_LOCKED: lock current orientation */ Q_INVOKABLE void setRequestedOrientation(int orientation); /** * Query the desired orientation of the application. * * @sa setRequestedOrientation */ Q_INVOKABLE int getRequestedOrientation(); /** * Checks whether a sensor type from the QtSensor module is supported on * the current platform. * * @param sensorType Classname of a sensor from the QtSensor module * to be checked (e.g. "QTiltSensor"). */ Q_INVOKABLE bool sensorIsSupported(const QString& sensorType); /** * Toggles activation of screensaver on android * * @param value Whether screensaver should be disabled (true) or * enabled (false). */ Q_INVOKABLE void setKeepScreenOn(bool value); /// @endcond public slots: /** * Returns the resource root-path used for GCompris resources. */ QString getResourceDataPath(); /** * Returns an absolute path to a language specific sound/voices file. If * the file is already absolute only the token replacement is applied. * * @param file A templated relative path to a language specific file. Any * occurrence of the '$LOCALE' placeholder will be replaced by * the currently active locale string. * Any occurrence of '$CA' placeholder will be replaced by * the current compressed audio format ('ogg' or 'aac). * Example: 'voices-$CA/$LOCALE/misc/click_on_letter.$CA' * @returns An absolute path to the corresponding resource file. */ Q_INVOKABLE QString getAudioFilePath(const QString &file); /** * Returns an absolute path to a language specific sound/voices file. If * the file is already absolute only the token replacement is applied. * * @param file A templated relative path to a language specific file. Any * occurrence of the '$LOCALE' placeholder will be replaced by * the currently active locale string. * Any occurrence of '$CA' placeholder will be replaced by * the current compressed audio format ('ogg' or 'aac). * Example: 'voices-$CA/$LOCALE/misc/click_on_letter.$CA' * @param localeName the locale for which to get this audio file * @returns An absolute path to the corresponding resource file. */ Q_INVOKABLE QString getAudioFilePathForLocale(const QString &file, const QString &localeName); /** * Returns an absolute path to a language specific resource file. * * Generalization of getAudioFilePath(). * @sa getAudioFilePath */ Q_INVOKABLE QString getLocaleFilePath(const QString &file); /** * @returns A list of systems-fonts that should be excluded from font * selection. */ Q_INVOKABLE QStringList getSystemExcludedFonts(); /** * @returns A list of fonts contained in the fonts resources. */ Q_INVOKABLE QStringList getFontsFromRcc(); /** * @returns A list of background music contained in the background music resources. */ Q_INVOKABLE QStringList getBackgroundMusicFromRcc(); /** * Stores a screenshot in the passed @p file. * * @param file Absolute destination filename. */ Q_INVOKABLE void screenshot(const QString &file); void notifyPortraitMode(); Q_INVOKABLE void notifyFullscreenChanged(); protected: qreal getSizeWithRatio(const qreal height) { return ratio() * height; } signals: void applicationWidthChanged(); void portraitModeChanged(); void ratioChanged(); void fontRatioChanged(); void applicationSettingsChanged(); void fullscreenChanged(); void useOpenGLChanged(); void isBox2DInstalledChanged(); private: static ApplicationInfo *m_instance; int m_applicationWidth; Platform m_platform; bool m_isPortraitMode; bool m_isMobile; bool m_useOpenGL; bool m_isBox2DInstalled; qreal m_ratio; qreal m_fontRatio; // Symbols fonts that user can't see QStringList m_excludedFonts; QStringList m_fontsFromRcc; QStringList m_backgroundMusicFromRcc; static QQuickWindow *m_window; }; #endif // APPLICATIONINFO_H diff --git a/src/core/DownloadManager.h b/src/core/DownloadManager.h index 520c4aa95..bf207cf60 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://\/\ * * 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 + * - 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 */ diff --git a/src/core/main.cpp b/src/core/main.cpp index c0a701b9b..8b3f4e52e 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -1,296 +1,296 @@ /* GCompris - main.cpp * * Copyright (C) 2014 Bruno Coudoin * * Authors: * Bruno Coudoin * * 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include "GComprisPlugin.h" #include "ApplicationInfo.h" #include "ActivityInfoTree.h" #include "DownloadManager.h" bool loadAndroidTranslation(QTranslator &translator, const QString &locale) { QFile file("assets:/share/GCompris/gcompris_" + locale + ".qm"); file.open(QIODevice::ReadOnly); QDataStream in(&file); uchar *data = (uchar*)malloc(file.size()); if(!file.exists()) qDebug() << "file assets:/share/GCompris/gcompris_" << locale << ".qm does not exist"; in.readRawData((char*)data, file.size()); if(!translator.load(data, file.size())) { qDebug() << "Unable to load translation for locale " << locale << ", use en_US by default"; free(data); return false; } // Do not free data, it is still needed by translator return true; } // Return the locale QString loadTranslation(QSettings &config, QTranslator &translator) { QString locale; // Get locale locale = config.value("General/locale", GC_DEFAULT_LOCALE).toString(); if(locale == GC_DEFAULT_LOCALE) locale = QString(QLocale::system().name() + ".UTF-8"); if(locale == "C.UTF-8" || locale == "en_US.UTF-8") return "en_US"; // Load translation // Remove .UTF8 locale.remove(".UTF-8"); #if defined(Q_OS_ANDROID) if(!loadAndroidTranslation(translator, locale)) loadAndroidTranslation(translator, ApplicationInfo::localeShort(locale)); #else #if (defined(Q_OS_LINUX) || defined(Q_OS_UNIX)) // only useful for translators: load from $application_dir/../share/... if exists as it is where kde scripts install translations if(translator.load("gcompris_qt.qm", QString("%1/../share/locale/%2/LC_MESSAGES").arg(QCoreApplication::applicationDirPath(), locale))) { qDebug() << "load translation for locale " << locale << " in " << QString("%1/../share/locale/%2/LC_MESSAGES").arg(QCoreApplication::applicationDirPath(), locale); } else if(translator.load("gcompris_qt.qm", QString("%1/../share/locale/%2/LC_MESSAGES").arg(QCoreApplication::applicationDirPath(), locale.split('_')[0]))) { qDebug() << "load translation for locale " << locale << " in " << QString("%1/../share/locale/%2/LC_MESSAGES").arg(QCoreApplication::applicationDirPath(), locale.split('_')[0]); } else #endif if(!translator.load("gcompris_" + locale, QString("%1/%2/translations").arg(QCoreApplication::applicationDirPath(), GCOMPRIS_DATA_FOLDER))) { qDebug() << "Unable to load translation for locale " << locale << ", use en_US by default"; } #endif return locale; } int main(int argc, char *argv[]) { // Disable it because we already support HDPI display natively qunsetenv("QT_DEVICE_PIXEL_RATIO"); QApplication app(argc, argv); app.setOrganizationName("KDE"); app.setApplicationName(GCOMPRIS_APPLICATION_NAME); app.setOrganizationDomain("kde.org"); app.setApplicationVersion(ApplicationInfo::GCVersion()); #if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) // Set desktop file name, as the built-in (orgDomain + appName) is not // the one we use (because appName is gcompris-qt, not gcompris) QGuiApplication::setDesktopFileName("org.kde.gcompris"); #endif //add a variable to disable default fullscreen on Mac, see below.. #if defined(Q_OS_MAC) // Sandboxing on MacOSX as documented in: - // http://doc.qt.io/qt-5/osx-deployment.html + // https://doc.qt.io/qt-5/osx-deployment.html QDir dir(QGuiApplication::applicationDirPath()); dir.cdUp(); dir.cd("Plugins"); QGuiApplication::setLibraryPaths(QStringList(dir.absolutePath())); #endif // Local scope for config QSettings config(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/gcompris/" + GCOMPRIS_APPLICATION_NAME + ".conf", QSettings::IniFormat); // Load translations QTranslator translator; loadTranslation(config, translator); // Apply translation app.installTranslator(&translator); QCommandLineParser parser; parser.setApplicationDescription("GCompris is an educational software for children 2 to 10"); parser.addHelpOption(); parser.addVersionOption(); QCommandLineOption exportActivitiesAsSQL("export-activities-as-sql", "Export activities as SQL"); parser.addOption(exportActivitiesAsSQL); QCommandLineOption clDefaultCursor(QStringList() << "c" << "cursor", QObject::tr("Run GCompris with the default system cursor.")); parser.addOption(clDefaultCursor); QCommandLineOption clNoCursor(QStringList() << "C" << "nocursor", QObject::tr("Run GCompris without cursor (touch screen mode).")); parser.addOption(clNoCursor); QCommandLineOption clFullscreen(QStringList() << "f" << "fullscreen", QObject::tr("Run GCompris in fullscreen mode.")); parser.addOption(clFullscreen); QCommandLineOption clWindow(QStringList() << "w" << "window", QObject::tr("Run GCompris in window mode.")); parser.addOption(clWindow); QCommandLineOption clSound(QStringList() << "s" << "sound", QObject::tr("Run GCompris with sound enabled.")); parser.addOption(clSound); QCommandLineOption clMute(QStringList() << "m" << "mute", QObject::tr("Run GCompris without sound.")); parser.addOption(clMute); QCommandLineOption clWithoutKioskMode(QStringList() << "disable-kioskmode", QObject::tr("Disable the kiosk mode (default).")); parser.addOption(clWithoutKioskMode); QCommandLineOption clWithKioskMode(QStringList() << "enable-kioskmode", QObject::tr("Enable the kiosk mode.")); parser.addOption(clWithKioskMode); QCommandLineOption clSoftwareRenderer(QStringList() << "software-renderer", QObject::tr("Use software renderer instead of openGL (slower but should run with any graphical card, needs Qt 5.8 minimum).")); parser.addOption(clSoftwareRenderer); QCommandLineOption clOpenGLRenderer(QStringList() << "opengl-renderer", QObject::tr("Use openGL renderer instead of software (faster but crash potentially depending on your graphical card).")); parser.addOption(clOpenGLRenderer); parser.process(app); GComprisPlugin plugin; plugin.registerTypes("GCompris"); ActivityInfoTree::registerResources(); // Tell media players to stop playing, it's GCompris time ApplicationInfo::getInstance()->requestAudioFocus(); // Must be done after ApplicationSettings is constructed because we get an // async callback from the payment system ApplicationSettings::getInstance()->checkPayment(); // Disable default fullscreen launch on Mac as it's a bit broken, window is behind desktop bars #if defined(Q_OS_MAC) bool isFullscreen = false; #else // for other platforms, fullscreen is the default value bool isFullscreen = true; #endif { isFullscreen = config.value("General/fullscreen", isFullscreen).toBool(); // Set the cursor image bool defaultCursor = config.value("General/defaultCursor", false).toBool(); if(!defaultCursor && !parser.isSet(clDefaultCursor)) QGuiApplication::setOverrideCursor( QCursor(QPixmap(":/gcompris/src/core/resource/cursor.svg"), 0, 0)); // Hide the cursor bool noCursor = config.value("General/noCursor", false).toBool(); if(noCursor || parser.isSet(clNoCursor)) QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor)); } // Update execution counter ApplicationSettings::getInstance()->setExeCount(ApplicationSettings::getInstance()->exeCount() + 1); if(parser.isSet(clFullscreen)) { isFullscreen = true; } if(parser.isSet(clWindow)) { isFullscreen = false; } if(parser.isSet(clMute)) { ApplicationSettings::getInstance()->setIsAudioEffectsEnabled(false); ApplicationSettings::getInstance()->setIsAudioVoicesEnabled(false); } if(parser.isSet(clSound)) { ApplicationSettings::getInstance()->setIsAudioEffectsEnabled(true); ApplicationSettings::getInstance()->setIsAudioVoicesEnabled(true); } if(parser.isSet(clWithoutKioskMode)) { ApplicationSettings::getInstance()->setKioskMode(false); } if(parser.isSet(clWithKioskMode)) { ApplicationSettings::getInstance()->setKioskMode(true); } if(parser.isSet(clSoftwareRenderer)) { ApplicationSettings::getInstance()->setRenderer(QStringLiteral("software")); } if(parser.isSet(clOpenGLRenderer)) { ApplicationSettings::getInstance()->setRenderer(QStringLiteral("opengl")); } // Set the renderer used const QString &renderer = ApplicationSettings::getInstance()->renderer(); ApplicationInfo::getInstance()->setUseOpenGL(renderer != QLatin1String("software")); #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0) if(renderer == QLatin1String("software")) QQuickWindow::setSceneGraphBackend(QSGRendererInterface::Software); else if(renderer == QLatin1String("opengl")) QQuickWindow::setSceneGraphBackend(QSGRendererInterface::OpenGL); #endif QQmlApplicationEngine engine(QUrl("qrc:/gcompris/src/core/main.qml")); QObject::connect(&engine, &QQmlApplicationEngine::quit, DownloadManager::getInstance(), &DownloadManager::shutdown); // add import path for shipped qml modules: #ifdef SAILFISHOS engine.addImportPath(QStringLiteral("%1/../share/%2/lib/qml") .arg(QCoreApplication::applicationDirPath()).arg(GCOMPRIS_APPLICATION_NAME)); #else engine.addImportPath(QStringLiteral("%1/../lib/qml") .arg(QCoreApplication::applicationDirPath())); #endif ApplicationInfo::getInstance()->setBox2DInstalled(engine); if(parser.isSet(exportActivitiesAsSQL)) { ActivityInfoTree *menuTree(qobject_cast(ActivityInfoTree::menuTreeProvider(&engine, nullptr))); menuTree->exportAsSQL(); exit(0); } QObject *topLevel = engine.rootObjects().value(0); QQuickWindow *window = qobject_cast(topLevel); if (window == nullptr) { qWarning("Error: Your root item has to be a Window."); return -1; } ApplicationInfo::setWindow(window); window->setIcon(QIcon(QPixmap(QString::fromUtf8(":/gcompris/src/core/resource/gcompris-icon.png")))); if(isFullscreen) { window->showFullScreen(); } else { window->show(); } return app.exec(); }