File indexing completed on 2024-04-21 14:43:49
0001 /* GCompris - DownloadManager.h 0002 * 0003 * SPDX-FileCopyrightText: 2014 Holger Kaelberer <holger.k@elberer.de> 0004 * 0005 * Authors: 0006 * Holger Kaelberer <holger.k@elberer.de> 0007 * 0008 * SPDX-License-Identifier: GPL-3.0-or-later 0009 */ 0010 0011 #ifndef DOWNLOADMANAGER_H 0012 #define DOWNLOADMANAGER_H 0013 0014 #include <QCryptographicHash> 0015 #include <QFile> 0016 #include <QNetworkAccessManager> 0017 #include <QNetworkReply> 0018 #include <QMutex> 0019 #include <QString> 0020 #include <QUrl> 0021 #include <QQmlEngine> 0022 #include <QJSEngine> 0023 0024 namespace GCompris { 0025 Q_NAMESPACE 0026 enum ResourceType { 0027 NONE, 0028 VOICES, 0029 WORDSET, 0030 BACKGROUND_MUSIC, 0031 FULL 0032 }; 0033 Q_ENUM_NS(ResourceType); 0034 } 0035 0036 /** 0037 * @class DownloadManager 0038 * @short A singleton class responsible for downloading, updating and 0039 * maintaining remote resources. 0040 * @ingroup infrastructure 0041 * 0042 * DownloadManager is responsible for downloading, updating and registering 0043 * additional resources (in Qt's binary .rcc format) used by GCompris. 0044 * It downloads from a upstream URL (ApplicationSettings.downloadServerUrl) to the 0045 * default local writable location. Downloads are based on common relative 0046 * resource paths, such that a URL of the form 0047 * 0048 * <tt>https://\<server-base\>/\<path/to/my/resource.rcc\></tt> 0049 * 0050 * will be downloaded to a local path 0051 * 0052 * <tt>/\<ApplicationSettings::getInstance()->cachePath()\>/\<path/to/my/resource.rcc\></tt> 0053 * 0054 * and registered with a resource root path 0055 * 0056 * <tt>qrc:/\<path/to/my\>/</tt> 0057 * 0058 * Internally resources are uniquely identified by their <em>relative resource 0059 * path</em> 0060 * 0061 * <tt>\<path/to/my/resource.rcc\></tt> 0062 * (e.g. <tt>data3/voices-ogg/voices-en.rcc></tt>) 0063 * 0064 * Downloading and verification of local files is controlled by MD5 0065 * checksums that are expected to be stored in @c Contents files in each 0066 * upstream directory according to the syntax produced by the @c md5sum 0067 * tool. The checksums are used for checking whether a local rcc file is 0068 * up-to-date (to avoid unnecesary rcc downloads) and to verify that the 0069 * transfer was complete. Only valid rcc files (with correct checksums) 0070 * are registered. 0071 * 0072 * A resource file must reference the "/gcompris/data" prefix with 0073 * \<qresource prefix="/gcompris/data"\>. All data are loaded and referenced 0074 * from this prefix. It is possible to check if a data is registered with 0075 * isDataRegistered. 0076 * 0077 * @sa DownloadDialog, ApplicationSettings.downloadServerUrl, 0078 * ApplicationSettings.isAutomaticDownloadsEnabled, 0079 * ApplicationSettings.cachePath 0080 */ 0081 class DownloadManager : public QObject 0082 { 0083 Q_OBJECT 0084 0085 friend class DownloadManagerTest; 0086 0087 private: 0088 DownloadManager(); // prohibit external creation, we are a singleton! 0089 static DownloadManager *_instance; // singleton instance 0090 0091 /** Container for a full download job */ 0092 typedef struct DownloadJob 0093 { 0094 QUrl url; ///< url of the currently running sub-job 0095 QFile file; ///< target file for the currently running sub-job 0096 QNetworkReply *reply = nullptr; ///< reply object for the currently running sub-job 0097 QList<QUrl> queue; ///< q of remaining sub jobs (QList for convenience) 0098 QMap<QString, QString> contents; ///< checksum map for download verification 0099 QList<QUrl> knownContentsUrls; ///< store already tried upstream Contents files (for infinite loop protection) 0100 0101 GCompris::ResourceType resourceType = GCompris::ResourceType::NONE; 0102 QVariantMap extraInfos = {}; 0103 0104 qint64 bytesReceived = 0; 0105 qint64 bytesTotal = 0; 0106 bool downloadFinished = false; 0107 int downloadResult = 0; 0108 explicit DownloadJob(const GCompris::ResourceType rt, const QVariantMap &extra = {}) : 0109 resourceType(rt), extraInfos(extra) { } 0110 } DownloadJob; 0111 0112 QList<DownloadJob *> activeJobs; ///< track active jobs to allow for parallel downloads 0113 QMutex jobsMutex; ///< not sure if we need to expect concurrent access, better lockit! 0114 0115 static const QString contentsFilename; 0116 static const QString localFolderForData; 0117 static const QCryptographicHash::Algorithm hashMethod = QCryptographicHash::Md5; 0118 0119 QList<QString> registeredResources; 0120 QMutex rcMutex; ///< not sure if we need to expect concurrent access, better lockit! 0121 0122 QNetworkAccessManager accessManager; 0123 QUrl serverUrl; 0124 0125 QMap<QString, QString> resourceTypeToLocalFileName; ///< Key constructed from Contents file, values are the filenames in local 0126 0127 /** 0128 * Get the platform-specific path storing downloaded resources. 0129 * 0130 * Uses QStandardPaths::writableLocation(QStandardPaths::CacheLocation) 0131 * which returns 0132 * - on desktop linux $HOME/.cache/KDE/gcompris-qt/ 0133 * - on other platforms check <https://doc.qt.io/qt-5/qstandardpaths.html> 0134 * 0135 * @return An absolute path. 0136 */ 0137 QString getSystemDownloadPath() const; 0138 0139 /** 0140 * Get all paths that are used for storing resources. 0141 * 0142 * @returns A list of absolute paths used for storing local resources. 0143 * The caller should keep the returned list order when looking for 0144 * resources, for now the lists contains: 0145 * 1. data folder in the application path 0146 * 2. getSystemDownloadPath() 0147 * 3. QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)/gcompris-qt 0148 * which is 0149 * - $HOME/.local/share/gcompris-qt (on linux desktop) 0150 * - /storage/sdcard0/GCompris (on android) 0151 * 4. [QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation)]/gcompris-qt 0152 * which is on GNU/Linux 0153 * - $HOME/.local/share/KDE/gcompris-qt 0154 * - $HOME/.local/share/gcompris-qt 0155 * - $HOME/.local/share/applications/gcompris-qt 0156 * - /usr/local/share/KDE/gcompris-qt 0157 * - /usr/share/KDE/gcompris-qt 0158 */ 0159 const QStringList getSystemResourcePaths() const; 0160 QString getResourceRootForFilename(const QString &filename) const; 0161 QString getFilenameForUrl(const QUrl &url) const; 0162 QUrl getUrlForFilename(const QString &filename) const; 0163 0164 QString getLocalSubFolderForData(const GCompris::ResourceType &rt) const; 0165 /** 0166 * Transforms the passed relative path to an absolute resource path of an 0167 * existing .rcc file, honouring the order of the systemResourcePaths 0168 * 0169 * @returns The absolute path of the .rcc file if it exists, QString() 0170 * otherwise 0171 */ 0172 QString getAbsoluteResourcePath(const QString &path) const; 0173 0174 /** 0175 * Transforms the passed absolute path to a relative resource path if 0176 * possible. 0177 * 0178 * @returns The relative path if it could be generated, QString() otherwise. 0179 */ 0180 QString getRelativeResourcePath(const QString &path) const; 0181 QString tempFilenameForFilename(const QString &filename) const; 0182 QString filenameForTempFilename(const QString &tempFilename) const; 0183 0184 bool checkDownloadRestriction() const; 0185 DownloadJob *getJobByReply(QNetworkReply *r); 0186 DownloadJob *getJobByType_locked(GCompris::ResourceType rt, const QVariantMap &data) const; 0187 0188 /** Start a new download specified by the passed DownloadJob */ 0189 bool download(DownloadJob *job); 0190 0191 /** Parses upstream Contents file and build checksum map. */ 0192 bool parseContents(DownloadJob *job); 0193 0194 /** Compares the checksum of the file in filename with the contents map in 0195 * the passed DownloadJob */ 0196 bool checksumMatches(DownloadJob *job, const QString &filename) const; 0197 0198 bool registerResourceAbsolute(const QString &filename); 0199 0200 /** Unregisters the passed resource 0201 * 0202 * Caller must lock rcMutex. 0203 */ 0204 void unregisterResource_locked(const QString &filename); 0205 bool isRegistered(const QString &filename) const; 0206 0207 #if 0 0208 QStringList getLocalResources(); 0209 #endif 0210 0211 private Q_SLOTS: 0212 0213 /** Handle a finished download. 0214 * 0215 * Called whenever a single download (sub-job) has finished. Responsible 0216 * for iterating over possibly remaining sub-jobs of our DownloadJob. 0217 */ 0218 void finishDownload(); 0219 void finishAllDownloads(int code); 0220 void downloadReadyRead(); 0221 void handleError(QNetworkReply::NetworkError code); 0222 0223 public: 0224 // public interface: 0225 0226 /** 0227 * Possible return codes of a finished download 0228 */ 0229 enum DownloadFinishedCode { 0230 Success = 0, /**< Download finished successfully */ 0231 Error = 1, /**< Download error */ 0232 NoChange = 2 /**< Local files are up-to-date, no download was needed */ 0233 }; 0234 0235 virtual ~DownloadManager(); 0236 0237 /** 0238 * Registers DownloadManager singleton in the QML engine. 0239 */ 0240 static QObject *downloadManagerProvider(QQmlEngine *engine, 0241 QJSEngine *scriptEngine); 0242 static DownloadManager *getInstance(); 0243 0244 /** 0245 * Generates a relative voices resources file-path for a given @p locale. 0246 * 0247 * @param locale Locale name string of the form \<language\>_\<country\>. 0248 * 0249 * @returns A relative voices resource path. 0250 */ 0251 Q_INVOKABLE QString getVoicesResourceForLocale(const QString &locale) const; 0252 0253 Q_INVOKABLE QString getResourcePath(/*const GCompris::ResourceType &*/ int rt, const QVariantMap &data) const; 0254 0255 // @returns A relative background music resource path. 0256 Q_INVOKABLE QString getBackgroundMusicResources() const; 0257 /** 0258 * Checks whether the given relative resource @p path exists locally. 0259 * 0260 * @param path A relative resource path. 0261 */ 0262 Q_INVOKABLE bool haveLocalResource(const QString &path) const; 0263 0264 /** 0265 * For each external dataset type check if there is a Contents file to load 0266 * and load its data if there is one 0267 */ 0268 Q_INVOKABLE void initializeAssets(); 0269 /** 0270 * Whether any download is currently running. 0271 */ 0272 Q_INVOKABLE bool downloadIsRunning() const; 0273 0274 /** 0275 * Whether the passed relative @p data is registered. 0276 * 0277 * For example, if you have a resource file which loads files under the 0278 * 'words' path like 'words/one.png'. You can call this method with 'words/one.png' 0279 * or with 'words'. 0280 * 0281 * @param data Relative resource path (file or directory). 0282 * 0283 * @sa areVoicesRegistered 0284 */ 0285 Q_INVOKABLE bool isDataRegistered(const QString &data) const; 0286 0287 /** 0288 * Whether voices for the locale passed in parameter are registered. 0289 * 0290 * @sa isDataRegistered 0291 */ 0292 Q_INVOKABLE bool areVoicesRegistered(const QString &locale) const; 0293 0294 /** 0295 * Registers a rcc resource file given by a relative resource path 0296 * 0297 * @param filename Relative resource path. 0298 */ 0299 Q_INVOKABLE bool registerResource(const QString &filename); 0300 0301 public Q_SLOTS: 0302 0303 /** Emitted when a download in progressing. 0304 * 0305 * Job is retrieved using sender() method. 0306 * 0307 * @param bytesReceived Downloaded bytes for this job. 0308 * @param bytesTotal Total bytes to download for this job. 0309 */ 0310 void downloadInProgress(qint64 bytesReceived, qint64 bytesTotal); 0311 0312 /** 0313 * Updates a resource @p path from the upstream server unless prohibited 0314 * by the settings and registers it if possible. 0315 * 0316 * If not prohibited by the setting 'isAutomaticDownloadsEnabled' checks 0317 * for an available upstream resource specified by @p path. 0318 * If the corresponding local resource does not exist or is out of date, 0319 * the resource is downloaded and registered. 0320 * 0321 * If automatic downloads/updates are prohibited and a local copy of the 0322 * specified resource exists, it is registered. 0323 * 0324 * @param path A relative resource path. 0325 * 0326 * @returns success 0327 */ 0328 Q_INVOKABLE bool updateResource(/*const GCompris::ResourceType &*/ int rt, const QVariantMap &extra); 0329 0330 /** 0331 * Download a resource specified by the relative resource @p path and 0332 * register it if possible. 0333 * 0334 * If a corresponding local resource exists, an update will only be 0335 * downloaded if it is not up-to-date according to checksum comparison. 0336 * Whenever at the end we have a valid .rcc file it will be registered. 0337 * 0338 * @param path A relative resource path. 0339 * 0340 * @returns success 0341 */ 0342 Q_INVOKABLE bool downloadResource(int rt, const QVariantMap &extra = {}); 0343 0344 /** 0345 * Shutdown DownloadManager instance. 0346 * 0347 * Aborts all currently running downloads. 0348 */ 0349 Q_INVOKABLE void shutdown(); 0350 0351 /** 0352 * Abort all currently running downloads. 0353 */ 0354 Q_INVOKABLE void abortDownloads(); 0355 0356 #if 0 0357 Q_INVOKABLE bool checkForUpdates(); // might be helpful later with other use-cases! 0358 Q_INVOKABLE void registerLocalResources(); 0359 #endif 0360 0361 Q_SIGNALS: 0362 0363 /** Emitted when a download error occurs. 0364 * 0365 * @param code enum NetworkError code. 0366 * @param msg Error string. 0367 */ 0368 void error(int code, const QString &msg); 0369 0370 /** Emitted when a download has started. 0371 * 0372 * @param resource Relative resource path of the started download. 0373 */ 0374 void downloadStarted(const QString &resource); 0375 0376 /** Emitted during a running download. 0377 * 0378 * All values refer to the currently active sub-job. 0379 * 0380 * @param bytesReceived Downloaded bytes. 0381 * @param bytesTotal Total bytes to download. 0382 */ 0383 void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); 0384 0385 /** Emitted when a download has finished. 0386 * 0387 * Also emitted in error cases. 0388 * 0389 * @param code DownloadFinishedCode. FIXME: when using DownloadFinishedCode 0390 * instead of int the code will not be passed to the QML layer, 0391 * use QENUMS? 0392 */ 0393 void downloadFinished(int code); 0394 0395 /** Emitted when all downloads have finished. 0396 * 0397 * If all downloads are successful, code is Success. 0398 * Else if at least one download is in error, code is Error. 0399 */ 0400 void allDownloadsFinished(int code); 0401 0402 /** Emitted when a resource has been registered. 0403 * 0404 * @param resource Relative resource path of the registered resource. 0405 * 0406 * @sa voicesRegistered 0407 */ 0408 void resourceRegistered(const QString &resource); 0409 0410 /** Emitted when voices resources for current locale have been registered. 0411 * 0412 * Convenience signal and special case of resourceRegistered. 0413 * 0414 * @sa resourceRegistered 0415 */ 0416 void voicesRegistered(); 0417 0418 /** Emitted when background music has been registered. 0419 * 0420 * Convenience signal and special case of resourceRegistered. 0421 * 0422 * @sa resourceRegistered 0423 */ 0424 void backgroundMusicRegistered(); 0425 }; 0426 0427 #endif /* DOWNLOADMANAGER_H */