Compare commits

..

5 Commits

Author SHA1 Message Date
Brübach, Lukas
2c0c8b416a [Oracle] Qt version guard for regexp 2025-12-03 10:57:56 +01:00
Brübach, Lukas
65a3423009 [Oracle] Lint. 2025-12-03 10:53:08 +01:00
Brübach, Lukas
4ddbc8d018 [Oracle] Include QRegularExpression. 2025-12-03 10:51:11 +01:00
Brübach, Lukas
d4bf40694a [Oracle] Correct size and only do this if the user has not set a non-default url. 2025-12-03 10:44:40 +01:00
Brübach, Lukas
ec98bcf95d [Oracle] Add low-memory check (<=4GiB) and use mtgxml url instead. 2025-12-03 10:43:40 +01:00
16 changed files with 317 additions and 220 deletions

View File

@@ -154,10 +154,8 @@ void DeckEditorDeckDockWidget::createDeckDock()
&DeckEditorDeckDockWidget::setBannerCard);
bannerCardComboBox->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible());
deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckModel->getDeckList()->getTags());
deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckModel->getDeckList());
deckTagsDisplayWidget->setHidden(!SettingsCache::instance().getDeckEditorTagsWidgetVisible());
connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, this,
&DeckEditorDeckDockWidget::setTags);
activeGroupCriteriaLabel = new QLabel(this);
@@ -385,13 +383,6 @@ void DeckEditorDeckDockWidget::setBannerCard(int /* changedIndex */)
emit deckModified();
}
void DeckEditorDeckDockWidget::setTags(const QStringList &tags)
{
deckModel->getDeckList()->setTags(tags);
deckEditor->setModified(true);
emit deckModified();
}
void DeckEditorDeckDockWidget::syncDeckListBannerCardWithComboBox()
{
auto [name, id] = bannerCardComboBox->currentData().value<QPair<QString, QString>>();
@@ -460,7 +451,7 @@ void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel()
sortDeckModelToDeckView();
expandAll();
deckTagsDisplayWidget->setTags(deckModel->getDeckList()->getTags());
deckTagsDisplayWidget->setDeckList(deckModel->getDeckList());
}
void DeckEditorDeckDockWidget::sortDeckModelToDeckView()
@@ -493,7 +484,7 @@ void DeckEditorDeckDockWidget::cleanDeck()
emit deckModified();
emit deckChanged();
updateBannerCardComboBox();
deckTagsDisplayWidget->setTags(deckModel->getDeckList()->getTags());
deckTagsDisplayWidget->setDeckList(deckModel->getDeckList());
}
void DeckEditorDeckDockWidget::recursiveExpand(const QModelIndex &index)
@@ -588,7 +579,7 @@ bool DeckEditorDeckDockWidget::swapCard(const QModelIndex &currentIndex)
QModelIndex newCardIndex = card ? deckModel->addCard(card, otherZoneName)
// Third argument (true) says create the card no matter what, even if not in DB
: deckModel->addPreferredPrintingCard(cardName, otherZoneName, true);
recursiveExpand(proxy->mapFromSource(newCardIndex));
recursiveExpand(proxy->mapToSource(newCardIndex));
return true;
}
@@ -609,18 +600,8 @@ void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString z
}
deckView->clearSelection();
setCurrentProxyIndex(idx);
offsetCountAtIndex(proxy->mapFromSource(idx), -1);
}
void DeckEditorDeckDockWidget::setCurrentProxyIndex(const QModelIndex &index)
{
deckView->setCurrentIndex(proxy->mapFromSource(index));
}
void DeckEditorDeckDockWidget::scrollToProxyIndex(const QModelIndex &index)
{
deckView->setCurrentIndex(proxy->mapFromSource(index));
deckView->setCurrentIndex(proxy->mapToSource(idx));
offsetCountAtIndex(idx, -1);
}
void DeckEditorDeckDockWidget::actDecrementSelection()

View File

