diff --git a/launcher/FileIgnoreProxy.cpp b/launcher/FileIgnoreProxy.cpp index df06c3c75..89c91ec1d 100644 --- a/launcher/FileIgnoreProxy.cpp +++ b/launcher/FileIgnoreProxy.cpp @@ -40,12 +40,11 @@ #include #include #include -#include #include "FileSystem.h" #include "SeparatorPrefixTree.h" #include "StringUtils.h" -FileIgnoreProxy::FileIgnoreProxy(QString root, QObject* parent) : QSortFilterProxyModel(parent), root(root) {} +FileIgnoreProxy::FileIgnoreProxy(QString root, QObject* parent) : QSortFilterProxyModel(parent), m_root(root) {} // NOTE: Sadly, we have to do sorting ourselves. bool FileIgnoreProxy::lessThan(const QModelIndex& left, const QModelIndex& right) const { @@ -104,10 +103,10 @@ QVariant FileIgnoreProxy::data(const QModelIndex& index, int role) const if (index.column() == 0 && role == Qt::CheckStateRole) { QFileSystemModel* fsm = qobject_cast(sourceModel()); auto blockedPath = relPath(fsm->filePath(sourceIndex)); - auto cover = blocked.cover(blockedPath); + auto cover = m_blocked.cover(blockedPath); if (!cover.isNull()) { return QVariant(Qt::Unchecked); - } else if (blocked.exists(blockedPath)) { + } else if (m_blocked.exists(blockedPath)) { return QVariant(Qt::PartiallyChecked); } else { return QVariant(Qt::Checked); @@ -130,7 +129,7 @@ bool FileIgnoreProxy::setData(const QModelIndex& index, const QVariant& value, i QString FileIgnoreProxy::relPath(const QString& path) const { - return QDir(root).relativeFilePath(path); + return QDir(m_root).relativeFilePath(path); } bool FileIgnoreProxy::setFilterState(QModelIndex index, Qt::CheckState state) @@ -146,18 +145,18 @@ bool FileIgnoreProxy::setFilterState(QModelIndex index, Qt::CheckState state) bool changed = false; if (state == Qt::Unchecked) { // blocking a path - auto& node = blocked.insert(blockedPath); + auto& node = m_blocked.insert(blockedPath); // get rid of all blocked nodes below node.clear(); changed = true; } else if (state == Qt::Checked || state == Qt::PartiallyChecked) { - if (!blocked.remove(blockedPath)) { - auto cover = blocked.cover(blockedPath); + if (!m_blocked.remove(blockedPath)) { + auto cover = m_blocked.cover(blockedPath); qDebug() << "Blocked by cover" << cover; // uncover - blocked.remove(cover); + m_blocked.remove(cover); // block all contents, except for any cover - QModelIndex rootIndex = fsm->index(FS::PathCombine(root, cover)); + QModelIndex rootIndex = fsm->index(FS::PathCombine(m_root, cover)); QModelIndex doing = rootIndex; int row = 0; QStack todo; @@ -179,7 +178,7 @@ bool FileIgnoreProxy::setFilterState(QModelIndex index, Qt::CheckState state) todo.push(node); } else { // or just block this one. - blocked.insert(relpath); + m_blocked.insert(relpath); } row++; } @@ -229,7 +228,7 @@ bool FileIgnoreProxy::shouldExpand(QModelIndex index) return false; } auto blockedPath = relPath(fsm->filePath(sourceIndex)); - auto found = blocked.find(blockedPath); + auto found = m_blocked.find(blockedPath); if (found) { return !found->leaf(); } @@ -239,8 +238,8 @@ bool FileIgnoreProxy::shouldExpand(QModelIndex index) void FileIgnoreProxy::setBlockedPaths(QStringList paths) { beginResetModel(); - blocked.clear(); - blocked.insert(paths); + m_blocked.clear(); + m_blocked.insert(paths); endResetModel(); } @@ -272,5 +271,30 @@ bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const bool FileIgnoreProxy::filterFile(const QString& fileName) const { - return blocked.covers(fileName) || ignoreFile(QFileInfo(QDir(root), fileName)); + return m_blocked.covers(fileName) || ignoreFile(QFileInfo(QDir(m_root), fileName)); +} + +void FileIgnoreProxy::loadBlockedPathsFromFile(const QString& fileName) +{ + QFile ignoreFile(fileName); + if (!ignoreFile.open(QIODevice::ReadOnly)) { + return; + } + auto ignoreData = ignoreFile.readAll(); + auto string = QString::fromUtf8(ignoreData); +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + setBlockedPaths(string.split('\n', Qt::SkipEmptyParts)); +#else + setBlockedPaths(string.split('\n', QString::SkipEmptyParts)); +#endif +} + +void FileIgnoreProxy::saveBlockedPathsToFile(const QString& fileName) +{ + auto ignoreData = blockedPaths().toStringList().join('\n').toUtf8(); + try { + FS::write(fileName, ignoreData); + } catch (const Exception& e) { + qWarning() << e.cause(); + } } diff --git a/launcher/FileIgnoreProxy.h b/launcher/FileIgnoreProxy.h index e01a2651e..25d85ab60 100644 --- a/launcher/FileIgnoreProxy.h +++ b/launcher/FileIgnoreProxy.h @@ -61,8 +61,8 @@ class FileIgnoreProxy : public QSortFilterProxyModel { void setBlockedPaths(QStringList paths); - inline const SeparatorPrefixTree<'/'>& blockedPaths() const { return blocked; } - inline SeparatorPrefixTree<'/'>& blockedPaths() { return blocked; } + inline const SeparatorPrefixTree<'/'>& blockedPaths() const { return m_blocked; } + inline SeparatorPrefixTree<'/'>& blockedPaths() { return m_blocked; } // list of file names that need to be removed completely from model inline QStringList& ignoreFilesWithName() { return m_ignoreFiles; } @@ -71,6 +71,10 @@ class FileIgnoreProxy : public QSortFilterProxyModel { bool filterFile(const QString& fileName) const; + void loadBlockedPathsFromFile(const QString& fileName); + + void saveBlockedPathsToFile(const QString& fileName); + protected: bool filterAcceptsColumn(int source_column, const QModelIndex& source_parent) const; bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const; @@ -78,8 +82,8 @@ class FileIgnoreProxy : public QSortFilterProxyModel { bool ignoreFile(QFileInfo file) const; private: - const QString root; - SeparatorPrefixTree<'/'> blocked; + const QString m_root; + SeparatorPrefixTree<'/'> m_blocked; QStringList m_ignoreFiles; SeparatorPrefixTree<'/'> m_ignoreFilePaths; }; diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp index d25cd32b6..51e338503 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.cpp +++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp @@ -60,40 +60,40 @@ #include "SeparatorPrefixTree.h" ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget* parent) - : QDialog(parent), ui(new Ui::ExportInstanceDialog), m_instance(instance) + : QDialog(parent), m_ui(new Ui::ExportInstanceDialog), m_instance(instance) { - ui->setupUi(this); + m_ui->setupUi(this); auto model = new QFileSystemModel(this); - model->setIconProvider(&icons); + model->setIconProvider(&m_icons); auto root = instance->instanceRoot(); - proxyModel = new FileIgnoreProxy(root, this); - proxyModel->setSourceModel(model); + m_proxyModel = new FileIgnoreProxy(root, this); + m_proxyModel->setSourceModel(model); auto prefix = QDir(instance->instanceRoot()).relativeFilePath(instance->gameRoot()); - proxyModel->ignoreFilesWithPath().insert({ FS::PathCombine(prefix, "logs"), FS::PathCombine(prefix, "crash-reports") }); - proxyModel->ignoreFilesWithName().append({ ".DS_Store", "thumbs.db", "Thumbs.db" }); - proxyModel->ignoreFilesWithPath().insert( - { FS::PathCombine(prefix, ".cache"), FS::PathCombine(prefix, ".fabric"), FS::PathCombine(prefix, ".quilt") }); - loadPackIgnore(); + for (auto path : { "logs", "crash-reports", ".cache", ".fabric", ".quilt" }) { + m_proxyModel->ignoreFilesWithPath().insert(FS::PathCombine(prefix, path)); + } + m_proxyModel->ignoreFilesWithName().append({ ".DS_Store", "thumbs.db", "Thumbs.db" }); + m_proxyModel->loadBlockedPathsFromFile(ignoreFileName()); - ui->treeView->setModel(proxyModel); - ui->treeView->setRootIndex(proxyModel->mapFromSource(model->index(root))); - ui->treeView->sortByColumn(0, Qt::AscendingOrder); + m_ui->treeView->setModel(m_proxyModel); + m_ui->treeView->setRootIndex(m_proxyModel->mapFromSource(model->index(root))); + m_ui->treeView->sortByColumn(0, Qt::AscendingOrder); - connect(proxyModel, SIGNAL(rowsInserted(QModelIndex, int, int)), SLOT(rowsInserted(QModelIndex, int, int))); + connect(m_proxyModel, SIGNAL(rowsInserted(QModelIndex, int, int)), SLOT(rowsInserted(QModelIndex, int, int))); model->setFilter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden); model->setRootPath(root); - auto headerView = ui->treeView->header(); + auto headerView = m_ui->treeView->header(); headerView->setSectionResizeMode(QHeaderView::ResizeToContents); headerView->setSectionResizeMode(0, QHeaderView::Stretch); - ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); - ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); + m_ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); + m_ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); } ExportInstanceDialog::~ExportInstanceDialog() { - delete ui; + delete m_ui; } /// Save icon to instance's folder is needed @@ -144,7 +144,7 @@ void ExportInstanceDialog::doExport() auto files = QFileInfoList(); if (!MMCZip::collectFileListRecursively(m_instance->instanceRoot(), nullptr, &files, - std::bind(&FileIgnoreProxy::filterFile, proxyModel, std::placeholders::_1))) { + std::bind(&FileIgnoreProxy::filterFile, m_proxyModel, std::placeholders::_1))) { QMessageBox::warning(this, tr("Error"), tr("Unable to export instance")); QDialog::done(QDialog::Rejected); return; @@ -164,7 +164,7 @@ void ExportInstanceDialog::doExport() void ExportInstanceDialog::done(int result) { - savePackIgnore(); + m_proxyModel->saveBlockedPathsToFile(ignoreFileName()); if (result == QDialog::Accepted) { doExport(); return; @@ -176,13 +176,13 @@ void ExportInstanceDialog::rowsInserted(QModelIndex parent, int top, int bottom) { // WARNING: possible off-by-one? for (int i = top; i < bottom; i++) { - auto node = proxyModel->index(i, 0, parent); - if (proxyModel->shouldExpand(node)) { + auto node = m_proxyModel->index(i, 0, parent); + if (m_proxyModel->shouldExpand(node)) { auto expNode = node.parent(); if (!expNode.isValid()) { continue; } - ui->treeView->expand(node); + m_ui->treeView->expand(node); } } } @@ -191,30 +191,3 @@ QString ExportInstanceDialog::ignoreFileName() { return FS::PathCombine(m_instance->instanceRoot(), ".packignore"); } - -void ExportInstanceDialog::loadPackIgnore() -{ - auto filename = ignoreFileName(); - QFile ignoreFile(filename); - if (!ignoreFile.open(QIODevice::ReadOnly)) { - return; - } - auto ignoreData = ignoreFile.readAll(); - auto string = QString::fromUtf8(ignoreData); -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) - proxyModel->setBlockedPaths(string.split('\n', Qt::SkipEmptyParts)); -#else - proxyModel->setBlockedPaths(string.split('\n', QString::SkipEmptyParts)); -#endif -} - -void ExportInstanceDialog::savePackIgnore() -{ - auto ignoreData = proxyModel->blockedPaths().toStringList().join('\n').toUtf8(); - auto filename = ignoreFileName(); - try { - FS::write(filename, ignoreData); - } catch (const Exception& e) { - qWarning() << e.cause(); - } -} diff --git a/launcher/ui/dialogs/ExportInstanceDialog.h b/launcher/ui/dialogs/ExportInstanceDialog.h index 183681f57..989e1635a 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.h +++ b/launcher/ui/dialogs/ExportInstanceDialog.h @@ -60,15 +60,13 @@ class ExportInstanceDialog : public QDialog { private: void doExport(); - void loadPackIgnore(); - void savePackIgnore(); QString ignoreFileName(); private: - Ui::ExportInstanceDialog* ui; + Ui::ExportInstanceDialog* m_ui; InstancePtr m_instance; - FileIgnoreProxy* proxyModel; - FastFileIconProvider icons; + FileIgnoreProxy* m_proxyModel; + FastFileIconProvider m_icons; private slots: void rowsInserted(QModelIndex parent, int top, int bottom); diff --git a/launcher/ui/dialogs/ExportPackDialog.cpp b/launcher/ui/dialogs/ExportPackDialog.cpp index 879d19a53..303df94a1 100644 --- a/launcher/ui/dialogs/ExportPackDialog.cpp +++ b/launcher/ui/dialogs/ExportPackDialog.cpp @@ -29,104 +29,102 @@ #include #include #include -#include "FastFileIconProvider.h" #include "FileSystem.h" #include "MMCZip.h" #include "modplatform/modrinth/ModrinthPackExportTask.h" ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider) - : QDialog(parent), instance(instance), ui(new Ui::ExportPackDialog), m_provider(provider) + : QDialog(parent), m_instance(instance), m_ui(new Ui::ExportPackDialog), m_provider(provider) { Q_ASSERT(m_provider == ModPlatform::ResourceProvider::MODRINTH || m_provider == ModPlatform::ResourceProvider::FLAME); - ui->setupUi(this); - ui->name->setPlaceholderText(instance->name()); - ui->name->setText(instance->settings()->get("ExportName").toString()); - ui->version->setText(instance->settings()->get("ExportVersion").toString()); - ui->optionalFiles->setChecked(instance->settings()->get("ExportOptionalFiles").toBool()); + m_ui->setupUi(this); + m_ui->name->setPlaceholderText(instance->name()); + m_ui->name->setText(instance->settings()->get("ExportName").toString()); + m_ui->version->setText(instance->settings()->get("ExportVersion").toString()); + m_ui->optionalFiles->setChecked(instance->settings()->get("ExportOptionalFiles").toBool()); if (m_provider == ModPlatform::ResourceProvider::MODRINTH) { setWindowTitle(tr("Export Modrinth Pack")); - ui->authorLabel->hide(); - ui->author->hide(); + m_ui->authorLabel->hide(); + m_ui->author->hide(); - ui->summary->setPlainText(instance->settings()->get("ExportSummary").toString()); + m_ui->summary->setPlainText(instance->settings()->get("ExportSummary").toString()); } else { setWindowTitle(tr("Export CurseForge Pack")); - ui->summaryLabel->hide(); - ui->summary->hide(); + m_ui->summaryLabel->hide(); + m_ui->summary->hide(); - ui->author->setText(instance->settings()->get("ExportAuthor").toString()); + m_ui->author->setText(instance->settings()->get("ExportAuthor").toString()); } // ensure a valid pack is generated // the name and version fields mustn't be empty - connect(ui->name, &QLineEdit::textEdited, this, &ExportPackDialog::validate); - connect(ui->version, &QLineEdit::textEdited, this, &ExportPackDialog::validate); + connect(m_ui->name, &QLineEdit::textEdited, this, &ExportPackDialog::validate); + connect(m_ui->version, &QLineEdit::textEdited, this, &ExportPackDialog::validate); // the instance name can technically be empty validate(); QFileSystemModel* model = new QFileSystemModel(this); - model->setIconProvider(&icons); + model->setIconProvider(&m_icons); // use the game root - everything outside cannot be exported - const QDir root(instance->gameRoot()); - proxy = new FileIgnoreProxy(instance->gameRoot(), this); - proxy->ignoreFilesWithPath().insert({ "logs", "crash-reports", ".cache", ".fabric", ".quilt" }); - proxy->ignoreFilesWithName().append({ ".DS_Store", "thumbs.db", "Thumbs.db" }); - proxy->setSourceModel(model); + const QDir instanceRoot(instance->instanceRoot()); + m_proxy = new FileIgnoreProxy(instance->instanceRoot(), this); + auto prefix = QDir(instance->instanceRoot()).relativeFilePath(instance->gameRoot()); + for (auto path : { "logs", "crash-reports", ".cache", ".fabric", ".quilt" }) { + m_proxy->ignoreFilesWithPath().insert(FS::PathCombine(prefix, path)); + } + m_proxy->ignoreFilesWithName().append({ ".DS_Store", "thumbs.db", "Thumbs.db" }); + m_proxy->setSourceModel(model); + m_proxy->loadBlockedPathsFromFile(ignoreFileName()); const QDir::Filters filter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden); - for (const QString& file : root.entryList(filter)) { - if (!(file == "mods" || file == "coremods" || file == "datapacks" || file == "config" || file == "options.txt" || - file == "servers.dat")) - proxy->blockedPaths().insert(file); - } - MinecraftInstance* mcInstance = dynamic_cast(instance.get()); if (mcInstance) { for (auto& resourceModel : mcInstance->resourceLists()) if (resourceModel->indexDir().exists()) - proxy->ignoreFilesWithPath().insert(root.relativeFilePath(resourceModel->indexDir().absolutePath())); + m_proxy->ignoreFilesWithPath().insert(instanceRoot.relativeFilePath(resourceModel->indexDir().absolutePath())); } - ui->files->setModel(proxy); - ui->files->setRootIndex(proxy->mapFromSource(model->index(instance->gameRoot()))); - ui->files->sortByColumn(0, Qt::AscendingOrder); + m_ui->files->setModel(m_proxy); + m_ui->files->setRootIndex(m_proxy->mapFromSource(model->index(instance->gameRoot()))); + m_ui->files->sortByColumn(0, Qt::AscendingOrder); model->setFilter(filter); model->setRootPath(instance->gameRoot()); - QHeaderView* headerView = ui->files->header(); + QHeaderView* headerView = m_ui->files->header(); headerView->setSectionResizeMode(QHeaderView::ResizeToContents); headerView->setSectionResizeMode(0, QHeaderView::Stretch); - ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); - ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); + m_ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); + m_ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); } ExportPackDialog::~ExportPackDialog() { - delete ui; + delete m_ui; } void ExportPackDialog::done(int result) { - auto settings = instance->settings(); - settings->set("ExportName", ui->name->text()); - settings->set("ExportVersion", ui->version->text()); - settings->set("ExportOptionalFiles", ui->optionalFiles->isChecked()); + m_proxy->saveBlockedPathsToFile(ignoreFileName()); + auto settings = m_instance->settings(); + settings->set("ExportName", m_ui->name->text()); + settings->set("ExportVersion", m_ui->version->text()); + settings->set("ExportOptionalFiles", m_ui->optionalFiles->isChecked()); if (m_provider == ModPlatform::ResourceProvider::MODRINTH) - settings->set("ExportSummary", ui->summary->toPlainText()); + settings->set("ExportSummary", m_ui->summary->toPlainText()); else - settings->set("ExportAuthor", ui->author->text()); + settings->set("ExportAuthor", m_ui->author->text()); if (result == Accepted) { - const QString name = ui->name->text().isEmpty() ? instance->name() : ui->name->text(); + const QString name = m_ui->name->text().isEmpty() ? m_instance->name() : m_ui->name->text(); const QString filename = FS::RemoveInvalidFilenameChars(name); QString output; @@ -148,11 +146,11 @@ void ExportPackDialog::done(int result) Task* task; if (m_provider == ModPlatform::ResourceProvider::MODRINTH) { - task = new ModrinthPackExportTask(name, ui->version->text(), ui->summary->toPlainText(), ui->optionalFiles->isChecked(), - instance, output, std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1)); + task = new ModrinthPackExportTask(name, m_ui->version->text(), m_ui->summary->toPlainText(), m_ui->optionalFiles->isChecked(), + m_instance, output, std::bind(&FileIgnoreProxy::filterFile, m_proxy, std::placeholders::_1)); } else { - task = new FlamePackExportTask(name, ui->version->text(), ui->author->text(), ui->optionalFiles->isChecked(), instance, output, - std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1)); + task = new FlamePackExportTask(name, m_ui->version->text(), m_ui->author->text(), m_ui->optionalFiles->isChecked(), m_instance, + output, std::bind(&FileIgnoreProxy::filterFile, m_proxy, std::placeholders::_1)); } connect(task, &Task::failed, @@ -174,6 +172,11 @@ void ExportPackDialog::done(int result) void ExportPackDialog::validate() { - ui->buttonBox->button(QDialogButtonBox::Ok) - ->setDisabled(m_provider == ModPlatform::ResourceProvider::MODRINTH && ui->version->text().isEmpty()); + m_ui->buttonBox->button(QDialogButtonBox::Ok) + ->setDisabled(m_provider == ModPlatform::ResourceProvider::MODRINTH && m_ui->version->text().isEmpty()); +} + +QString ExportPackDialog::ignoreFileName() +{ + return FS::PathCombine(m_instance->instanceRoot(), ".packignore"); } diff --git a/launcher/ui/dialogs/ExportPackDialog.h b/launcher/ui/dialogs/ExportPackDialog.h index 830c24d25..092288d49 100644 --- a/launcher/ui/dialogs/ExportPackDialog.h +++ b/launcher/ui/dialogs/ExportPackDialog.h @@ -41,9 +41,12 @@ class ExportPackDialog : public QDialog { void validate(); private: - const InstancePtr instance; - Ui::ExportPackDialog* ui; - FileIgnoreProxy* proxy; - FastFileIconProvider icons; + QString ignoreFileName(); + + private: + const InstancePtr m_instance; + Ui::ExportPackDialog* m_ui; + FileIgnoreProxy* m_proxy; + FastFileIconProvider m_icons; const ModPlatform::ResourceProvider m_provider; };