Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CurseForge URL handling #981

Merged
merged 20 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 11 additions & 0 deletions cmake/MacOSXBundleInfo.plist.in
Expand Up @@ -67,5 +67,16 @@
<string>Alternate</string>
</dict>
</array>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>Curseforge</string>
<key>CFBundleURLSchemes</key>
<array>
<string>curseforge</string>
</array>
</dict>
</array>
</dict>
</plist>
59 changes: 31 additions & 28 deletions launcher/Application.cpp
Expand Up @@ -216,9 +216,12 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
{{"s", "server"}, "Join the specified server on launch (only valid in combination with --launch)", "address"},
{{"a", "profile"}, "Use the account specified by its profile name (only valid in combination with --launch)", "profile"},
{"alive", "Write a small '" + liveCheckFile + "' file after the launcher starts"},
{{"I", "import"}, "Import instance from specified zip (local path or URL)", "file"},
{{"I", "import"}, "Import instance or resource from specified local path or URL", "url"},
{"show", "Opens the window for the specified instance (by instance ID)", "show"}
});
// Has to be positional for some OS to handle that properly
parser.addPositionalArgument("URL", "Import the resource(s) at the given URL(s) (same as -I / --import)", "[URL...]");

parser.addHelpOption();
parser.addVersionOption();

Expand All @@ -231,16 +234,15 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)

m_instanceIdToShowWindowOf = parser.value("show");

for (auto zip_path : parser.values("import")){
m_zipsToImport.append(QUrl::fromLocalFile(QFileInfo(zip_path).absoluteFilePath()));
for (auto url : parser.values("import")){
m_urlsToImport.append(normalizeImportUrl(url));
}

// treat unspecified positional arguments as import urls
for (auto zip_path : parser.positionalArguments()) {
m_zipsToImport.append(QUrl::fromLocalFile(QFileInfo(zip_path).absoluteFilePath()));
for (auto url : parser.positionalArguments()) {
m_urlsToImport.append(normalizeImportUrl(url));
}


// error if --launch is missing with --server or --profile
if((!m_serverToJoin.isEmpty() || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty())
{
Expand Down Expand Up @@ -343,12 +345,11 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
activate.command = "activate";
m_peerInstance->sendMessage(activate.serialize(), timeout);

if(!m_zipsToImport.isEmpty())
{
for (auto zip_url : m_zipsToImport) {
if (!m_urlsToImport.isEmpty()) {
for (auto url : m_urlsToImport) {
ApplicationMessage import;
import.command = "import";
import.args.insert("path", zip_url.toString());
import.args.insert("url", url.toString());
m_peerInstance->sendMessage(import.serialize(), timeout);
}
}
Expand Down Expand Up @@ -1025,10 +1026,10 @@ void Application::performMainStartupAction()
showMainWindow(false);
qDebug() << "<> Main window shown.";
}
if(!m_zipsToImport.isEmpty())
if(!m_urlsToImport.isEmpty())
{
qDebug() << "<> Importing from zip:" << m_zipsToImport;
m_mainWindow->processURLs( m_zipsToImport );
qDebug() << "<> Importing from url:" << m_urlsToImport;
m_mainWindow->processURLs( m_urlsToImport );
}
}

Expand Down Expand Up @@ -1069,22 +1070,16 @@ void Application::messageReceived(const QByteArray& message)

auto & command = received.command;

if(command == "activate")
{
if (command == "activate") {
showMainWindow();
}
else if(command == "import")
{
QString path = received.args["path"];
if(path.isEmpty())
{
} else if (command == "import") {
QString url = received.args["url"];
if (url.isEmpty()) {
qWarning() << "Received" << command << "message without a zip path/URL.";
return;
}
m_mainWindow->processURLs({ QUrl::fromLocalFile(QFileInfo(path).absoluteFilePath()) });
}
else if(command == "launch")
{
m_mainWindow->processURLs({ normalizeImportUrl(url) });
} else if (command == "launch") {
QString id = received.args["id"];
QString server = received.args["server"];
QString profile = received.args["profile"];
Expand Down Expand Up @@ -1124,9 +1119,7 @@ void Application::messageReceived(const QByteArray& message)
serverObject,
accountObject
);
}
else
{
} else {
qWarning() << "Received invalid message" << message;
}
}
Expand Down Expand Up @@ -1733,3 +1726,13 @@ void Application::triggerUpdateCheck()
qDebug() << "Updater not available.";
}
}

QUrl Application::normalizeImportUrl(QString const& url)
{
auto local_file = QFileInfo(url);
if (local_file.exists()) {
return QUrl::fromLocalFile(local_file.absoluteFilePath());
} else {
return QUrl::fromUserInput(url);
}
}
4 changes: 3 additions & 1 deletion launcher/Application.h
Expand Up @@ -211,6 +211,8 @@ class Application : public QApplication

int suitableMaxMem();

QUrl normalizeImportUrl(QString const& url);

signals:
void updateAllowedChanged(bool status);
void globalSettingsAboutToOpen();
Expand Down Expand Up @@ -314,7 +316,7 @@ private slots:
QString m_serverToJoin;
QString m_profileToUse;
bool m_liveCheck = false;
QList<QUrl> m_zipsToImport;
QList<QUrl> m_urlsToImport;
QString m_instanceIdToShowWindowOf;
std::unique_ptr<QFile> logFile;
};
38 changes: 22 additions & 16 deletions launcher/InstanceImportTask.cpp
Expand Up @@ -47,8 +47,11 @@
#include "modplatform/technic/TechnicPackProcessor.h"
#include "modplatform/modrinth/ModrinthInstanceCreationTask.h"
#include "modplatform/flame/FlameInstanceCreationTask.h"
// FIXME : move this over to FlameInstanceCreationTask
#include "Json.h"
Trial97 marked this conversation as resolved.
Show resolved Hide resolved

#include "settings/INISettingsObject.h"
#include "tasks/Task.h"

#include <QtConcurrentRun>
#include <algorithm>
Expand Down Expand Up @@ -87,25 +90,27 @@ void InstanceImportTask::executeTask()
setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString()));
m_downloadRequired = true;