@@ -70,8 +70,6 @@ public slots:
void actRemoveCard();
void offsetCountAtIndex(const QModelIndex &idx, int offset);
void expandAll();
void setCurrentProxyIndex(const QModelIndex &index);
void scrollToProxyIndex(const QModelIndex &index);
signals:
void nameChanged();
@@ -114,7 +112,6 @@ private slots:
void updateName(const QString &name);
void updateComments();
void setBannerCard(int);
void setTags(const QStringList &tags);
void syncDeckListBannerCardWithComboBox();
void updateHash();
void refreshShortcuts();

View File

@@ -190,7 +190,7 @@ void CardAmountWidget::addPrinting(const QString &zone)
newCardIndex = deckModel->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(),
rootCard.getPrinting().getProperty("num"));
deckEditor->deckDockWidget->setCurrentProxyIndex(newCardIndex);
deckView->setCurrentIndex(newCardIndex);
deckView->setFocus(Qt::FocusReason::MouseFocusReason);
deckEditor->setModified(true);
}
@@ -256,7 +256,7 @@ void CardAmountWidget::offsetCountAtIndex(const QModelIndex &idx, int offset)
const int count = deckModel->data(numberIndex, Qt::EditRole).toInt();
const int new_count = count + offset;
deckEditor->deckDockWidget->setCurrentProxyIndex(numberIndex);
deckView->setCurrentIndex(numberIndex);
if (new_count <= 0) {
deckModel->removeRow(idx.row(), idx.parent());

View File

@@ -185,7 +185,7 @@ void PrintingSelector::selectCard(const int changeBy)
}
if (nextIndex.isValid()) {
deckEditor->deckDockWidget->setCurrentProxyIndex(nextIndex);
deckView->setCurrentIndex(nextIndex);
deckView->setFocus(Qt::FocusReason::MouseFocusReason);
}
}

View File

