diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index cea759428..30b7ba314 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -166,7 +166,7 @@ bool DeckList::readElement(QXmlStreamReader *xml) return true; } -void writeMetadata(QXmlStreamWriter *xml, const DeckList::Metadata &metadata) +static void writeMetadata(QXmlStreamWriter *xml, const DeckList::Metadata &metadata) { xml->writeTextElement("lastLoadedTimestamp", metadata.lastLoadedTimestamp); xml->writeTextElement("deckname", metadata.name); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_player_reference.h b/libcockatrice_network/libcockatrice/network/server/remote/server_player_reference.h index 07b2d3d2b..b478a4244 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_player_reference.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_player_reference.h @@ -24,7 +24,7 @@ public: { return playerId; } - bool operator==(const PlayerReference &other) + bool operator==(const PlayerReference &other) const { return ((roomId == other.roomId) && (gameId == other.gameId) && (playerId == other.playerId)); } diff --git a/libcockatrice_utility/CMakeLists.txt b/libcockatrice_utility/CMakeLists.txt index 0575c260f..be07ad2f4 100644 --- a/libcockatrice_utility/CMakeLists.txt +++ b/libcockatrice_utility/CMakeLists.txt @@ -6,12 +6,17 @@ set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) set(UTILITY_SOURCES libcockatrice/utility/expression.cpp libcockatrice/utility/levenshtein.cpp - libcockatrice/utility/passwordhasher.cpp + libcockatrice/utility/passwordhasher.cpp libcockatrice/utility/system_memory_querier.cpp ) set(UTILITY_HEADERS - libcockatrice/utility/color.h libcockatrice/utility/expression.h libcockatrice/utility/levenshtein.h - libcockatrice/utility/macros.h libcockatrice/utility/passwordhasher.h libcockatrice/utility/trice_limits.h + libcockatrice/utility/color.h + libcockatrice/utility/expression.h + libcockatrice/utility/levenshtein.h + libcockatrice/utility/macros.h + libcockatrice/utility/passwordhasher.h + libcockatrice/utility/system_memory_querier.h + libcockatrice/utility/trice_limits.h ) add_library(libcockatrice_utility STATIC ${UTILITY_SOURCES} ${UTILITY_HEADERS}) 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..b4553f8d2 --- /dev/null +++ b/libcockatrice_utility/libcockatrice/utility/system_memory_querier.cpp @@ -0,0 +1,116 @@ +#include "system_memory_querier.h" + +#ifdef Q_OS_WIN +#include +#endif + +#ifdef Q_OS_LINUX +#include +#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:")) { +#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 + } + } + 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:")) { +#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; + } + } + 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..8a15fecdd --- /dev/null +++ b/libcockatrice_utility/libcockatrice/utility/system_memory_querier.h @@ -0,0 +1,25 @@ +#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 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 total >= (qulonglong)gib * GiB; + } +}; + +#endif // COCKATRICE_SYSTEM_MEMORY_QUERIER_H diff --git a/oracle/src/pages.cpp b/oracle/src/pages.cpp index 46edf1cf8..0b500a29a 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" @@ -43,6 +44,7 @@ #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" @@ -185,6 +187,32 @@ 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) { + 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();