feat(symlinks&hardlinks): linkup copy dialog

Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
This commit is contained in:
Rachel Powers 2023-02-08 14:30:45 -08:00
parent 8ba51c7900
commit 5978882378
9 changed files with 96 additions and 26 deletions

View File

@ -420,7 +420,7 @@ void create_link::runPrivlaged(const QString& offset)
qint64 byteswritten = clientConnection->write(block);
bool bytesflushed = clientConnection->flush();
qDebug() << "block flushed" << byteswritten << bytesflushed;
//clientConnection->disconnectFromServer();
});
qDebug() << "Listening on pipe" << serverName;
@ -437,7 +437,6 @@ void create_link::runPrivlaged(const QString& offset)
}
void ExternalLinkFileProcess::runLinkFile() {
QString fileLinkExe = PathCombine(QCoreApplication::instance()->applicationDirPath(), BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink");
QString params = "-s " + m_server;
@ -463,7 +462,7 @@ void ExternalLinkFileProcess::runLinkFile() {
ShExecInfo.lpFile = programNameWin;
ShExecInfo.lpParameters = paramsWin;
ShExecInfo.lpDirectory = NULL;
ShExecInfo.nShow = SW_NORMAL;
ShExecInfo.nShow = SW_HIDE;
ShExecInfo.hInstApp = NULL;
ShellExecuteEx(&ShExecInfo);

View File

@ -211,16 +211,21 @@ class create_link : public QObject {
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
int totalLinked() { return m_linked; }
void runPrivlaged() { runPrivlaged(QString()); }
void runPrivlaged(const QString& offset);
int totalLinked() { return m_linked; }
QList<LinkResult> getResults() { return m_path_results; }
signals:
void fileLinked(const QString& srcName, const QString& dstName);
void linkFailed(const QString& srcName, const QString& dstName, const QString& err_msg, int err_value);
void finishedPrivlaged(bool gotResults);
void finished();
void finishedPrivlaged(bool gotResults);
private:
bool operator()(const QString& offset, bool dryRun = false);

View File

@ -103,9 +103,9 @@ bool InstanceCopyPrefs::isUseHardLinksEnabled() const
return useHardLinks;
}
bool InstanceCopyPrefs::isLinkWorldsEnabled() const
bool InstanceCopyPrefs::isDontLinkSavesEnabled() const
{
return linkWorlds;
return dontLinkSaves;
}
// ======= Setters =======
@ -159,7 +159,7 @@ void InstanceCopyPrefs::enableUseHardLinks(bool b)
useHardLinks = b;
}
void InstanceCopyPrefs::enableLinkWorlds(bool b)
void InstanceCopyPrefs::enableDontLinkSaves(bool b)
{
linkWorlds = b;
dontLinkSaves = b;
}

View File

@ -21,7 +21,7 @@ struct InstanceCopyPrefs {
[[nodiscard]] bool isCopyScreenshotsEnabled() const;
[[nodiscard]] bool isLinkFilesEnabled() const;
[[nodiscard]] bool isUseHardLinksEnabled() const;
[[nodiscard]] bool isLinkWorldsEnabled() const;
[[nodiscard]] bool isDontLinkSavesEnabled() const;
// Setters
void enableCopySaves(bool b);
void enableKeepPlaytime(bool b);
@ -33,7 +33,7 @@ struct InstanceCopyPrefs {
void enableCopyScreenshots(bool b);
void enableLinkFiles(bool b);
void enableUseHardLinks(bool b);
void enableLinkWorlds(bool b);
void enableDontLinkSaves(bool b);
protected: // data
bool copySaves = true;
@ -46,5 +46,5 @@ struct InstanceCopyPrefs {
bool copyScreenshots = true;
bool linkFiles = false;
bool useHardLinks = false;
bool linkWorlds = true;
bool dontLinkSaves = false;
};

View File

@ -11,6 +11,11 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyP
m_keepPlaytime = prefs.isKeepPlaytimeEnabled();
QString filters = prefs.getSelectedFiltersAsRegex();
m_useLinks = prefs.isLinkFilesEnabled();
m_useHardLinks = prefs.isUseHardLinksEnabled();
m_copySaves = prefs.isDontLinkSavesEnabled() && prefs.isCopySavesEnabled();
if (!filters.isEmpty())
{
// Set regex filter:
@ -25,11 +30,71 @@ void InstanceCopyTask::executeTask()
{
setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]{
auto copySaves = [&](){
FS::copy savesCopy(FS::PathCombine(m_origInstance->instanceRoot(), "saves") , FS::PathCombine(m_stagingPath, "saves"));
savesCopy.followSymlinks(false);
return savesCopy();
};
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this, copySaves]{
if (m_useLinks) {
FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath);
folderLink.linkRecursively(true).useHardLinks(m_useHardLinks).matcher(m_matcher.get());
bool there_were_errors = false;
if(!folderLink()){
#if defined Q_OS_WIN32
if (!m_useHardLinks) {
qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
qDebug() << "atempting to run with privelage";
QEventLoop loop;
bool got_priv_results = false;
connect(&folderLink, &FS::create_link::finishedPrivlaged, this, [&](bool gotResults){
if (!gotResults) {
qDebug() << "Privlaged run exited without results!";
}
got_priv_results = gotResults;
loop.quit();
});
folderLink.runPrivlaged();
loop.exec(); // wait for the finished signal
for (auto result : folderLink.getResults()) {
if (result.err_value != 0) {
there_were_errors = true;
}
}
if (m_copySaves) {
there_were_errors |= !copySaves();
}
return got_priv_results && !there_were_errors;
} else {
qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str();
}
#else
qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str();
#endif return false;
}
if (m_copySaves) {
there_were_errors |= !copySaves();
}
return !there_were_errors;
} else {
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
folderCopy.followSymlinks(false).matcher(m_matcher.get());
return folderCopy();
}
});
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &InstanceCopyTask::copyFinished);
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &InstanceCopyTask::copyAborted);

View File

@ -30,4 +30,7 @@ private:
QFutureWatcher<bool> m_copyFutureWatcher;
std::unique_ptr<IPathMatcher> m_matcher;
bool m_keepPlaytime;
bool m_useLinks = false;
bool m_useHardLinks = false;
bool m_copySaves = true;
};