@@ -155,7 +155,7 @@ void AbstractTabDeckEditor::addCardHelper(const ExactCard &card, QString zoneNam
QModelIndex newCardIndex = deckDockWidget->deckModel->addCard(card, zoneName);
deckDockWidget->expandAll();
deckDockWidget->deckView->clearSelection();
deckDockWidget->setCurrentProxyIndex(newCardIndex);
deckDockWidget->deckView->setCurrentIndex(newCardIndex);
setModified(true);
databaseDisplayDockWidget->searchEdit->setSelection(0, databaseDisplayDockWidget->searchEdit->text().length());

View File

@@ -232,8 +232,8 @@ void TabDeckEditorVisual::processMainboardCardClick(QMouseEvent *event,
return;
} else {
// Normal click = clear selection, select this, set current
deckDockWidget->setCurrentProxyIndex(idx);
deckDockWidget->scrollToProxyIndex(idx);
deckDockWidget->deckView->setCurrentIndex(idx);
deckDockWidget->deckView->scrollTo(idx);
return;
}

View File

@@ -18,16 +18,6 @@ VisualDatabaseDisplayFilterSaveLoadWidget::VisualDatabaseDisplayFilterSaveLoadWi
layout = new QVBoxLayout(this);
setLayout(layout);
// Filter search input
searchInput = new QLineEdit(this);
layout->addWidget(searchInput);
connect(searchInput, &QLineEdit::textChanged, this, &VisualDatabaseDisplayFilterSaveLoadWidget::applySearchFilter);
// File list container
fileListWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
layout->addWidget(fileListWidget);
// Input for filter filename
filenameInput = new QLineEdit(this);
layout->addWidget(filenameInput);
@@ -35,20 +25,18 @@ VisualDatabaseDisplayFilterSaveLoadWidget::VisualDatabaseDisplayFilterSaveLoadWi
// Save button
saveButton = new QPushButton(this);
layout->addWidget(saveButton);
// Disable save if empty
saveButton->setEnabled(false);
connect(filenameInput, &QLineEdit::textChanged, this,
[this](const QString &text) { saveButton->setEnabled(!text.trimmed().isEmpty()); });
connect(saveButton, &QPushButton::clicked, this, &VisualDatabaseDisplayFilterSaveLoadWidget::saveFilter);
// File list container
fileListWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
layout->addWidget(fileListWidget);
refreshFilterList(); // Populate the file list on startup
retranslateUi();
}
void VisualDatabaseDisplayFilterSaveLoadWidget::retranslateUi()
{
searchInput->setPlaceholderText(tr("Search filter..."));
saveButton->setText(tr("Save Filter"));
saveButton->setToolTip(tr("Save all currently applied filters to a file"));
filenameInput->setPlaceholderText(tr("Enter filename..."));
@@ -124,36 +112,42 @@ void VisualDatabaseDisplayFilterSaveLoadWidget::loadFilter(const QString &filena
emit filterModel->layoutChanged();
}
void VisualDatabaseDisplayFilterSaveLoadWidget::applySearchFilter(const QString &text)
{
fileListWidget->clearLayout();
QString filter = text.trimmed();
QStringList filtered = allFilterFiles;
if (!filter.isEmpty()) {
filtered = filtered.filter(QRegularExpression(filter, QRegularExpression::CaseInsensitiveOption));
}
for (const QString &filename : filtered) {
FilterDisplayWidget *filterWidget = new FilterDisplayWidget(this, filename, filterModel);
fileListWidget->addWidget(filterWidget);
connect(filterWidget, &FilterDisplayWidget::filterLoadRequested, this,
&VisualDatabaseDisplayFilterSaveLoadWidget::loadFilter);
connect(filterWidget, &FilterDisplayWidget::filterDeleted, this,
&VisualDatabaseDisplayFilterSaveLoadWidget::refreshFilterList);
}
}
void VisualDatabaseDisplayFilterSaveLoadWidget::refreshFilterList()
{
fileListWidget->clearLayout();
fileButtons.clear();
// Clear existing widgets
for (auto buttonPair : fileButtons) {
buttonPair.first->deleteLater();
buttonPair.second->deleteLater();
}
fileButtons.clear(); // Clear the list of buttons
// Refresh the filter file list
QDir dir(SettingsCache::instance().getFiltersPath());
allFilterFiles = dir.entryList({"*.json"}, QDir::Files, QDir::Name);
QStringList filterFiles = dir.entryList(QStringList() << "*.json", QDir::Files, QDir::Name);
applySearchFilter(searchInput->text());
// Loop through the filter files and create widgets for them
for (const QString &filename : filterFiles) {
bool alreadyAdded = false;
// Check if the widget for this filter file already exists to avoid duplicates
for (const auto &pair : fileButtons) {
if (pair.first->text() == filename) {
alreadyAdded = true;
break;
}
}
if (!alreadyAdded) {
// Create a new custom widget for the filter
FilterDisplayWidget *filterWidget = new FilterDisplayWidget(this, filename, filterModel);
fileListWidget->addWidget(filterWidget);
// Connect signals to handle loading and deletion
connect(filterWidget, &FilterDisplayWidget::filterLoadRequested, this,
&VisualDatabaseDisplayFilterSaveLoadWidget::loadFilter);
connect(filterWidget, &FilterDisplayWidget::filterDeleted, this,
&VisualDatabaseDisplayFilterSaveLoadWidget::refreshFilterList);
}
}
}

View File

@@ -27,7 +27,6 @@ public:
void saveFilter();
void loadFilter(const QString &filename);
void applySearchFilter(const QString &text);
void refreshFilterList();
void deleteFilter(const QString &filename, QPushButton *deleteButton);
@@ -38,11 +37,9 @@ private:
FilterTreeModel *filterModel;
QVBoxLayout *layout;
QLineEdit *searchInput;
FlowWidget *fileListWidget;
QLineEdit *filenameInput;
QPushButton *saveButton;
QStringList allFilterFiles;
FlowWidget *fileListWidget;
QMap<QString, QPair<QPushButton *, QPushButton *>> fileButtons;
};

