From e46ac131a3fb7439b8d9abdbd54e0d968a9339dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Br=C3=BCbach=2C=20Lukas?= Date: Wed, 3 Dec 2025 10:37:50 +0100 Subject: [PATCH 1/7] [Oracle] Add low-memory check (<=4GiB) and use mtgxml url instead. --- libcockatrice_utility/CMakeLists.txt | 2 + .../utility/system_memory_querier.cpp | 107 ++++++++++++++++++ .../utility/system_memory_querier.h | 19 ++++ oracle/src/pages.cpp | 19 ++++ 4 files changed, 147 insertions(+) create mode 100644 libcockatrice_utility/libcockatrice/utility/system_memory_querier.cpp create mode 100644 libcockatrice_utility/libcockatrice/utility/system_memory_querier.h diff --git a/libcockatrice_utility/CMakeLists.txt b/libcockatrice_utility/CMakeLists.txt index 5e6fa986d..cb71c0635 100644 --- a/libcockatrice_utility/CMakeLists.txt +++ b/libcockatrice_utility/CMakeLists.txt @@ -12,6 +12,7 @@ set(UTILITY_SOURCES libcockatrice/utility/passwordhasher.cpp libcockatrice/utility/report_categories.cpp libcockatrice/utility/server_rate_limiter.cpp + libcockatrice/utility/system_memory_querier.cpp libcockatrice/utility/translation_loader.cpp libcockatrice/utility/warning_categories.cpp ) @@ -34,6 +35,7 @@ set(UTILITY_HEADERS libcockatrice/utility/zone_names.h libcockatrice/utility/days_years_between.h libcockatrice/utility/server_rate_limiter.h + libcockatrice/utility/system_memory_querier.h libcockatrice/utility/translation_loader.h libcockatrice/utility/warning_categories.h ) diff --git a/libcockatrice_utility/libcockatrice/utility/system_memory_querier.cpp b/libcockatrice_utility/libcockatrice/utility/system_memory_querier.cpp new file mode 100644 index 000000000..e42aee987 --- /dev/null +++ b/libcockatrice_utility/libcockatrice/utility/system_memory_querier.cpp @@ -0,0 +1,107 @@ +#include "system_memory_querier.h" + +#ifdef Q_OS_WIN +#include +#endif + +#ifdef Q_OS_LINUX +#include +#include +#endif + +#ifdef Q_OS_MACOS +#include +#include +#include +#include +#endif + +qulonglong SystemMemoryQuerier::totalMemoryBytes() +{ +#if defined(Q_OS_WIN) + + MEMORYSTATUSEX statex; + statex.dwLength = sizeof(statex); + if (GlobalMemoryStatusEx(&statex)) + return statex.ullTotalPhys; + return 0; + +#elif defined(Q_OS_LINUX) + + QFile file("/proc/meminfo"); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + return 0; + + QTextStream in(&file); + while (!in.atEnd()) { + QString line = in.readLine(); + if (line.startsWith("MemTotal:")) { + QStringList parts = line.split(QRegExp("\\s+"), Qt::SkipEmptyParts); + if (parts.size() >= 2) + return parts[1].toULongLong() * 1024; // kB → bytes + } + } + return 0; + +#elif defined(Q_OS_MACOS) + + int mib[2] = {CTL_HW, HW_MEMSIZE}; + qulonglong memsize = 0; + size_t len = sizeof(memsize); + + if (sysctl(mib, 2, &memsize, &len, nullptr, 0) == 0) + return memsize; + + return 0; + +#else + return 0; +#endif +} + +qulonglong SystemMemoryQuerier::availableMemoryBytes() +{ +#if defined(Q_OS_WIN) + + MEMORYSTATUSEX statex; + statex.dwLength = sizeof(statex); + if (GlobalMemoryStatusEx(&statex)) + return statex.ullAvailPhys; + return 0; + +#elif defined(Q_OS_LINUX) + + QFile file("/proc/meminfo"); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + return 0; + + QTextStream in(&file); + while (!in.atEnd()) { + QString line = in.readLine(); + if (line.startsWith("MemAvailable:")) { + QStringList parts = line.split(QRegExp("\\s+"), Qt::SkipEmptyParts); + if (parts.size() >= 2) + return parts[1].toULongLong() * 1024; + } + } + return 0; + +#elif defined(Q_OS_MACOS) + + vm_size_t pageSize; + host_page_size(mach_host_self(), &pageSize); + + mach_msg_type_number_t count = HOST_VM_INFO_COUNT; + vm_statistics64_data_t vmstat; + + if (host_statistics64(mach_host_self(), HOST_VM_INFO, (host_info64_t)&vmstat, &count) != KERN_SUCCESS) + return 0; + + qulonglong freeBytes = (qulonglong)vmstat.free_count * (qulonglong)pageSize; + + return freeBytes; + +#else + return 0; +#endif +} diff --git a/libcockatrice_utility/libcockatrice/utility/system_memory_querier.h b/libcockatrice_utility/libcockatrice/utility/system_memory_querier.h new file mode 100644 index 000000000..44fd9f6f9 --- /dev/null +++ b/libcockatrice_utility/libcockatrice/utility/system_memory_querier.h @@ -0,0 +1,19 @@ +#ifndef COCKATRICE_SYSTEM_MEMORY_QUERIER_H +#define COCKATRICE_SYSTEM_MEMORY_QUERIER_H + +#include + +class SystemMemoryQuerier +{ +public: + static qulonglong totalMemoryBytes(); + static qulonglong availableMemoryBytes(); + + static bool hasAtLeastGiB(int gib) + { + const qulonglong GiB = 1024ull * 1024ull * 1024ull; + return totalMemoryBytes() >= (qulonglong)gib * GiB; + } +}; + +#endif // COCKATRICE_SYSTEM_MEMORY_QUERIER_H diff --git a/oracle/src/pages.cpp b/oracle/src/pages.cpp index 8c286fb46..15151c51e 100644 --- a/oracle/src/pages.cpp +++ b/oracle/src/pages.cpp @@ -1,6 +1,7 @@ #include "pages.h" #include "client/settings/cache_settings.h" +#include "libcockatrice/utility/system_memory_querier.h" #include "main.h" #include "oracleimporter.h" #include "oraclewizard.h" @@ -72,6 +73,7 @@ class QWidget; #define MTGJSON_V4_URL_COMPONENT "mtgjson.com/files/" #define ALLSETS_URL_FALLBACK "https://www.mtgjson.com/api/v5/AllPrintings.json" #define MTGJSON_VERSION_URL "https://www.mtgjson.com/api/v5/Meta.json" +#define MTGXML_URL "https://github.com/ebbit1q/mtgxml/releases/latest/download/mtg.xml.xz" #ifdef HAS_LZMA #define ALLSETS_URL "https://www.mtgjson.com/api/v5/AllPrintings.json.xz" @@ -324,6 +326,23 @@ void LoadSetsPage::initializePage() { urlLineEdit->setText(wizard()->settings->value("allsetsurl", ALLSETS_URL).toString()); + // Memory check because Oracle parsing fails on systems with less than 4GiB for MTGJsons allPrintings.json + if (!SystemMemoryQuerier::hasAtLeastGiB(33)) { + // Ask user whether to switch URL + QMessageBox msgBox( + QMessageBox::Question, tr("Low Memory Detected"), + tr("Your system has less than 4 GiB of memory.\n" + "Using the default AllPrintings URL may cause high memory usage and is known to fail as a result.\n\n" + "Would you like to switch to the direct-download pre-parsed URL instead? (Updated daily)"), + QMessageBox::Yes | QMessageBox::No, this); + + msgBox.setDefaultButton(QMessageBox::Yes); + + if (msgBox.exec() == QMessageBox::Yes) { + urlLineEdit->setText(MTGXML_URL); + } + } + progressLabel->hide(); progressBar->hide(); From 8243143e4e84e6c456dd0b7b4f6468eb633d3b42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Br=C3=BCbach=2C=20Lukas?= Date: Wed, 3 Dec 2025 10:44:40 +0100 Subject: [PATCH 2/7] [Oracle] Correct size and only do this if the user has not set a non-default url. --- oracle/src/pages.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oracle/src/pages.cpp b/oracle/src/pages.cpp index 15151c51e..e77d3b08a 100644 --- a/oracle/src/pages.cpp +++ b/oracle/src/pages.cpp @@ -327,7 +327,7 @@ void LoadSetsPage::initializePage() urlLineEdit->setText(wizard()->settings->value("allsetsurl", ALLSETS_URL).toString()); // Memory check because Oracle parsing fails on systems with less than 4GiB for MTGJsons allPrintings.json - if (!SystemMemoryQuerier::hasAtLeastGiB(33)) { + if (!SystemMemoryQuerier::hasAtLeastGiB(4) && urlLineEdit->text() == ALLSETS_URL) { // Ask user whether to switch URL QMessageBox msgBox( QMessageBox::Question, tr("Low Memory Detected"), From ed32c4ce6b014d0727abf78d458204137f3e4dee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Br=C3=BCbach=2C=20Lukas?= Date: Wed, 3 Dec 2025 10:51:02 +0100 Subject: [PATCH 3/7] [Oracle] Include QRegularExpression. --- .../libcockatrice/utility/system_memory_querier.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libcockatrice_utility/libcockatrice/utility/system_memory_querier.cpp b/libcockatrice_utility/libcockatrice/utility/system_memory_querier.cpp index e42aee987..9a4539256 100644 --- a/libcockatrice_utility/libcockatrice/utility/system_memory_querier.cpp +++ b/libcockatrice_utility/libcockatrice/utility/system_memory_querier.cpp @@ -6,6 +6,7 @@ #ifdef Q_OS_LINUX #include +#include #include #endif From d36b366f1a96c9e3ee02db26ea00d9d48618f571 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Br=C3=BCbach=2C=20Lukas?= Date: Wed, 3 Dec 2025 10:57:56 +0100 Subject: [PATCH 4/7] [Oracle] Qt version guard for regexp --- .../libcockatrice/utility/system_memory_querier.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libcockatrice_utility/libcockatrice/utility/system_memory_querier.cpp b/libcockatrice_utility/libcockatrice/utility/system_memory_querier.cpp index 9a4539256..46c24f69c 100644 --- a/libcockatrice_utility/libcockatrice/utility/system_memory_querier.cpp +++ b/libcockatrice_utility/libcockatrice/utility/system_memory_querier.cpp @@ -37,7 +37,11 @@ qulonglong SystemMemoryQuerier::totalMemoryBytes() while (!in.atEnd()) { QString line = in.readLine(); if (line.startsWith("MemTotal:")) { - QStringList parts = line.split(QRegExp("\\s+"), Qt::SkipEmptyParts); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + QStringList parts = line.split(QRegExp("\\s+"), QString::SkipEmptyParts); +#else + QStringList parts = line.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); +#endif if (parts.size() >= 2) return parts[1].toULongLong() * 1024; // kB → bytes } From d5094a8d006bafe82d416ac8a371d0617aaf16ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Sun, 11 Oct 2026 15:25:36 +0200 Subject: [PATCH 5/7] [Oracle] Fix Qt6 build and treat unknown memory as sufficient Use QRegularExpression behind the same Qt version guard as totalMemoryBytes() in availableMemoryBytes(), where a bare QRegExp broke every Qt6 Linux build. hasAtLeastGiB() now treats a 0 reading (unsupported platform or failed query) as unknown and skips the low-memory prompt instead of misreading it as low memory. --- .../libcockatrice/utility/system_memory_querier.cpp | 6 +++++- .../libcockatrice/utility/system_memory_querier.h | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/libcockatrice_utility/libcockatrice/utility/system_memory_querier.cpp b/libcockatrice_utility/libcockatrice/utility/system_memory_querier.cpp index 46c24f69c..b4553f8d2 100644 --- a/libcockatrice_utility/libcockatrice/utility/system_memory_querier.cpp +++ b/libcockatrice_utility/libcockatrice/utility/system_memory_querier.cpp @@ -84,7 +84,11 @@ qulonglong SystemMemoryQuerier::availableMemoryBytes() while (!in.atEnd()) { QString line = in.readLine(); if (line.startsWith("MemAvailable:")) { - QStringList parts = line.split(QRegExp("\\s+"), Qt::SkipEmptyParts); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + QStringList parts = line.split(QRegExp("\\s+"), QString::SkipEmptyParts); +#else + QStringList parts = line.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); +#endif if (parts.size() >= 2) return parts[1].toULongLong() * 1024; } diff --git a/libcockatrice_utility/libcockatrice/utility/system_memory_querier.h b/libcockatrice_utility/libcockatrice/utility/system_memory_querier.h index 44fd9f6f9..8a15fecdd 100644 --- a/libcockatrice_utility/libcockatrice/utility/system_memory_querier.h +++ b/libcockatrice_utility/libcockatrice/utility/system_memory_querier.h @@ -11,8 +11,14 @@ public: static bool hasAtLeastGiB(int gib) { + const qulonglong total = totalMemoryBytes(); + // 0 means the amount could not be determined (unsupported platform or failed query). + // Treat it as unknown and skip the check instead of misreading it as low memory. + if (total == 0) { + return true; + } const qulonglong GiB = 1024ull * 1024ull * 1024ull; - return totalMemoryBytes() >= (qulonglong)gib * GiB; + return total >= (qulonglong)gib * GiB; } }; From 88ee27b7f000407c3c783737ef61be4a06d69f89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Sun, 11 Oct 2026 17:51:52 +0200 Subject: [PATCH 6/7] [Oracle] Skip low-memory prompt in background mode, guard with HAS_LZMA --- oracle/src/pages.cpp | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/oracle/src/pages.cpp b/oracle/src/pages.cpp index e77d3b08a..5807793be 100644 --- a/oracle/src/pages.cpp +++ b/oracle/src/pages.cpp @@ -327,21 +327,30 @@ void LoadSetsPage::initializePage() urlLineEdit->setText(wizard()->settings->value("allsetsurl", ALLSETS_URL).toString()); // Memory check because Oracle parsing fails on systems with less than 4GiB for MTGJsons allPrintings.json + // The fallback URL is xz-compressed, so only offer it in builds that can read it. + // During background (automatic) updates there is no user to answer, so switch silently. +#ifdef HAS_LZMA if (!SystemMemoryQuerier::hasAtLeastGiB(4) && urlLineEdit->text() == ALLSETS_URL) { - // Ask user whether to switch URL - QMessageBox msgBox( - QMessageBox::Question, tr("Low Memory Detected"), - tr("Your system has less than 4 GiB of memory.\n" - "Using the default AllPrintings URL may cause high memory usage and is known to fail as a result.\n\n" - "Would you like to switch to the direct-download pre-parsed URL instead? (Updated daily)"), - QMessageBox::Yes | QMessageBox::No, this); - - msgBox.setDefaultButton(QMessageBox::Yes); - - if (msgBox.exec() == QMessageBox::Yes) { + if (wizard()->backgroundMode) { urlLineEdit->setText(MTGXML_URL); + } else { + // Ask user whether to switch URL + QMessageBox msgBox( + QMessageBox::Question, tr("Low Memory Detected"), + tr("Your system has less than 4 GiB of memory.\n" + "Using the default AllPrintings URL may cause high memory usage and is known to fail as a " + "result.\n\n" + "Would you like to switch to the direct-download pre-parsed URL instead? (Updated daily)"), + QMessageBox::Yes | QMessageBox::No, this); + + msgBox.setDefaultButton(QMessageBox::Yes); + + if (msgBox.exec() == QMessageBox::Yes) { + urlLineEdit->setText(MTGXML_URL); + } } } +#endif progressLabel->hide(); progressBar->hide(); From 2e77270cb8d1f03150fbd30832d5b96a23ddd8c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= Date: Sun, 1 Nov 2026 21:39:27 +0100 Subject: [PATCH 7/7] [Oracle] Apply clang-format to the memory querier --- .../utility/system_memory_querier.cpp | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/libcockatrice_utility/libcockatrice/utility/system_memory_querier.cpp b/libcockatrice_utility/libcockatrice/utility/system_memory_querier.cpp index b4553f8d2..2f6834a04 100644 --- a/libcockatrice_utility/libcockatrice/utility/system_memory_querier.cpp +++ b/libcockatrice_utility/libcockatrice/utility/system_memory_querier.cpp @@ -23,15 +23,17 @@ qulonglong SystemMemoryQuerier::totalMemoryBytes() MEMORYSTATUSEX statex; statex.dwLength = sizeof(statex); - if (GlobalMemoryStatusEx(&statex)) + if (GlobalMemoryStatusEx(&statex)) { return statex.ullTotalPhys; + } return 0; #elif defined(Q_OS_LINUX) QFile file("/proc/meminfo"); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { return 0; + } QTextStream in(&file); while (!in.atEnd()) { @@ -42,8 +44,9 @@ qulonglong SystemMemoryQuerier::totalMemoryBytes() #else QStringList parts = line.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); #endif - if (parts.size() >= 2) + if (parts.size() >= 2) { return parts[1].toULongLong() * 1024; // kB → bytes + } } } return 0; @@ -54,8 +57,9 @@ qulonglong SystemMemoryQuerier::totalMemoryBytes() qulonglong memsize = 0; size_t len = sizeof(memsize); - if (sysctl(mib, 2, &memsize, &len, nullptr, 0) == 0) + if (sysctl(mib, 2, &memsize, &len, nullptr, 0) == 0) { return memsize; + } return 0; @@ -70,15 +74,17 @@ qulonglong SystemMemoryQuerier::availableMemoryBytes() MEMORYSTATUSEX statex; statex.dwLength = sizeof(statex); - if (GlobalMemoryStatusEx(&statex)) + if (GlobalMemoryStatusEx(&statex)) { return statex.ullAvailPhys; + } return 0; #elif defined(Q_OS_LINUX) QFile file("/proc/meminfo"); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { return 0; + } QTextStream in(&file); while (!in.atEnd()) { @@ -89,8 +95,9 @@ qulonglong SystemMemoryQuerier::availableMemoryBytes() #else QStringList parts = line.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts); #endif - if (parts.size() >= 2) + if (parts.size() >= 2) { return parts[1].toULongLong() * 1024; + } } } return 0; @@ -103,8 +110,9 @@ qulonglong SystemMemoryQuerier::availableMemoryBytes() mach_msg_type_number_t count = HOST_VM_INFO_COUNT; vm_statistics64_data_t vmstat; - if (host_statistics64(mach_host_self(), HOST_VM_INFO, (host_info64_t)&vmstat, &count) != KERN_SUCCESS) + if (host_statistics64(mach_host_self(), HOST_VM_INFO, (host_info64_t)&vmstat, &count) != KERN_SUCCESS) { return 0; + } qulonglong freeBytes = (qulonglong)vmstat.free_count * (qulonglong)pageSize;