const QString path(m_sourceUrl.host() + '/' + m_sourceUrl.path());

auto entry = APPLICATION->metacache()->resolveEntry("general", path);
entry->setStale(true);
m_archivePath = entry->getFullPath();

m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry));

connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propogateStepProgress);
connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed);
connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted);

m_filesNetJob->start();
downloadFromUrl();
}
}

void InstanceImportTask::downloadFromUrl()
{
const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path();
auto entry = APPLICATION->metacache()->resolveEntry("general", path);
entry->setStale(true);
m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry));
m_archivePath = entry->getFullPath();

connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propogateStepProgress);
connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed);
connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted);
m_filesNetJob->start();
}

Scrumplex marked this conversation as resolved.
Show resolved Hide resolved
void InstanceImportTask::downloadSucceeded()
{
processZipPack();
Expand Down Expand Up @@ -396,3 +401,4 @@ void InstanceImportTask::processModrinth()

inst_creation_task->start();
}

1 change: 1 addition & 0 deletions launcher/InstanceImportTask.h
Expand Up @@ -106,4 +106,5 @@ private slots:

//FIXME: nuke
QWidget* m_parent;
void downloadFromUrl();
};
8 changes: 4 additions & 4 deletions launcher/minecraft/mod/tasks/LocalResourceParse.cpp
Expand Up @@ -44,7 +44,10 @@ static const QMap<PackedResourceType, QString> s_packed_type_names = {
namespace ResourceUtils {
PackedResourceType identify(QFileInfo file){
if (file.exists() && file.isFile()) {
if (ResourcePackUtils::validate(file)) {
if (ModUtils::validate(file)) { // Mods can also contain resource and data packs
qDebug() << file.fileName() << "is a mod";
return PackedResourceType::Mod;
} else if (ResourcePackUtils::validate(file)) {
qDebug() << file.fileName() << "is a resource pack";
return PackedResourceType::ResourcePack;
} else if (TexturePackUtils::validate(file)) {
Expand All @@ -53,9 +56,6 @@ PackedResourceType identify(QFileInfo file){
} else if (DataPackUtils::validate(file)) {
qDebug() << file.fileName() << "is a data pack";
return PackedResourceType::DataPack;
} else if (ModUtils::validate(file)) {
qDebug() << file.fileName() << "is a mod";
return PackedResourceType::Mod;
} else if (WorldSaveUtils::validate(file)) {
qDebug() << file.fileName() << "is a world save";
return PackedResourceType::WorldSave;
Expand Down
12 changes: 12 additions & 0 deletions launcher/modplatform/flame/FlameAPI.cpp
Expand Up @@ -217,6 +217,18 @@ Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) c
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
static QList<ResourceAPI::SortingMethod> s_sorts = { { 1, "Featured", QObject::tr("Sort by Featured") },
{ 2, "Popularity", QObject::tr("Sort by Popularity") },
Expand Down
1 change: 1 addition & 0 deletions launcher/modplatform/flame/FlameAPI.h
Expand Up @@ -17,6 +17,7 @@ class FlameAPI : public NetworkResourceAPI {
Task::Ptr getProjects(QStringList addonIds, QByteArray* response) const override;
Task::Ptr matchFingerprints(const QList<uint>& fingerprints, QByteArray* response);
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;

Expand Down