View File

@@ -13,8 +13,8 @@
#include <QHBoxLayout>
#include <QMessageBox>
DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, const QStringList &_tags)
: QWidget(_parent), currentTags(_tags)
DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckList *_deckList)
: QWidget(_parent), deckList(nullptr)
{
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
@@ -27,14 +27,16 @@ DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_par
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
layout->addWidget(flowWidget);
if (_deckList) {
setDeckList(_deckList);
}
refreshTags();
layout->addWidget(flowWidget);
}
void DeckPreviewDeckTagsDisplayWidget::setTags(const QStringList &_tags)
void DeckPreviewDeckTagsDisplayWidget::setDeckList(DeckList *_deckList)
{
currentTags = _tags;
deckList = _deckList;
refreshTags();
}
@@ -42,7 +44,7 @@ void DeckPreviewDeckTagsDisplayWidget::refreshTags()
{
flowWidget->clearLayout();
for (const QString &tag : currentTags) {
for (const QString &tag : deckList->getTags()) {
flowWidget->addWidget(new DeckPreviewTagDisplayWidget(this, tag));
}
@@ -69,45 +71,7 @@ static QStringList getAllFiles(const QString &filePath)
return allFiles;
}
/**
* Gets all tags that appear in the deck folder
*/
static QStringList findAllKnownTags()
{
QStringList allFiles = getAllFiles(SettingsCache::instance().getDeckPath());
QStringList knownTags;
auto loader = DeckLoader(nullptr);
for (const QString &file : allFiles) {
loader.loadFromFile(file, DeckLoader::getFormatFromName(file), false);
QStringList tags = loader.getDeckList()->getTags();
knownTags.append(tags);
knownTags.removeDuplicates();
}
return knownTags;
}
void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg()
{
if (qobject_cast<DeckPreviewWidget *>(parentWidget())) {
// If we're the child of a DeckPreviewWidget, then we need to handle conversion
auto *deckPreviewWidget = qobject_cast<DeckPreviewWidget *>(parentWidget());
bool canAddTags = promptFileConversionIfRequired(deckPreviewWidget);
if (canAddTags) {
QStringList knownTags = deckPreviewWidget->visualDeckStorageWidget->tagFilterWidget->getAllKnownTags();
execTagDialog(knownTags);
}
} else {
// If we're the child of an AbstractTabDeckEditor, then we don't bother with conversion
QStringList knownTags = findAllKnownTags();
execTagDialog(knownTags);
}
}
static bool confirmOverwriteIfExists(QWidget *parent, const QString &filePath)
bool confirmOverwriteIfExists(QWidget *parent, const QString &filePath)
{
QFileInfo fileInfo(filePath);
QString newFileName = QDir::toNativeSeparators(fileInfo.path() + "/" + fileInfo.completeBaseName() + ".cod");
@@ -122,70 +86,98 @@ static bool confirmOverwriteIfExists(QWidget *parent, const QString &filePath)
return true; // Safe to proceed
}
static void convertFileToCockatriceFormat(DeckPreviewWidget *deckPreviewWidget)
void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg()
{
deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath);
deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastLoadInfo().fileName;
deckPreviewWidget->refreshBannerCardText();
if (qobject_cast<DeckPreviewWidget *>(parentWidget())) {
auto *deckPreviewWidget = qobject_cast<DeckPreviewWidget *>(parentWidget());
QStringList knownTags = deckPreviewWidget->visualDeckStorageWidget->tagFilterWidget->getAllKnownTags();
QStringList activeTags = deckList->getTags();
bool canAddTags = true;
if (DeckLoader::getFormatFromName(deckPreviewWidget->filePath) != DeckLoader::CockatriceFormat) {
canAddTags = false;
// Retrieve saved preference if the prompt is disabled
if (!SettingsCache::instance().getVisualDeckStoragePromptForConversion()) {
if (SettingsCache::instance().getVisualDeckStorageAlwaysConvert()) {
if (!confirmOverwriteIfExists(this, deckPreviewWidget->filePath))
return;
deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath);
deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastLoadInfo().fileName;
deckPreviewWidget->refreshBannerCardText();
canAddTags = true;
}
} else {
// Show the dialog to the user
DialogConvertDeckToCodFormat conversionDialog(parentWidget());
if (conversionDialog.exec() == QDialog::Accepted) {
if (!confirmOverwriteIfExists(this, deckPreviewWidget->filePath))
return;
deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath);
deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastLoadInfo().fileName;
deckPreviewWidget->refreshBannerCardText();
canAddTags = true;
if (conversionDialog.dontAskAgain()) {
SettingsCache::instance().setVisualDeckStoragePromptForConversion(false);
SettingsCache::instance().setVisualDeckStorageAlwaysConvert(true);
}
} else {
SettingsCache::instance().setVisualDeckStorageAlwaysConvert(false);
if (conversionDialog.dontAskAgain()) {
SettingsCache::instance().setVisualDeckStoragePromptForConversion(false);
} else {
SettingsCache::instance().setVisualDeckStoragePromptForConversion(true);
}
}
}
}
if (canAddTags) {
DeckPreviewTagDialog dialog(knownTags, activeTags);
if (dialog.exec() == QDialog::Accepted) {
QStringList updatedTags = dialog.getActiveTags();
deckList->setTags(updatedTags);
deckPreviewWidget->deckLoader->saveToFile(deckPreviewWidget->filePath, DeckLoader::CockatriceFormat);
refreshTags();
}
}
} else if (parentWidget()) {
// If we're the child of an AbstractTabDeckEditor, we are buried under a ton of childWidgets in the
// DeckInfoDock.
QWidget *currentParent = parentWidget();
while (currentParent) {
if (qobject_cast<AbstractTabDeckEditor *>(currentParent)) {
break;
}
currentParent = currentParent->parentWidget();
}
if (qobject_cast<AbstractTabDeckEditor *>(currentParent)) {
auto *deckEditor = qobject_cast<AbstractTabDeckEditor *>(currentParent);
QStringList knownTags;
QStringList allFiles = getAllFiles(SettingsCache::instance().getDeckPath());
DeckLoader loader(this);
for (const QString &file : allFiles) {
loader.loadFromFile(file, DeckLoader::getFormatFromName(file), false);
QStringList tags = loader.getDeckList()->getTags();
knownTags.append(tags);
knownTags.removeDuplicates();
}
QStringList activeTags = deckList->getTags();
DeckPreviewTagDialog dialog(knownTags, activeTags);
if (dialog.exec() == QDialog::Accepted) {
QStringList updatedTags = dialog.getActiveTags();
deckList->setTags(updatedTags);
deckEditor->setModified(true);
refreshTags();
}
}
}
}
/**
* Checks if the deck's file format supports tags.
* If not, then prompt the user for file conversion.
* @return whether the resulting file can support adding tags
*/
bool DeckPreviewDeckTagsDisplayWidget::promptFileConversionIfRequired(DeckPreviewWidget *deckPreviewWidget)
{
if (DeckLoader::getFormatFromName(deckPreviewWidget->filePath) == DeckLoader::CockatriceFormat) {
return true;
}
// Retrieve saved preference if the prompt is disabled
if (!SettingsCache::instance().getVisualDeckStoragePromptForConversion()) {
if (!SettingsCache::instance().getVisualDeckStorageAlwaysConvert()) {
return false;
}
if (!confirmOverwriteIfExists(this, deckPreviewWidget->filePath)) {
return false;
}
convertFileToCockatriceFormat(deckPreviewWidget);
return true;
}
// Show the dialog to the user
DialogConvertDeckToCodFormat conversionDialog(parentWidget());
if (conversionDialog.exec() != QDialog::Accepted) {
SettingsCache::instance().setVisualDeckStoragePromptForConversion(!conversionDialog.dontAskAgain());
SettingsCache::instance().setVisualDeckStorageAlwaysConvert(false);
return false;
}
// Try to convert file
if (!confirmOverwriteIfExists(this, deckPreviewWidget->filePath)) {
return false;
}
convertFileToCockatriceFormat(deckPreviewWidget);
if (conversionDialog.dontAskAgain()) {
SettingsCache::instance().setVisualDeckStoragePromptForConversion(false);
SettingsCache::instance().setVisualDeckStorageAlwaysConvert(true);
}
return true;
}
void DeckPreviewDeckTagsDisplayWidget::execTagDialog(const QStringList &knownTags)
{
DeckPreviewTagDialog dialog(knownTags, currentTags);
if (dialog.exec() == QDialog::Accepted) {
QStringList updatedTags = dialog.getActiveTags();
if (updatedTags != currentTags) {
setTags(updatedTags);
emit tagsChanged(updatedTags);
}
}
}

