Merge pull request #3195 from kb-1000/technic-import
Technic pack import
This commit is contained in:
commit
4c62776044
@ -477,6 +477,15 @@ set(MODPACKSCH_SOURCES
|
|||||||
modplatform/modpacksch/FTBPackManifest.cpp
|
modplatform/modpacksch/FTBPackManifest.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(TECHNIC_SOURCES
|
||||||
|
modplatform/technic/SingleZipPackInstallTask.h
|
||||||
|
modplatform/technic/SingleZipPackInstallTask.cpp
|
||||||
|
modplatform/technic/SolderPackInstallTask.h
|
||||||
|
modplatform/technic/SolderPackInstallTask.cpp
|
||||||
|
modplatform/technic/TechnicPackProcessor.h
|
||||||
|
modplatform/technic/TechnicPackProcessor.cpp
|
||||||
|
)
|
||||||
|
|
||||||
add_unit_test(Index
|
add_unit_test(Index
|
||||||
SOURCES meta/Index_test.cpp
|
SOURCES meta/Index_test.cpp
|
||||||
LIBS MultiMC_logic
|
LIBS MultiMC_logic
|
||||||
@ -508,6 +517,7 @@ set(LOGIC_SOURCES
|
|||||||
${FTB_SOURCES}
|
${FTB_SOURCES}
|
||||||
${FLAME_SOURCES}
|
${FLAME_SOURCES}
|
||||||
${MODPACKSCH_SOURCES}
|
${MODPACKSCH_SOURCES}
|
||||||
|
${TECHNIC_SOURCES}
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(MultiMC_logic SHARED ${LOGIC_SOURCES})
|
add_library(MultiMC_logic SHARED ${LOGIC_SOURCES})
|
||||||
|
@ -98,6 +98,7 @@ void Env::initHttpMetaCache()
|
|||||||
m_metacache->addBase("general", QDir("cache").absolutePath());
|
m_metacache->addBase("general", QDir("cache").absolutePath());
|
||||||
m_metacache->addBase("FTBPacks", QDir("cache/FTBPacks").absolutePath());
|
m_metacache->addBase("FTBPacks", QDir("cache/FTBPacks").absolutePath());
|
||||||
m_metacache->addBase("ModpacksCHPacks", QDir("cache/ModpacksCHPacks").absolutePath());
|
m_metacache->addBase("ModpacksCHPacks", QDir("cache/ModpacksCHPacks").absolutePath());
|
||||||
|
m_metacache->addBase("TechnicPacks", QDir("cache/TechnicPacks").absolutePath());
|
||||||
m_metacache->addBase("TwitchPacks", QDir("cache/TwitchPacks").absolutePath());
|
m_metacache->addBase("TwitchPacks", QDir("cache/TwitchPacks").absolutePath());
|
||||||
m_metacache->addBase("skins", QDir("accounts/skins").absolutePath());
|
m_metacache->addBase("skins", QDir("accounts/skins").absolutePath());
|
||||||
m_metacache->addBase("root", QDir::currentPath());
|
m_metacache->addBase("root", QDir::currentPath());
|
||||||
|
@ -1,3 +1,18 @@
|
|||||||
|
/* Copyright 2013-2020 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "InstanceImportTask.h"
|
#include "InstanceImportTask.h"
|
||||||
#include "BaseInstance.h"
|
#include "BaseInstance.h"
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
@ -15,6 +30,8 @@
|
|||||||
#include "modplatform/flame/FileResolvingTask.h"
|
#include "modplatform/flame/FileResolvingTask.h"
|
||||||
#include "modplatform/flame/PackManifest.h"
|
#include "modplatform/flame/PackManifest.h"
|
||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
|
#include <quazipdir.h>
|
||||||
|
#include "modplatform/technic/TechnicPackProcessor.h"
|
||||||
|
|
||||||
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl)
|
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl)
|
||||||
{
|
{
|
||||||
@ -23,8 +40,6 @@ InstanceImportTask::InstanceImportTask(const QUrl sourceUrl)
|
|||||||
|
|
||||||
void InstanceImportTask::executeTask()
|
void InstanceImportTask::executeTask()
|
||||||
{
|
{
|
||||||
InstancePtr newInstance;
|
|
||||||
|
|
||||||
if (m_sourceUrl.isLocalFile())
|
if (m_sourceUrl.isLocalFile())
|
||||||
{
|
{
|
||||||
m_archivePath = m_sourceUrl.toLocalFile();
|
m_archivePath = m_sourceUrl.toLocalFile();
|
||||||
@ -82,6 +97,7 @@ void InstanceImportTask::processZipPack()
|
|||||||
|
|
||||||
QStringList blacklist = {"instance.cfg", "manifest.json"};
|
QStringList blacklist = {"instance.cfg", "manifest.json"};
|
||||||
QString mmcFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg");
|
QString mmcFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg");
|
||||||
|
bool technicFound = QuaZipDir(m_packZip.get()).exists("/bin/modpack.jar") || QuaZipDir(m_packZip.get()).exists("/bin/version.json");
|
||||||
QString flameFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json");
|
QString flameFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json");
|
||||||
QString root;
|
QString root;
|
||||||
if(!mmcFound.isNull())
|
if(!mmcFound.isNull())
|
||||||
@ -91,6 +107,14 @@ void InstanceImportTask::processZipPack()
|
|||||||
root = mmcFound;
|
root = mmcFound;
|
||||||
m_modpackType = ModpackType::MultiMC;
|
m_modpackType = ModpackType::MultiMC;
|
||||||
}
|
}
|
||||||
|
else if (technicFound)
|
||||||
|
{
|
||||||
|
// process as Technic pack
|
||||||
|
qDebug() << "Technic:" << technicFound;
|
||||||
|
extractDir.mkpath(".minecraft");
|
||||||
|
extractDir.cd(".minecraft");
|
||||||
|
m_modpackType = ModpackType::Technic;
|
||||||
|
}
|
||||||
else if(!flameFound.isNull())
|
else if(!flameFound.isNull())
|
||||||
{
|
{
|
||||||
// process as Flame pack
|
// process as Flame pack
|
||||||
@ -98,7 +122,6 @@ void InstanceImportTask::processZipPack()
|
|||||||
root = flameFound;
|
root = flameFound;
|
||||||
m_modpackType = ModpackType::Flame;
|
m_modpackType = ModpackType::Flame;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(m_modpackType == ModpackType::Unknown)
|
if(m_modpackType == ModpackType::Unknown)
|
||||||
{
|
{
|
||||||
emitFailed(tr("Archive does not contain a recognized modpack type."));
|
emitFailed(tr("Archive does not contain a recognized modpack type."));
|
||||||
@ -161,6 +184,9 @@ void InstanceImportTask::extractFinished()
|
|||||||
case ModpackType::MultiMC:
|
case ModpackType::MultiMC:
|
||||||
processMultiMC();
|
processMultiMC();
|
||||||
return;
|
return;
|
||||||
|
case ModpackType::Technic:
|
||||||
|
processTechnic();
|
||||||
|
return;
|
||||||
case ModpackType::Unknown:
|
case ModpackType::Unknown:
|
||||||
emitFailed(tr("Archive does not contain a recognized modpack type."));
|
emitFailed(tr("Archive does not contain a recognized modpack type."));
|
||||||
return;
|
return;
|
||||||
@ -371,6 +397,14 @@ void InstanceImportTask::processFlame()
|
|||||||
m_modIdResolver->start();
|
m_modIdResolver->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InstanceImportTask::processTechnic()
|
||||||
|
{
|
||||||
|
shared_qobject_ptr<Technic::TechnicPackProcessor> packProcessor = new Technic::TechnicPackProcessor();
|
||||||
|
connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &InstanceImportTask::emitSucceeded);
|
||||||
|
connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &InstanceImportTask::emitFailed);
|
||||||
|
packProcessor->run(m_globalSettings, m_instName, m_instIcon, m_stagingPath);
|
||||||
|
}
|
||||||
|
|
||||||
void InstanceImportTask::processMultiMC()
|
void InstanceImportTask::processMultiMC()
|
||||||
{
|
{
|
||||||
// FIXME: copy from FolderInstanceProvider!!! FIX IT!!!
|
// FIXME: copy from FolderInstanceProvider!!! FIX IT!!!
|
||||||
|
@ -1,3 +1,18 @@
|
|||||||
|
/* Copyright 2013-2020 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "InstanceTask.h"
|
#include "InstanceTask.h"
|
||||||
@ -29,6 +44,7 @@ private:
|
|||||||
void processZipPack();
|
void processZipPack();
|
||||||
void processMultiMC();
|
void processMultiMC();
|
||||||
void processFlame();
|
void processFlame();
|
||||||
|
void processTechnic();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void downloadSucceeded();
|
void downloadSucceeded();
|
||||||
@ -49,6 +65,7 @@ private: /* data */
|
|||||||
enum class ModpackType{
|
enum class ModpackType{
|
||||||
Unknown,
|
Unknown,
|
||||||
MultiMC,
|
MultiMC,
|
||||||
Flame
|
Flame,
|
||||||
|
Technic
|
||||||
} m_modpackType = ModpackType::Unknown;
|
} m_modpackType = ModpackType::Unknown;
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/* Copyright 2013-2019 MultiMC Contributors
|
/* Copyright 2013-2020 MultiMC Contributors
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/* Copyright 2013-2019 MultiMC Contributors
|
/* Copyright 2013-2020 MultiMC Contributors
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -67,5 +67,4 @@ namespace MMCZip
|
|||||||
* \return The list of the full paths of the files extracted, empty on failure.
|
* \return The list of the full paths of the files extracted, empty on failure.
|
||||||
*/
|
*/
|
||||||
QStringList MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString dir);
|
QStringList MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString dir);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
129
api/logic/modplatform/technic/SingleZipPackInstallTask.cpp
Normal file
129
api/logic/modplatform/technic/SingleZipPackInstallTask.cpp
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
/* Copyright 2013-2020 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "SingleZipPackInstallTask.h"
|
||||||
|
|
||||||
|
#include "Env.h"
|
||||||
|
#include "MMCZip.h"
|
||||||
|
#include "TechnicPackProcessor.h"
|
||||||
|
|
||||||
|
#include <QtConcurrent>
|
||||||
|
|
||||||
|
Technic::SingleZipPackInstallTask::SingleZipPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion)
|
||||||
|
{
|
||||||
|
m_sourceUrl = sourceUrl;
|
||||||
|
m_minecraftVersion = minecraftVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Technic::SingleZipPackInstallTask::executeTask()
|
||||||
|
{
|
||||||
|
setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString()));
|
||||||
|
|
||||||
|
const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path();
|
||||||
|
auto entry = ENV.metacache()->resolveEntry("general", path);
|
||||||
|
entry->setStale(true);
|
||||||
|
m_filesNetJob.reset(new NetJob(tr("Modpack download")));
|
||||||
|
m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry));
|
||||||
|
m_archivePath = entry->getFullPath();
|
||||||
|
auto job = m_filesNetJob.get();
|
||||||
|
connect(job, &NetJob::succeeded, this, &Technic::SingleZipPackInstallTask::downloadSucceeded);
|
||||||
|
connect(job, &NetJob::progress, this, &Technic::SingleZipPackInstallTask::downloadProgressChanged);
|
||||||
|
connect(job, &NetJob::failed, this, &Technic::SingleZipPackInstallTask::downloadFailed);
|
||||||
|
m_filesNetJob->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Technic::SingleZipPackInstallTask::downloadSucceeded()
|
||||||
|
{
|
||||||
|
setStatus(tr("Extracting modpack"));
|
||||||
|
QDir extractDir(m_stagingPath);
|
||||||
|
qDebug() << "Attempting to create instance from" << m_archivePath;
|
||||||
|
|
||||||
|
// open the zip and find relevant files in it
|
||||||
|
m_packZip.reset(new QuaZip(m_archivePath));
|
||||||
|
if (!m_packZip->open(QuaZip::mdUnzip))
|
||||||
|
{
|
||||||
|
emitFailed(tr("Unable to open supplied modpack zip file."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), QString(""), extractDir.absolutePath());
|
||||||
|
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &Technic::SingleZipPackInstallTask::extractFinished);
|
||||||
|
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &Technic::SingleZipPackInstallTask::extractAborted);
|
||||||
|
m_extractFutureWatcher.setFuture(m_extractFuture);
|
||||||
|
m_filesNetJob.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Technic::SingleZipPackInstallTask::downloadFailed(QString reason)
|
||||||
|
{
|
||||||
|
emitFailed(reason);
|
||||||
|
m_filesNetJob.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Technic::SingleZipPackInstallTask::downloadProgressChanged(qint64 current, qint64 total)
|
||||||
|
{
|
||||||
|
setProgress(current / 2, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Technic::SingleZipPackInstallTask::extractFinished()
|
||||||
|
{
|
||||||
|
m_packZip.reset();
|
||||||
|
if (m_extractFuture.result().isEmpty())
|
||||||
|
{
|
||||||
|
emitFailed(tr("Failed to extract modpack"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QDir extractDir(m_stagingPath);
|
||||||
|
|
||||||
|
qDebug() << "Fixing permissions for extracted pack files...";
|
||||||
|
QDirIterator it(extractDir, QDirIterator::Subdirectories);
|
||||||
|
while (it.hasNext())
|
||||||
|
{
|
||||||
|
auto filepath = it.next();
|
||||||
|
QFileInfo file(filepath);
|
||||||
|
auto permissions = QFile::permissions(filepath);
|
||||||
|
auto origPermissions = permissions;
|
||||||
|
if (file.isDir())
|
||||||
|
{
|
||||||
|
// Folder +rwx for current user
|
||||||
|
permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// File +rw for current user
|
||||||
|
permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser;
|
||||||
|
}
|
||||||
|
if (origPermissions != permissions)
|
||||||
|
{
|
||||||
|
if (!QFile::setPermissions(filepath, permissions))
|
||||||
|
{
|
||||||
|
logWarning(tr("Could not fix permissions for %1").arg(filepath));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qDebug() << "Fixed" << filepath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_qobject_ptr<Technic::TechnicPackProcessor> packProcessor = new Technic::TechnicPackProcessor();
|
||||||
|
connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &Technic::SingleZipPackInstallTask::emitSucceeded);
|
||||||
|
connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &Technic::SingleZipPackInstallTask::emitFailed);
|
||||||
|
packProcessor->run(m_globalSettings, m_instName, m_instIcon, m_stagingPath, m_minecraftVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Technic::SingleZipPackInstallTask::extractAborted()
|
||||||
|
{
|
||||||
|
emitFailed(tr("Instance import has been aborted."));
|
||||||
|
}
|
64
api/logic/modplatform/technic/SingleZipPackInstallTask.h
Normal file
64
api/logic/modplatform/technic/SingleZipPackInstallTask.h
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/* Copyright 2013-2020 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef TECHNIC_SINGLEZIPPACKINSTALLTASK_H
|
||||||
|
#define TECHNIC_SINGLEZIPPACKINSTALLTASK_H
|
||||||
|
|
||||||
|
#include "InstanceTask.h"
|
||||||
|
#include "net/NetJob.h"
|
||||||
|
#include "multimc_logic_export.h"
|
||||||
|
|
||||||
|
#include "quazip.h"
|
||||||
|
|
||||||
|
#include <QFutureWatcher>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
namespace Technic {
|
||||||
|
|
||||||
|
class MULTIMC_LOGIC_EXPORT SingleZipPackInstallTask : public InstanceTask
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
SingleZipPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void executeTask() override;
|
||||||
|
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void downloadSucceeded();
|
||||||
|
void downloadFailed(QString reason);
|
||||||
|
void downloadProgressChanged(qint64 current, qint64 total);
|
||||||
|
void extractFinished();
|
||||||
|
void extractAborted();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QUrl m_sourceUrl;
|
||||||
|
QString m_minecraftVersion;
|
||||||
|
QString m_archivePath;
|
||||||
|
NetJobPtr m_filesNetJob;
|
||||||
|
std::unique_ptr<QuaZip> m_packZip;
|
||||||
|
QFuture<QStringList> m_extractFuture;
|
||||||
|
QFutureWatcher<QStringList> m_extractFutureWatcher;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Technic
|
||||||
|
|
||||||
|
#endif // TECHNIC_SINGLEZIPPACKINSTALLTASK_H
|
194
api/logic/modplatform/technic/SolderPackInstallTask.cpp
Normal file
194
api/logic/modplatform/technic/SolderPackInstallTask.cpp
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
/* Copyright 2013-2020 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "SolderPackInstallTask.h"
|
||||||
|
|
||||||
|
#include <FileSystem.h>
|
||||||
|
#include <Json.h>
|
||||||
|
#include <QtConcurrentRun>
|
||||||
|
#include <MMCZip.h>
|
||||||
|
#include "TechnicPackProcessor.h"
|
||||||
|
|
||||||
|
Technic::SolderPackInstallTask::SolderPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion)
|
||||||
|
{
|
||||||
|
m_sourceUrl = sourceUrl;
|
||||||
|
m_minecraftVersion = minecraftVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Technic::SolderPackInstallTask::executeTask()
|
||||||
|
{
|
||||||
|
setStatus(tr("Finding recommended version:\n%1").arg(m_sourceUrl.toString()));
|
||||||
|
m_filesNetJob.reset(new NetJob(tr("Finding recommended version")));
|
||||||
|
m_filesNetJob->addNetAction(Net::Download::makeByteArray(m_sourceUrl, &m_response));
|
||||||
|
auto job = m_filesNetJob.get();
|
||||||
|
connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::versionSucceeded);
|
||||||
|
connect(job, &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed);
|
||||||
|
m_filesNetJob->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Technic::SolderPackInstallTask::versionSucceeded()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
QJsonDocument doc = Json::requireDocument(m_response);
|
||||||
|
QJsonObject obj = Json::requireObject(doc);
|
||||||
|
QString version = Json::requireString(obj, "recommended", "__placeholder__");
|
||||||
|
m_sourceUrl = m_sourceUrl.toString() + '/' + version;
|
||||||
|
}
|
||||||
|
catch (const JSONValidationError &e)
|
||||||
|
{
|
||||||
|
emitFailed(e.cause());
|
||||||
|
m_filesNetJob.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus(tr("Resolving modpack files:\n%1").arg(m_sourceUrl.toString()));
|
||||||
|
m_filesNetJob.reset(new NetJob(tr("Resolving modpack files")));
|
||||||
|
m_filesNetJob->addNetAction(Net::Download::makeByteArray(m_sourceUrl, &m_response));
|
||||||
|
auto job = m_filesNetJob.get();
|
||||||
|
connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::fileListSucceeded);
|
||||||
|
connect(job, &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed);
|
||||||
|
m_filesNetJob->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Technic::SolderPackInstallTask::fileListSucceeded()
|
||||||
|
{
|
||||||
|
setStatus(tr("Downloading modpack:"));
|
||||||
|
QStringList modUrls;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
QJsonDocument doc = Json::requireDocument(m_response);
|
||||||
|
QJsonObject obj = Json::requireObject(doc);
|
||||||
|
QString minecraftVersion = Json::ensureString(obj, "minecraft", QString(), "__placeholder__");
|
||||||
|
if (!minecraftVersion.isEmpty())
|
||||||
|
m_minecraftVersion = minecraftVersion;
|
||||||
|
QJsonArray mods = Json::requireArray(obj, "mods", "'mods'");
|
||||||
|
for (auto mod: mods)
|
||||||
|
{
|
||||||
|
QJsonObject modObject = Json::requireObject(mod);
|
||||||
|
modUrls.append(Json::requireString(modObject, "url", "'url'"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const JSONValidationError &e)
|
||||||
|
{
|
||||||
|
emitFailed(e.cause());
|
||||||
|
m_filesNetJob.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_filesNetJob.reset(new NetJob(tr("Downloading modpack")));
|
||||||
|
int i = 0;
|
||||||
|
for (auto &modUrl: modUrls)
|
||||||
|
{
|
||||||
|
m_filesNetJob->addNetAction(Net::Download::makeFile(modUrl, m_outputDir.filePath(QString("%1").arg(i))));
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_modCount = modUrls.size();
|
||||||
|
|
||||||
|
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &Technic::SolderPackInstallTask::downloadSucceeded);
|
||||||
|
connect(m_filesNetJob.get(), &NetJob::progress, this, &Technic::SolderPackInstallTask::downloadProgressChanged);
|
||||||
|
connect(m_filesNetJob.get(), &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed);
|
||||||
|
m_filesNetJob->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Technic::SolderPackInstallTask::downloadSucceeded()
|
||||||
|
{
|
||||||
|
setStatus(tr("Extracting modpack"));
|
||||||
|
m_filesNetJob.reset();
|
||||||
|
m_extractFuture = QtConcurrent::run([this]()
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
QString extractDir = FS::PathCombine(m_stagingPath, ".minecraft");
|
||||||
|
FS::ensureFolderPathExists(extractDir);
|
||||||
|
|
||||||
|
while (m_modCount > i)
|
||||||
|
{
|
||||||
|
if (MMCZip::extractDir(m_outputDir.filePath(QString("%1").arg(i)), extractDir).isEmpty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &Technic::SolderPackInstallTask::extractFinished);
|
||||||
|
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &Technic::SolderPackInstallTask::extractAborted);
|
||||||
|
m_extractFutureWatcher.setFuture(m_extractFuture);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Technic::SolderPackInstallTask::downloadFailed(QString reason)
|
||||||
|
{
|
||||||
|
emitFailed(reason);
|
||||||
|
m_filesNetJob.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Technic::SolderPackInstallTask::downloadProgressChanged(qint64 current, qint64 total)
|
||||||
|
{
|
||||||
|
setProgress(current / 2, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Technic::SolderPackInstallTask::extractFinished()
|
||||||
|
{
|
||||||
|
if (!m_extractFuture.result())
|
||||||
|
{
|
||||||
|
emitFailed(tr("Failed to extract modpack"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QDir extractDir(m_stagingPath);
|
||||||
|
|
||||||
|
qDebug() << "Fixing permissions for extracted pack files...";
|
||||||
|
QDirIterator it(extractDir, QDirIterator::Subdirectories);
|
||||||
|
while (it.hasNext())
|
||||||
|
{
|
||||||
|
auto filepath = it.next();
|
||||||
|
QFileInfo file(filepath);
|
||||||
|
auto permissions = QFile::permissions(filepath);
|
||||||
|
auto origPermissions = permissions;
|
||||||
|
if(file.isDir())
|
||||||
|
{
|
||||||
|
// Folder +rwx for current user
|
||||||
|
permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// File +rw for current user
|
||||||
|
permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser;
|
||||||
|
}
|
||||||
|
if(origPermissions != permissions)
|
||||||
|
{
|
||||||
|
if(!QFile::setPermissions(filepath, permissions))
|
||||||
|
{
|
||||||
|
logWarning(tr("Could not fix permissions for %1").arg(filepath));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qDebug() << "Fixed" << filepath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_qobject_ptr<Technic::TechnicPackProcessor> packProcessor = new Technic::TechnicPackProcessor();
|
||||||
|
connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &Technic::SolderPackInstallTask::emitSucceeded);
|
||||||
|
connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &Technic::SolderPackInstallTask::emitFailed);
|
||||||
|
packProcessor->run(m_globalSettings, m_instName, m_instIcon, m_stagingPath, m_minecraftVersion, true); // TODO: pass the minecraft version down
|
||||||
|
}
|
||||||
|
|
||||||
|
void Technic::SolderPackInstallTask::extractAborted()
|
||||||
|
{
|
||||||
|
emitFailed(tr("Instance import has been aborted."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
57
api/logic/modplatform/technic/SolderPackInstallTask.h
Normal file
57
api/logic/modplatform/technic/SolderPackInstallTask.h
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/* Copyright 2013-2020 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <InstanceTask.h>
|
||||||
|
#include <net/NetJob.h>
|
||||||
|
#include <tasks/Task.h>
|
||||||
|
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
|
||||||
|
namespace Technic
|
||||||
|
{
|
||||||
|
class MULTIMC_LOGIC_EXPORT SolderPackInstallTask : public InstanceTask
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit SolderPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
//! Entry point for tasks.
|
||||||
|
virtual void executeTask() override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void versionSucceeded();
|
||||||
|
void fileListSucceeded();
|
||||||
|
void downloadSucceeded();
|
||||||
|
void downloadFailed(QString reason);
|
||||||
|
void downloadProgressChanged(qint64 current, qint64 total);
|
||||||
|
void extractFinished();
|
||||||
|
void extractAborted();
|
||||||
|
|
||||||
|
private:
|
||||||
|
NetJobPtr m_filesNetJob;
|
||||||
|
QUrl m_sourceUrl;
|
||||||
|
QString m_minecraftVersion;
|
||||||
|
QByteArray m_response;
|
||||||
|
QTemporaryDir m_outputDir;
|
||||||
|
int m_modCount;
|
||||||
|
QFuture<bool> m_extractFuture;
|
||||||
|
QFutureWatcher<bool> m_extractFutureWatcher;
|
||||||
|
};
|
||||||
|
}
|
201
api/logic/modplatform/technic/TechnicPackProcessor.cpp
Normal file
201
api/logic/modplatform/technic/TechnicPackProcessor.cpp
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
/* Copyright 2020 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "TechnicPackProcessor.h"
|
||||||
|
|
||||||
|
#include <FileSystem.h>
|
||||||
|
#include <Json.h>
|
||||||
|
#include <minecraft/MinecraftInstance.h>
|
||||||
|
#include <minecraft/PackProfile.h>
|
||||||
|
#include <quazip.h>
|
||||||
|
#include <quazipdir.h>
|
||||||
|
#include <quazipfile.h>
|
||||||
|
#include <settings/INISettingsObject.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
|
||||||
|
void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, const QString &instName, const QString &instIcon, const QString &stagingPath, const QString &minecraftVersion, const bool isSolder)
|
||||||
|
{
|
||||||
|
QString minecraftPath = FS::PathCombine(stagingPath, ".minecraft");
|
||||||
|
QString configPath = FS::PathCombine(stagingPath, "instance.cfg");
|
||||||
|
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
|
||||||
|
instanceSettings->registerSetting("InstanceType", "Legacy");
|
||||||
|
instanceSettings->set("InstanceType", "OneSix");
|
||||||
|
MinecraftInstance instance(globalSettings, instanceSettings, stagingPath);
|
||||||
|
|
||||||
|
instance.setName(instName);
|
||||||
|
|
||||||
|
if (instIcon != "default")
|
||||||
|
{
|
||||||
|
instance.setIconKey(instIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto components = instance.getPackProfile();
|
||||||
|
components->buildingFromScratch();
|
||||||
|
|
||||||
|
QByteArray data;
|
||||||
|
|
||||||
|
QString modpackJar = FS::PathCombine(minecraftPath, "bin", "modpack.jar");
|
||||||
|
QString versionJson = FS::PathCombine(minecraftPath, "bin", "version.json");
|
||||||
|
QString fmlMinecraftVersion;
|
||||||
|
if (QFile::exists(modpackJar))
|
||||||
|
{
|
||||||
|
QuaZip zipFile(modpackJar);
|
||||||
|
if (!zipFile.open(QuaZip::mdUnzip))
|
||||||
|
{
|
||||||
|
emit failed(tr("Unable to open \"bin/modpack.jar\" file!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QuaZipDir zipFileRoot(&zipFile, "/");
|
||||||
|
if (zipFileRoot.exists("/version.json"))
|
||||||
|
{
|
||||||
|
if (zipFileRoot.exists("/fmlversion.properties"))
|
||||||
|
{
|
||||||
|
zipFile.setCurrentFile("fmlversion.properties");
|
||||||
|
QuaZipFile file(&zipFile);
|
||||||
|
if (!file.open(QIODevice::ReadOnly))
|
||||||
|
{
|
||||||
|
emit failed(tr("Unable to open \"fmlversion.properties\"!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QByteArray fmlVersionData = file.readAll();
|
||||||
|
file.close();
|
||||||
|
INIFile iniFile;
|
||||||
|
iniFile.loadFile(fmlVersionData);
|
||||||
|
// If not present, this evaluates to a null string
|
||||||
|
fmlMinecraftVersion = iniFile["fmlbuild.mcversion"].toString();
|
||||||
|
}
|
||||||
|
zipFile.setCurrentFile("version.json", QuaZip::csSensitive);
|
||||||
|
QuaZipFile file(&zipFile);
|
||||||
|
if (!file.open(QIODevice::ReadOnly))
|
||||||
|
{
|
||||||
|
emit failed(tr("Unable to open \"version.json\"!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data = file.readAll();
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (minecraftVersion.isEmpty())
|
||||||
|
emit failed(tr("Could not find \"version.json\" inside \"bin/modpack.jar\", but minecraft version is unknown"));
|
||||||
|
components->setComponentVersion("net.minecraft", minecraftVersion, true);
|
||||||
|
components->installJarMods({modpackJar});
|
||||||
|
|
||||||
|
// Forge for 1.4.7 and for 1.5.2 require extra libraries.
|
||||||
|
// Figure out the forge version and add it as a component
|
||||||
|
// (the code still comes from the jar mod installed above)
|
||||||
|
if (zipFileRoot.exists("/forgeversion.properties"))
|
||||||
|
{
|
||||||
|
zipFile.setCurrentFile("forgeversion.properties", QuaZip::csSensitive);
|
||||||
|
QuaZipFile file(&zipFile);
|
||||||
|
if (!file.open(QIODevice::ReadOnly))
|
||||||
|
{
|
||||||
|
// Really shouldn't happen, but error handling shall not be forgotten
|
||||||
|
emit failed(tr("Unable to open \"forgeversion.properties\""));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QByteArray forgeVersionData = file.readAll();
|
||||||
|
file.close();
|
||||||
|
INIFile iniFile;
|
||||||
|
iniFile.loadFile(forgeVersionData);
|
||||||
|
QString major, minor, revision, build;
|
||||||
|
major = iniFile["forge.major.number"].toString();
|
||||||
|
minor = iniFile["forge.minor.number"].toString();
|
||||||
|
revision = iniFile["forge.revision.number"].toString();
|
||||||
|
build = iniFile["forge.build.number"].toString();
|
||||||
|
|
||||||
|
if (major.isEmpty() || minor.isEmpty() || revision.isEmpty() || build.isEmpty())
|
||||||
|
{
|
||||||
|
emit failed(tr("Invalid \"forgeversion.properties\"!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
components->setComponentVersion("net.minecraftforge", major + '.' + minor + '.' + revision + '.' + build);
|
||||||
|
}
|
||||||
|
|
||||||
|
components->saveNow();
|
||||||
|
emit succeeded();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (QFile::exists(versionJson))
|
||||||
|
{
|
||||||
|
QFile file(versionJson);
|
||||||
|
if (!file.open(QIODevice::ReadOnly))
|
||||||
|
{
|
||||||
|
emit failed(tr("Unable to open \"version.json\"!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data = file.readAll();
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This is the "Vanilla" modpack, excluded by the search code
|
||||||
|
emit failed(tr("Unable to find a \"version.json\"!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
QJsonDocument doc = Json::requireDocument(data);
|
||||||
|
QJsonObject root = Json::requireObject(doc, "version.json");
|
||||||
|
QString minecraftVersion = Json::ensureString(root, "inheritsFrom", QString(), "");
|
||||||
|
if (minecraftVersion.isEmpty())
|
||||||
|
{
|
||||||
|
if (fmlMinecraftVersion.isEmpty())
|
||||||
|
{
|
||||||
|
emit failed(tr("Could not understand \"version.json\":\ninheritsFrom is missing"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
minecraftVersion = fmlMinecraftVersion;
|
||||||
|
}
|
||||||
|
components->setComponentVersion("net.minecraft", minecraftVersion, true);
|
||||||
|
for (auto library: Json::ensureArray(root, "libraries", {}))
|
||||||
|
{
|
||||||
|
if (!library.isObject())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto libraryObject = Json::ensureObject(library, {}, "");
|
||||||
|
auto libraryName = Json::ensureString(libraryObject, "name", "", "");
|
||||||
|
|
||||||
|
if (libraryName.startsWith("net.minecraftforge:forge:") && libraryName.contains('-'))
|
||||||
|
{
|
||||||
|
components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1));
|
||||||
|
}
|
||||||
|
else if (libraryName.startsWith("net.minecraftforge:minecraftforge:"))
|
||||||
|
{
|
||||||
|
components->setComponentVersion("net.minecraftforge", libraryName.section(':', 2));
|
||||||
|
}
|
||||||
|
else if (libraryName.startsWith("net.fabricmc:fabric-loader:"))
|
||||||
|
{
|
||||||
|
components->setComponentVersion("net.fabricmc.fabric-loader", libraryName.section(':', 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const JSONValidationError &e)
|
||||||
|
{
|
||||||
|
emit failed(tr("Could not understand \"version.json\":\n") + e.cause());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
components->saveNow();
|
||||||
|
emit succeeded();
|
||||||
|
}
|
37
api/logic/modplatform/technic/TechnicPackProcessor.h
Normal file
37
api/logic/modplatform/technic/TechnicPackProcessor.h
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/* Copyright 2020 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include "settings/SettingsObject.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace Technic
|
||||||
|
{
|
||||||
|
// not exporting it, only used in SingleZipPackInstallTask, InstanceImportTask and SolderPackInstallTask
|
||||||
|
class TechnicPackProcessor : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void succeeded();
|
||||||
|
void failed(QString reason);
|
||||||
|
|
||||||
|
public:
|
||||||
|
void run(SettingsObjectPtr globalSettings, const QString &instName, const QString &instIcon, const QString &stagingPath, const QString &minecraftVersion=QString(), const bool isSolder = false);
|
||||||
|
};
|
||||||
|
}
|
@ -214,3 +214,5 @@ bool NetJob::addNetAction(NetActionPtr action)
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NetJob::~NetJob() = default;
|
||||||
|
@ -34,7 +34,7 @@ public:
|
|||||||
{
|
{
|
||||||
setObjectName(job_name);
|
setObjectName(job_name);
|
||||||
}
|
}
|
||||||
virtual ~NetJob() {}
|
virtual ~NetJob();
|
||||||
|
|
||||||
bool addNetAction(NetActionPtr action);
|
bool addNetAction(NetActionPtr action);
|
||||||
|
|
||||||
|
@ -137,6 +137,10 @@ SET(MULTIMC_SOURCES
|
|||||||
pages/modplatform/twitch/TwitchModel.h
|
pages/modplatform/twitch/TwitchModel.h
|
||||||
pages/modplatform/twitch/TwitchPage.cpp
|
pages/modplatform/twitch/TwitchPage.cpp
|
||||||
pages/modplatform/twitch/TwitchPage.h
|
pages/modplatform/twitch/TwitchPage.h
|
||||||
|
pages/modplatform/technic/TechnicModel.cpp
|
||||||
|
pages/modplatform/technic/TechnicModel.h
|
||||||
|
pages/modplatform/technic/TechnicPage.cpp
|
||||||
|
pages/modplatform/technic/TechnicPage.h
|
||||||
pages/modplatform/ImportPage.cpp
|
pages/modplatform/ImportPage.cpp
|
||||||
pages/modplatform/ImportPage.h
|
pages/modplatform/ImportPage.h
|
||||||
|
|
||||||
@ -257,6 +261,7 @@ SET(MULTIMC_UIS
|
|||||||
pages/modplatform/ftb/FtbPage.ui
|
pages/modplatform/ftb/FtbPage.ui
|
||||||
pages/modplatform/legacy_ftb/Page.ui
|
pages/modplatform/legacy_ftb/Page.ui
|
||||||
pages/modplatform/twitch/TwitchPage.ui
|
pages/modplatform/twitch/TwitchPage.ui
|
||||||
|
pages/modplatform/technic/TechnicPage.ui
|
||||||
pages/modplatform/ImportPage.ui
|
pages/modplatform/ImportPage.ui
|
||||||
|
|
||||||
# Dialogs
|
# Dialogs
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/* Copyright 2013-2019 MultiMC Contributors
|
/* Copyright 2013-2020 MultiMC Contributors
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -38,6 +38,8 @@
|
|||||||
#include <pages/modplatform/legacy_ftb/Page.h>
|
#include <pages/modplatform/legacy_ftb/Page.h>
|
||||||
#include <pages/modplatform/twitch/TwitchPage.h>
|
#include <pages/modplatform/twitch/TwitchPage.h>
|
||||||
#include <pages/modplatform/ImportPage.h>
|
#include <pages/modplatform/ImportPage.h>
|
||||||
|
#include <pages/modplatform/technic/TechnicPage.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString & url, QWidget *parent)
|
NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString & url, QWidget *parent)
|
||||||
@ -122,12 +124,14 @@ QList<BasePage *> NewInstanceDialog::getPages()
|
|||||||
{
|
{
|
||||||
importPage = new ImportPage(this);
|
importPage = new ImportPage(this);
|
||||||
twitchPage = new TwitchPage(this);
|
twitchPage = new TwitchPage(this);
|
||||||
|
auto technicPage = new TechnicPage(this);
|
||||||
return
|
return
|
||||||
{
|
{
|
||||||
new VanillaPage(this),
|
new VanillaPage(this),
|
||||||
importPage,
|
importPage,
|
||||||
new FtbPage(this),
|
new FtbPage(this),
|
||||||
new LegacyFTB::Page(this),
|
new LegacyFTB::Page(this),
|
||||||
|
technicPage,
|
||||||
twitchPage
|
twitchPage
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
40
application/pages/modplatform/technic/TechnicData.h
Normal file
40
application/pages/modplatform/technic/TechnicData.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/* Copyright 2020 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QList>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
|
||||||
|
namespace Technic {
|
||||||
|
struct Modpack {
|
||||||
|
QString slug;
|
||||||
|
|
||||||
|
QString name;
|
||||||
|
QString logoUrl;
|
||||||
|
QString logoName;
|
||||||
|
|
||||||
|
bool broken = true;
|
||||||
|
|
||||||
|
QString url;
|
||||||
|
bool isSolder = false;
|
||||||
|
QString minecraftVersion;
|
||||||
|
|
||||||
|
bool metadataLoaded = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(Technic::Modpack)
|
223
application/pages/modplatform/technic/TechnicModel.cpp
Normal file
223
application/pages/modplatform/technic/TechnicModel.cpp
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
/* Copyright 2020 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "TechnicModel.h"
|
||||||
|
#include "Env.h"
|
||||||
|
#include "MultiMC.h"
|
||||||
|
|
||||||
|
#include <QIcon>
|
||||||
|
|
||||||
|
|
||||||
|
Technic::ListModel::ListModel(QObject *parent) : QAbstractListModel(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Technic::ListModel::~ListModel()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant Technic::ListModel::data(const QModelIndex& index, int role) const
|
||||||
|
{
|
||||||
|
int pos = index.row();
|
||||||
|
if(pos >= modpacks.size() || pos < 0 || !index.isValid())
|
||||||
|
{
|
||||||
|
return QString("INVALID INDEX %1").arg(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
Modpack pack = modpacks.at(pos);
|
||||||
|
if(role == Qt::DisplayRole)
|
||||||
|
{
|
||||||
|
return pack.name;
|
||||||
|
}
|
||||||
|
else if(role == Qt::DecorationRole)
|
||||||
|
{
|
||||||
|
if(m_logoMap.contains(pack.logoName))
|
||||||
|
{
|
||||||
|
return (m_logoMap.value(pack.logoName));
|
||||||
|
}
|
||||||
|
QIcon icon = MMC->getThemedIcon("screenshot-placeholder");
|
||||||
|
((ListModel *)this)->requestLogo(pack.logoName, pack.logoUrl);
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
else if(role == Qt::UserRole)
|
||||||
|
{
|
||||||
|
QVariant v;
|
||||||
|
v.setValue(pack);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
int Technic::ListModel::columnCount(const QModelIndex&) const
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Technic::ListModel::rowCount(const QModelIndex&) const
|
||||||
|
{
|
||||||
|
return modpacks.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Technic::ListModel::searchWithTerm(const QString& term)
|
||||||
|
{
|
||||||
|
if(currentSearchTerm == term) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentSearchTerm = term;
|
||||||
|
if(jobPtr) {
|
||||||
|
jobPtr->abort();
|
||||||
|
searchState = ResetRequested;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
beginResetModel();
|
||||||
|
modpacks.clear();
|
||||||
|
endResetModel();
|
||||||
|
searchState = None;
|
||||||
|
}
|
||||||
|
performSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Technic::ListModel::performSearch()
|
||||||
|
{
|
||||||
|
NetJob *netJob = new NetJob("Technic::Search");
|
||||||
|
auto searchUrl = QString(
|
||||||
|
"https://api.technicpack.net/search?build=multimc&q=%1"
|
||||||
|
).arg(currentSearchTerm);
|
||||||
|
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
|
||||||
|
jobPtr = netJob;
|
||||||
|
jobPtr->start();
|
||||||
|
QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::searchRequestFinished);
|
||||||
|
QObject::connect(netJob, &NetJob::failed, this, &ListModel::searchRequestFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Technic::ListModel::searchRequestFinished()
|
||||||
|
{
|
||||||
|
jobPtr.reset();
|
||||||
|
|
||||||
|
QJsonParseError parse_error;
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
|
||||||
|
if(parse_error.error != QJsonParseError::NoError)
|
||||||
|
{
|
||||||
|
qWarning() << "Error while parsing JSON response from Technic at " << parse_error.offset << " reason: " << parse_error.errorString();
|
||||||
|
qWarning() << response;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<Modpack> newList;
|
||||||
|
auto objs = doc["modpacks"].toArray();
|
||||||
|
for (auto technicPack: objs) {
|
||||||
|
Modpack pack;
|
||||||
|
auto technicPackObject = technicPack.toObject();
|
||||||
|
pack.name = technicPackObject["name"].toString();
|
||||||
|
pack.slug = technicPackObject["slug"].toString();
|
||||||
|
if (pack.slug == "vanilla")
|
||||||
|
continue;
|
||||||
|
if (technicPackObject["iconUrl"].isString())
|
||||||
|
{
|
||||||
|
pack.logoUrl = technicPackObject["iconUrl"].toString();
|
||||||
|
pack.logoName = pack.logoUrl.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pack.logoUrl = "null";
|
||||||
|
pack.logoName = "null";
|
||||||
|
}
|
||||||
|
pack.broken = false;
|
||||||
|
newList.append(pack);
|
||||||
|
}
|
||||||
|
searchState = Finished;
|
||||||
|
beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1);
|
||||||
|
modpacks.append(newList);
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Technic::ListModel::getLogo(const QString& logo, const QString& logoUrl, Technic::LogoCallback callback)
|
||||||
|
{
|
||||||
|
if(m_logoMap.contains(logo))
|
||||||
|
{
|
||||||
|
callback(ENV.metacache()->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo))->getFullPath());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
requestLogo(logo, logoUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Technic::ListModel::searchRequestFailed()
|
||||||
|
{
|
||||||
|
jobPtr.reset();
|
||||||
|
|
||||||
|
if(searchState == ResetRequested)
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
modpacks.clear();
|
||||||
|
endResetModel();
|
||||||
|
|
||||||
|
performSearch();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
searchState = Finished;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Technic::ListModel::logoLoaded(QString logo, QString out)
|
||||||
|
{
|
||||||
|
m_loadingLogos.removeAll(logo);
|
||||||
|
m_logoMap.insert(logo, QIcon(out));
|
||||||
|
for(int i = 0; i < modpacks.size(); i++)
|
||||||
|
{
|
||||||
|
if(modpacks[i].logoName == logo)
|
||||||
|
{
|
||||||
|
emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Technic::ListModel::logoFailed(QString logo)
|
||||||
|
{
|
||||||
|
m_failedLogos.append(logo);
|
||||||
|
m_loadingLogos.removeAll(logo);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Technic::ListModel::requestLogo(QString logo, QString url)
|
||||||
|
{
|
||||||
|
if(m_loadingLogos.contains(logo) || m_failedLogos.contains(logo) || logo == "null")
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MetaEntryPtr entry = ENV.metacache()->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo));
|
||||||
|
NetJob *job = new NetJob(QString("Technic Icon Download %1").arg(logo));
|
||||||
|
job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
|
||||||
|
|
||||||
|
auto fullPath = entry->getFullPath();
|
||||||
|
|
||||||
|
QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath]
|
||||||
|
{
|
||||||
|
logoLoaded(logo, fullPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(job, &NetJob::failed, this, [this, logo]
|
||||||
|
{
|
||||||
|
logoFailed(logo);
|
||||||
|
});
|
||||||
|
|
||||||
|
job->start();
|
||||||
|
|
||||||
|
m_loadingLogos.append(logo);
|
||||||
|
}
|
70
application/pages/modplatform/technic/TechnicModel.h
Normal file
70
application/pages/modplatform/technic/TechnicModel.h
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/* Copyright 2020 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QModelIndex>
|
||||||
|
|
||||||
|
#include "TechnicData.h"
|
||||||
|
#include "net/NetJob.h"
|
||||||
|
|
||||||
|
namespace Technic {
|
||||||
|
|
||||||
|
typedef std::function<void(QString)> LogoCallback;
|
||||||
|
|
||||||
|
class ListModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
ListModel(QObject *parent);
|
||||||
|
virtual ~ListModel();
|
||||||
|
|
||||||
|
virtual QVariant data(const QModelIndex& index, int role) const;
|
||||||
|
virtual int columnCount(const QModelIndex& parent) const;
|
||||||
|
virtual int rowCount(const QModelIndex& parent) const;
|
||||||
|
|
||||||
|
void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback);
|
||||||
|
void searchWithTerm(const QString & term);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void searchRequestFinished();
|
||||||
|
void searchRequestFailed();
|
||||||
|
|
||||||
|
void logoFailed(QString logo);
|
||||||
|
void logoLoaded(QString logo, QString out);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void performSearch();
|
||||||
|
void requestLogo(QString logo, QString url);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<Modpack> modpacks;
|
||||||
|
QStringList m_failedLogos;
|
||||||
|
QStringList m_loadingLogos;
|
||||||
|
QMap<QString, QIcon> m_logoMap;
|
||||||
|
QMap<QString, LogoCallback> waitingCallbacks;
|
||||||
|
|
||||||
|
QString currentSearchTerm;
|
||||||
|
enum SearchState {
|
||||||
|
None,
|
||||||
|
ResetRequested,
|
||||||
|
Finished
|
||||||
|
} searchState = None;
|
||||||
|
NetJobPtr jobPtr;
|
||||||
|
QByteArray response;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
204
application/pages/modplatform/technic/TechnicPage.cpp
Normal file
204
application/pages/modplatform/technic/TechnicPage.cpp
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
/* Copyright 2013-2020 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "TechnicPage.h"
|
||||||
|
#include "ui_TechnicPage.h"
|
||||||
|
|
||||||
|
#include "MultiMC.h"
|
||||||
|
#include "dialogs/NewInstanceDialog.h"
|
||||||
|
#include "TechnicModel.h"
|
||||||
|
#include <QKeyEvent>
|
||||||
|
#include "modplatform/technic/SingleZipPackInstallTask.h"
|
||||||
|
#include "modplatform/technic/SolderPackInstallTask.h"
|
||||||
|
#include "Json.h"
|
||||||
|
|
||||||
|
TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget *parent)
|
||||||
|
: QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
connect(ui->searchButton, &QPushButton::clicked, this, &TechnicPage::triggerSearch);
|
||||||
|
ui->searchEdit->installEventFilter(this);
|
||||||
|
model = new Technic::ListModel(this);
|
||||||
|
ui->packView->setModel(model);
|
||||||
|
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TechnicPage::onSelectionChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TechnicPage::eventFilter(QObject* watched, QEvent* event)
|
||||||
|
{
|
||||||
|
if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
|
||||||
|
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||||
|
if (keyEvent->key() == Qt::Key_Return) {
|
||||||
|
triggerSearch();
|
||||||
|
keyEvent->accept();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QWidget::eventFilter(watched, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
TechnicPage::~TechnicPage()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TechnicPage::shouldDisplay() const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TechnicPage::openedImpl()
|
||||||
|
{
|
||||||
|
dialog->setSuggestedPack();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TechnicPage::triggerSearch() {
|
||||||
|
model->searchWithTerm(ui->searchEdit->text());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TechnicPage::onSelectionChanged(QModelIndex first, QModelIndex second)
|
||||||
|
{
|
||||||
|
if(!first.isValid())
|
||||||
|
{
|
||||||
|
if(isOpened)
|
||||||
|
{
|
||||||
|
dialog->setSuggestedPack();
|
||||||
|
}
|
||||||
|
//ui->frame->clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = model->data(first, Qt::UserRole).value<Technic::Modpack>();
|
||||||
|
suggestCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TechnicPage::suggestCurrent()
|
||||||
|
{
|
||||||
|
if (!isOpened)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (current.broken)
|
||||||
|
{
|
||||||
|
dialog->setSuggestedPack();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString editedLogoName;
|
||||||
|
editedLogoName = "technic_" + current.logoName.section(".", 0, 0);
|
||||||
|
model->getLogo(current.logoName, current.logoUrl, [this, editedLogoName](QString logo)
|
||||||
|
{
|
||||||
|
dialog->setSuggestedIconFromFile(logo, editedLogoName);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (current.metadataLoaded)
|
||||||
|
{
|
||||||
|
metadataLoaded();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NetJob *netJob = new NetJob(QString("Technic::PackMeta(%1)").arg(current.name));
|
||||||
|
std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
|
||||||
|
QString slug = current.slug;
|
||||||
|
netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.technicpack.net/modpack/%1?build=multimc").arg(slug), response.get()));
|
||||||
|
QObject::connect(netJob, &NetJob::succeeded, this, [this, response, slug]
|
||||||
|
{
|
||||||
|
if (current.slug != slug)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QJsonParseError parse_error;
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||||
|
QJsonObject obj = doc.object();
|
||||||
|
if(parse_error.error != QJsonParseError::NoError)
|
||||||
|
{
|
||||||
|
qWarning() << "Error while parsing JSON response from Technic at " << parse_error.offset << " reason: " << parse_error.errorString();
|
||||||
|
qWarning() << *response;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!obj.contains("url"))
|
||||||
|
{
|
||||||
|
qWarning() << "Json doesn't contain an url key";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QJsonValueRef url = obj["url"];
|
||||||
|
if (url.isString())
|
||||||
|
{
|
||||||
|
current.url = url.toString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!obj.contains("solder"))
|
||||||
|
{
|
||||||
|
qWarning() << "Json doesn't contain a valid url or solder key";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QJsonValueRef solderUrl = obj["solder"];
|
||||||
|
if (solderUrl.isString())
|
||||||
|
{
|
||||||
|
current.url = solderUrl.toString();
|
||||||
|
current.isSolder = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qWarning() << "Json doesn't contain a valid url or solder key";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
current.minecraftVersion = Json::ensureString(obj, "minecraft", QString(), "__placeholder__");
|
||||||
|
current.metadataLoaded = true;
|
||||||
|
metadataLoaded();
|
||||||
|
});
|
||||||
|
netJob->start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// expects current.metadataLoaded to be true
|
||||||
|
void TechnicPage::metadataLoaded()
|
||||||
|
{
|
||||||
|
/*QString text = "";
|
||||||
|
QString name = current.name;
|
||||||
|
|
||||||
|
if (current.websiteUrl.isEmpty())
|
||||||
|
text = name;
|
||||||
|
else
|
||||||
|
text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
|
||||||
|
if (!current.authors.empty()) {
|
||||||
|
auto authorToStr = [](Technic::ModpackAuthor & author) {
|
||||||
|
if(author.url.isEmpty()) {
|
||||||
|
return author.name;
|
||||||
|
}
|
||||||
|
return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name);
|
||||||
|
};
|
||||||
|
QStringList authorStrs;
|
||||||
|
for(auto & author: current.authors) {
|
||||||
|
authorStrs.push_back(authorToStr(author));
|
||||||
|
}
|
||||||
|
text += tr(" by ") + authorStrs.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
ui->frame->setModText(text);
|
||||||
|
ui->frame->setModDescription(current.description);*/
|
||||||
|
if (!current.isSolder)
|
||||||
|
{
|
||||||
|
dialog->setSuggestedPack(current.name, new Technic::SingleZipPackInstallTask(current.url, current.minecraftVersion));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while (current.url.endsWith('/')) current.url.chop(1);
|
||||||
|
dialog->setSuggestedPack(current.name, new Technic::SolderPackInstallTask(current.url + "/modpack/" + current.slug, current.minecraftVersion));
|
||||||
|
}
|
||||||
|
}
|
78
application/pages/modplatform/technic/TechnicPage.h
Normal file
78
application/pages/modplatform/technic/TechnicPage.h
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/* Copyright 2013-2020 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include "pages/BasePage.h"
|
||||||
|
#include <MultiMC.h>
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
#include "TechnicData.h"
|
||||||
|
|
||||||
|
namespace Ui
|
||||||
|
{
|
||||||
|
class TechnicPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
class NewInstanceDialog;
|
||||||
|
|
||||||
|
namespace Technic {
|
||||||
|
class ListModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TechnicPage : public QWidget, public BasePage
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit TechnicPage(NewInstanceDialog* dialog, QWidget *parent = 0);
|
||||||
|
virtual ~TechnicPage();
|
||||||
|
virtual QString displayName() const override
|
||||||
|
{
|
||||||
|
return tr("Technic");
|
||||||
|
}
|
||||||
|
virtual QIcon icon() const override
|
||||||
|
{
|
||||||
|
return MMC->getThemedIcon("technic");
|
||||||
|
}
|
||||||
|
virtual QString id() const override
|
||||||
|
{
|
||||||
|
return "technic";
|
||||||
|
}
|
||||||
|
virtual QString helpPage() const override
|
||||||
|
{
|
||||||
|
return "Technic-platform";
|
||||||
|
}
|
||||||
|
virtual bool shouldDisplay() const override;
|
||||||
|
|
||||||
|
void openedImpl() override;
|
||||||
|
|
||||||
|
bool eventFilter(QObject* watched, QEvent* event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void suggestCurrent();
|
||||||
|
void metadataLoaded();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void triggerSearch();
|
||||||
|
void onSelectionChanged(QModelIndex first, QModelIndex second);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::TechnicPage *ui = nullptr;
|
||||||
|
NewInstanceDialog* dialog = nullptr;
|
||||||
|
Technic::ListModel* model = nullptr;
|
||||||
|
Technic::Modpack current;
|
||||||
|
};
|
62
application/pages/modplatform/technic/TechnicPage.ui
Normal file
62
application/pages/modplatform/technic/TechnicPage.ui
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>TechnicPage</class>
|
||||||
|
<widget class="QWidget" name="TechnicPage">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>546</width>
|
||||||
|
<height>405</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QWidget" name="widget" native="true">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="searchEdit"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="searchButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Search</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QListView" name="packView">
|
||||||
|
<property name="horizontalScrollBarPolicy">
|
||||||
|
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||||
|
</property>
|
||||||
|
<property name="alternatingRowColors">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="iconSize">
|
||||||
|
<size>
|
||||||
|
<width>48</width>
|
||||||
|
<height>48</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
@ -104,7 +104,7 @@ void ListModel::requestLogo(QString logo, QString url)
|
|||||||
job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
|
job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
|
||||||
|
|
||||||
auto fullPath = entry->getFullPath();
|
auto fullPath = entry->getFullPath();
|
||||||
QObject::connect(job, &NetJob::finished, this, [this, logo, fullPath]
|
QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath]
|
||||||
{
|
{
|
||||||
emit logoLoaded(logo, QIcon(fullPath));
|
emit logoLoaded(logo, QIcon(fullPath));
|
||||||
if(waitingCallbacks.contains(logo))
|
if(waitingCallbacks.contains(logo))
|
||||||
|
BIN
application/resources/assets/underconstruction.png
Normal file
BIN
application/resources/assets/underconstruction.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
Loading…
Reference in New Issue
Block a user