Compare commits

..

23 commits

Author SHA1 Message Date
Alexandru Ionut Tripon 9a9e8573aa
[release-11.x] chore: bump version (#5376) 2026-04-12 18:38:53 +03:00
Octol1ttle 399482270f
chore: bump version
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
2026-04-12 20:35:15 +05:00
Alexandru Ionut Tripon 50df409b28
[Backport release-11.x] Updater: Do not reset current task in finished signal (#5372) 2026-04-12 11:52:52 +03:00
Octol1ttle add3d01f84 fix(updater): do not reset current task in finished signal
The order of signals in case of a success is "succeeded"->"finished"

The "succeeded" signal may launch another download if the updater needs to fetch more pages
But if we reset the task then the newly started download will be disposed and the updater will softlock

Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit 9b270f783e)
2026-04-12 08:40:20 +00:00
Alexandru Ionut Tripon a71b8d8fe3
[Backport release-11.x] enable modpack changelog for modrinth page (#5360) 2026-04-11 09:36:29 +03:00
Trial97 6d38b34c00 enable modpack changelog for modrinth page
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit f3ff0a730a)
2026-04-11 05:37:04 +00:00
Alexandru Ionut Tripon 85f19da603
[Backport release-11.x] fix pack upgrade (#5356) 2026-04-10 20:32:09 +03:00
Trial97 239be1ec43 fix pack upgrade
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit b7344af313)
2026-04-10 17:31:48 +00:00
Alexandru Ionut Tripon 34349a6810
[Backport release-11.x] Allow disabling low RAM warning (#5350) 2026-04-10 15:23:20 +03:00
Octol1ttle fb7e4da4e6 Change LowMemWarning default to always enabled
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit 4b3aedd5d0)
2026-04-10 09:31:16 +00:00
Octol1ttle f766cdd847 change(EnsureAvailableMemory): add lenience
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit 658a1391f8)
2026-04-10 09:31:16 +00:00
Octol1ttle 789c656463 feat: allow disabling low RAM warning
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit c044ed36af)
2026-04-10 09:31:16 +00:00
Alexandru Ionut Tripon 2d01dfa4f2
[Backport release-11.x] fix McClient (#5344) 2026-04-10 00:50:18 +03:00
Alexandru Ionut Tripon 9231fe8592
[Backport release-11.x] Don't count JAR mods when checking offline libraries (#5343) 2026-04-10 00:49:58 +03:00
Alexandru Ionut Tripon 25fee30f95
[Backport release-11.x] CI/Nix: Bump macOS (#5342) 2026-04-10 00:49:42 +03:00
Alexandru Ionut Tripon ec6a173608
[Backport release-11.x] fix(PrintInstanceInfo): add break before OS info (#5341) 2026-04-10 00:49:24 +03:00
Alexandru Ionut Tripon b7d70dc1c1
chore: bump to 11.0.1 (#5340) 2026-04-10 00:49:09 +03:00
Octol1ttle 09f0467e81 refactor: McClient
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit 91616ae9b6)
2026-04-09 21:06:40 +00:00
Octol1ttle fe02ad8524 fix(McClient): do not use unsigned type for response length
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit 2fe0569bd6)
2026-04-09 21:06:40 +00:00
Octol1ttle f66796e806 fix: don't count JAR mods when checking offline libraries
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit ec4484282c)
2026-04-09 20:56:32 +00:00
Octol1ttle 4cd8c343fe fix(CI/nix): bump macOS
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit 724c9a4a2c)
2026-04-09 20:47:44 +00:00
Octol1ttle 960e1bac87 fix(PrintInstanceInfo): add break before OS info
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit 4cf8cf7d18)
2026-04-09 20:46:36 +00:00
Trial97 838e7fb8d2 chore: bump to 11.0.1
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2026-04-09 23:33:42 +03:00
55 changed files with 253 additions and 431 deletions

View file

@ -55,7 +55,7 @@ runs:
# TODO(@getchoo): Get this working on MSYS2!
- name: Setup ccache
if: ${{ (runner.os != 'Windows' || inputs.msystem == '') && inputs.build-type == 'Debug' }}
uses: hendrikmuhs/ccache-action@v1.2.23
uses: hendrikmuhs/ccache-action@v1.2.22
with:
variant: sccache
create-symlink: ${{ runner.os != 'Windows' }}

View file

@ -91,7 +91,7 @@ runs:
- name: Retrieve ccache cache (MinGW)
if: ${{ inputs.msystem != '' && inputs.build-type == 'Debug' }}
uses: actions/cache@v5.0.5
uses: actions/cache@v5.0.4
with:
path: '${{ github.workspace }}\.ccache'
key: ${{ runner.os }}-mingw-w64-ccache-${{ github.run_id }}

View file

@ -24,7 +24,7 @@ jobs:
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Create backport PRs
uses: korthout/backport-action@v4.4
uses: korthout/backport-action@v4.3.0
with:
# Config README: https://github.com/korthout/backport-action#backport-action
pull_description: |-

View file

@ -29,7 +29,7 @@ jobs:
submodules: "true"
- name: Setup sccache
uses: hendrikmuhs/ccache-action@v1.2.23
uses: hendrikmuhs/ccache-action@v1.2.22
with:
variant: sccache

View file

@ -33,7 +33,7 @@ jobs:
- arch: arm64
os: ubuntu-24.04-arm
- arch: amd64
os: ubuntu-24.04
os: ubuntu-24.04-arm
runs-on: ${{ matrix.os }}

View file

@ -94,7 +94,7 @@ jobs:
- name: Create release
id: create_release
uses: softprops/action-gh-release@v3
uses: softprops/action-gh-release@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
tag_name: ${{ github.ref }}

View file

@ -20,7 +20,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- uses: cachix/install-nix-action@616559265b40713947b9c190a8ff4b507b5df49b # v31
- uses: cachix/install-nix-action@96951a368ba55167b55f1c916f7d416bac6505fe # v31
- uses: DeterminateSystems/update-flake-lock@v28
with:

View file

@ -179,9 +179,9 @@ set(Launcher_LOGIN_CALLBACK_URL "https://prismlauncher.org/successful-login" CAC
set(Launcher_LEGACY_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE STRING "URL for legacy (<=1.5.2) FML Libraries.")
######## Set version numbers ########
set(Launcher_VERSION_MAJOR 12)
set(Launcher_VERSION_MAJOR 11)
set(Launcher_VERSION_MINOR 0)
set(Launcher_VERSION_PATCH 0)
set(Launcher_VERSION_PATCH 2)
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}")
set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}.0")

View file

@ -18,11 +18,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1776169885,
"narHash": "sha256-Gk2T0tDDDAs319hp/ak+bAIUG5bPMvnNEjPV8CS86Fg=",
"rev": "4bd9165a9165d7b5e33ae57f3eecbcb28fb231c9",
"lastModified": 1774709303,
"narHash": "sha256-D4ely1FsBcvtj/qSrNhSWpq+CUZKNiKwJIxpxnfy9o4=",
"rev": "8110df5ad7abf5d4c0f6fb0f8f978390e77f9685",
"type": "tarball",
"url": "https://releases.nixos.org/nixos/unstable/nixos-26.05pre980183.4bd9165a9165/nixexprs.tar.xz"
"url": "https://releases.nixos.org/nixos/unstable/nixos-26.05pre971119.8110df5ad7ab/nixexprs.tar.xz"
},
"original": {
"type": "tarball",

View file

@ -871,7 +871,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
resetIfInvalid(m_settings->registerSetting("LegacyFMLLibsURLOverride", "").get());
}
m_settings->registerSetting("MetaRefreshOnLaunch", true);
m_settings->registerSetting("CloseAfterLaunch", false);
m_settings->registerSetting("QuitAfterGameStop", false);

View file

