fix: when given a remost resource, download and identify it before import.

Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
This commit is contained in:
Rachel Powers 2023-05-15 16:34:33 -07:00
parent b1ffc8ddab
commit fc656b6927
9 changed files with 159 additions and 38 deletions

View File

@ -44,7 +44,10 @@ static const QMap<PackedResourceType, QString> s_packed_type_names = {
namespace ResourceUtils { namespace ResourceUtils {
PackedResourceType identify(QFileInfo file){ PackedResourceType identify(QFileInfo file){
if (file.exists() && file.isFile()) { if (file.exists() && file.isFile()) {
if (ResourcePackUtils::validate(file)) { if (ModUtils::validate(file)) {
qDebug() << file.fileName() << "is a mod";
return PackedResourceType::Mod;
} else if (ResourcePackUtils::validate(file)) {
qDebug() << file.fileName() << "is a resource pack"; qDebug() << file.fileName() << "is a resource pack";
return PackedResourceType::ResourcePack; return PackedResourceType::ResourcePack;
} else if (TexturePackUtils::validate(file)) { } else if (TexturePackUtils::validate(file)) {
@ -53,9 +56,6 @@ PackedResourceType identify(QFileInfo file){
} else if (DataPackUtils::validate(file)) { } else if (DataPackUtils::validate(file)) {
qDebug() << file.fileName() << "is a data pack"; qDebug() << file.fileName() << "is a data pack";
return PackedResourceType::DataPack; return PackedResourceType::DataPack;
} else if (ModUtils::validate(file)) {
qDebug() << file.fileName() << "is a mod";
return PackedResourceType::Mod;
} else if (WorldSaveUtils::validate(file)) { } else if (WorldSaveUtils::validate(file)) {
qDebug() << file.fileName() << "is a world save"; qDebug() << file.fileName() << "is a world save";
return PackedResourceType::WorldSave; return PackedResourceType::WorldSave;

View File

@ -217,6 +217,18 @@ Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) c
return netJob; return netJob;
} }
Task::Ptr FlameAPI::getFile(const QString& addonId, const QString& fileId, QByteArray* response) const
{
auto netJob = makeShared<NetJob>(QString("Flame::GetFile"), APPLICATION->network());
netJob->addNetAction(
Net::Download::makeByteArray(QUrl(QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(addonId, fileId)), response));
QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
QObject::connect(netJob.get(), &NetJob::failed, [addonId, fileId] { qDebug() << "Flame API file failure" << addonId << fileId; });
return netJob;
}
// https://docs.curseforge.com/?python#tocS_ModsSearchSortField // https://docs.curseforge.com/?python#tocS_ModsSearchSortField
static QList<ResourceAPI::SortingMethod> s_sorts = { { 1, "Featured", QObject::tr("Sort by Featured") }, static QList<ResourceAPI::SortingMethod> s_sorts = { { 1, "Featured", QObject::tr("Sort by Featured") },
{ 2, "Popularity", QObject::tr("Sort by Popularity") }, { 2, "Popularity", QObject::tr("Sort by Popularity") },

View File

@ -17,6 +17,7 @@ class FlameAPI : public NetworkResourceAPI {
Task::Ptr getProjects(QStringList addonIds, QByteArray* response) const override; Task::Ptr getProjects(QStringList addonIds, QByteArray* response) const override;
Task::Ptr matchFingerprints(const QList<uint>& fingerprints, QByteArray* response); Task::Ptr matchFingerprints(const QList<uint>& fingerprints, QByteArray* response);
Task::Ptr getFiles(const QStringList& fileIds, QByteArray* response) const; Task::Ptr getFiles(const QStringList& fileIds, QByteArray* response) const;
Task::Ptr getFile(const QString& addonId, const QString& fileId, QByteArray* response) const;
[[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override; [[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override;

View File

@ -86,6 +86,7 @@
#include <net/NetJob.h> #include <net/NetJob.h>
#include <net/Download.h> #include <net/Download.h>
#include <news/NewsChecker.h> #include <news/NewsChecker.h>
#include <qurl.h>
#include <tools/BaseProfiler.h> #include <tools/BaseProfiler.h>
#include <updater/ExternalUpdater.h> #include <updater/ExternalUpdater.h>
#include <DesktopServices.h> #include <DesktopServices.h>
@ -116,11 +117,15 @@
#include "minecraft/mod/ShaderPackFolderModel.h" #include "minecraft/mod/ShaderPackFolderModel.h"
#include "minecraft/WorldList.h" #include "minecraft/WorldList.h"
#include "modplatform/flame/FlameAPI.h"
#include "KonamiCode.h" #include "KonamiCode.h"
#include "InstanceImportTask.h" #include "InstanceImportTask.h"
#include "InstanceCopyTask.h" #include "InstanceCopyTask.h"
#include "Json.h"
#include "MMCTime.h" #include "MMCTime.h"
namespace { namespace {
@ -981,7 +986,7 @@ void MainWindow::finalizeInstance(InstancePtr inst)
} }
} }
void MainWindow::addInstance(QString url) void MainWindow::addInstance(const QString& url, const QMap<QString, QString>& extra_info)
{ {
QString groupName; QString groupName;
do do
@ -1003,7 +1008,7 @@ void MainWindow::addInstance(QString url)
groupName = APPLICATION->settings()->get("LastUsedGroupForNewInstance").toString(); groupName = APPLICATION->settings()->get("LastUsedGroupForNewInstance").toString();
} }
NewInstanceDialog newInstDlg(groupName, url, this); NewInstanceDialog newInstDlg(groupName, url, extra_info, this);
if (!newInstDlg.exec()) if (!newInstDlg.exec())
return; return;
@ -1031,18 +1036,103 @@ void MainWindow::processURLs(QList<QUrl> urls)
if (url.scheme().isEmpty()) if (url.scheme().isEmpty())
url.setScheme("file"); url.setScheme("file");
if (!url.isLocalFile()) { // probably instance/modpack QMap<QString, QString> extra_info;
addInstance(url.toString()); QUrl local_url;
break; if (!url.isLocalFile()) { // download the remote resource and identify
QUrl dl_url;
if(url.scheme() == "curseforge") {
// need to find the download link for the modpack / resource
// format of url curseforge://install?addonId=IDHERE&fileId=IDHERE
QUrlQuery query(url);
auto addonId = query.allQueryItemValues("addonId")[0];
auto fileId = query.allQueryItemValues("fileId")[0];
extra_info.insert("pack_id", addonId);
extra_info.insert("pack_version_id", fileId);
auto array = new QByteArray();
auto api = FlameAPI();
auto job = api.getFile(addonId, fileId, array);
QString resource_name;
connect(job.get(), &Task::failed, this,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
connect(job.get(), &Task::succeeded, this, [this, array, addonId, fileId, &dl_url, &resource_name] {
qDebug() << "Returned CFURL Json:\n" << array->toStdString().c_str();
auto doc = Json::requireDocument(*array);
// No way to find out if it's a mod or a modpack before here
// And also we need to check if it ends with .zip, instead of any better way
auto fileName = Json::ensureString(Json::ensureObject(Json::ensureObject(doc.object()), "data"), "fileName");
// Have to use ensureString then use QUrl to get proper url encoding
dl_url = QUrl(Json::ensureString(Json::ensureObject(Json::ensureObject(doc.object()), "data"), "downloadUrl",
"", "downloadUrl"));
if (!dl_url.isValid()) {
CustomMessageBox::selectable(this, tr("Error"), tr("The modpack, mod, or resource is blocked ! Please download it manually \n%1").arg(dl_url.toDisplayString()),
QMessageBox::Critical)
->show();
return;
}
QFileInfo dl_file(dl_url.fileName());
resource_name = Json::ensureString(Json::ensureObject(Json::ensureObject(doc.object()), "data"), "displayName",
dl_file.completeBaseName(), "displayName");
});
{ // drop stack
ProgressDialog dlUrlDialod(this);
dlUrlDialod.setSkipButton(true, tr("Abort"));
dlUrlDialod.execWithTask(job.get());
}
// dialog->setSuggestedPack(pack_name, new InstanceImportTask(dl_url, this, std::move(extra_info)));
// dialog->setSuggestedIcon("default");
} else {
dl_url = url;
}
if (!dl_url.isValid()) {
continue; // no valid url to download this resource
}
const QString path = dl_url.host() + '/' + dl_url.path();
auto entry = APPLICATION->metacache()->resolveEntry("general", path);
entry->setStale(true);
auto dl_job = unique_qobject_ptr<NetJob>(new NetJob(tr("Modpack download"), APPLICATION->network()));
dl_job->addNetAction(Net::Download::makeCached(dl_url, entry));
auto archivePath = entry->getFullPath();
bool dl_success = false;
connect(dl_job.get(), &Task::failed, this, [this](QString reason){CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
connect(dl_job.get(), &Task::succeeded, this, [&dl_success]{dl_success = true;});
{ // drop stack
ProgressDialog dlUrlDialod(this);
dlUrlDialod.setSkipButton(true, tr("Abort"));
dlUrlDialod.execWithTask(dl_job.get());
}
if (!dl_success) {
continue; // no local file to identify
}
local_url = QUrl::fromLocalFile(archivePath);
} else {
local_url = url;
} }
auto localFileName = QDir::toNativeSeparators(url.toLocalFile()) ; auto localFileName = QDir::toNativeSeparators(local_url.toLocalFile()) ;
QFileInfo localFileInfo(localFileName); QFileInfo localFileInfo(localFileName);
auto type = ResourceUtils::identify(localFileInfo); auto type = ResourceUtils::identify(localFileInfo);
if (ResourceUtils::ValidResourceTypes.count(type) == 0) { // probably instance/modpack if (ResourceUtils::ValidResourceTypes.count(type) == 0) { // probably instance/modpack
addInstance(localFileName); addInstance(localFileName, extra_info);
continue; continue;
} }

View File

@ -210,7 +210,7 @@ private slots:
private: private:
void retranslateUi(); void retranslateUi();
void addInstance(QString url = QString()); void addInstance(const QString& url = QString(), const QMap<QString, QString>& extra_info = {});
void activateInstance(InstancePtr instance); void activateInstance(InstancePtr instance);
void setCatBackground(bool enabled); void setCatBackground(bool enabled);
void updateInstanceToolIcon(QString new_icon); void updateInstanceToolIcon(QString new_icon);

View File

@ -62,9 +62,10 @@
#include "ui/pages/modplatform/modrinth/ModrinthPage.h" #include "ui/pages/modplatform/modrinth/ModrinthPage.h"
#include "ui/pages/modplatform/technic/TechnicPage.h" #include "ui/pages/modplatform/technic/TechnicPage.h"
NewInstanceDialog::NewInstanceDialog(const QString& initialGroup,
const QString& url,
NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString & url, QWidget *parent) const QMap<QString, QString>& extra_info,
QWidget* parent)
: QDialog(parent), ui(new Ui::NewInstanceDialog) : QDialog(parent), ui(new Ui::NewInstanceDialog)
{ {
ui->setupUi(this); ui->setupUi(this);
@ -128,6 +129,7 @@ NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString
QUrl actualUrl(url); QUrl actualUrl(url);
m_container->selectPage("import"); m_container->selectPage("import");
importPage->setUrl(url); importPage->setUrl(url);
importPage->setExtraInfo(extra_info);
} }
updateDialogState(); updateDialogState();

View File

@ -55,24 +55,27 @@ class NewInstanceDialog : public QDialog, public BasePageProvider
Q_OBJECT Q_OBJECT
public: public:
explicit NewInstanceDialog(const QString & initialGroup, const QString & url = QString(), QWidget *parent = 0); explicit NewInstanceDialog(const QString& initialGroup,
~NewInstanceDialog(); const QString& url = QString(),
const QMap<QString, QString>& extra_info = {},
QWidget* parent = 0);
~NewInstanceDialog();
void updateDialogState(); void updateDialogState();
void setSuggestedPack(const QString& name = QString(), InstanceTask * task = nullptr); void setSuggestedPack(const QString& name = QString(), InstanceTask* task = nullptr);
void setSuggestedPack(const QString& name, QString version, InstanceTask * task = nullptr); void setSuggestedPack(const QString& name, QString version, InstanceTask* task = nullptr);
void setSuggestedIconFromFile(const QString &path, const QString &name); void setSuggestedIconFromFile(const QString& path, const QString& name);
void setSuggestedIcon(const QString &key); void setSuggestedIcon(const QString& key);
InstanceTask * extractTask(); InstanceTask* extractTask();
QString dialogTitle() override; QString dialogTitle() override;
QList<BasePage *> getPages() override; QList<BasePage*> getPages() override;
QString instName() const; QString instName() const;
QString instGroup() const; QString instGroup() const;
QString iconKey() const; QString iconKey() const;
public slots: public slots:
void accept() override; void accept() override;

View File

@ -35,15 +35,20 @@
*/ */
#include "ImportPage.h" #include "ImportPage.h"
#include <qdebug.h>
#include <qmap.h>
#include "ui/dialogs/ProgressDialog.h" #include "ui/dialogs/ProgressDialog.h"
#include "ui_ImportPage.h" #include "ui_ImportPage.h"
#include <QFileDialog> #include <QFileDialog>
#include <QValidator> #include <QValidator>
#include <utility>
#include "ui/dialogs/NewInstanceDialog.h" #include "ui/dialogs/NewInstanceDialog.h"
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
#include "modplatform/flame/FlameAPI.h"
#include "Json.h" #include "Json.h"
#include "InstanceImportTask.h" #include "InstanceImportTask.h"
@ -119,7 +124,9 @@ void ImportPage::updateState()
if (fi.exists() && (isZip || isMRPack)) { if (fi.exists() && (isZip || isMRPack)) {
QFileInfo fi(url.fileName()); QFileInfo fi(url.fileName());
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this)); auto extra_info = QMap(m_extra_info);
qDebug() << "Pack Extra Info" << extra_info << m_extra_info;
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this, std::move(extra_info)));
dialog->setSuggestedIcon("default"); dialog->setSuggestedIcon("default");
} }
} else if (url.scheme() == "curseforge") { } else if (url.scheme() == "curseforge") {
@ -129,14 +136,13 @@ void ImportPage::updateState()
auto addonId = query.allQueryItemValues("addonId")[0]; auto addonId = query.allQueryItemValues("addonId")[0];
auto fileId = query.allQueryItemValues("fileId")[0]; auto fileId = query.allQueryItemValues("fileId")[0];
auto array = new QByteArray(); auto array = new QByteArray();
auto req = unique_qobject_ptr<NetJob>(new NetJob("Curseforge Meta", APPLICATION->network()));
req->addNetAction(
Net::Download::makeByteArray(QUrl(QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(addonId, fileId)), array));
connect(req.get(), &NetJob::finished, [array] { delete array; }); auto api = FlameAPI();
connect(req.get(), &NetJob::failed, this, auto job = api.getFile(addonId, fileId, array);
connect(job.get(), &NetJob::failed, this,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
connect(req.get(), &NetJob::succeeded, this, [this, array, addonId, fileId] { connect(job.get(), &NetJob::succeeded, this, [this, array, addonId, fileId] {
qDebug() << "Returned CFURL Json:\n" << array->toStdString().c_str(); qDebug() << "Returned CFURL Json:\n" << array->toStdString().c_str();
auto doc = Json::requireDocument(*array); auto doc = Json::requireDocument(*array);
// No way to find out if it's a mod or a modpack before here // No way to find out if it's a mod or a modpack before here
@ -170,7 +176,7 @@ void ImportPage::updateState()
}); });
ProgressDialog dlUrlDialod(this); ProgressDialog dlUrlDialod(this);
dlUrlDialod.setSkipButton(true, tr("Abort")); dlUrlDialod.setSkipButton(true, tr("Abort"));
dlUrlDialod.execWithTask(req.get()); dlUrlDialod.execWithTask(job.get());
return; return;
} else { } else {
if (input.endsWith("?client=y")) { if (input.endsWith("?client=y")) {
@ -180,7 +186,8 @@ void ImportPage::updateState()
} }
// hook, line and sinker. // hook, line and sinker.
QFileInfo fi(url.fileName()); QFileInfo fi(url.fileName());
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this)); auto extra_info = QMap(m_extra_info);
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this, std::move(extra_info)));
dialog->setSuggestedIcon("default"); dialog->setSuggestedIcon("default");
} }
} else { } else {
@ -194,6 +201,11 @@ void ImportPage::setUrl(const QString& url)
updateState(); updateState();
} }
void ImportPage::setExtraInfo(const QMap<QString, QString>& extra_info) {
m_extra_info = QMap(extra_info); // copy
updateState();
}
void ImportPage::on_modpackBtn_clicked() void ImportPage::on_modpackBtn_clicked()
{ {
auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString(); auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString();

View File

@ -76,7 +76,7 @@ public:
void setUrl(const QString & url); void setUrl(const QString & url);
void openedImpl() override; void openedImpl() override;
void setExtraInfo(const QMap<QString, QString>& extra_info);
private slots: private slots:
void on_modpackBtn_clicked(); void on_modpackBtn_clicked();
void updateState(); void updateState();
@ -87,5 +87,6 @@ private:
private: private:
Ui::ImportPage *ui = nullptr; Ui::ImportPage *ui = nullptr;
NewInstanceDialog* dialog = nullptr; NewInstanceDialog* dialog = nullptr;
QMap<QString, QString> m_extra_info = {};
}; };