View File

@ -88,7 +88,7 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
ui->linkFilesGroup->setChecked(m_selectedOptions.isLinkFilesEnabled());
ui->hardLinksCheckbox->setChecked(m_selectedOptions.isUseHardLinksEnabled());
ui->linkWorldsCheckbox->setChecked(m_selectedOptions.isLinkWorldsEnabled());
ui->dontLinkSavesCheckbox->setChecked(m_selectedOptions.isDontLinkSavesEnabled());
}
CopyInstanceDialog::~CopyInstanceDialog()
@ -179,6 +179,7 @@ void CopyInstanceDialog::on_selectAllCheckbox_stateChanged(int state)
void CopyInstanceDialog::on_copySavesCheckbox_stateChanged(int state)
{
m_selectedOptions.enableCopySaves(state == Qt::Checked);
ui->dontLinkSavesCheckbox->setChecked((state == Qt::Checked) && ui->dontLinkSavesCheckbox->isChecked());
updateSelectAllCheckbox();
}
@ -235,7 +236,7 @@ void CopyInstanceDialog::on_hardLinksCheckbox_stateChanged(int state)
m_selectedOptions.enableUseHardLinks(state == Qt::Checked);
}
void CopyInstanceDialog::on_linkWorldsCheckbox_stateChanged(int state)
void CopyInstanceDialog::on_dontLinkSavesCheckbox_stateChanged(int state)
{
m_selectedOptions.enableLinkWorlds(state == Qt::Checked);
m_selectedOptions.enableDontLinkSaves(state == Qt::Checked);
}

View File

@ -57,7 +57,7 @@ slots:
void on_copyScreenshotsCheckbox_stateChanged(int state);
void on_linkFilesGroup_toggled(bool checked);
void on_hardLinksCheckbox_stateChanged(int state);
void on_linkWorldsCheckbox_stateChanged(int state);
void on_dontLinkSavesCheckbox_stateChanged(int state);
private:
void checkAllCheckboxes(const bool& b);

View File

@ -240,17 +240,14 @@
</widget>
</item>
<item>
<widget class="QCheckBox" name="linkWorldsCheckbox">
<widget class="QCheckBox" name="dontLinkSavesCheckbox">
<property name="toolTip">
<string>World save data will be linked and thus shared between instances.</string>
<string>If "copy saves" is selected world save data will be copied instead of linked and thus not shared between instances.</string>
</property>
<property name="text">
<string>Link worlds</string>
<string>Don't link saves</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="tristate">
<bool>false</bool>
</property>
</widget>