@ -63,7 +63,7 @@ class BaseVersionList : public QAbstractListModel {
* The task returned by this function should reset the model when it's done.
* \return A pointer to a task that reloads the version list.
*/
virtual Task::Ptr getLoadTask(bool forceReload = false) = 0;
virtual Task::Ptr getLoadTask() = 0;
//! Checks whether or not the list is loaded. If this returns false, the list should be
// loaded.

View file

@ -141,6 +141,7 @@ uint64_t HardwareInfo::availableRamMiB()
}
#elif defined(Q_OS_MACOS)
#include "mach/mach.h"
#include "sys/sysctl.h"
QString HardwareInfo::cpuInfo()
@ -170,34 +171,20 @@ uint64_t HardwareInfo::totalRamMiB()
uint64_t HardwareInfo::availableRamMiB()
{
mach_port_t host_port = mach_host_self();
mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
vm_statistics64_data_t vm_stats;
if (host_statistics64(host_port, HOST_VM_INFO64, reinterpret_cast<host_info64_t>(&vm_stats), &count) == KERN_SUCCESS) {
// transforming bytes -> mib
return (vm_stats.free_count + vm_stats.inactive_count) * vm_page_size / 1024 / 1024;
}
qWarning() << "Could not get available RAM: host_statistics64";
return 0;
}
MacOSHardwareInfo::MemoryPressureLevel MacOSHardwareInfo::memoryPressureLevel()
{
uint32_t level;
size_t levelSize = sizeof level;
if (sysctlbyname("kern.memorystatus_vm_pressure_level", &level, &levelSize, nullptr, 0) == 0) {
return static_cast<MemoryPressureLevel>(level);
}
qWarning() << "Could not get memory pressure level: sysctlbyname";
return MemoryPressureLevel::Normal;
}
QString MacOSHardwareInfo::memoryPressureLevelName()
{
// The names are internal, users refer to levels by their graph colors in Activity Monitor
switch (memoryPressureLevel()) {
case MemoryPressureLevel::Normal:
return "Green";
case MemoryPressureLevel::Warning:
return "Yellow";
case MemoryPressureLevel::Critical:
return "Red";
}
}
#elif defined(Q_OS_LINUX)
#include <fstream>

View file

@ -27,16 +27,3 @@ uint64_t totalRamMiB();
uint64_t availableRamMiB();
QStringList gpuInfo();
} // namespace HardwareInfo
#ifdef Q_OS_MACOS
namespace MacOSHardwareInfo {
enum class MemoryPressureLevel : uint8_t {
Normal = 1,
Warning = 2,
Critical = 4,
};
MemoryPressureLevel memoryPressureLevel();
QString memoryPressureLevelName();
} // namespace MacOSHardwareInfo
#endif

View file

@ -18,7 +18,7 @@ bool InstanceCreationTask::abort()
return m_gameFilesTask->abort();
}
return InstanceTask::abort();
return true;
}
void InstanceCreationTask::executeTask()

View file

@ -51,9 +51,8 @@ JavaInstallList::JavaInstallList(QObject* parent, bool onlyManagedVersions)
: BaseVersionList(parent), m_only_managed_versions(onlyManagedVersions)
{}
Task::Ptr JavaInstallList::getLoadTask(bool forceReload)
Task::Ptr JavaInstallList::getLoadTask()
{
Q_UNUSED(forceReload)
load();
return getCurrentTask();
}

View file

