forked from qt-creator/qt-creator
TaskTree: Implement AssetDownloader example
This is an equivalent of the downloader used for the car-configurator example. Change-Id: Ia0ab13ea7d4557d375b44aa2ba06be5a81651251 Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
@@ -13,6 +13,7 @@ Project {
|
||||
"shootout/shootout.qbs",
|
||||
"spinner/spinner.qbs",
|
||||
"subdirfilecontainer/subdirfilecontainer.qbs",
|
||||
"tasking/assetdownloader/assetdownloader.qbs",
|
||||
"tasking/dataexchange/dataexchange.qbs",
|
||||
"tasking/demo/demo.qbs",
|
||||
"tasking/imagescaling/imagescaling.qbs",
|
||||
|
@@ -1,3 +1,4 @@
|
||||
add_subdirectory(assetdownloader)
|
||||
add_subdirectory(dataexchange)
|
||||
add_subdirectory(demo)
|
||||
add_subdirectory(imagescaling)
|
||||
|
8
tests/manual/tasking/assetdownloader/CMakeLists.txt
Normal file
8
tests/manual/tasking/assetdownloader/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
add_qtc_test(tst_tasking_assetdownloader
|
||||
MANUALTEST
|
||||
DEPENDS Tasking Qt::Concurrent Qt::Network Qt::Widgets Qt::GuiPrivate Qt::CorePrivate
|
||||
SOURCES
|
||||
assetdownloader.cpp
|
||||
assetdownloader.h
|
||||
main.cpp
|
||||
)
|
532
tests/manual/tasking/assetdownloader/assetdownloader.cpp
Normal file
532
tests/manual/tasking/assetdownloader/assetdownloader.cpp
Normal file
@@ -0,0 +1,532 @@
|
||||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#include "assetdownloader.h"
|
||||
|
||||
#include <tasking/concurrentcall.h>
|
||||
#include <tasking/networkquery.h>
|
||||
#include <tasking/tasktreerunner.h>
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
||||
#include <QtCore/private/qzipreader_p.h>
|
||||
#else
|
||||
#include <QtGui/private/qzipreader_p.h>
|
||||
#endif
|
||||
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QStandardPaths>
|
||||
#include <QtCore/QTemporaryDir>
|
||||
#include <QtCore/QTemporaryFile>
|
||||
|
||||
using namespace Tasking;
|
||||
|
||||
struct DownloadableAssets
|
||||
{
|
||||
QUrl remoteUrl;
|
||||
QList<QUrl> allAssets;
|
||||
QList<QUrl> assetsToDownload;
|
||||
};
|
||||
|
||||
class AssetDownloaderPrivate
|
||||
{
|
||||
public:
|
||||
AssetDownloaderPrivate(AssetDownloader *q) : m_q(q) {}
|
||||
|
||||
AssetDownloader *m_q = nullptr;
|
||||
TaskTreeRunner m_taskTreeRunner;
|
||||
QString m_lastProgressText;
|
||||
|
||||
QNetworkAccessManager *m_manager = nullptr;
|
||||
QString m_jsonFileName;
|
||||
QString m_zipFileName;
|
||||
QDir m_preferredLocalDownloadDir =
|
||||
QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
|
||||
QUrl m_offlineAssetsFilePath;
|
||||
QUrl m_downloadBase;
|
||||
|
||||
void setProgress(int progressValue, int progressMaximum, const QString &progressText)
|
||||
{
|
||||
m_lastProgressText = progressText;
|
||||
emit m_q->progressChanged(progressValue, progressMaximum, progressText);
|
||||
}
|
||||
void updateProgress(int progressValue, int progressMaximum)
|
||||
{
|
||||
setProgress(progressValue, progressMaximum, m_lastProgressText);
|
||||
}
|
||||
void clearProgress(const QString &progressText)
|
||||
{
|
||||
setProgress(0, 0, progressText);
|
||||
}
|
||||
|
||||
void setupDownload(NetworkQuery *query, const QString &progressText)
|
||||
{
|
||||
query->setNetworkAccessManager(m_manager);
|
||||
clearProgress(progressText);
|
||||
QObject::connect(query, &NetworkQuery::started, query, [this, query] {
|
||||
QNetworkReply *reply = query->reply();
|
||||
QObject::connect(reply, &QNetworkReply::downloadProgress,
|
||||
query, [this](qint64 bytesReceived, qint64 totalBytes) {
|
||||
updateProgress((totalBytes > 0) ? 100.0 * bytesReceived / totalBytes : 0, 100);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
static bool isWritableDir(const QDir &dir)
|
||||
{
|
||||
if (dir.exists()) {
|
||||
QTemporaryFile file(dir.filePath(QString::fromLatin1("tmp")));
|
||||
return file.open();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool sameFileContent(const QFileInfo &first, const QFileInfo &second)
|
||||
{
|
||||
if (first.exists() ^ second.exists())
|
||||
return false;
|
||||
|
||||
if (first.size() != second.size())
|
||||
return false;
|
||||
|
||||
QFile firstFile(first.absoluteFilePath());
|
||||
QFile secondFile(second.absoluteFilePath());
|
||||
|
||||
if (firstFile.open(QFile::ReadOnly) && secondFile.open(QFile::ReadOnly)) {
|
||||
char char1;
|
||||
char char2;
|
||||
int readBytes1 = 0;
|
||||
int readBytes2 = 0;
|
||||
while (!firstFile.atEnd()) {
|
||||
readBytes1 = firstFile.read(&char1, 1);
|
||||
readBytes2 = secondFile.read(&char2, 1);
|
||||
if (readBytes1 != readBytes2 || readBytes1 != 1)
|
||||
return false;
|
||||
if (char1 != char2)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool createDirectory(const QDir &dir)
|
||||
{
|
||||
if (dir.exists())
|
||||
return true;
|
||||
|
||||
if (!createDirectory(dir.absoluteFilePath(QString::fromUtf8(".."))))
|
||||
return false;
|
||||
|
||||
return dir.mkpath(QString::fromUtf8("."));
|
||||
}
|
||||
|
||||
static bool canBeALocalBaseDir(const QDir &dir)
|
||||
{
|
||||
if (dir.exists())
|
||||
return !dir.isEmpty() || isWritableDir(dir);
|
||||
return createDirectory(dir) && isWritableDir(dir);
|
||||
}
|
||||
|
||||
static QString pathFromUrl(const QUrl &url)
|
||||
{
|
||||
return url.isLocalFile() ? url.toLocalFile() : url.toString();
|
||||
}
|
||||
|
||||
static QList<QUrl> filterDownloadableAssets(const QList<QUrl> &assetFiles, const QDir &expectedDir)
|
||||
{
|
||||
QList<QUrl> downloadList;
|
||||
std::copy_if(assetFiles.begin(), assetFiles.end(), std::back_inserter(downloadList),
|
||||
[&](const QUrl &assetPath) {
|
||||
return !QFileInfo::exists(expectedDir.absoluteFilePath(assetPath.toString()));
|
||||
});
|
||||
return downloadList;
|
||||
}
|
||||
|
||||
static bool allAssetsPresent(const QList<QUrl> &assetFiles, const QDir &expectedDir)
|
||||
{
|
||||
return std::all_of(assetFiles.begin(), assetFiles.end(), [&](const QUrl &assetPath) {
|
||||
return QFileInfo::exists(expectedDir.absoluteFilePath(assetPath.toString()));
|
||||
});
|
||||
}
|
||||
|
||||
AssetDownloader::AssetDownloader(QObject *parent)
|
||||
: QObject(parent)
|
||||
, d(new AssetDownloaderPrivate(this))
|
||||
{}
|
||||
|
||||
AssetDownloader::~AssetDownloader() = default;
|
||||
|
||||
void AssetDownloader::setNetworkAccessManager(QNetworkAccessManager *manager)
|
||||
{
|
||||
d->m_manager = manager;
|
||||
}
|
||||
|
||||
QUrl AssetDownloader::downloadBase() const
|
||||
{
|
||||
return d->m_downloadBase;
|
||||
}
|
||||
|
||||
void AssetDownloader::setDownloadBase(const QUrl &downloadBase)
|
||||
{
|
||||
if (d->m_downloadBase != downloadBase) {
|
||||
d->m_downloadBase = downloadBase;
|
||||
emit downloadBaseChanged(d->m_downloadBase);
|
||||
}
|
||||
}
|
||||
|
||||
QUrl AssetDownloader::preferredLocalDownloadDir() const
|
||||
{
|
||||
return QUrl::fromLocalFile(d->m_preferredLocalDownloadDir.absolutePath());
|
||||
}
|
||||
|
||||
void AssetDownloader::setPreferredLocalDownloadDir(const QUrl &localDir)
|
||||
{
|
||||
if (!localDir.isLocalFile())
|
||||
qWarning() << "preferredLocalDownloadDir Should be a local directory";
|
||||
|
||||
const QString path = pathFromUrl(localDir);
|
||||
if (d->m_preferredLocalDownloadDir != path) {
|
||||
d->m_preferredLocalDownloadDir.setPath(path);
|
||||
emit preferredLocalDownloadDirChanged(preferredLocalDownloadDir());
|
||||
}
|
||||
}
|
||||
|
||||
QUrl AssetDownloader::offlineAssetsFilePath() const
|
||||
{
|
||||
return d->m_offlineAssetsFilePath;
|
||||
}
|
||||
|
||||
void AssetDownloader::setOfflineAssetsFilePath(const QUrl &offlineAssetsFilePath)
|
||||
{
|
||||
if (d->m_offlineAssetsFilePath != offlineAssetsFilePath) {
|
||||
d->m_offlineAssetsFilePath = offlineAssetsFilePath;
|
||||
emit offlineAssetsFilePathChanged(d->m_offlineAssetsFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
QString AssetDownloader::jsonFileName() const
|
||||
{
|
||||
return d->m_jsonFileName;
|
||||
}
|
||||
|
||||
void AssetDownloader::setJsonFileName(const QString &jsonFileName)
|
||||
{
|
||||
if (d->m_jsonFileName != jsonFileName) {
|
||||
d->m_jsonFileName = jsonFileName;
|
||||
emit jsonFileNameChanged(d->m_jsonFileName);
|
||||
}
|
||||
}
|
||||
|
||||
QString AssetDownloader::zipFileName() const
|
||||
{
|
||||
return d->m_zipFileName;
|
||||
}
|
||||
|
||||
void AssetDownloader::setZipFileName(const QString &zipFileName)
|
||||
{
|
||||
if (d->m_zipFileName != zipFileName) {
|
||||
d->m_zipFileName = zipFileName;
|
||||
emit zipFileNameChanged(d->m_zipFileName);
|
||||
}
|
||||
}
|
||||
|
||||
static QDir baseLocalDir(const QDir &preferredLocalDir)
|
||||
{
|
||||
if (canBeALocalBaseDir(preferredLocalDir))
|
||||
return preferredLocalDir;
|
||||
|
||||
qWarning().noquote() << "AssetDownloader: Cannot set \"" << preferredLocalDir
|
||||
<< "\" as a local download directory!";
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
|
||||
}
|
||||
|
||||
static void precheckLocalFile(const QUrl &url)
|
||||
{
|
||||
if (url.isEmpty())
|
||||
return;
|
||||
QFile file(pathFromUrl(url));
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
qWarning() << "Cannot open local file" << url;
|
||||
}
|
||||
|
||||
static void readAssetsFileContent(QPromise<DownloadableAssets> &promise, const QByteArray &content)
|
||||
{
|
||||
const QJsonObject json = QJsonDocument::fromJson(content).object();
|
||||
const QJsonArray assetsArray = json[u"assets"].toArray();
|
||||
DownloadableAssets result;
|
||||
result.remoteUrl = json[u"url"].toString();
|
||||
for (const QJsonValue &asset : assetsArray) {
|
||||
if (promise.isCanceled())
|
||||
return;
|
||||
result.allAssets.append(asset.toString());
|
||||
}
|
||||
result.assetsToDownload = result.allAssets;
|
||||
|
||||
if (result.allAssets.isEmpty() || result.remoteUrl.isEmpty())
|
||||
promise.future().cancel();
|
||||
else
|
||||
promise.addResult(result);
|
||||
}
|
||||
|
||||
static void unzip(QPromise<void> &promise, const QByteArray &content, const QDir &directory,
|
||||
const QString &fileName)
|
||||
{
|
||||
const QString zipFilePath = directory.absoluteFilePath(fileName);
|
||||
QFile zipFile(zipFilePath);
|
||||
if (!zipFile.open(QIODevice::WriteOnly)) {
|
||||
promise.future().cancel();
|
||||
return;
|
||||
}
|
||||
zipFile.write(content);
|
||||
zipFile.close();
|
||||
|
||||
if (promise.isCanceled())
|
||||
return;
|
||||
|
||||
QZipReader reader(zipFilePath);
|
||||
const bool extracted = reader.extractAll(directory.absolutePath());
|
||||
reader.close();
|
||||
if (extracted)
|
||||
QFile::remove(zipFilePath);
|
||||
else
|
||||
promise.future().cancel();
|
||||
}
|
||||
|
||||
static void writeAsset(QPromise<void> &promise, const QByteArray &content, const QString &filePath)
|
||||
{
|
||||
const QFileInfo fileInfo(filePath);
|
||||
QFile file(fileInfo.absoluteFilePath());
|
||||
if (!createDirectory(fileInfo.dir()) || !file.open(QFile::WriteOnly)) {
|
||||
promise.future().cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (promise.isCanceled())
|
||||
return;
|
||||
|
||||
file.write(content);
|
||||
file.close();
|
||||
}
|
||||
|
||||
static void copyAndCheck(QPromise<void> &promise, const QString &sourcePath, const QString &destPath)
|
||||
{
|
||||
QFile sourceFile(sourcePath);
|
||||
QFile destFile(destPath);
|
||||
const QFileInfo sourceFileInfo(sourceFile.fileName());
|
||||
const QFileInfo destFileInfo(destFile.fileName());
|
||||
|
||||
if (destFile.exists() && !destFile.remove()) {
|
||||
promise.future().cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!createDirectory(destFileInfo.absolutePath())) {
|
||||
promise.future().cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (promise.isCanceled())
|
||||
return;
|
||||
|
||||
if (!sourceFile.copy(destFile.fileName()) && !sameFileContent(sourceFileInfo, destFileInfo))
|
||||
promise.future().cancel();
|
||||
}
|
||||
|
||||
void AssetDownloader::start()
|
||||
{
|
||||
if (d->m_taskTreeRunner.isRunning())
|
||||
return;
|
||||
|
||||
struct InternalStorage
|
||||
{
|
||||
QTemporaryDir temporaryDir;
|
||||
QDir tempDir;
|
||||
QDir baseLocalDir;
|
||||
QByteArray jsonContent;
|
||||
DownloadableAssets assets;
|
||||
QByteArray zipContent;
|
||||
int doneCount = 0;
|
||||
};
|
||||
|
||||
const Storage<InternalStorage> storage;
|
||||
|
||||
const auto onSetup = [this, storage] {
|
||||
if (!storage->temporaryDir.isValid()) {
|
||||
qWarning() << "Cannot create a temporary directory.";
|
||||
return SetupResult::StopWithError;
|
||||
}
|
||||
storage->tempDir = storage->temporaryDir.path();
|
||||
storage->baseLocalDir = baseLocalDir(d->m_preferredLocalDownloadDir);
|
||||
precheckLocalFile(d->m_offlineAssetsFilePath);
|
||||
return SetupResult::Continue;
|
||||
};
|
||||
|
||||
const auto onJsonDownloadSetup = [this](NetworkQuery &query) {
|
||||
query.setRequest(QNetworkRequest(d->m_downloadBase.resolved(d->m_jsonFileName)));
|
||||
d->setupDownload(&query, tr("Downloading JSON file..."));
|
||||
};
|
||||
const auto onJsonDownloadDone = [this, storage](const NetworkQuery &query, DoneWith result) {
|
||||
if (result == DoneWith::Success) {
|
||||
storage->jsonContent = query.reply()->readAll();
|
||||
return DoneResult::Success;
|
||||
}
|
||||
qWarning() << "Cannot download" << d->m_downloadBase.resolved(d->m_jsonFileName)
|
||||
<< query.reply()->errorString();
|
||||
if (d->m_offlineAssetsFilePath.isEmpty()) {
|
||||
qWarning() << "Also there is no local file as a replacement";
|
||||
return DoneResult::Error;
|
||||
}
|
||||
|
||||
QFile file(pathFromUrl(d->m_offlineAssetsFilePath));
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qWarning() << "Also failed to open" << d->m_offlineAssetsFilePath;
|
||||
return DoneResult::Error;
|
||||
}
|
||||
|
||||
storage->jsonContent = file.readAll();
|
||||
return DoneResult::Success;
|
||||
};
|
||||
|
||||
const auto onReadAssetsFileSetup = [storage](ConcurrentCall<DownloadableAssets> &async) {
|
||||
async.setConcurrentCallData(readAssetsFileContent, storage->jsonContent);
|
||||
};
|
||||
const auto onReadAssetsFileDone = [storage](const ConcurrentCall<DownloadableAssets> &async) {
|
||||
storage->assets = async.result();
|
||||
};
|
||||
|
||||
const auto onSkipIfAllAssetsPresent = [storage] {
|
||||
return allAssetsPresent(storage->assets.allAssets, storage->baseLocalDir)
|
||||
? SetupResult::StopWithSuccess : SetupResult::Continue;
|
||||
};
|
||||
|
||||
const auto onZipDownloadSetup = [this, storage](NetworkQuery &query) {
|
||||
if (d->m_zipFileName.isEmpty())
|
||||
return SetupResult::StopWithSuccess;
|
||||
|
||||
query.setRequest(QNetworkRequest(d->m_downloadBase.resolved(d->m_zipFileName)));
|
||||
d->setupDownload(&query, tr("Downloading zip file..."));
|
||||
return SetupResult::Continue;
|
||||
};
|
||||
const auto onZipDownloadDone = [storage](const NetworkQuery &query, DoneWith result) {
|
||||
if (result == DoneWith::Success)
|
||||
storage->zipContent = query.reply()->readAll();
|
||||
return DoneResult::Success; // Ignore zip download failure
|
||||
};
|
||||
|
||||
const auto onUnzipSetup = [this, storage](ConcurrentCall<void> &async) {
|
||||
if (storage->zipContent.isEmpty())
|
||||
return SetupResult::StopWithSuccess;
|
||||
|
||||
async.setConcurrentCallData(unzip, storage->zipContent, storage->tempDir, d->m_zipFileName);
|
||||
d->clearProgress(tr("Unzipping..."));
|
||||
return SetupResult::Continue;
|
||||
};
|
||||
const auto onUnzipDone = [storage](DoneWith result) {
|
||||
if (result == DoneWith::Success) {
|
||||
// Avoid downloading assets that are present in unzipped tree
|
||||
InternalStorage &storageData = *storage;
|
||||
storageData.assets.assetsToDownload =
|
||||
filterDownloadableAssets(storageData.assets.allAssets, storageData.tempDir);
|
||||
} else {
|
||||
qWarning() << "ZipFile failed";
|
||||
}
|
||||
return DoneResult::Success; // Ignore unzip failure
|
||||
};
|
||||
|
||||
const LoopUntil downloadIterator([storage](int iteration) {
|
||||
return iteration < storage->assets.assetsToDownload.count();
|
||||
});
|
||||
|
||||
const Storage<QByteArray> assetStorage;
|
||||
|
||||
const auto onAssetsDownloadGroupSetup = [this, storage] {
|
||||
d->setProgress(0, storage->assets.assetsToDownload.size(), tr("Downloading assets..."));
|
||||
};
|
||||
|
||||
const auto onAssetDownloadSetup = [this, storage, downloadIterator](NetworkQuery &query) {
|
||||
query.setNetworkAccessManager(d->m_manager);
|
||||
query.setRequest(QNetworkRequest(storage->assets.remoteUrl.resolved(
|
||||
storage->assets.assetsToDownload.at(downloadIterator.iteration()))));
|
||||
};
|
||||
const auto onAssetDownloadDone = [assetStorage](const NetworkQuery &query, DoneWith result) {
|
||||
if (result == DoneWith::Success)
|
||||
*assetStorage = query.reply()->readAll();
|
||||
};
|
||||
|
||||
const auto onAssetWriteSetup = [storage, downloadIterator, assetStorage](
|
||||
ConcurrentCall<void> &async) {
|
||||
const QString filePath = storage->tempDir.absoluteFilePath(
|
||||
storage->assets.assetsToDownload.at(downloadIterator.iteration()).toString());
|
||||
async.setConcurrentCallData(writeAsset, *assetStorage, filePath);
|
||||
};
|
||||
const auto onAssetWriteDone = [this, storage](DoneWith result) {
|
||||
if (result != DoneWith::Success) {
|
||||
qWarning() << "Asset write failed";
|
||||
return;
|
||||
}
|
||||
InternalStorage &storageData = *storage;
|
||||
++storageData.doneCount;
|
||||
d->updateProgress(storageData.doneCount, storageData.assets.assetsToDownload.size());
|
||||
};
|
||||
|
||||
const LoopUntil copyIterator([storage](int iteration) {
|
||||
return iteration < storage->assets.allAssets.count();
|
||||
});
|
||||
|
||||
const auto onAssetsCopyGroupSetup = [this, storage] {
|
||||
storage->doneCount = 0;
|
||||
d->setProgress(0, storage->assets.allAssets.size(), tr("Copying assets..."));
|
||||
};
|
||||
|
||||
const auto onAssetCopySetup = [storage, copyIterator](ConcurrentCall<void> &async) {
|
||||
const QString fileName = storage->assets.allAssets.at(copyIterator.iteration()).toString();
|
||||
const QString sourcePath = storage->tempDir.absoluteFilePath(fileName);
|
||||
const QString destPath = storage->baseLocalDir.absoluteFilePath(fileName);
|
||||
async.setConcurrentCallData(copyAndCheck, sourcePath, destPath);
|
||||
};
|
||||
const auto onAssetCopyDone = [this, storage] (DoneWith result) {
|
||||
if (result != DoneWith::Success) {
|
||||
qWarning() << "Asset copy failed";
|
||||
return;
|
||||
}
|
||||
InternalStorage &storageData = *storage;
|
||||
++storageData.doneCount;
|
||||
d->updateProgress(storageData.doneCount, storageData.assets.allAssets.size());
|
||||
};
|
||||
|
||||
const Group root {
|
||||
storage,
|
||||
onGroupSetup(onSetup),
|
||||
NetworkQueryTask(onJsonDownloadSetup, onJsonDownloadDone),
|
||||
ConcurrentCallTask<DownloadableAssets>(onReadAssetsFileSetup, onReadAssetsFileDone, CallDoneIf::Success),
|
||||
Group {
|
||||
onGroupSetup(onSkipIfAllAssetsPresent),
|
||||
NetworkQueryTask(onZipDownloadSetup, onZipDownloadDone),
|
||||
ConcurrentCallTask<void>(onUnzipSetup, onUnzipDone),
|
||||
Group {
|
||||
parallelIdealThreadCountLimit,
|
||||
downloadIterator,
|
||||
onGroupSetup(onAssetsDownloadGroupSetup),
|
||||
Group {
|
||||
assetStorage,
|
||||
NetworkQueryTask(onAssetDownloadSetup, onAssetDownloadDone),
|
||||
ConcurrentCallTask<void>(onAssetWriteSetup, onAssetWriteDone)
|
||||
}
|
||||
},
|
||||
Group {
|
||||
parallelIdealThreadCountLimit,
|
||||
copyIterator,
|
||||
onGroupSetup(onAssetsCopyGroupSetup),
|
||||
ConcurrentCallTask<void>(onAssetCopySetup, onAssetCopyDone)
|
||||
}
|
||||
}
|
||||
};
|
||||
d->m_taskTreeRunner.start(root,[this](TaskTree *) { emit started(); },
|
||||
[this](DoneWith result) { emit finished(result == DoneWith::Success); });
|
||||
}
|
102
tests/manual/tasking/assetdownloader/assetdownloader.h
Normal file
102
tests/manual/tasking/assetdownloader/assetdownloader.h
Normal file
@@ -0,0 +1,102 @@
|
||||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#ifndef ASSETDOWNLOADER_H
|
||||
#define ASSETDOWNLOADER_H
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
#include <memory>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QNetworkAccessManager;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class AssetDownloaderPrivate;
|
||||
|
||||
class AssetDownloader : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(
|
||||
QUrl downloadBase
|
||||
READ downloadBase
|
||||
WRITE setDownloadBase
|
||||
NOTIFY downloadBaseChanged)
|
||||
|
||||
Q_PROPERTY(
|
||||
QUrl preferredLocalDownloadDir
|
||||
READ preferredLocalDownloadDir
|
||||
WRITE setPreferredLocalDownloadDir
|
||||
NOTIFY preferredLocalDownloadDirChanged)
|
||||
|
||||
Q_PROPERTY(
|
||||
QUrl offlineAssetsFilePath
|
||||
READ offlineAssetsFilePath
|
||||
WRITE setOfflineAssetsFilePath
|
||||
NOTIFY offlineAssetsFilePathChanged)
|
||||
|
||||
Q_PROPERTY(
|
||||
QString jsonFileName
|
||||
READ jsonFileName
|
||||
WRITE setJsonFileName
|
||||
NOTIFY jsonFileNameChanged)
|
||||
|
||||
Q_PROPERTY(
|
||||
QString zipFileName
|
||||
READ zipFileName
|
||||
WRITE setZipFileName
|
||||
NOTIFY zipFileNameChanged)
|
||||
|
||||
public:
|
||||
AssetDownloader(QObject *parent = nullptr);
|
||||
~AssetDownloader();
|
||||
|
||||
void setNetworkAccessManager(QNetworkAccessManager *manager);
|
||||
|
||||
QUrl downloadBase() const;
|
||||
void setDownloadBase(const QUrl &downloadBase);
|
||||
|
||||
QUrl preferredLocalDownloadDir() const;
|
||||
void setPreferredLocalDownloadDir(const QUrl &localDir);
|
||||
|
||||
QUrl offlineAssetsFilePath() const;
|
||||
void setOfflineAssetsFilePath(const QUrl &offlineAssetsFilePath);
|
||||
|
||||
QString jsonFileName() const;
|
||||
void setJsonFileName(const QString &jsonFileName);
|
||||
|
||||
QString zipFileName() const;
|
||||
void setZipFileName(const QString &zipFileName);
|
||||
|
||||
public Q_SLOTS:
|
||||
void start();
|
||||
|
||||
Q_SIGNALS:
|
||||
void started();
|
||||
void finished(bool success);
|
||||
void progressChanged(int progressValue, int progressMaximum, const QString &progressText);
|
||||
|
||||
void downloadBaseChanged(const QUrl &);
|
||||
void preferredLocalDownloadDirChanged(const QUrl &url);
|
||||
void offlineAssetsFilePathChanged(const QUrl &);
|
||||
void jsonFileNameChanged(const QString &);
|
||||
void zipFileNameChanged(const QString &);
|
||||
|
||||
private:
|
||||
std::unique_ptr<AssetDownloaderPrivate> d;
|
||||
};
|
||||
|
||||
#endif // ASSETDOWNLOADER_H
|
13
tests/manual/tasking/assetdownloader/assetdownloader.qbs
Normal file
13
tests/manual/tasking/assetdownloader/assetdownloader.qbs
Normal file
@@ -0,0 +1,13 @@
|
||||
QtcManualTest {
|
||||
name: "Tasking assetdownloader"
|
||||
type: ["application"]
|
||||
|
||||
Depends { name: "Qt"; submodules: ["concurrent", "network", "widgets", "core-private", "gui-private"] }
|
||||
Depends { name: "Tasking" }
|
||||
|
||||
files: [
|
||||
"assetdownloader.cpp",
|
||||
"assetdownloader.h",
|
||||
"main.cpp",
|
||||
]
|
||||
}
|
50
tests/manual/tasking/assetdownloader/main.cpp
Normal file
50
tests/manual/tasking/assetdownloader/main.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#include "assetdownloader.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QProgressDialog>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
app.setOrganizationName("QtProject");
|
||||
app.setApplicationName("Asset Downloader");
|
||||
|
||||
QProgressDialog progress;
|
||||
progress.setAutoClose(false);
|
||||
progress.setRange(0, 0);
|
||||
QObject::connect(&progress, &QProgressDialog::canceled, &app, &QApplication::quit);
|
||||
|
||||
QNetworkAccessManager manager;
|
||||
|
||||
AssetDownloader downloader;
|
||||
downloader.setNetworkAccessManager(&manager);
|
||||
downloader.setJsonFileName("car-configurator-assets-v1.json");
|
||||
downloader.setZipFileName("car-configurator-assets-v1.zip");
|
||||
downloader.setDownloadBase(QUrl("https://download.qt.io/learning/examples/"));
|
||||
|
||||
QObject::connect(&downloader, &AssetDownloader::started,
|
||||
&progress, &QProgressDialog::show);
|
||||
QObject::connect(&downloader, &AssetDownloader::progressChanged, &progress,
|
||||
[&](int progressValue, int progressMaximum, const QString &progressText) {
|
||||
progress.setLabelText(progressText);
|
||||
progress.setMaximum(progressMaximum);
|
||||
progress.setValue(progressValue);
|
||||
});
|
||||
QObject::connect(&downloader, &AssetDownloader::finished, &progress, [&](bool success) {
|
||||
progress.reset();
|
||||
progress.hide();
|
||||
if (success)
|
||||
QMessageBox::information(nullptr, "Asset Downloader", "Download Finished Successfully.");
|
||||
else
|
||||
QMessageBox::warning(nullptr, "Asset Downloader", "Download Finished with an Error.");
|
||||
});
|
||||
|
||||
downloader.start();
|
||||
|
||||
return app.exec();
|
||||
}
|
Reference in New Issue
Block a user