NOISSUE use new mojang assets locations
This commit is contained in:
parent
2929ca7413
commit
d587720010
@ -13,6 +13,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <QFileInfo>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QDirIterator>
|
#include <QDirIterator>
|
||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
@ -23,7 +24,8 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
#include "AssetsUtils.h"
|
#include "AssetsUtils.h"
|
||||||
#include <FileSystem.h>
|
#include "FileSystem.h"
|
||||||
|
#include "net/MD5EtagDownload.h"
|
||||||
|
|
||||||
namespace AssetsUtils
|
namespace AssetsUtils
|
||||||
{
|
{
|
||||||
@ -32,7 +34,7 @@ namespace AssetsUtils
|
|||||||
* Returns true on success, with index populated
|
* Returns true on success, with index populated
|
||||||
* index is undefined otherwise
|
* index is undefined otherwise
|
||||||
*/
|
*/
|
||||||
bool loadAssetsIndexJson(QString path, AssetsIndex *index)
|
bool loadAssetsIndexJson(QString assetsId, QString path, AssetsIndex *index)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
{
|
{
|
||||||
@ -56,6 +58,7 @@ bool loadAssetsIndexJson(QString path, AssetsIndex *index)
|
|||||||
qCritical() << "Failed to read assets index file" << path;
|
qCritical() << "Failed to read assets index file" << path;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
index->id = assetsId;
|
||||||
|
|
||||||
// Read the file and close it.
|
// Read the file and close it.
|
||||||
QByteArray jsonData = file.readAll();
|
QByteArray jsonData = file.readAll();
|
||||||
@ -143,7 +146,7 @@ QDir reconstructAssets(QString assetsId)
|
|||||||
<< objectDir.path() << virtualDir.path() << virtualRoot.path();
|
<< objectDir.path() << virtualDir.path() << virtualRoot.path();
|
||||||
|
|
||||||
AssetsIndex index;
|
AssetsIndex index;
|
||||||
bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(indexPath, &index);
|
bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, &index);
|
||||||
|
|
||||||
if (loadAssetsIndex && index.isVirtual)
|
if (loadAssetsIndex && index.isVirtual)
|
||||||
{
|
{
|
||||||
@ -182,3 +185,46 @@ QDir reconstructAssets(QString assetsId)
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NetActionPtr AssetObject::getDownloadAction()
|
||||||
|
{
|
||||||
|
QFileInfo objectFile(getLocalPath());
|
||||||
|
if ((!objectFile.isFile()) || (objectFile.size() != size))
|
||||||
|
{
|
||||||
|
auto objectDL = MD5EtagDownload::make(getUrl(), objectFile.filePath());
|
||||||
|
objectDL->m_total_progress = size;
|
||||||
|
return objectDL;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString AssetObject::getLocalPath()
|
||||||
|
{
|
||||||
|
return "assets/objects/" + getRelPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl AssetObject::getUrl()
|
||||||
|
{
|
||||||
|
return QUrl("http://resources.download.minecraft.net/" + getRelPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
QString AssetObject::getRelPath()
|
||||||
|
{
|
||||||
|
return hash.left(2) + "/" + hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetJobPtr AssetsIndex::getDownloadJob()
|
||||||
|
{
|
||||||
|
auto job = new NetJob(QObject::tr("Assets for %1").arg(id));
|
||||||
|
for (auto &object : objects.values())
|
||||||
|
{
|
||||||
|
auto dl = object.getDownloadAction();
|
||||||
|
if(dl)
|
||||||
|
{
|
||||||
|
job->addNetAction(dl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(job->size())
|
||||||
|
return job;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
@ -17,22 +17,32 @@
|
|||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
|
#include "net/NetAction.h"
|
||||||
|
#include "net/NetJob.h"
|
||||||
|
|
||||||
struct AssetObject
|
struct AssetObject
|
||||||
{
|
{
|
||||||
|
QString getRelPath();
|
||||||
|
QUrl getUrl();
|
||||||
|
QString getLocalPath();
|
||||||
|
NetActionPtr getDownloadAction();
|
||||||
|
|
||||||
QString hash;
|
QString hash;
|
||||||
qint64 size;
|
qint64 size;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AssetsIndex
|
struct AssetsIndex
|
||||||
{
|
{
|
||||||
|
NetJobPtr getDownloadJob();
|
||||||
|
|
||||||
|
QString id;
|
||||||
QMap<QString, AssetObject> objects;
|
QMap<QString, AssetObject> objects;
|
||||||
bool isVirtual = false;
|
bool isVirtual = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace AssetsUtils
|
namespace AssetsUtils
|
||||||
{
|
{
|
||||||
bool loadAssetsIndexJson(QString file, AssetsIndex* index);
|
bool loadAssetsIndexJson(QString id, QString file, AssetsIndex* index);
|
||||||
/// Reconstruct a virtual assets folder for the given assets ID and return the folder
|
/// Reconstruct a virtual assets folder for the given assets ID and return the folder
|
||||||
QDir reconstructAssets(QString assetsId);
|
QDir reconstructAssets(QString assetsId);
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ void MinecraftProfile::clear()
|
|||||||
{
|
{
|
||||||
m_minecraftVersion.clear();
|
m_minecraftVersion.clear();
|
||||||
m_minecraftVersionType.clear();
|
m_minecraftVersionType.clear();
|
||||||
m_minecraftAssets.clear();
|
m_minecraftAssets.reset();
|
||||||
m_minecraftArguments.clear();
|
m_minecraftArguments.clear();
|
||||||
m_tweakers.clear();
|
m_tweakers.clear();
|
||||||
m_mainClass.clear();
|
m_mainClass.clear();
|
||||||
@ -420,9 +420,12 @@ void MinecraftProfile::applyMinecraftVersionType(const QString& type)
|
|||||||
applyString(type, this->m_minecraftVersionType);
|
applyString(type, this->m_minecraftVersionType);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MinecraftProfile::applyMinecraftAssets(const QString& assets)
|
void MinecraftProfile::applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets)
|
||||||
{
|
{
|
||||||
applyString(assets, this->m_minecraftAssets);
|
if(assets)
|
||||||
|
{
|
||||||
|
m_minecraftAssets = assets;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MinecraftProfile::applyTraits(const QSet<QString>& traits)
|
void MinecraftProfile::applyTraits(const QSet<QString>& traits)
|
||||||
@ -544,18 +547,11 @@ QString MinecraftProfile::getMinecraftVersionType() const
|
|||||||
return m_minecraftVersionType;
|
return m_minecraftVersionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MinecraftProfile::getMinecraftAssets() const
|
std::shared_ptr<MojangAssetIndexInfo> MinecraftProfile::getMinecraftAssets() const
|
||||||
{
|
{
|
||||||
// HACK: deny april fools. my head hurts enough already.
|
if(!m_minecraftAssets)
|
||||||
QDate now = QDate::currentDate();
|
|
||||||
bool isAprilFools = now.month() == 4 && now.day() == 1;
|
|
||||||
if (m_minecraftAssets.endsWith("_af") && !isAprilFools)
|
|
||||||
{
|
{
|
||||||
return m_minecraftAssets.left(m_minecraftAssets.length() - 3);
|
return std::make_shared<MojangAssetIndexInfo>("legacy");
|
||||||
}
|
|
||||||
if (m_minecraftAssets.isEmpty())
|
|
||||||
{
|
|
||||||
return QLatin1Literal("legacy");
|
|
||||||
}
|
}
|
||||||
return m_minecraftAssets;
|
return m_minecraftAssets;
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include "Library.h"
|
#include "Library.h"
|
||||||
#include "VersionFile.h"
|
#include "VersionFile.h"
|
||||||
#include "JarMod.h"
|
#include "JarMod.h"
|
||||||
|
#include "MojangDownloadInfo.h"
|
||||||
|
|
||||||
#include "multimc_logic_export.h"
|
#include "multimc_logic_export.h"
|
||||||
|
|
||||||
@ -90,19 +91,19 @@ public: /* application of profile variables from patches */
|
|||||||
void applyAppletClass(const QString& appletClass);
|
void applyAppletClass(const QString& appletClass);
|
||||||
void applyMinecraftArguments(const QString& minecraftArguments);
|
void applyMinecraftArguments(const QString& minecraftArguments);
|
||||||
void applyMinecraftVersionType(const QString& type);
|
void applyMinecraftVersionType(const QString& type);
|
||||||
void applyMinecraftAssets(const QString& assets);
|
void applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets);
|
||||||
void applyTraits(const QSet<QString> &traits);
|
void applyTraits(const QSet<QString> &traits);
|
||||||
void applyTweakers(const QStringList &tweakers);
|
void applyTweakers(const QStringList &tweakers);
|
||||||
void applyJarMods(const QList<JarmodPtr> &jarMods);
|
void applyJarMods(const QList<JarmodPtr> &jarMods);
|
||||||
void applyLibrary(LibraryPtr library);
|
void applyLibrary(LibraryPtr library);
|
||||||
void applyProblemSeverity(ProblemSeverity severity);
|
void applyProblemSeverity(ProblemSeverity severity);
|
||||||
|
|
||||||
public: /* getters for proifile variables */
|
public: /* getters for profile variables */
|
||||||
QString getMinecraftVersion() const;
|
QString getMinecraftVersion() const;
|
||||||
QString getMainClass() const;
|
QString getMainClass() const;
|
||||||
QString getAppletClass() const;
|
QString getAppletClass() const;
|
||||||
QString getMinecraftVersionType() const;
|
QString getMinecraftVersionType() const;
|
||||||
QString getMinecraftAssets() const;
|
MojangAssetIndexInfo::Ptr getMinecraftAssets() const;
|
||||||
QString getMinecraftArguments() const;
|
QString getMinecraftArguments() const;
|
||||||
const QSet<QString> & getTraits() const;
|
const QSet<QString> & getTraits() const;
|
||||||
const QStringList & getTweakers() const;
|
const QStringList & getTweakers() const;
|
||||||
@ -136,7 +137,7 @@ private: /* data */
|
|||||||
QString m_minecraftVersionType;
|
QString m_minecraftVersionType;
|
||||||
|
|
||||||
/// Assets type - "legacy" or a version ID
|
/// Assets type - "legacy" or a version ID
|
||||||
QString m_minecraftAssets;
|
MojangAssetIndexInfo::Ptr m_minecraftAssets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* arguments that should be used for launching minecraft
|
* arguments that should be used for launching minecraft
|
||||||
|
@ -157,11 +157,15 @@ void MojangVersionFormat::readVersionProperties(const QJsonObject &in, VersionFi
|
|||||||
}
|
}
|
||||||
Bits::readString(in, "type", out->type);
|
Bits::readString(in, "type", out->type);
|
||||||
|
|
||||||
|
Bits::readString(in, "assets", out->assets);
|
||||||
if(in.contains("assetIndex"))
|
if(in.contains("assetIndex"))
|
||||||
{
|
{
|
||||||
out->mojangAssetIndex = assetIndexFromJson(requireObject(in, "assetIndex"));
|
out->mojangAssetIndex = assetIndexFromJson(requireObject(in, "assetIndex"));
|
||||||
}
|
}
|
||||||
Bits::readString(in, "assets", out->assets);
|
else if (!out->assets.isNull())
|
||||||
|
{
|
||||||
|
out->mojangAssetIndex = std::make_shared<MojangAssetIndexInfo>(out->assets);
|
||||||
|
}
|
||||||
|
|
||||||
out->m_releaseTime = timeFromS3Time(in.value("releaseTime").toString(""));
|
out->m_releaseTime = timeFromS3Time(in.value("releaseTime").toString(""));
|
||||||
out->m_updateTime = timeFromS3Time(in.value("time").toString(""));
|
out->m_updateTime = timeFromS3Time(in.value("time").toString(""));
|
||||||
@ -231,7 +235,6 @@ void MojangVersionFormat::writeVersionProperties(const VersionFile* in, QJsonObj
|
|||||||
writeString(out, "mainClass", in->mainClass);
|
writeString(out, "mainClass", in->mainClass);
|
||||||
writeString(out, "minecraftArguments", in->minecraftArguments);
|
writeString(out, "minecraftArguments", in->minecraftArguments);
|
||||||
writeString(out, "type", in->type);
|
writeString(out, "type", in->type);
|
||||||
writeString(out, "assets", in->assets);
|
|
||||||
if(!in->m_releaseTime.isNull())
|
if(!in->m_releaseTime.isNull())
|
||||||
{
|
{
|
||||||
writeString(out, "releaseTime", timeToS3Time(in->m_releaseTime));
|
writeString(out, "releaseTime", timeToS3Time(in->m_releaseTime));
|
||||||
@ -244,6 +247,7 @@ void MojangVersionFormat::writeVersionProperties(const VersionFile* in, QJsonObj
|
|||||||
{
|
{
|
||||||
out.insert("minimumLauncherVersion", in->minimumLauncherVersion);
|
out.insert("minimumLauncherVersion", in->minimumLauncherVersion);
|
||||||
}
|
}
|
||||||
|
writeString(out, "assets", in->assets);
|
||||||
if(in->mojangAssetIndex && in->mojangAssetIndex->known)
|
if(in->mojangAssetIndex && in->mojangAssetIndex->known)
|
||||||
{
|
{
|
||||||
out.insert("assetIndex", assetIndexToJson(in->mojangAssetIndex));
|
out.insert("assetIndex", assetIndexToJson(in->mojangAssetIndex));
|
||||||
|
@ -40,7 +40,7 @@ void VersionFile::applyTo(MinecraftProfile *profile)
|
|||||||
{
|
{
|
||||||
profile->applyMinecraftVersionType(type);
|
profile->applyMinecraftVersionType(type);
|
||||||
}
|
}
|
||||||
profile->applyMinecraftAssets(assets);
|
profile->applyMinecraftAssets(mojangAssetIndex);
|
||||||
profile->applyTweakers(addTweakers);
|
profile->applyTweakers(addTweakers);
|
||||||
|
|
||||||
profile->applyJarMods(jarMods);
|
profile->applyJarMods(jarMods);
|
||||||
|
@ -125,14 +125,15 @@ QStringList OneSixInstance::processMinecraftArgs(AuthSessionPtr session)
|
|||||||
QString absRootDir = QDir(minecraftRoot()).absolutePath();
|
QString absRootDir = QDir(minecraftRoot()).absolutePath();
|
||||||
token_mapping["game_directory"] = absRootDir;
|
token_mapping["game_directory"] = absRootDir;
|
||||||
QString absAssetsDir = QDir("assets/").absolutePath();
|
QString absAssetsDir = QDir("assets/").absolutePath();
|
||||||
token_mapping["game_assets"] = AssetsUtils::reconstructAssets(m_profile->getMinecraftAssets()).absolutePath();
|
auto assets = m_profile->getMinecraftAssets();
|
||||||
|
token_mapping["game_assets"] = AssetsUtils::reconstructAssets(assets->id).absolutePath();
|
||||||
|
|
||||||
token_mapping["user_properties"] = session->serializeUserProperties();
|
token_mapping["user_properties"] = session->serializeUserProperties();
|
||||||
token_mapping["user_type"] = session->user_type;
|
token_mapping["user_type"] = session->user_type;
|
||||||
|
|
||||||
// 1.7.3+ assets tokens
|
// 1.7.3+ assets tokens
|
||||||
token_mapping["assets_root"] = absAssetsDir;
|
token_mapping["assets_root"] = absAssetsDir;
|
||||||
token_mapping["assets_index_name"] = m_profile->getMinecraftAssets();
|
token_mapping["assets_index_name"] = assets->id;
|
||||||
|
|
||||||
QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts);
|
QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts);
|
||||||
for (int i = 0; i < parts.length(); i++)
|
for (int i = 0; i < parts.length(); i++)
|
||||||
|
@ -88,9 +88,9 @@ void OneSixUpdate::assetIndexStart()
|
|||||||
setStatus(tr("Updating assets index..."));
|
setStatus(tr("Updating assets index..."));
|
||||||
OneSixInstance *inst = (OneSixInstance *)m_inst;
|
OneSixInstance *inst = (OneSixInstance *)m_inst;
|
||||||
auto profile = inst->getMinecraftProfile();
|
auto profile = inst->getMinecraftProfile();
|
||||||
QString assetName = profile->getMinecraftAssets();
|
auto assets = profile->getMinecraftAssets();
|
||||||
QUrl indexUrl = "http://" + URLConstants::AWS_DOWNLOAD_INDEXES + assetName + ".json";
|
QUrl indexUrl = assets->url;
|
||||||
QString localPath = assetName + ".json";
|
QString localPath = assets->id + ".json";
|
||||||
auto job = new NetJob(tr("Asset index for %1").arg(inst->name()));
|
auto job = new NetJob(tr("Asset index for %1").arg(inst->name()));
|
||||||
|
|
||||||
auto metacache = ENV.metacache();
|
auto metacache = ENV.metacache();
|
||||||
@ -114,36 +114,23 @@ void OneSixUpdate::assetIndexFinished()
|
|||||||
|
|
||||||
OneSixInstance *inst = (OneSixInstance *)m_inst;
|
OneSixInstance *inst = (OneSixInstance *)m_inst;
|
||||||
auto profile = inst->getMinecraftProfile();
|
auto profile = inst->getMinecraftProfile();
|
||||||
QString assetName = profile->getMinecraftAssets();
|
auto assets = profile->getMinecraftAssets();
|
||||||
|
|
||||||
QString asset_fname = "assets/indexes/" + assetName + ".json";
|
QString asset_fname = "assets/indexes/" + assets->id + ".json";
|
||||||
if (!AssetsUtils::loadAssetsIndexJson(asset_fname, &index))
|
// FIXME: this looks like a job for a generic validator based on json schema?
|
||||||
|
if (!AssetsUtils::loadAssetsIndexJson(assets->id, asset_fname, &index))
|
||||||
{
|
{
|
||||||
auto metacache = ENV.metacache();
|
auto metacache = ENV.metacache();
|
||||||
auto entry = metacache->resolveEntry("asset_indexes", assetName + ".json");
|
auto entry = metacache->resolveEntry("asset_indexes", assets->id + ".json");
|
||||||
metacache->evictEntry(entry);
|
metacache->evictEntry(entry);
|
||||||
emitFailed(tr("Failed to read the assets index!"));
|
emitFailed(tr("Failed to read the assets index!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<Md5EtagDownloadPtr> dls;
|
auto job = index.getDownloadJob();
|
||||||
for (auto object : index.objects.values())
|
if(job)
|
||||||
{
|
|
||||||
QString objectName = object.hash.left(2) + "/" + object.hash;
|
|
||||||
QFileInfo objectFile("assets/objects/" + objectName);
|
|
||||||
if ((!objectFile.isFile()) || (objectFile.size() != object.size))
|
|
||||||
{
|
|
||||||
auto objectDL = MD5EtagDownload::make(QUrl("http://" + URLConstants::RESOURCE_BASE + objectName), objectFile.filePath());
|
|
||||||
objectDL->m_total_progress = object.size;
|
|
||||||
dls.append(objectDL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (dls.size())
|
|
||||||
{
|
{
|
||||||
setStatus(tr("Getting the assets files from Mojang..."));
|
setStatus(tr("Getting the assets files from Mojang..."));
|
||||||
auto job = new NetJob(tr("Assets for %1").arg(inst->name()));
|
jarlibDownloadJob = job;
|
||||||
for (auto dl : dls)
|
|
||||||
job->addNetAction(dl);
|
|
||||||
jarlibDownloadJob.reset(job);
|
|
||||||
connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(assetsFinished()));
|
connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(assetsFinished()));
|
||||||
connect(jarlibDownloadJob.get(), &NetJob::failed, this, &OneSixUpdate::assetsFailed);
|
connect(jarlibDownloadJob.get(), &NetJob::failed, this, &OneSixUpdate::assetsFailed);
|
||||||
connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64)));
|
connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64)));
|
||||||
|
@ -35,16 +35,15 @@ class MULTIMC_LOGIC_EXPORT NetJob : public Task
|
|||||||
public:
|
public:
|
||||||
explicit NetJob(QString job_name) : Task(), m_job_name(job_name) {}
|
explicit NetJob(QString job_name) : Task(), m_job_name(job_name) {}
|
||||||
virtual ~NetJob() {}
|
virtual ~NetJob() {}
|
||||||
template <typename T> bool addNetAction(T action)
|
bool addNetAction(NetActionPtr action)
|
||||||
{
|
{
|
||||||
NetActionPtr base = std::static_pointer_cast<NetAction>(action);
|
action->m_index_within_job = downloads.size();
|
||||||
base->m_index_within_job = downloads.size();
|
|
||||||
downloads.append(action);
|
downloads.append(action);
|
||||||
part_info pi;
|
part_info pi;
|
||||||
{
|
{
|
||||||
pi.current_progress = base->currentProgress();
|
pi.current_progress = action->currentProgress();
|
||||||
pi.total_progress = base->totalProgress();
|
pi.total_progress = action->totalProgress();
|
||||||
pi.failures = base->numberOfFailures();
|
pi.failures = action->numberOfFailures();
|
||||||
}
|
}
|
||||||
parts_progress.append(pi);
|
parts_progress.append(pi);
|
||||||
total_progress += pi.total_progress;
|
total_progress += pi.total_progress;
|
||||||
@ -52,11 +51,11 @@ public:
|
|||||||
if (isRunning())
|
if (isRunning())
|
||||||
{
|
{
|
||||||
setProgress(current_progress, total_progress);
|
setProgress(current_progress, total_progress);
|
||||||
connect(base.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int)));
|
connect(action.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int)));
|
||||||
connect(base.get(), SIGNAL(failed(int)), SLOT(partFailed(int)));
|
connect(action.get(), SIGNAL(failed(int)), SLOT(partFailed(int)));
|
||||||
connect(base.get(), SIGNAL(netActionProgress(int, qint64, qint64)),
|
connect(action.get(), SIGNAL(netActionProgress(int, qint64, qint64)),
|
||||||
SLOT(partProgress(int, qint64, qint64)));
|
SLOT(partProgress(int, qint64, qint64)));
|
||||||
base->start();
|
action->start();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user