Merge pull request #243 from Scrumplex/migration-dialog
Closes https://github.com/PrismLauncher/PrismLauncher/issues/46 Closes https://github.com/PrismLauncher/PrismLauncher/issues/204
This commit is contained in:
commit
c74f852364
@ -1,4 +1,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-FileCopyrightText: 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prism Launcher - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
@ -38,10 +41,14 @@
|
|||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
|
|
||||||
|
#include "DataMigrationTask.h"
|
||||||
#include "net/PasteUpload.h"
|
#include "net/PasteUpload.h"
|
||||||
|
#include "pathmatcher/MultiMatcher.h"
|
||||||
|
#include "pathmatcher/SimplePrefixMatcher.h"
|
||||||
#include "ui/MainWindow.h"
|
#include "ui/MainWindow.h"
|
||||||
#include "ui/InstanceWindow.h"
|
#include "ui/InstanceWindow.h"
|
||||||
|
|
||||||
|
#include "ui/dialogs/ProgressDialog.h"
|
||||||
#include "ui/instanceview/AccessibleInstanceView.h"
|
#include "ui/instanceview/AccessibleInstanceView.h"
|
||||||
|
|
||||||
#include "ui/pages/BasePageProvider.h"
|
#include "ui/pages/BasePageProvider.h"
|
||||||
@ -301,22 +308,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
dataPath = foo.absolutePath();
|
dataPath = foo.absolutePath();
|
||||||
adjustedBy = "Persistent data path";
|
adjustedBy = "Persistent data path";
|
||||||
|
|
||||||
QDir polymcData(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation), "PolyMC"));
|
|
||||||
if (polymcData.exists()) {
|
|
||||||
dataPath = polymcData.absolutePath();
|
|
||||||
adjustedBy = "PolyMC data path";
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
|
||||||
// TODO: this should be removed in a future version
|
|
||||||
// TODO: provide a migration path similar to macOS migration
|
|
||||||
QDir bar(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation), "polymc"));
|
|
||||||
if (bar.exists()) {
|
|
||||||
dataPath = bar.absolutePath();
|
|
||||||
adjustedBy = "Legacy data path";
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef Q_OS_MACOS
|
#ifndef Q_OS_MACOS
|
||||||
if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) {
|
if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) {
|
||||||
dataPath = m_rootPath;
|
dataPath = m_rootPath;
|
||||||
@ -439,6 +430,15 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
qDebug() << "<> Log initialized.";
|
qDebug() << "<> Log initialized.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
bool migrated = false;
|
||||||
|
|
||||||
|
if (!migrated)
|
||||||
|
migrated = handleDataMigration(dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../PolyMC"), "PolyMC", "polymc.cfg");
|
||||||
|
if (!migrated)
|
||||||
|
migrated = handleDataMigration(dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../multimc"), "MultiMC", "multimc.cfg");
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
||||||
qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT;
|
qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT;
|
||||||
@ -1605,3 +1605,88 @@ int Application::suitableMaxMem()
|
|||||||
|
|
||||||
return maxMemoryAlloc;
|
return maxMemoryAlloc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Application::handleDataMigration(const QString& currentData,
|
||||||
|
const QString& oldData,
|
||||||
|
const QString& name,
|
||||||
|
const QString& configFile) const
|
||||||
|
{
|
||||||
|
QString nomigratePath = FS::PathCombine(currentData, name + "_nomigrate.txt");
|
||||||
|
QStringList configPaths = { FS::PathCombine(oldData, configFile), FS::PathCombine(oldData, BuildConfig.LAUNCHER_CONFIGFILE) };
|
||||||
|
|
||||||
|
QLocale locale;
|
||||||
|
|
||||||
|
// Is there a valid config at the old location?
|
||||||
|
bool configExists = false;
|
||||||
|
for (QString configPath : configPaths) {
|
||||||
|
configExists |= QFileInfo::exists(configPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!configExists || QFileInfo::exists(nomigratePath)) {
|
||||||
|
qDebug() << "<> No migration needed from" << name;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString message;
|
||||||
|
bool currentExists = QFileInfo::exists(FS::PathCombine(currentData, BuildConfig.LAUNCHER_CONFIGFILE));
|
||||||
|
|
||||||
|
if (currentExists) {
|
||||||
|
message = tr("Old data from %1 was found, but you already have existing data for %2. Sadly you will need to migrate yourself. Do "
|
||||||
|
"you want to be reminded of the pending data migration next time you start %2?")
|
||||||
|
.arg(name, BuildConfig.LAUNCHER_DISPLAYNAME);
|
||||||
|
} else {
|
||||||
|
message = tr("It looks like you used %1 before. Do you want to migrate your data to the new location of %2?")
|
||||||
|
.arg(name, BuildConfig.LAUNCHER_DISPLAYNAME);
|
||||||
|
|
||||||
|
QFileInfo logInfo(FS::PathCombine(oldData, name + "-0.log"));
|
||||||
|
if (logInfo.exists()) {
|
||||||
|
QString lastModified = logInfo.lastModified().toString(locale.dateFormat());
|
||||||
|
message = tr("It looks like you used %1 on %2 before. Do you want to migrate your data to the new location of %3?")
|
||||||
|
.arg(name, lastModified, BuildConfig.LAUNCHER_DISPLAYNAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QMessageBox::StandardButton askMoveDialogue =
|
||||||
|
QMessageBox::question(nullptr, BuildConfig.LAUNCHER_DISPLAYNAME, message, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||||
|
|
||||||
|
auto setDoNotMigrate = [&nomigratePath] {
|
||||||
|
QFile file(nomigratePath);
|
||||||
|
file.open(QIODevice::WriteOnly);
|
||||||
|
};
|
||||||
|
|
||||||
|
// create no-migrate file if user doesn't want to migrate
|
||||||
|
if (askMoveDialogue != QMessageBox::Yes) {
|
||||||
|
qDebug() << "<> Migration declined for" << name;
|
||||||
|
setDoNotMigrate();
|
||||||
|
return currentExists; // cancel further migrations, if we already have a data directory
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentExists) {
|
||||||
|
// Migrate!
|
||||||
|
auto matcher = std::make_shared<MultiMatcher>();
|
||||||
|
matcher->add(std::make_shared<SimplePrefixMatcher>(configFile));
|
||||||
|
matcher->add(std::make_shared<SimplePrefixMatcher>(
|
||||||
|
BuildConfig.LAUNCHER_CONFIGFILE)); // it's possible that we already used that directory before
|
||||||
|
matcher->add(std::make_shared<SimplePrefixMatcher>("accounts.json"));
|
||||||
|
matcher->add(std::make_shared<SimplePrefixMatcher>("accounts/"));
|
||||||
|
matcher->add(std::make_shared<SimplePrefixMatcher>("assets/"));
|
||||||
|
matcher->add(std::make_shared<SimplePrefixMatcher>("icons/"));
|
||||||
|
matcher->add(std::make_shared<SimplePrefixMatcher>("instances/"));
|
||||||
|
matcher->add(std::make_shared<SimplePrefixMatcher>("libraries/"));
|
||||||
|
matcher->add(std::make_shared<SimplePrefixMatcher>("mods/"));
|
||||||
|
matcher->add(std::make_shared<SimplePrefixMatcher>("themes/"));
|
||||||
|
|
||||||
|
ProgressDialog diag;
|
||||||
|
DataMigrationTask task(nullptr, oldData, currentData, matcher);
|
||||||
|
if (diag.execWithTask(&task)) {
|
||||||
|
qDebug() << "<> Migration succeeded";
|
||||||
|
setDoNotMigrate();
|
||||||
|
} else {
|
||||||
|
QString reason = task.failReason();
|
||||||
|
QMessageBox::critical(nullptr, BuildConfig.LAUNCHER_DISPLAYNAME, tr("Migration failed! Reason: %1").arg(reason));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qWarning() << "<> Migration was skipped, due to existing data";
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
@ -231,6 +231,7 @@ private slots:
|
|||||||
void setupWizardFinished(int status);
|
void setupWizardFinished(int status);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool handleDataMigration(const QString & currentData, const QString & oldData, const QString & name, const QString & configFile) const;
|
||||||
bool createSetupWizard();
|
bool createSetupWizard();
|
||||||
void performMainStartupAction();
|
void performMainStartupAction();
|
||||||
|
|
||||||
|
@ -97,6 +97,7 @@ set(PATHMATCHER_SOURCES
|
|||||||
pathmatcher/IPathMatcher.h
|
pathmatcher/IPathMatcher.h
|
||||||
pathmatcher/MultiMatcher.h
|
pathmatcher/MultiMatcher.h
|
||||||
pathmatcher/RegexpMatcher.h
|
pathmatcher/RegexpMatcher.h
|
||||||
|
pathmatcher/SimplePrefixMatcher.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(NET_SOURCES
|
set(NET_SOURCES
|
||||||
@ -575,6 +576,8 @@ SET(LAUNCHER_SOURCES
|
|||||||
# Application base
|
# Application base
|
||||||
Application.h
|
Application.h
|
||||||
Application.cpp
|
Application.cpp
|
||||||
|
DataMigrationTask.h
|
||||||
|
DataMigrationTask.cpp
|
||||||
UpdateController.cpp
|
UpdateController.cpp
|
||||||
UpdateController.h
|
UpdateController.h
|
||||||
ApplicationMessage.h
|
ApplicationMessage.h
|
||||||
|
96
launcher/DataMigrationTask.cpp
Normal file
96
launcher/DataMigrationTask.cpp
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
#include "DataMigrationTask.h"
|
||||||
|
|
||||||
|
#include "FileSystem.h"
|
||||||
|
|
||||||
|
#include <QDirIterator>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QMap>
|
||||||
|
|
||||||
|
#include <QtConcurrent>
|
||||||
|
|
||||||
|
DataMigrationTask::DataMigrationTask(QObject* parent,
|
||||||
|
const QString& sourcePath,
|
||||||
|
const QString& targetPath,
|
||||||
|
const IPathMatcher::Ptr pathMatcher)
|
||||||
|
: Task(parent), m_sourcePath(sourcePath), m_targetPath(targetPath), m_pathMatcher(pathMatcher), m_copy(sourcePath, targetPath)
|
||||||
|
{
|
||||||
|
m_copy.matcher(m_pathMatcher.get()).whitelist(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataMigrationTask::executeTask()
|
||||||
|
{
|
||||||
|
setStatus(tr("Scanning files..."));
|
||||||
|
|
||||||
|
// 1. Scan
|
||||||
|
// Check how many files we gotta copy
|
||||||
|
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] {
|
||||||
|
return m_copy(true); // dry run to collect amount of files
|
||||||
|
});
|
||||||
|
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::dryRunFinished);
|
||||||
|
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &DataMigrationTask::dryRunAborted);
|
||||||
|
m_copyFutureWatcher.setFuture(m_copyFuture);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataMigrationTask::dryRunFinished()
|
||||||
|
{
|
||||||
|
disconnect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::dryRunFinished);
|
||||||
|
disconnect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &DataMigrationTask::dryRunAborted);
|
||||||
|
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||||
|
if (!m_copyFuture.isValid() || !m_copyFuture.result()) {
|
||||||
|
#else
|
||||||
|
if (!m_copyFuture.result()) {
|
||||||
|
#endif
|
||||||
|
emitFailed(tr("Failed to scan source path."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Copy
|
||||||
|
// Actually copy all files now.
|
||||||
|
m_toCopy = m_copy.totalCopied();
|
||||||
|
connect(&m_copy, &FS::copy::fileCopied, [&, this](const QString& relativeName) {
|
||||||
|
QString shortenedName = relativeName;
|
||||||
|
// shorten the filename to hopefully fit into one line
|
||||||
|
if (shortenedName.length() > 50)
|
||||||
|
shortenedName = relativeName.left(20) + "…" + relativeName.right(29);
|
||||||
|
setProgress(m_copy.totalCopied(), m_toCopy);
|
||||||
|
setStatus(tr("Copying %1…").arg(shortenedName));
|
||||||
|
});
|
||||||
|
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] {
|
||||||
|
return m_copy(false); // actually copy now
|
||||||
|
});
|
||||||
|
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::copyFinished);
|
||||||
|
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &DataMigrationTask::copyAborted);
|
||||||
|
m_copyFutureWatcher.setFuture(m_copyFuture);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataMigrationTask::dryRunAborted()
|
||||||
|
{
|
||||||
|
emitFailed(tr("Aborted"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataMigrationTask::copyFinished()
|
||||||
|
{
|
||||||
|
disconnect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::copyFinished);
|
||||||
|
disconnect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &DataMigrationTask::copyAborted);
|
||||||
|
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||||
|
if (!m_copyFuture.isValid() || !m_copyFuture.result()) {
|
||||||
|
#else
|
||||||
|
if (!m_copyFuture.result()) {
|
||||||
|
#endif
|
||||||
|
emitFailed(tr("Some paths could not be copied!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emitSucceeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataMigrationTask::copyAborted()
|
||||||
|
{
|
||||||
|
emitFailed(tr("Aborted"));
|
||||||
|
}
|
42
launcher/DataMigrationTask.h
Normal file
42
launcher/DataMigrationTask.h
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "FileSystem.h"
|
||||||
|
#include "pathmatcher/IPathMatcher.h"
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
|
#include <QFuture>
|
||||||
|
#include <QFutureWatcher>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Migrate existing data from other MMC-like launchers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class DataMigrationTask : public Task {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit DataMigrationTask(QObject* parent, const QString& sourcePath, const QString& targetPath, const IPathMatcher::Ptr pathmatcher);
|
||||||
|
~DataMigrationTask() override = default;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void executeTask() override;
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
void dryRunFinished();
|
||||||
|
void dryRunAborted();
|
||||||
|
void copyFinished();
|
||||||
|
void copyAborted();
|
||||||
|
|
||||||
|
private:
|
||||||
|
const QString& m_sourcePath;
|
||||||
|
const QString& m_targetPath;
|
||||||
|
const IPathMatcher::Ptr m_pathMatcher;
|
||||||
|
|
||||||
|
FS::copy m_copy;
|
||||||
|
int m_toCopy = 0;
|
||||||
|
QFuture<bool> m_copyFuture;
|
||||||
|
QFutureWatcher<bool> m_copyFutureWatcher;
|
||||||
|
};
|
@ -152,9 +152,10 @@ bool ensureFolderPathExists(QString foldernamepath)
|
|||||||
/// @brief Copies a directory and it's contents from src to dest
|
/// @brief Copies a directory and it's contents from src to dest
|
||||||
/// @param offset subdirectory form src to copy to dest
|
/// @param offset subdirectory form src to copy to dest
|
||||||
/// @return if there was an error during the filecopy
|
/// @return if there was an error during the filecopy
|
||||||
bool copy::operator()(const QString& offset)
|
bool copy::operator()(const QString& offset, bool dryRun)
|
||||||
{
|
{
|
||||||
using copy_opts = fs::copy_options;
|
using copy_opts = fs::copy_options;
|
||||||
|
m_copied = 0; // reset counter
|
||||||
|
|
||||||
// NOTE always deep copy on windows. the alternatives are too messy.
|
// NOTE always deep copy on windows. the alternatives are too messy.
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
@ -174,13 +175,14 @@ bool copy::operator()(const QString& offset)
|
|||||||
|
|
||||||
// Function that'll do the actual copying
|
// Function that'll do the actual copying
|
||||||
auto copy_file = [&](QString src_path, QString relative_dst_path) {
|
auto copy_file = [&](QString src_path, QString relative_dst_path) {
|
||||||
if (m_blacklist && m_blacklist->matches(relative_dst_path))
|
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto dst_path = PathCombine(dst, relative_dst_path);
|
auto dst_path = PathCombine(dst, relative_dst_path);
|
||||||
ensureFilePathExists(dst_path);
|
if (!dryRun) {
|
||||||
|
ensureFilePathExists(dst_path);
|
||||||
fs::copy(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), opt, err);
|
fs::copy(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), opt, err);
|
||||||
|
}
|
||||||
if (err) {
|
if (err) {
|
||||||
qWarning() << "Failed to copy files:" << QString::fromStdString(err.message());
|
qWarning() << "Failed to copy files:" << QString::fromStdString(err.message());
|
||||||
qDebug() << "Source file:" << src_path;
|
qDebug() << "Source file:" << src_path;
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFlags>
|
#include <QFlags>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
namespace FS {
|
namespace FS {
|
||||||
|
|
||||||
@ -76,9 +77,10 @@ bool ensureFilePathExists(QString filenamepath);
|
|||||||
bool ensureFolderPathExists(QString filenamepath);
|
bool ensureFolderPathExists(QString filenamepath);
|
||||||
|
|
||||||
/// @brief Copies a directory and it's contents from src to dest
|
/// @brief Copies a directory and it's contents from src to dest
|
||||||
class copy {
|
class copy : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
copy(const QString& src, const QString& dst)
|
copy(const QString& src, const QString& dst, QObject* parent = nullptr) : QObject(parent)
|
||||||
{
|
{
|
||||||
m_src.setPath(src);
|
m_src.setPath(src);
|
||||||
m_dst.setPath(dst);
|
m_dst.setPath(dst);
|
||||||
@ -88,21 +90,35 @@ class copy {
|
|||||||
m_followSymlinks = follow;
|
m_followSymlinks = follow;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
copy& blacklist(const IPathMatcher* filter)
|
copy& matcher(const IPathMatcher* filter)
|
||||||
{
|
{
|
||||||
m_blacklist = filter;
|
m_matcher = filter;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
bool operator()() { return operator()(QString()); }
|
copy& whitelist(bool whitelist)
|
||||||
|
{
|
||||||
|
m_whitelist = whitelist;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
|
||||||
|
|
||||||
|
int totalCopied() { return m_copied; }
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void fileCopied(const QString& relativeName);
|
||||||
|
// TODO: maybe add a "shouldCopy" signal in the future?
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool operator()(const QString& offset);
|
bool operator()(const QString& offset, bool dryRun = false);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_followSymlinks = true;
|
bool m_followSymlinks = true;
|
||||||
const IPathMatcher* m_blacklist = nullptr;
|
const IPathMatcher* m_matcher = nullptr;
|
||||||
|
bool m_whitelist = false;
|
||||||
QDir m_src;
|
QDir m_src;
|
||||||
QDir m_dst;
|
QDir m_dst;
|
||||||
|
int m_copied;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,9 +26,11 @@ void InstanceCopyTask::executeTask()
|
|||||||
setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
|
setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
|
||||||
|
|
||||||
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
|
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
|
||||||
folderCopy.followSymlinks(false).blacklist(m_matcher.get());
|
folderCopy.followSymlinks(false).matcher(m_matcher.get());
|
||||||
|
|
||||||
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy);
|
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&folderCopy]{
|
||||||
|
return folderCopy();
|
||||||
|
});
|
||||||
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &InstanceCopyTask::copyFinished);
|
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &InstanceCopyTask::copyFinished);
|
||||||
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &InstanceCopyTask::copyAborted);
|
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &InstanceCopyTask::copyAborted);
|
||||||
m_copyFutureWatcher.setFuture(m_copyFuture);
|
m_copyFutureWatcher.setFuture(m_copyFuture);
|
||||||
|
25
launcher/pathmatcher/SimplePrefixMatcher.h
Normal file
25
launcher/pathmatcher/SimplePrefixMatcher.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include "IPathMatcher.h"
|
||||||
|
|
||||||
|
class SimplePrefixMatcher : public IPathMatcher {
|
||||||
|
public:
|
||||||
|
virtual ~SimplePrefixMatcher(){};
|
||||||
|
SimplePrefixMatcher(const QString& prefix)
|
||||||
|
{
|
||||||
|
m_prefix = prefix;
|
||||||
|
m_isPrefix = prefix.endsWith('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool matches(const QString& string) const override
|
||||||
|
{
|
||||||
|
if (m_isPrefix)
|
||||||
|
return string.startsWith(m_prefix);
|
||||||
|
return string == m_prefix;
|
||||||
|
}
|
||||||
|
QString m_prefix;
|
||||||
|
bool m_isPrefix = false;
|
||||||
|
};
|
@ -44,7 +44,8 @@ void ProgressDialog::setSkipButton(bool present, QString label)
|
|||||||
void ProgressDialog::on_skipButton_clicked(bool checked)
|
void ProgressDialog::on_skipButton_clicked(bool checked)
|
||||||
{
|
{
|
||||||
Q_UNUSED(checked);
|
Q_UNUSED(checked);
|
||||||
task->abort();
|
if (ui->skipButton->isEnabled()) // prevent other triggers from aborting
|
||||||
|
task->abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
ProgressDialog::~ProgressDialog()
|
ProgressDialog::~ProgressDialog()
|
||||||
|
@ -126,7 +126,7 @@ slots:
|
|||||||
qDebug() << tempDir.path();
|
qDebug() << tempDir.path();
|
||||||
qDebug() << target_dir.path();
|
qDebug() << target_dir.path();
|
||||||
FS::copy c(folder, target_dir.path());
|
FS::copy c(folder, target_dir.path());
|
||||||
c.blacklist(new RegexpMatcher("[.]?mcmeta"));
|
c.matcher(new RegexpMatcher("[.]?mcmeta"));
|
||||||
c();
|
c();
|
||||||
|
|
||||||
for(auto entry: target_dir.entryList())
|
for(auto entry: target_dir.entryList())
|
||||||
@ -147,6 +147,41 @@ slots:
|
|||||||
f();
|
f();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test_copy_with_whitelist()
|
||||||
|
{
|
||||||
|
QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder");
|
||||||
|
auto f = [&folder]()
|
||||||
|
{
|
||||||
|
QTemporaryDir tempDir;
|
||||||
|
tempDir.setAutoRemove(true);
|
||||||
|
qDebug() << "From:" << folder << "To:" << tempDir.path();
|
||||||
|
|
||||||
|
QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder"));
|
||||||
|
qDebug() << tempDir.path();
|
||||||
|
qDebug() << target_dir.path();
|
||||||
|
FS::copy c(folder, target_dir.path());
|
||||||
|
c.matcher(new RegexpMatcher("[.]?mcmeta"));
|
||||||
|
c.whitelist(true);
|
||||||
|
c();
|
||||||
|
|
||||||
|
for(auto entry: target_dir.entryList())
|
||||||
|
{
|
||||||
|
qDebug() << entry;
|
||||||
|
}
|
||||||
|
QVERIFY(target_dir.entryList().contains("pack.mcmeta"));
|
||||||
|
QVERIFY(!target_dir.entryList().contains("assets"));
|
||||||
|
};
|
||||||
|
|
||||||
|
// first try variant without trailing /
|
||||||
|
QVERIFY(!folder.endsWith('/'));
|
||||||
|
f();
|
||||||
|
|
||||||
|
// then variant with trailing /
|
||||||
|
folder.append('/');
|
||||||
|
QVERIFY(folder.endsWith('/'));
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
|
||||||
void test_copy_with_dot_hidden()
|
void test_copy_with_dot_hidden()
|
||||||
{
|
{
|
||||||
QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder");
|
QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user