@ -35,7 +35,7 @@ class JavaInstallList : public BaseVersionList {
public:
explicit JavaInstallList(QObject* parent = 0, bool onlyManagedVersions = false);
Task::Ptr getLoadTask(bool forceReload = false) override;
Task::Ptr getLoadTask() override;
bool isLoaded() override;
const BaseVersion::Ptr at(int i) const override;
int count() const override;

View file

@ -82,12 +82,12 @@ QUrl BaseEntity::url() const
return QUrl(metaOverride).resolved(localFilename());
}
Task::Ptr BaseEntity::loadTask(Net::Mode mode, bool forceReload)
Task::Ptr BaseEntity::loadTask(Net::Mode mode)
{
if (m_task && m_task->isRunning()) {
return m_task;
}
m_task.reset(new BaseEntityLoadTask(this, mode, forceReload));
m_task.reset(new BaseEntityLoadTask(this, mode));
return m_task;
}
@ -107,9 +107,7 @@ BaseEntity::LoadStatus BaseEntity::status() const
return m_load_status;
}
BaseEntityLoadTask::BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode, bool forceReload)
: m_entity(parent), m_mode(mode), m_force_reload(forceReload)
{}
BaseEntityLoadTask::BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode) : m_entity(parent), m_mode(mode) {}
void BaseEntityLoadTask::executeTask()
{
@ -127,11 +125,9 @@ void BaseEntityLoadTask::executeTask()
}
// on online the hash needs to match
const auto& expected = m_entity->m_sha256;
const auto& actual = m_entity->m_file_sha256;
hashMatches = expected == actual;
hashMatches = m_entity->m_sha256 == m_entity->m_file_sha256;
if (m_mode == Net::Mode::Online && !m_entity->m_sha256.isEmpty() && !hashMatches) {
throw Exception(QString("Checksum mismatch, expected sha256: %1, got: %2").arg(expected, actual));
throw Exception("mismatched checksum");
}
// load local file
@ -153,18 +149,13 @@ void BaseEntityLoadTask::executeTask()
auto wasLoadedOffline = m_entity->m_load_status != BaseEntity::LoadStatus::NotLoaded && m_mode == Net::Mode::Offline;
// if has is not present allways fetch from remote(e.g. the main index file), else only fetch if hash doesn't match
auto wasLoadedRemote = m_entity->m_sha256.isEmpty() ? m_entity->m_load_status == BaseEntity::LoadStatus::Remote : hashMatches;
if (wasLoadedOffline || (wasLoadedRemote && !m_force_reload)) {
if (wasLoadedOffline || wasLoadedRemote) {
emitSucceeded();
return;
}
m_task.reset(new NetJob(QObject::tr("Download of meta file %1").arg(m_entity->localFilename()), APPLICATION->network()));
auto url = m_entity->url();
auto entry = APPLICATION->metacache()->resolveEntry("meta", m_entity->localFilename());
if (m_force_reload) {
// clear validators so manual refreshes fetch a fresh body
entry->setETag({});
entry->setRemoteChangedTimestamp({});
}
entry->setStale(true);
auto dl = Net::ApiDownload::makeCached(url, entry);
/*

View file

@ -43,7 +43,7 @@ class BaseEntity {
void setSha256(QString sha256);
virtual void parse(const QJsonObject& obj) = 0;
[[nodiscard]] Task::Ptr loadTask(Net::Mode loadType = Net::Mode::Online, bool forceReload = false);
[[nodiscard]] Task::Ptr loadTask(Net::Mode loadType = Net::Mode::Online);
protected:
QString m_sha256; // the expected sha256
@ -58,7 +58,7 @@ class BaseEntityLoadTask : public Task {
Q_OBJECT
public:
explicit BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode, bool forceReload);
explicit BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode);
~BaseEntityLoadTask() override = default;
virtual void executeTask() override;
@ -68,7 +68,6 @@ class BaseEntityLoadTask : public Task {
private:
BaseEntity* m_entity;
Net::Mode m_mode;
bool m_force_reload = false;
NetJob::Ptr m_task;
};
} // namespace Meta

View file

@ -15,7 +15,6 @@
#include "Index.h"
#include "Application.h"
#include "JsonFormat.h"
#include "QObjectPtr.h"
#include "VersionList.h"
@ -136,7 +135,7 @@ void Index::connectVersionList(const int row, const VersionList::Ptr& list)
Task::Ptr Index::loadVersion(const QString& uid, const QString& version, Net::Mode mode, bool force)
{
if (mode == Net::Mode::Offline || !APPLICATION->settings()->get("MetaRefreshOnLaunch").toBool()) {
if (mode == Net::Mode::Offline) {
return get(uid, version)->loadTask(mode);
}

View file

@ -32,11 +32,11 @@ VersionList::VersionList(const QString& uid, QObject* parent) : BaseVersionList(
setObjectName("Version list: " + uid);
}
Task::Ptr VersionList::getLoadTask(bool forceReload)
Task::Ptr VersionList::getLoadTask()
{
auto loadTask = makeShared<SequentialTask>(tr("Load meta for %1", "This is for the task name that loads the meta index.").arg(m_uid));
loadTask->addTask(APPLICATION->metadataIndex()->loadTask(Net::Mode::Online, forceReload));
loadTask->addTask(this->loadTask(Net::Mode::Online, forceReload));
loadTask->addTask(APPLICATION->metadataIndex()->loadTask(Net::Mode::Online));
loadTask->addTask(this->loadTask(Net::Mode::Online));
return loadTask;
}

View file

@ -37,7 +37,7 @@ class VersionList : public BaseVersionList, public BaseEntity {
enum Roles { UidRole = Qt::UserRole + 100, TimeRole, RequiresRole, VersionPtrRole };
bool isLoaded() override;
Task::Ptr getLoadTask(bool forceReload = false) override;
Task::Ptr getLoadTask() override;
const BaseVersion::Ptr at(int i) const override;
int count() const override;
void sortVersions() override;

View file

@ -149,7 +149,7 @@ QList<Net::NetRequest::Ptr> Library::getDownloads(const RuntimeContext& runtimeC
if (sha1.size()) {
auto dl = Net::ApiDownload::makeCached(url, entry, options);
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, sha1));
qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url << "expected sha1:" << sha1;
qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
out.append(dl);
} else {
out.append(Net::ApiDownload::makeCached(url, entry, options));

View file

@ -157,8 +157,7 @@ bool WorldList::resetIcon(int row)
return false;
World& m = m_worlds[row];
if (m.resetIcon()) {
QModelIndex modelIndex = index(row, NameColumn);
emit dataChanged(modelIndex, modelIndex, { WorldList::IconFileRole });
emit dataChanged(index(row), index(row), { WorldList::IconFileRole });
return true;
}
return false;
@ -427,7 +426,7 @@ void WorldList::loadWorldsAsync()
m_worlds[row].setSize(size);
// Notify views
QModelIndex modelIndex = index(row, SizeColumn);
QModelIndex modelIndex = index(row);
emit dataChanged(modelIndex, modelIndex, { SizeRole });
}
},

View file

@ -25,72 +25,22 @@ EnsureAvailableMemory::EnsureAvailableMemory(LaunchTask* parent, MinecraftInstan
void EnsureAvailableMemory::executeTask()
{
#ifdef Q_OS_MACOS
QString text;
switch (MacOSHardwareInfo::memoryPressureLevel()) {
case MacOSHardwareInfo::MemoryPressureLevel::Normal:
emitSucceeded();
return;
case MacOSHardwareInfo::MemoryPressureLevel::Warning:
text =
tr("The system is under increased memory pressure.\n"
"This may lead to lag or slowdowns.\n"
"If possible, close other applications before continuing.\n\n"
"Launch anyway?");
break;
case MacOSHardwareInfo::MemoryPressureLevel::Critical:
text =
tr("Your system is under critical memory pressure.\n"
"This may lead to severe slowdowns, crashes or system instability.\n"
"It is recommended to close other applications or restart your system.\n\n"
"Launch anyway?");
break;
}
bool shouldAbort = false;
if (m_instance->settings()->get("LowMemWarning").toBool()) {
auto* dialog = CustomMessageBox::selectable(nullptr, tr("High memory pressure"), text, QMessageBox::Icon::Warning,
QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No,
QMessageBox::StandardButton::No);
shouldAbort = dialog->exec() == QMessageBox::No;
dialog->deleteLater();
}
const auto message = tr("The system is under high memory pressure");
if (shouldAbort) {
emit logLine(message, MessageLevel::Fatal);
emitFailed(message);
return;
}
emit logLine(message, MessageLevel::Warning);
emitSucceeded();
#else
const uint64_t available = HardwareInfo::availableRamMiB();
if (available == 0) {
// could not read
emitSucceeded();
return;
}
const uint64_t min = m_instance->settings()->get("MinMemAlloc").toUInt();
const uint64_t max = m_instance->settings()->get("MaxMemAlloc").toUInt();
const uint64_t required = std::max(min, max);
const uint64_t settingMin = m_instance->settings()->get("MinMemAlloc").toUInt();
const uint64_t settingMax = m_instance->settings()->get("MaxMemAlloc").toUInt();
const uint64_t max = std::max(settingMin, settingMax);
if (static_cast<double>(max) * 0.9 > static_cast<double>(available)) {
if (static_cast<double>(required) * 0.9 > static_cast<double>(available)) {
bool shouldAbort = false;
if (m_instance->settings()->get("LowMemWarning").toBool()) {
auto* dialog = CustomMessageBox::selectable(
nullptr, tr("Low free memory"),
tr("There might not be enough free RAM to launch this instance with the current memory settings.\n\n"
"Maximum allocated: %1 MiB\nFree: %2 MiB (out of %3 MiB total)\n\n"
"Launch anyway? This may cause slowdowns in the game and your system.")
.arg(max)
.arg(available)
.arg(HardwareInfo::totalRamMiB()),
nullptr, tr("Not enough RAM"),
tr("There is not enough RAM available to launch this instance with the current memory settings.\n\n"
"Required: %1 MiB\nAvailable: %2 MiB\n\n"
"Continue anyway? This may cause slowdowns in the game and your system.")
.arg(required)
.arg(available),
QMessageBox::Icon::Warning, QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No,
QMessageBox::StandardButton::No);
@ -109,5 +59,4 @@ void EnsureAvailableMemory::executeTask()
}
emitSucceeded();
#endif
}

View file

@ -68,12 +68,7 @@ void PrintInstanceInfo::executeTask()
::runPciconf(log);
#else
log << "CPU: " + HardwareInfo::cpuInfo();
#ifdef Q_OS_MACOS
log << "Memory pressure level: " + MacOSHardwareInfo::memoryPressureLevelName();
#else
log << QString("RAM: %1 MiB (available: %2 MiB)").arg(HardwareInfo::totalRamMiB()).arg(HardwareInfo::availableRamMiB());
#endif
#endif
log.append(HardwareInfo::gpuInfo());
log << "";

View file

@ -121,7 +121,7 @@ bool SkinList::update()
auto folderContents = m_dir.entryInfoList();
// if there are any untracked files...
for (QFileInfo entry : folderContents) {
if (!entry.isFile() || entry.suffix() != "png")
if (!entry.isFile() && entry.suffix() != "png")
continue;
SkinModel w(entry.absoluteFilePath());

View file

@ -148,9 +148,9 @@ Task::Ptr ResourceAPI::getProjectVersions(VersionSearchArgs&& args, Callback<QVe
return netJob;
}
Task::Ptr ResourceAPI::getProjectInfo(ProjectInfoArgs&& args, Callback<ModPlatform::IndexedPack::Ptr>&& callbacks, bool askRetry) const
Task::Ptr ResourceAPI::getProjectInfo(ProjectInfoArgs&& args, Callback<ModPlatform::IndexedPack::Ptr>&& callbacks) const
{
auto [job, response] = getProject(args.pack->addonId.toString(), askRetry);
auto [job, response] = getProject(args.pack->addonId.toString());
QObject::connect(job.get(), &NetJob::succeeded, [this, response, callbacks, args] {
auto pack = args.pack;
@ -284,7 +284,7 @@ QString ResourceAPI::mapMCVersionToModrinth(Version v) const
return verStr;
}
std::pair<Task::Ptr, QByteArray*> ResourceAPI::getProject(QString addonId, bool askRetry) const
std::pair<Task::Ptr, QByteArray*> ResourceAPI::getProject(QString addonId) const
{
auto project_url_optional = getInfoURL(addonId);
if (!project_url_optional.has_value())
@ -293,7 +293,6 @@ std::pair<Task::Ptr, QByteArray*> ResourceAPI::getProject(QString addonId, bool
auto project_url = project_url_optional.value();
auto netJob = makeShared<NetJob>(QString("%1::GetProject").arg(addonId), APPLICATION->network());
netJob->setAskRetry(askRetry);
auto [action, response] = Net::ApiDownload::makeByteArray(QUrl(project_url));
netJob->addNetAction(action);

View file

@ -115,10 +115,10 @@ class ResourceAPI {
public slots:
virtual Task::Ptr searchProjects(SearchArgs&&, Callback<QList<ModPlatform::IndexedPack::Ptr>>&&) const;
virtual std::pair<Task::Ptr, QByteArray*> getProject(QString addonId, bool askRetry = true) const;
virtual std::pair<Task::Ptr, QByteArray*> getProject(QString addonId) const;
virtual std::pair<Task::Ptr, QByteArray*> getProjects(QStringList addonIds) const = 0;
virtual Task::Ptr getProjectInfo(ProjectInfoArgs&&, Callback<ModPlatform::IndexedPack::Ptr>&&, bool askRetry = true) const;
virtual Task::Ptr getProjectInfo(ProjectInfoArgs&&, Callback<ModPlatform::IndexedPack::Ptr>&&) const;
Task::Ptr getProjectVersions(VersionSearchArgs&& args, Callback<QVector<ModPlatform::IndexedVersion>>&& callbacks) const;
virtual Task::Ptr getDependencyVersion(DependencySearchArgs&&, Callback<ModPlatform::IndexedVersion>&&) const;

View file

@ -38,6 +38,7 @@
#include "Validator.h"
#include <QCryptographicHash>
#include <QFile>
namespace Net {
class ChecksumValidator : public Validator {
@ -68,10 +69,10 @@ class ChecksumValidator : public Validator {
return true;
}
auto validate(QNetworkReply& reply) -> bool override
auto validate(QNetworkReply&) -> bool override
{
if (!m_expected.isEmpty() && m_expected != hash()) {
qWarning() << "Checksum mismatch for URL:" << reply.url().toString() << "expected:" << m_expected << "got:" << hash();
if (m_expected.size() && m_expected != hash()) {
qWarning() << "Checksum mismatch, download is bad.";
return false;
}
return true;

View file

@ -69,15 +69,11 @@ void NetJob::executeNextSubTask()
// We're finished, check for failures and retry if we can (up to 3 times)
if (isRunning() && m_queue.isEmpty() && m_doing.isEmpty() && !m_failed.isEmpty() && m_try < 3) {
m_try += 1;
m_failed.removeIf([this](QHash<Task*, Task::Ptr>::iterator task) {
// there is no point in retying on 404 Not Found
if (static_cast<Net::NetRequest*>(task->get())->replyStatusCode() == 404) {
return false;
}
m_done.remove(task->get());
m_queue.enqueue(*task);
return true;
});
while (!m_failed.isEmpty()) {
auto task = m_failed.take(*m_failed.keyBegin());
m_done.remove(task.get());
m_queue.enqueue(task);
}
}
ConcurrentTask::executeNextSubTask();
}
@ -104,18 +100,13 @@ auto NetJob::canAbort() const -> bool
auto NetJob::abort() -> bool
{
bool fullyAborted = true;
// fail all downloads on the queue
for (auto task : m_queue)
m_failed.insert(task.get(), task);
m_queue.clear();
if (m_doing.isEmpty()) {
// no downloads to abort, NetJob is not running
return true;
}
bool fullyAborted = true;
// abort active downloads
auto toKill = m_doing.values();
for (auto part : toKill) {

View file

@ -118,7 +118,7 @@ auto ImgurUpload::Sink::finalize(QNetworkReply&) -> Task::State
Net::NetRequest::Ptr ImgurUpload::make(ScreenShot::Ptr m_shot)
{
auto up = makeShared<ImgurUpload>(m_shot->m_file);
up->m_url = BuildConfig.IMGUR_BASE_URL + "image";
up->m_url = std::move(BuildConfig.IMGUR_BASE_URL + "image");
up->m_sink.reset(new Sink(m_shot));
up->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(QList<Net::HeaderPair>{
{ "Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toUtf8() }, { "Accept", "application/json" } }));

View file

@ -48,13 +48,6 @@ Task::Task(bool show_debug) : m_show_debug(show_debug)
setAutoDelete(false);
}
Task::~Task()
{
if (isRunning()) {
qCWarning(taskLogC) << "Task" << describe() << "disposed while running!";
}
}
void Task::setStatus(const QString& new_status)
{
if (m_status != new_status) {

View file

@ -94,7 +94,7 @@ class Task : public QObject, public QRunnable {
public:
explicit Task(bool show_debug_log = true);
~Task() override;
virtual ~Task() = default;
bool isRunning() const;
bool isFinished() const;
@ -165,7 +165,7 @@ class Task : public QObject, public QRunnable {
//! used by external code to ask the task to abort
virtual bool abort()
{
if (canAbort() && isRunning())
if (canAbort())
emitAborted();
return canAbort();
}

View file

@ -105,7 +105,6 @@
#include "ui/dialogs/NewInstanceDialog.h"
#include "ui/dialogs/NewsDialog.h"
#include "ui/dialogs/ProgressDialog.h"
#include "ui/dialogs/skins/SkinManageDialog.h"
#include "ui/instanceview/InstanceDelegate.h"
#include "ui/instanceview/InstanceProxyModel.h"
#include "ui/instanceview/InstanceView.h"
@ -181,7 +180,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
ui->instanceToolBar->insertSeparator(ui->actionLaunchInstance);
// restore the instance toolbar settings
const auto setting_name = QString("WideBarVisibility_%1").arg(ui->instanceToolBar->objectName());
auto const setting_name = QString("WideBarVisibility_%1").arg(ui->instanceToolBar->objectName());
instanceToolbarSetting = APPLICATION->settings()->getOrRegisterSetting(setting_name);
ui->instanceToolBar->setVisibilityState(QByteArray::fromBase64(instanceToolbarSetting->get().toString().toUtf8()));
@ -397,7 +396,6 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
// Shouldn't have to use lambdas here like this, but if I don't, the compiler throws a fit.
// Template hell sucks...
connect(APPLICATION->accounts(), &AccountList::defaultAccountChanged, [this] { defaultAccountChanged(); });
connect(APPLICATION->accounts(), &AccountList::listActivityChanged, [this] { defaultAccountChanged(); });
connect(APPLICATION->accounts(), &AccountList::listChanged, [this] { defaultAccountChanged(); });
// Show initial account
@ -655,9 +653,6 @@ void MainWindow::repopulateAccountsMenu()
auto accounts = APPLICATION->accounts();
MinecraftAccountPtr defaultAccount = accounts->defaultAccount();
bool canChangeSkin = defaultAccount && (defaultAccount->accountType() == AccountType::MSA) && !defaultAccount->isActive();
ui->actionManageSkins->setEnabled(canChangeSkin);
QString active_profileId = "";
if (defaultAccount) {
@ -714,7 +709,6 @@ void MainWindow::repopulateAccountsMenu()
connect(ui->actionNoDefaultAccount, &QAction::triggered, this, &MainWindow::changeActiveAccount);
ui->accountsMenu->addSeparator();
ui->accountsMenu->addAction(ui->actionManageSkins);
ui->accountsMenu->addAction(ui->actionManageAccounts);
accountsButtonMenu->addActions(ui->accountsMenu->actions());
@ -948,7 +942,9 @@ void MainWindow::processURLs(QList<QUrl> urls)
QUrl local_url;
if (!url.isLocalFile()) { // download the remote resource and identify
const bool isExternalURLImport = (url.host().toLower() == "import") || (url.path().startsWith("/import", Qt::CaseInsensitive));
const bool isExternalURLImport =
(url.host().toLower() == "import") ||
(url.path().startsWith("/import", Qt::CaseInsensitive));
QUrl dl_url;
if (url.scheme() == "curseforge" || (url.scheme() == BuildConfig.LAUNCHER_APP_BINARY_NAME && url.host() == "install")) {
@ -956,7 +952,7 @@ void MainWindow::processURLs(QList<QUrl> urls)
// format of url curseforge://install?addonId=IDHERE&fileId=IDHERE
// format of url binaryname://install?platform=curseforge&addonId=IDHERE&fileId=IDHERE
QUrlQuery query(url);
// check if this is a binaryname:// url
if (url.scheme() == BuildConfig.LAUNCHER_APP_BINARY_NAME) {
// check this is an curseforge platform request
@ -1019,7 +1015,8 @@ void MainWindow::processURLs(QList<QUrl> urls)
receivedData.insert(it->first, it->second);
emit APPLICATION->oauthReplyRecieved(receivedData);
continue;
} else if ((url.scheme() == "prismlauncher" || url.scheme() == BuildConfig.LAUNCHER_APP_BINARY_NAME) && isExternalURLImport) {
} else if ((url.scheme() == "prismlauncher" || url.scheme() == BuildConfig.LAUNCHER_APP_BINARY_NAME)
&& isExternalURLImport) {
// PrismLauncher URL protocol modpack import
// works for any prism fork
// preferred import format: prismlauncher://import?url=ENCODED
@ -1038,6 +1035,7 @@ void MainWindow::processURLs(QList<QUrl> urls)
// alternative import format: prismlauncher://import/ENCODED
if (encodedTarget.isEmpty()) {
QString p = path;
if (p.startsWith("/import/", Qt::CaseInsensitive)) {
@ -1052,9 +1050,12 @@ void MainWindow::processURLs(QList<QUrl> urls)
}
if (encodedTarget.isEmpty()) {
CustomMessageBox::selectable(this, tr("Error"), tr("Invalid import link: missing 'url' parameter."),
QMessageBox::Critical)
->show();
CustomMessageBox::selectable(
this,
tr("Error"),
tr("Invalid import link: missing 'url' parameter."),
QMessageBox::Critical
)->show();
continue;
}
@ -1064,15 +1065,23 @@ void MainWindow::processURLs(QList<QUrl> urls)
// Validate: only allow http(s)
if (!target.isValid() || (target.scheme() != "https" && target.scheme() != "http")) {
CustomMessageBox::selectable(this, tr("Error"), tr("Invalid import link: URL must be http(s)."), QMessageBox::Critical)
->show();
CustomMessageBox::selectable(
this,
tr("Error"),
tr("Invalid import link: URL must be http(s)."),
QMessageBox::Critical
)->show();
continue;
}
const auto res = QMessageBox::question(
this, tr("Install modpack"),
tr("Do you want to download and import a modpack from:\n%1\n\nURL:\n%2").arg(target.host(), target.toString()),
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
this,
tr("Install modpack"),
tr("Do you want to download and import a modpack from:\n%1\n\nURL:\n%2")
.arg(target.host(), target.toString()),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes
);
if (res != QMessageBox::Yes) {
continue;
}
@ -1387,16 +1396,6 @@ void MainWindow::on_actionEditInstance_triggered()
}
}
void MainWindow::on_actionManageSkins_triggered()
{
auto account = APPLICATION->accounts()->defaultAccount();
if (account && (account->accountType() == AccountType::MSA) && !account->isActive()) {
SkinManageDialog dialog(this, account);
dialog.exec();
}
}
void MainWindow::on_actionManageAccounts_triggered()
{
APPLICATION->ShowGlobalSettings(this, "accounts");

View file

@ -130,8 +130,6 @@ class MainWindow : public QMainWindow {
void on_actionSettings_triggered();
void on_actionManageSkins_triggered();
void on_actionManageAccounts_triggered();
void on_actionReportBug_triggered();

View file

@ -322,14 +322,6 @@
<enum>QAction::PreferencesRole</enum>
</property>
</action>
<action name="actionManageSkins">
<property name="icon">
<iconset theme="settings"/>
</property>
<property name="text">
<string>Manage &amp;Skins...</string>
</property>
</action>
<action name="actionManageAccounts">
<property name="icon">
<iconset theme="accounts"/>

View file

@ -101,7 +101,7 @@ InstallLoaderDialog::InstallLoaderDialog(PackProfile* profile, const QString& ui
buttonLayout->setContentsMargins(0, 0, 6, 6);
#endif
auto refreshButton = new QPushButton(tr("&Refresh"), this);
connect(refreshButton, &QPushButton::clicked, this, [this] { pageCast(container->selectedPage())->loadList(true); });
connect(refreshButton, &QPushButton::clicked, this, [this] { pageCast(container->selectedPage())->loadList(); });
buttonLayout->addWidget(refreshButton);
buttons->setOrientation(Qt::Horizontal);

View file

@ -252,7 +252,10 @@ void ProgressDialog::changeStepProgress(TaskStepProgress const& task_progress)
task_bar->setValue(mapped_current);
task_bar->setStatus(task_progress.status);
task_bar->setDetails(task_progress.details);
task_bar->setVisible(!task_progress.isDone());
if (task_progress.isDone()) {
task_bar->setVisible(false);
}
}
void ProgressDialog::changeProgress(qint64 current, qint64 total)

View file

@ -144,7 +144,7 @@ BaseVersion::Ptr VersionSelectDialog::selectedVersion() const
void VersionSelectDialog::on_refreshButton_clicked()
{
m_versionWidget->loadList(true);
m_versionWidget->loadList();
}
void VersionSelectDialog::setExactFilter(BaseVersionList::ModelRoles role, QString filter)

View file

@ -121,8 +121,8 @@ class InstallJavaPage : public QWidget, public BasePage {
void selectSearch() { javaVersionSelect->selectSearch(); }
void loadList()
{
majorVersionSelect->loadList(true);
javaVersionSelect->loadList(true);
majorVersionSelect->loadList();
javaVersionSelect->loadList();
}
public slots:

View file

@ -33,9 +33,9 @@ VersionList::VersionList(Meta::Version::Ptr version, QObject* parent) : BaseVers
sortVersions();
}
Task::Ptr VersionList::getLoadTask(bool forceReload)
Task::Ptr VersionList::getLoadTask()
{
auto task = m_version->loadTask(Net::Mode::Online, forceReload);
auto task = m_version->loadTask(Net::Mode::Online);
connect(task.get(), &Task::finished, this, &VersionList::sortVersions);
return task;
}

View file

@ -30,7 +30,7 @@ class VersionList : public BaseVersionList {
public:
explicit VersionList(Meta::Version::Ptr m_version, QObject* parent = 0);
Task::Ptr getLoadTask(bool forceReload = false) override;
Task::Ptr getLoadTask() override;
bool isLoaded() override;
const BaseVersion::Ptr at(int i) const override;
int count() const override;

View file

@ -143,7 +143,6 @@ void APIPage::loadSettings()
ui->msaClientID->setText(msaClientID);
QString metaURL = s->get("MetaURLOverride").toString();
ui->metaURL->setText(metaURL);
ui->metaRefreshOnLaunchCB->setCheckState(s->get("MetaRefreshOnLaunch").toBool() ? Qt::Checked : Qt::Unchecked);
QString resourceURL = s->get("ResourceURLOverride").toString();
ui->resourceURL->setText(resourceURL);
QString fmlLibsURL = s->get("LegacyFMLLibsURLOverride").toString();
@ -195,7 +194,6 @@ void APIPage::applySettings()
s->set("FallbackMRBlockedMods", ui->FallbackMRBlockedMods->checkState());
s->set("MetaURLOverride", metaURL.toString());
s->set("MetaRefreshOnLaunch", ui->metaRefreshOnLaunchCB->checkState() == Qt::Checked);
s->set("ResourceURLOverride", resourceURL.toString());
s->set("LegacyFMLLibsURLOverride", fmlLibsURL.toString());
QString flameKey = ui->flameKey->text();

View file

@ -32,9 +32,9 @@
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>825</width>
<height>1236</height>
<y>-262</y>
<width>820</width>
<height>908</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@ -126,13 +126,6 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="metaRefreshOnLaunchCB">
<property name="text">
<string>Refresh on launch</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View file

@ -45,6 +45,7 @@
#include <QFileSystemModel>
#include <QKeyEvent>
#include <QLineEdit>
#include <QMap>
#include <QMenu>
#include <QModelIndex>
#include <QMutableListIterator>
@ -52,8 +53,6 @@
#include <QRegularExpression>
#include <QSet>
#include <QStyledItemDelegate>
#include <memory>
#include <utility>
#include <Application.h>
#include "settings/SettingsObject.h"
@ -71,14 +70,9 @@
#include "RWStorage.h"
class ScreenshotsFSModel : public QFileSystemModel {
public:
bool canDropMimeData(const QMimeData* data,
const Qt::DropAction action,
const int row,
const int column,
const QModelIndex& parent) const override
bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const override
{
const QUrl root = QUrl::fromLocalFile(rootPath());
QUrl root = QUrl::fromLocalFile(rootPath());
// this disables reordering items inside the model
// by rejecting drops if the file is already inside the folder
if (data->hasUrls()) {
@ -98,8 +92,8 @@ using SharedIconCachePtr = std::shared_ptr<SharedIconCache>;
class ThumbnailingResult : public QObject {
Q_OBJECT
public slots:
void emitResultsReady(const QString& path) { emit resultsReady(path); }
void emitResultsFailed(const QString& path) { emit resultsFailed(path); }
inline void emitResultsReady(const QString& path) { emit resultsReady(path); }
inline void emitResultsFailed(const QString& path) { emit resultsFailed(path); }
signals:
void resultsReady(const QString& path);
void resultsFailed(const QString& path);
@ -107,32 +101,32 @@ class ThumbnailingResult : public QObject {
class ThumbnailRunnable : public QRunnable {
public:
ThumbnailRunnable(QString path, SharedIconCachePtr cache) : m_path(std::move(path)), m_cache(std::move(cache)) {}
void run() override
ThumbnailRunnable(QString path, SharedIconCachePtr cache)
{
const QFileInfo info(m_path);
if (info.isDir()) {
m_path = path;
m_cache = cache;
}
void run()
{
QFileInfo info(m_path);
if (info.isDir())
return;
}
if (info.suffix().compare("png", Qt::CaseInsensitive) != 0) {
if ((info.suffix().compare("png", Qt::CaseInsensitive) != 0))
return;
}
if (!m_cache->stale(m_path)) {
if (!m_cache->stale(m_path))
return;
}
const QImage image(m_path);
QImage image(m_path);
if (image.isNull()) {
m_resultEmitter.emitResultsFailed(m_path);
qDebug() << "Error loading screenshot (perhaps too large?):" + m_path;
return;
}
QImage small;
if (image.width() > image.height()) {
if (image.width() > image.height())
small = image.scaledToWidth(512).scaledToWidth(256, Qt::SmoothTransformation);
} else {
else
small = image.scaledToHeight(512).scaledToHeight(256, Qt::SmoothTransformation);
}
const QPoint offset((256 - small.width()) / 2, (256 - small.height()) / 2);
QPoint offset((256 - small.width()) / 2, (256 - small.height()) / 2);
QImage square(QSize(256, 256), QImage::Format_ARGB32);
square.fill(Qt::transparent);
@ -140,7 +134,7 @@ class ThumbnailRunnable : public QRunnable {
painter.drawImage(offset, small);
painter.end();
const QIcon icon(QPixmap::fromImage(square));
QIcon icon(QPixmap::fromImage(square));
m_cache->add(m_path, icon);
m_resultEmitter.emitResultsReady(m_path);
}
@ -154,62 +148,59 @@ class ThumbnailRunnable : public QRunnable {
class FilterModel : public QIdentityProxyModel {
Q_OBJECT
public:
explicit FilterModel(QObject* parent = nullptr) : QIdentityProxyModel(parent)
explicit FilterModel(QObject* parent = 0) : QIdentityProxyModel(parent)
{
m_thumbnailingPool.setMaxThreadCount(4);
m_thumbnailCache = std::make_shared<SharedIconCache>();
m_thumbnailCache->add("placeholder", QIcon::fromTheme("screenshot-placeholder"));
connect(&watcher, &QFileSystemWatcher::fileChanged, this, &FilterModel::fileChanged);
}
~FilterModel() override
virtual ~FilterModel()
{
m_thumbnailingPool.clear();
if (!m_thumbnailingPool.waitForDone(500)) {
if (!m_thumbnailingPool.waitForDone(500))
qDebug() << "Thumbnail pool took longer than 500ms to finish";
}
}
QVariant data(const QModelIndex& proxyIndex, const int role = Qt::DisplayRole) const override // NOLINT(*-default-arguments)
virtual QVariant data(const QModelIndex& proxyIndex, int role = Qt::DisplayRole) const
{
const auto* model = sourceModel();
if (!model) {
return {};
}
auto model = sourceModel();
if (!model)
return QVariant();
if (role == Qt::DisplayRole || role == Qt::EditRole) {
const QVariant result = model->data(mapToSource(proxyIndex), role);
QVariant result = sourceModel()->data(mapToSource(proxyIndex), role);
static const QRegularExpression s_removeChars("\\.png$");
return result.toString().remove(s_removeChars);
}
if (role == Qt::DecorationRole) {
const QVariant result = model->data(mapToSource(proxyIndex), QFileSystemModel::FilePathRole);
const QString filePath = result.toString();
QVariant result = sourceModel()->data(mapToSource(proxyIndex), QFileSystemModel::FilePathRole);
QString filePath = result.toString();
QIcon temp;
if (!watched.contains(filePath)) {
const_cast<QFileSystemWatcher&>(watcher).addPath(filePath);
const_cast<QSet<QString>&>(watched).insert(filePath);
((QFileSystemWatcher&)watcher).addPath(filePath);
((QSet<QString>&)watched).insert(filePath);
}
if (QIcon temp; m_thumbnailCache->get(filePath, temp)) {
if (m_thumbnailCache->get(filePath, temp)) {
return temp;
}
if (!m_failed.contains(filePath)) {
const_cast<FilterModel*>(this)->thumbnailImage(filePath);
((FilterModel*)this)->thumbnailImage(filePath);
}
return (m_thumbnailCache->get("placeholder"));
}
return model->data(mapToSource(proxyIndex), role);
return sourceModel()->data(mapToSource(proxyIndex), role);
}
bool setData(const QModelIndex& index, const QVariant& value, const int role = Qt::EditRole) override // NOLINT(*-default-arguments)
virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole)
{
auto* model = sourceModel();
if (!model) {
auto model = sourceModel();
if (!model)
return false;
}
if (role != Qt::EditRole) {
if (role != Qt::EditRole)
return false;
}
// FIXME: this is a workaround for a bug in QFileSystemModel, where it doesn't
// sort after renames
{
static_cast<QFileSystemModel*>(model)->setNameFilterDisables(true);
static_cast<QFileSystemModel*>(model)->setNameFilterDisables(false);
((QFileSystemModel*)model)->setNameFilterDisables(true);
((QFileSystemModel*)model)->setNameFilterDisables(false);
}
return model->setData(mapToSource(index), value.toString() + ".png", role);
}
@ -217,15 +208,15 @@ class FilterModel : public QIdentityProxyModel {
private:
void thumbnailImage(QString path)
{
auto* runnable = new ThumbnailRunnable(std::move(path), m_thumbnailCache);
auto runnable = new ThumbnailRunnable(path, m_thumbnailCache);
connect(&runnable->m_resultEmitter, &ThumbnailingResult::resultsReady, this, &FilterModel::thumbnailReady);
connect(&runnable->m_resultEmitter, &ThumbnailingResult::resultsFailed, this, &FilterModel::thumbnailFailed);
m_thumbnailingPool.start(runnable);
}
private slots:
void thumbnailReady(const QString& /*path*/) { emit layoutChanged(); }
void thumbnailFailed(const QString& path) { m_failed.insert(path); }
void fileChanged(const QString& filepath)
void thumbnailReady(QString path) { emit layoutChanged(); }
void thumbnailFailed(QString path) { m_failed.insert(path); }
void fileChanged(QString filepath)
{
m_thumbnailCache->setStale(filepath);
// reinsert the path...
@ -246,12 +237,13 @@ class FilterModel : public QIdentityProxyModel {
class CenteredEditingDelegate : public QStyledItemDelegate {
public:
explicit CenteredEditingDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {}
~CenteredEditingDelegate() override = default;
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override
explicit CenteredEditingDelegate(QObject* parent = 0) : QStyledItemDelegate(parent) {}
virtual ~CenteredEditingDelegate() {}
virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
auto* widget = QStyledItemDelegate::createEditor(parent, option, index);
if (auto* foo = dynamic_cast<QLineEdit*>(widget)) {
auto widget = QStyledItemDelegate::createEditor(parent, option, index);
auto foo = dynamic_cast<QLineEdit*>(widget);
if (foo) {
foo->setAlignment(Qt::AlignHCenter);
foo->setFrame(true);
foo->setMaximumWidth(192);
@ -260,11 +252,10 @@ class CenteredEditingDelegate : public QStyledItemDelegate {
}
};
ScreenshotsPage::ScreenshotsPage(QString path, QWidget* parent)
: QMainWindow(parent), ui(new Ui::ScreenshotsPage), m_folder(std::move(path))
ScreenshotsPage::ScreenshotsPage(QString path, QWidget* parent) : QMainWindow(parent), ui(new Ui::ScreenshotsPage)
{
m_model = std::make_shared<ScreenshotsFSModel>();
m_filterModel = std::make_shared<FilterModel>();
m_model.reset(new ScreenshotsFSModel());
m_filterModel.reset(new FilterModel());
m_filterModel->setSourceModel(m_model.get());
m_model->setFilter(QDir::Files);
m_model->setReadOnly(false);
@ -275,6 +266,7 @@ ScreenshotsPage::ScreenshotsPage(QString path, QWidget* parent)
constexpr int file_modified_column_index = 3;
m_model->sort(file_modified_column_index, Qt::DescendingOrder);
m_folder = path;
m_valid = FS::ensureFolderPathExists(m_folder);
ui->setupUi(this);
@ -291,19 +283,18 @@ ScreenshotsPage::ScreenshotsPage(QString path, QWidget* parent)
ui->listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
ui->listView->setItemDelegate(new CenteredEditingDelegate(this));
ui->listView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->listView, &QListView::customContextMenuRequested, this, &ScreenshotsPage::showContextMenu);
connect(ui->listView, &QListView::customContextMenuRequested, this, &ScreenshotsPage::ShowContextMenu);
connect(ui->listView, &QAbstractItemView::activated, this, &ScreenshotsPage::onItemActivated);
}
bool ScreenshotsPage::eventFilter(QObject* obj, QEvent* evt)
{
if (obj != ui->listView) {
if (obj != ui->listView)
return QWidget::eventFilter(obj, evt);
}
if (evt->type() != QEvent::KeyPress) {
return QWidget::eventFilter(obj, evt);
}
const auto* keyEvent = static_cast<QKeyEvent*>(evt);
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(evt);
if (keyEvent->matches(QKeySequence::Copy)) {
on_actionCopy_File_s_triggered();
@ -333,11 +324,11 @@ ScreenshotsPage::~ScreenshotsPage()
delete ui;
}
void ScreenshotsPage::showContextMenu(const QPoint& pos)
void ScreenshotsPage::ShowContextMenu(const QPoint& pos)
{
auto* menu = ui->toolBar->createContextMenu(this, tr("Context menu"));
auto menu = ui->toolBar->createContextMenu(this, tr("Context menu"));
if (ui->listView->selectionModel()->selectedIndexes().size() > 1) {
if (ui->listView->selectionModel()->selectedRows().size() > 1) {
menu->removeAction(ui->actionCopy_Image);
}
@ -352,75 +343,66 @@ QMenu* ScreenshotsPage::createPopupMenu()
return filteredMenu;
}
void ScreenshotsPage::onItemActivated(QModelIndex index) const
void ScreenshotsPage::onItemActivated(QModelIndex index)
{
if (!index.isValid()) {
if (!index.isValid())
return;
}
const auto info = m_model->fileInfo(index);
auto info = m_model->fileInfo(index);
DesktopServices::openPath(info);
}
void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection& /*selected*/) const
void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection& selected)
{
const auto selected = ui->listView->selectionModel()->selectedIndexes();
bool allReadable = !selected.isEmpty();
bool allWritable = !selected.isEmpty();
for (auto index : selected) {
if (!index.isValid()) {
for (auto index : selected.indexes()) {
if (!index.isValid())
break;
}
auto info = m_model->fileInfo(index);
if (!info.isReadable()) {
if (!info.isReadable())
allReadable = false;
}
if (!info.isWritable()) {
if (!info.isWritable())
allWritable = false;
}
}
ui->actionUpload->setEnabled(allReadable);
ui->actionCopy_Image->setEnabled(allReadable && selected.size() == 1);
ui->actionCopy_Image->setEnabled(allReadable);
ui->actionCopy_File_s->setEnabled(allReadable);
ui->actionDelete->setEnabled(allWritable);
ui->actionRename->setEnabled(allWritable);
}
void ScreenshotsPage::on_actionView_Folder_triggered() const
void ScreenshotsPage::on_actionView_Folder_triggered()
{
DesktopServices::openPath(m_folder, true);
}
void ScreenshotsPage::on_actionUpload_triggered()
{
auto selection = ui->listView->selectionModel()->selectedIndexes();
if (selection.isEmpty()) {
auto selection = ui->listView->selectionModel()->selectedRows();
if (selection.isEmpty())
return;
}
QString text;
const QUrl baseUrl(BuildConfig.IMGUR_BASE_URL);
if (selection.size() > 1) {
QUrl baseUrl(BuildConfig.IMGUR_BASE_URL);
if (selection.size() > 1)
text = tr("You are about to upload %1 screenshots to %2.\n"
"You should double-check for personal information.\n\n"
"Are you sure?")
.arg(QString::number(selection.size()), baseUrl.host());
} else {
else
text = tr("You are about to upload the selected screenshot to %1.\n"
"You should double-check for personal information.\n\n"
"Are you sure?")
.arg(baseUrl.host());
}
auto response = CustomMessageBox::selectable(this, "Confirm Upload", text, QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No,
QMessageBox::No)
->exec();
if (response != QMessageBox::Yes) {
if (response != QMessageBox::Yes)
return;
}
QList<ScreenShot::Ptr> uploaded;
auto job = NetJob::Ptr(new NetJob("Screenshot Upload", APPLICATION->network()));
@ -434,7 +416,7 @@ void ScreenshotsPage::on_actionUpload_triggered()
auto screenshot = std::make_shared<ScreenShot>(info);
job->addNetAction(ImgurUpload::make(screenshot));
connect(job.get(), &Task::failed, [this](const QString& reason) {
connect(job.get(), &Task::failed, [this](QString reason) {
CustomMessageBox::selectable(this, tr("Failed to upload screenshots!"), reason, QMessageBox::Critical)->show();
});
connect(job.get(), &Task::aborted, [this] {
@ -475,7 +457,7 @@ void ScreenshotsPage::on_actionUpload_triggered()
task.addTask(job);
task.addTask(albumTask);
connect(&task, &Task::failed, [this](const QString& reason) {
connect(&task, &Task::failed, [this](QString reason) {
CustomMessageBox::selectable(this, tr("Failed to upload screenshots!"), reason, QMessageBox::Critical)->show();
});
connect(&task, &Task::aborted, [this] {
@ -503,24 +485,24 @@ void ScreenshotsPage::on_actionUpload_triggered()
m_uploadActive = false;
}
void ScreenshotsPage::on_actionCopy_Image_triggered() const
void ScreenshotsPage::on_actionCopy_Image_triggered()
{
auto selection = ui->listView->selectionModel()->selectedIndexes();
auto selection = ui->listView->selectionModel()->selectedRows();
if (selection.size() < 1) {
return;
}
// You can only copy one image to the clipboard. In the case of multiple selected files, only the first one gets copied.
const auto item = selection.first();
const auto info = m_model->fileInfo(item);
const QImage image(info.absoluteFilePath());
auto item = selection[0];
auto info = m_model->fileInfo(item);
QImage image(info.absoluteFilePath());
Q_ASSERT(!image.isNull());
QApplication::clipboard()->setImage(image, QClipboard::Clipboard);
}
void ScreenshotsPage::on_actionCopy_File_s_triggered() const
void ScreenshotsPage::on_actionCopy_File_s_triggered()
{
auto selection = ui->listView->selectionModel()->selectedIndexes();
auto selection = ui->listView->selectionModel()->selectedRows();
if (selection.size() < 1) {
// Don't do anything so we don't empty the users clipboard
return;
@ -531,7 +513,7 @@ void ScreenshotsPage::on_actionCopy_File_s_triggered() const
auto info = m_model->fileInfo(item);
buf += "file:///" + info.absoluteFilePath() + "\r\n";
}
auto* mimeData = new QMimeData();
QMimeData* mimeData = new QMimeData();
mimeData->setData("text/uri-list", buf.toLocal8Bit());
QApplication::clipboard()->setMimeData(mimeData);
}
@ -540,43 +522,39 @@ void ScreenshotsPage::on_actionDelete_triggered()
{
auto selected = ui->listView->selectionModel()->selectedIndexes();
const qsizetype count = selected.size();
int count = ui->listView->selectionModel()->selectedRows().size();
QString text;
if (count > 1) {
if (count > 1)
text = tr("You are about to delete %1 screenshots.\n"
"This may be permanent and they will be gone from the folder.\n\n"
"Are you sure?")
.arg(count);
} else {
text =
tr("You are about to delete the selected screenshot.\n"
"This may be permanent and it will be gone from the folder.\n\n"
"Are you sure?");
}
else
text = tr("You are about to delete the selected screenshot.\n"
"This may be permanent and it will be gone from the folder.\n\n"
"Are you sure?")
.arg(count);
const auto response =
auto response =
CustomMessageBox::selectable(this, tr("Confirm Deletion"), text, QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No)->exec();
if (response != QMessageBox::Yes) {
if (response != QMessageBox::Yes)
return;
}
for (auto item : selected) {
if (FS::trash(m_model->filePath(item))) {
if (FS::trash(m_model->filePath(item)))
continue;
}
m_model->remove(item);
}
}
void ScreenshotsPage::on_actionRename_triggered() const
void ScreenshotsPage::on_actionRename_triggered()
{
auto selection = ui->listView->selectionModel()->selectedIndexes();
if (selection.isEmpty()) {
if (selection.isEmpty())
return;
}
ui->listView->edit(selection.first());
ui->listView->edit(selection[0]);
// TODO: mass renaming
}
@ -586,8 +564,8 @@ void ScreenshotsPage::openedImpl()
m_valid = FS::ensureFolderPathExists(m_folder);
}
if (m_valid) {
const QString path = QDir(m_folder).absolutePath();
const auto idx = m_model->setRootPath(path);
QString path = QDir(m_folder).absolutePath();
auto idx = m_model->setRootPath(path);
if (idx.isValid()) {
ui->listView->setModel(m_filterModel.get());
connect(ui->listView->selectionModel(), &QItemSelectionModel::selectionChanged, this,
@ -599,7 +577,7 @@ void ScreenshotsPage::openedImpl()
}
}
const auto setting_name = QString("WideBarVisibility_%1").arg(id());
auto const setting_name = QString("WideBarVisibility_%1").arg(id());
m_wide_bar_setting = APPLICATION->settings()->getOrRegisterSetting(setting_name);
ui->toolBar->setVisibilityState(QByteArray::fromBase64(m_wide_bar_setting->get().toString().toUtf8()));

View file

@ -78,14 +78,14 @@ class ScreenshotsPage : public QMainWindow, public BasePage {
private slots:
void on_actionUpload_triggered();
void on_actionCopy_Image_triggered() const;
void on_actionCopy_File_s_triggered() const;
void on_actionCopy_Image_triggered();
void on_actionCopy_File_s_triggered();
void on_actionDelete_triggered();
void on_actionRename_triggered() const;
void on_actionView_Folder_triggered() const;
void onItemActivated(QModelIndex) const;
void onCurrentSelectionChanged(const QItemSelection& selected) const;
void showContextMenu(const QPoint& pos);
void on_actionRename_triggered();
void on_actionView_Folder_triggered();
void onItemActivated(QModelIndex);
void onCurrentSelectionChanged(const QItemSelection& selected);
void ShowContextMenu(const QPoint& pos);
private:
Ui::ScreenshotsPage* ui;

View file

@ -80,14 +80,14 @@ void CustomPage::openedImpl()
void CustomPage::refresh()
{
ui->versionList->loadList(true);
ui->versionList->loadList();
}
void CustomPage::loaderRefresh()
{
if (ui->noneFilter->isChecked())
return;
ui->loaderVersionList->loadList(true);
ui->loaderVersionList->loadList();
}
void CustomPage::filterChanged()

View file

@ -139,18 +139,15 @@ void ResourceModel::search()
if (hasActiveSearchJob())
return;
if (m_search_state != SearchState::ResetRequested && m_search_term.startsWith("#")) {
if (m_search_term.startsWith("#")) {
auto projectId = m_search_term.mid(1);
if (!projectId.isEmpty()) {
ResourceAPI::Callback<ModPlatform::IndexedPack::Ptr> callbacks;
callbacks.on_fail = [this](QString reason, int network_error_code) {
callbacks.on_fail = [this](QString reason, int) {
if (!s_running_models.constFind(this).value())
return;
if (network_error_code == 404) {
m_search_state = SearchState::ResetRequested;
}
searchRequestFailed(reason, network_error_code);
searchRequestFailed(reason, -1);
};
callbacks.on_abort = [this] {
if (!s_running_models.constFind(this).value())
@ -165,7 +162,7 @@ void ResourceModel::search()
};
auto project = std::make_shared<ModPlatform::IndexedPack>();
project->addonId = projectId;
if (auto job = m_api->getProjectInfo({ project }, std::move(callbacks), false); job)
if (auto job = m_api->getProjectInfo({ project }, std::move(callbacks)); job)
runSearchJob(job);
return;
}
@ -410,9 +407,6 @@ void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int net
// Network error
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load mods."));
break;
case 404:
// 404 Not Found, some APIs return this when nothing is found, no need to bother the user
break;
case 409:
// 409 Gone, notify user to update
QMessageBox::critical(nullptr, tr("Error"),
@ -420,14 +414,7 @@ void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int net
break;
}
if (m_search_state == SearchState::ResetRequested) {
clearData();
m_next_search_offset = 0;
search();
} else {
m_search_state = SearchState::Finished;
}
m_search_state = SearchState::Finished;
}
void ResourceModel::searchRequestAborted()

View file

@ -165,20 +165,12 @@ void ListModel::fetchMore(const QModelIndex& parent)
void ListModel::performPaginatedSearch()
{
static const FlameAPI api;
// activate search by id only for numerical values because all CurseForge ids are numerical
static const QRegularExpression s_projectIdExpr("^\\#[0-9]+$");
if (m_searchState != ResetRequested && s_projectIdExpr.match(m_currentSearchTerm).hasMatch()) {
if (m_currentSearchTerm.startsWith("#")) {
auto projectId = m_currentSearchTerm.mid(1);
if (!projectId.isEmpty()) {
ResourceAPI::Callback<ModPlatform::IndexedPack::Ptr> callbacks;
callbacks.on_fail = [this](QString reason, int network_error_code) {
if (network_error_code == 404) {
m_searchState = ResetRequested;
}
searchRequestFailed(reason);
};
callbacks.on_fail = [this](QString reason, int) { searchRequestFailed(reason); };
callbacks.on_succeed = [this](auto& pack) { searchRequestForOneSucceeded(pack); };
callbacks.on_abort = [this] {
qCritical() << "Search task aborted by an unknown reason!";
@ -186,7 +178,7 @@ void ListModel::performPaginatedSearch()
};
auto project = std::make_shared<ModPlatform::IndexedPack>();
project->addonId = projectId;
if (auto job = api.getProjectInfo({ project }, std::move(callbacks), false); job) {
if (auto job = api.getProjectInfo({ project }, std::move(callbacks)); job) {
m_jobPtr = job;
m_jobPtr->start();
}

View file

@ -135,26 +135,20 @@ void ModpackListModel::performPaginatedSearch()
return;
static const ModrinthAPI api;
// Modrinth ids are not limited to numbers and can be any length
if (m_searchState != ResetRequested && m_currentSearchTerm.startsWith("#")) {
if (m_currentSearchTerm.startsWith("#")) {
auto projectId = m_currentSearchTerm.mid(1);
if (!projectId.isEmpty()) {
ResourceAPI::Callback<ModPlatform::IndexedPack::Ptr> callbacks;
callbacks.on_fail = [this](QString reason, int network_error_code) {
if (network_error_code == 404) {
m_searchState = ResetRequested;
}
searchRequestFailed(reason, network_error_code);
};
callbacks.on_fail = [this](QString reason, int) { searchRequestFailed(reason); };
callbacks.on_succeed = [this](auto& pack) { searchRequestForOneSucceeded(pack); };
callbacks.on_abort = [this] {
qCritical() << "Search task aborted by an unknown reason!";
searchRequestFailed("Aborted", 0);
searchRequestFailed("Aborted");
};
auto project = std::make_shared<ModPlatform::IndexedPack>();
project->addonId = projectId;
if (auto job = api.getProjectInfo({ project }, std::move(callbacks), false); job) {
if (auto job = api.getProjectInfo({ project }, std::move(callbacks)); job) {
m_jobPtr = job;
m_jobPtr->start();
}
@ -167,10 +161,10 @@ void ModpackListModel::performPaginatedSearch()
ResourceAPI::Callback<QList<ModPlatform::IndexedPack::Ptr>> callbacks{};
callbacks.on_succeed = [this](auto& doc) { searchRequestFinished(doc); };
callbacks.on_fail = [this](QString reason, int network_error_code) { searchRequestFailed(reason, network_error_code); };
callbacks.on_fail = [this](QString reason, int) { searchRequestFailed(reason); };
callbacks.on_abort = [this] {
qCritical() << "Search task aborted by an unknown reason!";
searchRequestFailed("Aborted", 0);
searchRequestFailed("Aborted");
};
auto netJob = api.searchProjects({ ModPlatform::ResourceType::Modpack, m_nextSearchOffset, m_currentSearchTerm, sort, m_filter->loaders,
@ -322,12 +316,13 @@ void ModpackListModel::searchRequestForOneSucceeded(ModPlatform::IndexedPack::Pt
endInsertRows();
}
void ModpackListModel::searchRequestFailed(QString reason, int network_error_code)
void ModpackListModel::searchRequestFailed(QString)
{
if (network_error_code == -1) {
// Unknown error in network stack
auto failed_action = dynamic_cast<NetJob*>(m_jobPtr.get())->getFailedActions().at(0);
if (failed_action->replyStatusCode() == -1) {
// Network error
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks."));
} else if (network_error_code == 409) {
} else if (failed_action->replyStatusCode() == 409) {
// 409 Gone, notify user to update
QMessageBox::critical(nullptr, tr("Error"),
//: %1 refers to the launcher itself

View file

@ -85,7 +85,7 @@ class ModpackListModel : public QAbstractListModel {
public slots:
void searchRequestFinished(QList<ModPlatform::IndexedPack::Ptr>& doc_all);
void searchRequestFailed(QString reason, int network_error_code);
void searchRequestFailed(QString reason);
void searchRequestForOneSucceeded(ModPlatform::IndexedPack::Ptr);
protected slots:

View file

@ -142,7 +142,7 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o
description_y -= opt.fontMetrics.height();
// On the bottom, aligned to the left after the icon, and featuring at most two lines of text (with some margin space to spare)
painter->drawText(description_x, description_y, remaining_width, num_lines * opt.fontMetrics.height(), Qt::TextWordWrap,
painter->drawText(description_x, description_y, remaining_width, cut_text.size() * opt.fontMetrics.height(), Qt::TextWordWrap,
description);
}

View file

@ -127,9 +127,9 @@ void VersionSelectWidget::closeEvent(QCloseEvent* event)
QWidget::closeEvent(event);
}
void VersionSelectWidget::loadList(bool forceReload)
void VersionSelectWidget::loadList()
{
m_load_task = m_vlist->getLoadTask(forceReload);
m_load_task = m_vlist->getLoadTask();
connect(m_load_task.get(), &Task::succeeded, this, &VersionSelectWidget::onTaskSucceeded);
connect(m_load_task.get(), &Task::failed, this, &VersionSelectWidget::onTaskFailed);
connect(m_load_task.get(), &Task::progress, this, &VersionSelectWidget::changeProgress);

View file

@ -57,7 +57,7 @@ class VersionSelectWidget : public QWidget {
void initialize(BaseVersionList* vlist, bool forceLoad = false);
//! Starts a task that loads the list.
void loadList(bool forceReload = false);
void loadList();
bool hasVersions() const;
BaseVersion::Ptr selectedVersion() const;

View file

@ -4,7 +4,6 @@
!include "x64.nsh"
AllowSkipFiles off
Unicode true
Name "@Launcher_DisplayName@"