View File

@@ -12,31 +12,21 @@
#include <QWidget>
inline bool confirmOverwriteIfExists(QWidget *parent, const QString &filePath);
class DeckPreviewWidget;
class DeckPreviewDeckTagsDisplayWidget : public QWidget
{
Q_OBJECT
QStringList currentTags;
FlowWidget *flowWidget;
public:
explicit DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, const QStringList &_tags);
void setTags(const QStringList &_tags);
explicit DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckList *_deckList);
void setDeckList(DeckList *_deckList);
void refreshTags();
DeckList *deckList;
FlowWidget *flowWidget;
public slots:
void openTagEditDlg();
private:
bool promptFileConversionIfRequired(DeckPreviewWidget *deckPreviewWidget);
void execTagDialog(const QStringList &knownTags);
signals:
/**
* Emitted when the tags have changed due to user interaction.
* @param tags The new list of tags.
*/
void tagsChanged(const QStringList &tags);
};
#endif // DECK_PREVIEW_DECK_TAGS_DISPLAY_WIDGET_H

View File

@@ -83,8 +83,7 @@ void DeckPreviewWidget::initializeUi(const bool deckLoadSuccess)
setFilePath(deckLoader->getLastLoadInfo().fileName);
colorIdentityWidget = new ColorIdentityWidget(this, getColorIdentity());
deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader->getDeckList()->getTags());
connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, this, &DeckPreviewWidget::setTags);
deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader->getDeckList());
bannerCardLabel = new QLabel(this);
bannerCardLabel->setObjectName("bannerCardLabel");
@@ -308,12 +307,6 @@ void DeckPreviewWidget::imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewC
emit deckLoadRequested(filePath);
}
void DeckPreviewWidget::setTags(const QStringList &tags)
{
deckLoader->getDeckList()->setTags(tags);
deckLoader->saveToFile(filePath, DeckLoader::CockatriceFormat);
}
QMenu *DeckPreviewWidget::createRightClickMenu()
{
auto *menu = new QMenu(this);

View File

@@ -72,8 +72,6 @@ private:
void addSetBannerCardMenu(QMenu *menu);
private slots:
void setTags(const QStringList &tags);
void actRenameDeck();
void actRenameFile();
void actDeleteFile();

View File

@@ -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})

View File

@@ -0,0 +1,112 @@
#include "system_memory_querier.h"
#ifdef Q_OS_WIN
#include <windows.h>
#endif
#ifdef Q_OS_LINUX
#include <QFile>
#include <QRegularExpression>
#include <QTextStream>
#endif
#ifdef Q_OS_MACOS
#include <mach/mach.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <unistd.h>
#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:")) {
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
}

View File

@@ -0,0 +1,19 @@
#ifndef COCKATRICE_SYSTEM_MEMORY_QUERIER_H
#define COCKATRICE_SYSTEM_MEMORY_QUERIER_H
#include <QtGlobal>
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

View File

@@ -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,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(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) {
urlLineEdit->setText(MTGXML_URL);
}
}
progressLabel->hide();
progressBar->hide();