mirror of
https://github.com/PrismLauncher/PrismLauncher.git
synced 2025-05-24 02:42:15 +02:00
refactor FlameCheckUpdate to remove qEventLoop
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
parent
22e0659a5f
commit
8e33608ddb
@ -102,57 +102,6 @@ QString FlameAPI::getModDescription(int modId)
|
|||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<ModPlatform::IndexedVersion> FlameAPI::getLatestVersions(VersionSearchArgs&& args)
|
|
||||||
{
|
|
||||||
auto versions_url_optional = getVersionsURL(args);
|
|
||||||
if (!versions_url_optional.has_value())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
auto versions_url = versions_url_optional.value();
|
|
||||||
|
|
||||||
QEventLoop loop;
|
|
||||||
|
|
||||||
auto netJob = makeShared<NetJob>(QString("Flame::GetLatestVersion(%1)").arg(args.pack.name), APPLICATION->network());
|
|
||||||
auto response = std::make_shared<QByteArray>();
|
|
||||||
QList<ModPlatform::IndexedVersion> ver;
|
|
||||||
|
|
||||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response));
|
|
||||||
|
|
||||||
QObject::connect(netJob.get(), &NetJob::succeeded, [response, args, &ver] {
|
|
||||||
QJsonParseError parse_error{};
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
|
||||||
if (parse_error.error != QJsonParseError::NoError) {
|
|
||||||
qWarning() << "Error while parsing JSON response from latest mod version at " << parse_error.offset
|
|
||||||
<< " reason: " << parse_error.errorString();
|
|
||||||
qWarning() << *response;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
auto obj = Json::requireObject(doc);
|
|
||||||
auto arr = Json::requireArray(obj, "data");
|
|
||||||
|
|
||||||
for (auto file : arr) {
|
|
||||||
auto file_obj = Json::requireObject(file);
|
|
||||||
ver.append(FlameMod::loadIndexedPackVersion(file_obj));
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Json::JsonException& e) {
|
|
||||||
qCritical() << "Failed to parse response from a version request.";
|
|
||||||
qCritical() << e.what();
|
|
||||||
qDebug() << doc;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
QObject::connect(netJob.get(), &NetJob::finished, &loop, &QEventLoop::quit);
|
|
||||||
|
|
||||||
netJob->start();
|
|
||||||
|
|
||||||
loop.exec();
|
|
||||||
|
|
||||||
return ver;
|
|
||||||
}
|
|
||||||
|
|
||||||
Task::Ptr FlameAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const
|
Task::Ptr FlameAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const
|
||||||
{
|
{
|
||||||
auto netJob = makeShared<NetJob>(QString("Flame::GetProjects"), APPLICATION->network());
|
auto netJob = makeShared<NetJob>(QString("Flame::GetProjects"), APPLICATION->network());
|
||||||
|
@ -15,7 +15,6 @@ class FlameAPI : public NetworkResourceAPI {
|
|||||||
QString getModFileChangelog(int modId, int fileId);
|
QString getModFileChangelog(int modId, int fileId);
|
||||||
QString getModDescription(int modId);
|
QString getModDescription(int modId);
|
||||||
|
|
||||||
QList<ModPlatform::IndexedVersion> getLatestVersions(VersionSearchArgs&& args);
|
|
||||||
std::optional<ModPlatform::IndexedVersion> getLatestVersion(QList<ModPlatform::IndexedVersion> versions,
|
std::optional<ModPlatform::IndexedVersion> getLatestVersion(QList<ModPlatform::IndexedVersion> versions,
|
||||||
QList<ModPlatform::ModLoaderType> instanceLoaders,
|
QList<ModPlatform::ModLoaderType> instanceLoaders,
|
||||||
ModPlatform::ModLoaderTypes fallback);
|
ModPlatform::ModLoaderTypes fallback);
|
||||||
@ -108,12 +107,6 @@ class FlameAPI : public NetworkResourceAPI {
|
|||||||
return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&');
|
return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&');
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
[[nodiscard]] std::optional<QString> getInfoURL(QString const& id) const override
|
|
||||||
{
|
|
||||||
return QString("https://api.curseforge.com/v1/mods/%1").arg(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override
|
[[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override
|
||||||
{
|
{
|
||||||
auto addonId = args.pack.addonId.toString();
|
auto addonId = args.pack.addonId.toString();
|
||||||
@ -129,6 +122,11 @@ class FlameAPI : public NetworkResourceAPI {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[nodiscard]] std::optional<QString> getInfoURL(QString const& id) const override
|
||||||
|
{
|
||||||
|
return QString("https://api.curseforge.com/v1/mods/%1").arg(id);
|
||||||
|
}
|
||||||
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
|
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
|
||||||
{
|
{
|
||||||
auto addonId = args.dependency.addonId.toString();
|
auto addonId = args.dependency.addonId.toString();
|
||||||
|
@ -3,115 +3,31 @@
|
|||||||
#include "FlameAPI.h"
|
#include "FlameAPI.h"
|
||||||
#include "FlameModIndex.h"
|
#include "FlameModIndex.h"
|
||||||
|
|
||||||
#include <MurmurHash2.h>
|
#include <QHash>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
|
|
||||||
|
#include "QObjectPtr.h"
|
||||||
#include "ResourceDownloadTask.h"
|
#include "ResourceDownloadTask.h"
|
||||||
|
|
||||||
#include "minecraft/mod/ModFolderModel.h"
|
|
||||||
#include "minecraft/mod/tasks/GetModDependenciesTask.h"
|
#include "minecraft/mod/tasks/GetModDependenciesTask.h"
|
||||||
|
|
||||||
|
#include "modplatform/ModIndex.h"
|
||||||
#include "net/ApiDownload.h"
|
#include "net/ApiDownload.h"
|
||||||
|
#include "net/NetJob.h"
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
static FlameAPI api;
|
static FlameAPI api;
|
||||||
|
|
||||||
bool FlameCheckUpdate::abort()
|
bool FlameCheckUpdate::abort()
|
||||||
{
|
{
|
||||||
m_was_aborted = true;
|
bool result = false;
|
||||||
if (m_net_job)
|
if (m_task && m_task->canAbort()) {
|
||||||
return m_net_job->abort();
|
result = m_task->abort();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
Task::abort();
|
||||||
ModPlatform::IndexedPack FlameCheckUpdate::getProjectInfo(ModPlatform::IndexedVersion& ver_info)
|
return result;
|
||||||
{
|
|
||||||
ModPlatform::IndexedPack pack;
|
|
||||||
|
|
||||||
QEventLoop loop;
|
|
||||||
|
|
||||||
auto get_project_job = new NetJob("Flame::GetProjectJob", APPLICATION->network());
|
|
||||||
|
|
||||||
auto response = std::make_shared<QByteArray>();
|
|
||||||
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(ver_info.addonId.toString());
|
|
||||||
auto dl = Net::ApiDownload::makeByteArray(url, response);
|
|
||||||
get_project_job->addNetAction(dl);
|
|
||||||
|
|
||||||
QObject::connect(get_project_job, &NetJob::succeeded, [response, &pack]() {
|
|
||||||
QJsonParseError parse_error{};
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
|
||||||
if (parse_error.error != QJsonParseError::NoError) {
|
|
||||||
qWarning() << "Error while parsing JSON response from FlameCheckUpdate at " << parse_error.offset
|
|
||||||
<< " reason: " << parse_error.errorString();
|
|
||||||
qWarning() << *response;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
auto doc_obj = Json::requireObject(doc);
|
|
||||||
auto data_obj = Json::requireObject(doc_obj, "data");
|
|
||||||
FlameMod::loadIndexedPack(pack, data_obj);
|
|
||||||
} catch (Json::JsonException& e) {
|
|
||||||
qWarning() << e.cause();
|
|
||||||
qDebug() << doc;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(get_project_job, &NetJob::failed, this, &FlameCheckUpdate::emitFailed);
|
|
||||||
QObject::connect(get_project_job, &NetJob::finished, [&loop, get_project_job] {
|
|
||||||
get_project_job->deleteLater();
|
|
||||||
loop.quit();
|
|
||||||
});
|
|
||||||
|
|
||||||
get_project_job->start();
|
|
||||||
loop.exec();
|
|
||||||
|
|
||||||
return pack;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModPlatform::IndexedVersion FlameCheckUpdate::getFileInfo(int addonId, int fileId)
|
|
||||||
{
|
|
||||||
ModPlatform::IndexedVersion ver;
|
|
||||||
|
|
||||||
QEventLoop loop;
|
|
||||||
|
|
||||||
auto get_file_info_job = new NetJob("Flame::GetFileInfoJob", APPLICATION->network());
|
|
||||||
|
|
||||||
auto response = std::make_shared<QByteArray>();
|
|
||||||
auto url = QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(QString::number(addonId), QString::number(fileId));
|
|
||||||
auto dl = Net::ApiDownload::makeByteArray(url, response);
|
|
||||||
get_file_info_job->addNetAction(dl);
|
|
||||||
|
|
||||||
QObject::connect(get_file_info_job, &NetJob::succeeded, [response, &ver]() {
|
|
||||||
QJsonParseError parse_error{};
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
|
||||||
if (parse_error.error != QJsonParseError::NoError) {
|
|
||||||
qWarning() << "Error while parsing JSON response from FlameCheckUpdate at " << parse_error.offset
|
|
||||||
<< " reason: " << parse_error.errorString();
|
|
||||||
qWarning() << *response;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
auto doc_obj = Json::requireObject(doc);
|
|
||||||
auto data_obj = Json::requireObject(doc_obj, "data");
|
|
||||||
ver = FlameMod::loadIndexedPackVersion(data_obj);
|
|
||||||
} catch (Json::JsonException& e) {
|
|
||||||
qWarning() << e.cause();
|
|
||||||
qDebug() << doc;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
connect(get_file_info_job, &NetJob::failed, this, &FlameCheckUpdate::emitFailed);
|
|
||||||
QObject::connect(get_file_info_job, &NetJob::finished, [&loop, get_file_info_job] {
|
|
||||||
get_file_info_job->deleteLater();
|
|
||||||
loop.quit();
|
|
||||||
});
|
|
||||||
|
|
||||||
get_file_info_job->start();
|
|
||||||
loop.exec();
|
|
||||||
|
|
||||||
return ver;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check for update:
|
/* Check for update:
|
||||||
@ -123,19 +39,55 @@ void FlameCheckUpdate::executeTask()
|
|||||||
{
|
{
|
||||||
setStatus(tr("Preparing resources for CurseForge..."));
|
setStatus(tr("Preparing resources for CurseForge..."));
|
||||||
|
|
||||||
int i = 0;
|
auto netJob = new NetJob("Get latest versions", APPLICATION->network());
|
||||||
|
connect(netJob, &Task::finished, this, &FlameCheckUpdate::collectBlockedMods);
|
||||||
|
|
||||||
|
connect(netJob, &Task::progress, this, &FlameCheckUpdate::setProgress);
|
||||||
|
connect(netJob, &Task::stepProgress, this, &FlameCheckUpdate::propagateStepProgress);
|
||||||
|
connect(netJob, &Task::details, this, &FlameCheckUpdate::setDetails);
|
||||||
for (auto* resource : m_resources) {
|
for (auto* resource : m_resources) {
|
||||||
setStatus(tr("Getting API response from CurseForge for '%1'...").arg(resource->name()));
|
auto versions_url_optional = api.getVersionsURL({ { resource->metadata()->project_id.toString() }, m_game_versions });
|
||||||
setProgress(i++, m_resources.size());
|
if (!versions_url_optional.has_value())
|
||||||
|
continue;
|
||||||
|
|
||||||
auto latest_vers = api.getLatestVersions({ { resource->metadata()->project_id.toString() }, m_game_versions });
|
auto response = std::make_shared<QByteArray>();
|
||||||
|
auto task = Net::ApiDownload::makeByteArray(versions_url_optional.value(), response);
|
||||||
|
|
||||||
// Check if we were aborted while getting the latest version
|
connect(task.get(), &Task::succeeded, this, [this, resource, response] { getLatestVersionCallback(resource, response); });
|
||||||
if (m_was_aborted) {
|
netJob->addNetAction(task);
|
||||||
aborted();
|
}
|
||||||
|
m_task.reset(netJob);
|
||||||
|
m_task->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, std::shared_ptr<QByteArray> response)
|
||||||
|
{
|
||||||
|
QJsonParseError parse_error{};
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||||
|
if (parse_error.error != QJsonParseError::NoError) {
|
||||||
|
qWarning() << "Error while parsing JSON response from latest mod version at " << parse_error.offset
|
||||||
|
<< " reason: " << parse_error.errorString();
|
||||||
|
qWarning() << *response;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto latest_ver = api.getLatestVersion(latest_vers, m_loaders_list, resource->metadata()->loaders);
|
|
||||||
|
// Fake pack with the necessary info to pass to the download task :)
|
||||||
|
auto pack = std::make_shared<ModPlatform::IndexedPack>();
|
||||||
|
pack->name = resource->name();
|
||||||
|
pack->slug = resource->metadata()->slug;
|
||||||
|
pack->addonId = resource->metadata()->project_id;
|
||||||
|
pack->provider = ModPlatform::ResourceProvider::FLAME;
|
||||||
|
try {
|
||||||
|
auto obj = Json::requireObject(doc);
|
||||||
|
auto arr = Json::requireArray(obj, "data");
|
||||||
|
|
||||||
|
FlameMod::loadIndexedPackVersions(*pack.get(), arr);
|
||||||
|
} catch (Json::JsonException& e) {
|
||||||
|
qCritical() << "Failed to parse response from a version request.";
|
||||||
|
qCritical() << e.what();
|
||||||
|
qDebug() << doc;
|
||||||
|
}
|
||||||
|
auto latest_ver = api.getLatestVersion(pack->versions, m_loaders_list, resource->metadata()->loaders);
|
||||||
|
|
||||||
setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(resource->name()));
|
setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(resource->name()));
|
||||||
|
|
||||||
@ -149,23 +101,14 @@ void FlameCheckUpdate::executeTask()
|
|||||||
reason = tr("No valid version found for this resource. It's probably unavailable for the current game version.");
|
reason = tr("No valid version found for this resource. It's probably unavailable for the current game version.");
|
||||||
|
|
||||||
emit checkFailed(resource, reason);
|
emit checkFailed(resource, reason);
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (latest_ver->downloadUrl.isEmpty() && latest_ver->fileId != resource->metadata()->file_id) {
|
if (latest_ver->downloadUrl.isEmpty() && latest_ver->fileId != resource->metadata()->file_id) {
|
||||||
auto pack = getProjectInfo(latest_ver.value());
|
m_blocked[resource] = latest_ver->fileId.toString();
|
||||||
auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, latest_ver->fileId.toString());
|
return;
|
||||||
emit checkFailed(resource, tr("Resource has a new update available, but is not downloadable using CurseForge."), recover_url);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fake pack with the necessary info to pass to the download task :)
|
|
||||||
auto pack = std::make_shared<ModPlatform::IndexedPack>();
|
|
||||||
pack->name = resource->name();
|
|
||||||
pack->slug = resource->metadata()->slug;
|
|
||||||
pack->addonId = resource->metadata()->project_id;
|
|
||||||
pack->provider = ModPlatform::ResourceProvider::FLAME;
|
|
||||||
if (!latest_ver->hash.isEmpty() &&
|
if (!latest_ver->hash.isEmpty() &&
|
||||||
(resource->metadata()->hash != latest_ver->hash || resource->status() == ResourceStatus::NOT_INSTALLED)) {
|
(resource->metadata()->hash != latest_ver->hash || resource->status() == ResourceStatus::NOT_INSTALLED)) {
|
||||||
auto old_version = resource->metadata()->version_number;
|
auto old_version = resource->metadata()->version_number;
|
||||||
@ -184,5 +127,75 @@ void FlameCheckUpdate::executeTask()
|
|||||||
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, latest_ver.value()));
|
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, latest_ver.value()));
|
||||||
}
|
}
|
||||||
|
|
||||||
emitSucceeded();
|
void FlameCheckUpdate::collectBlockedMods()
|
||||||
|
{
|
||||||
|
QStringList addonIds;
|
||||||
|
QHash<QString, Resource*> quickSearch;
|
||||||
|
for (auto const& resource : m_blocked.keys()) {
|
||||||
|
auto addonId = resource->metadata()->project_id.toString();
|
||||||
|
addonIds.append(addonId);
|
||||||
|
quickSearch[addonId] = resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto response = std::make_shared<QByteArray>();
|
||||||
|
Task::Ptr projTask;
|
||||||
|
|
||||||
|
if (addonIds.isEmpty()) {
|
||||||
|
emitSucceeded();
|
||||||
|
return;
|
||||||
|
} else if (addonIds.size() == 1) {
|
||||||
|
projTask = api.getProject(*addonIds.begin(), response);
|
||||||
|
} else {
|
||||||
|
projTask = api.getProjects(addonIds, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(projTask.get(), &Task::succeeded, this, [this, response, addonIds, quickSearch] {
|
||||||
|
QJsonParseError parse_error{};
|
||||||
|
auto doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||||
|
if (parse_error.error != QJsonParseError::NoError) {
|
||||||
|
qWarning() << "Error while parsing JSON response from Flame projects task at " << parse_error.offset
|
||||||
|
<< " reason: " << parse_error.errorString();
|
||||||
|
qWarning() << *response;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
QJsonArray entries;
|
||||||
|
if (addonIds.size() == 1)
|
||||||
|
entries = { Json::requireObject(Json::requireObject(doc), "data") };
|
||||||
|
else
|
||||||
|
entries = Json::requireArray(Json::requireObject(doc), "data");
|
||||||
|
|
||||||
|
for (auto entry : entries) {
|
||||||
|
auto entry_obj = Json::requireObject(entry);
|
||||||
|
|
||||||
|
auto id = QString::number(Json::requireInteger(entry_obj, "id"));
|
||||||
|
|
||||||
|
auto resource = quickSearch.find(id).value();
|
||||||
|
|
||||||
|
ModPlatform::IndexedPack pack;
|
||||||
|
try {
|
||||||
|
setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(resource->name()));
|
||||||
|
|
||||||
|
FlameMod::loadIndexedPack(pack, entry_obj);
|
||||||
|
auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, m_blocked[resource]);
|
||||||
|
emit checkFailed(resource, tr("Resource has a new update available, but is not downloadable using CurseForge."),
|
||||||
|
recover_url);
|
||||||
|
} catch (Json::JsonException& e) {
|
||||||
|
qDebug() << e.cause();
|
||||||
|
qDebug() << entries;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Json::JsonException& e) {
|
||||||
|
qDebug() << e.cause();
|
||||||
|
qDebug() << doc;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(projTask.get(), &Task::finished, this, &FlameCheckUpdate::emitSucceeded); // do not care much about error
|
||||||
|
connect(projTask.get(), &Task::progress, this, &FlameCheckUpdate::setProgress);
|
||||||
|
connect(projTask.get(), &Task::stepProgress, this, &FlameCheckUpdate::propagateStepProgress);
|
||||||
|
connect(projTask.get(), &Task::details, this, &FlameCheckUpdate::setDetails);
|
||||||
|
m_task.reset(projTask);
|
||||||
|
m_task->start();
|
||||||
}
|
}
|
@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "modplatform/CheckUpdateTask.h"
|
#include "modplatform/CheckUpdateTask.h"
|
||||||
#include "net/NetJob.h"
|
|
||||||
|
|
||||||
class FlameCheckUpdate : public CheckUpdateTask {
|
class FlameCheckUpdate : public CheckUpdateTask {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -19,12 +18,12 @@ class FlameCheckUpdate : public CheckUpdateTask {
|
|||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void executeTask() override;
|
void executeTask() override;
|
||||||
|
private slots:
|
||||||
|
void getLatestVersionCallback(Resource* resource, std::shared_ptr<QByteArray> response);
|
||||||
|
void collectBlockedMods();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info);
|
Task::Ptr m_task = nullptr;
|
||||||
ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId);
|
|
||||||
|
|
||||||
NetJob* m_net_job = nullptr;
|
QHash<Resource*, QString> m_blocked;
|
||||||
|
|
||||||
bool m_was_aborted = false;
|
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user