feat(reflink): hook up relink / clone on the copy dialog
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
This commit is contained in:
parent
c5bbe42b57
commit
397e7f0363
@ -103,6 +103,7 @@ namespace fs = ghc::filesystem;
|
|||||||
#include <fcntl.h> /* Definition of FICLONE* constants */
|
#include <fcntl.h> /* Definition of FICLONE* constants */
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||||
#include <sys/attr.h>
|
#include <sys/attr.h>
|
||||||
#include <sys/clonefile.h>
|
#include <sys/clonefile.h>
|
||||||
@ -880,6 +881,9 @@ FilesystemInfo statFS(QString path)
|
|||||||
info.name = storage_info.name();
|
info.name = storage_info.name();
|
||||||
info.rootPath = storage_info.rootPath();
|
info.rootPath = storage_info.rootPath();
|
||||||
|
|
||||||
|
qDebug() << "Pulling filesystem info for" << info.rootPath;
|
||||||
|
qDebug() << "Filesystem: " << fsTypeName << "detected" << getFilesystemTypeName(info.fsType);
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -995,33 +999,45 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec)
|
|||||||
// https://man7.org/linux/man-pages/man2/ioctl_ficlone.2.html
|
// https://man7.org/linux/man-pages/man2/ioctl_ficlone.2.html
|
||||||
|
|
||||||
int src_fd = open(src_path.c_str(), O_RDONLY);
|
int src_fd = open(src_path.c_str(), O_RDONLY);
|
||||||
if(!src_fd) {
|
if(src_fd == -1) {
|
||||||
qWarning() << "Failed to open file:" << src_path.c_str();
|
qWarning() << "Failed to open file:" << src_path.c_str();
|
||||||
qDebug() << "Error:" << strerror(errno);
|
qDebug() << "Error:" << strerror(errno);
|
||||||
ec = std::make_error_code(static_cast<std::errc>(errno));
|
ec = std::make_error_code(static_cast<std::errc>(errno));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
int dst_fd = open(dst_path.c_str(), O_WRONLY | O_TRUNC);
|
int dst_fd = open(dst_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
|
||||||
if(!dst_fd) {
|
if(dst_fd == -1) {
|
||||||
qWarning() << "Failed to open file:" << dst_path.c_str();
|
qWarning() << "Failed to open file:" << dst_path.c_str();
|
||||||
qDebug() << "Error:" << strerror(errno);
|
qDebug() << "Error:" << strerror(errno);
|
||||||
ec = std::make_error_code(static_cast<std::errc>(errno));
|
ec = std::make_error_code(static_cast<std::errc>(errno));
|
||||||
|
close(src_fd);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// attempt to clone
|
// attempt to clone
|
||||||
if(!ioctl(dst_fd, FICLONE, src_fd)){
|
if(ioctl(dst_fd, FICLONE, src_fd) == -1){
|
||||||
qWarning() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str();
|
qWarning() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str();
|
||||||
qDebug() << "Error:" << strerror(errno);
|
qDebug() << "Error:" << strerror(errno);
|
||||||
ec = std::make_error_code(static_cast<std::errc>(errno));
|
ec = std::make_error_code(static_cast<std::errc>(errno));
|
||||||
|
close(src_fd);
|
||||||
|
close(dst_fd);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if(close(src_fd)) {
|
||||||
|
qWarning() << "Failed to close file:" << src_path.c_str();
|
||||||
|
qDebug() << "Error:" << strerror(errno);
|
||||||
|
}
|
||||||
|
if(close(dst_fd)) {
|
||||||
|
qWarning() << "Failed to close file:" << dst_path.c_str();
|
||||||
|
qDebug() << "Error:" << strerror(errno);
|
||||||
|
}
|
||||||
|
|
||||||
#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||||
// TODO: use clonefile
|
// TODO: use clonefile
|
||||||
// clonefile(const char * src, const char * dst, int flags);
|
// clonefile(const char * src, const char * dst, int flags);
|
||||||
// https://www.manpagez.com/man/2/clonefile/
|
// https://www.manpagez.com/man/2/clonefile/
|
||||||
|
|
||||||
if (!clonefile(src_path.c_str(), dst_path.c_str(), 0)) {
|
qDebug() << "attempting file clone via clonefile" << src << "to" << dst;
|
||||||
|
if (clonefile(src_path.c_str(), dst_path.c_str(), 0) == -1) {
|
||||||
qWarning() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str();
|
qWarning() << "Failed to clone file:" << src_path.c_str() << "to" << dst_path.c_str();
|
||||||
qDebug() << "Error:" << strerror(errno);
|
qDebug() << "Error:" << strerror(errno);
|
||||||
ec = std::make_error_code(static_cast<std::errc>(errno));
|
ec = std::make_error_code(static_cast<std::errc>(errno));
|
||||||
|
@ -329,6 +329,7 @@ enum class FilesystemType {
|
|||||||
HFS,
|
HFS,
|
||||||
HFSPLUS,
|
HFSPLUS,
|
||||||
HFSX,
|
HFSX,
|
||||||
|
FUSEBLK,
|
||||||
UNKNOWN
|
UNKNOWN
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -346,6 +347,7 @@ static const QMap<FilesystemType, QString> s_filesystem_type_names = {
|
|||||||
{FilesystemType::HFS, QString("HFS")},
|
{FilesystemType::HFS, QString("HFS")},
|
||||||
{FilesystemType::HFSPLUS, QString("HFSPLUS")},
|
{FilesystemType::HFSPLUS, QString("HFSPLUS")},
|
||||||
{FilesystemType::HFSX, QString("HFSX")},
|
{FilesystemType::HFSX, QString("HFSX")},
|
||||||
|
{FilesystemType::FUSEBLK, QString("FUSEBLK")},
|
||||||
{FilesystemType::UNKNOWN, QString("UNKNOWN")}
|
{FilesystemType::UNKNOWN, QString("UNKNOWN")}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -365,6 +367,7 @@ static const QMap<QString, FilesystemType> s_filesystem_type_names_inverse = {
|
|||||||
{QString("HFSPLUS"), FilesystemType::HFSPLUS},
|
{QString("HFSPLUS"), FilesystemType::HFSPLUS},
|
||||||
{QString("HFSX"), FilesystemType::HFSX},
|
{QString("HFSX"), FilesystemType::HFSX},
|
||||||
{QString("HFS"), FilesystemType::HFS},
|
{QString("HFS"), FilesystemType::HFS},
|
||||||
|
{QString("FUSEBLK"), FilesystemType::FUSEBLK},
|
||||||
{QString("UNKNOWN"), FilesystemType::UNKNOWN}
|
{QString("UNKNOWN"), FilesystemType::UNKNOWN}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -113,6 +113,11 @@ bool InstanceCopyPrefs::isDontLinkSavesEnabled() const
|
|||||||
return dontLinkSaves;
|
return dontLinkSaves;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool InstanceCopyPrefs::isUseCloneEnabled() const
|
||||||
|
{
|
||||||
|
return useClone;
|
||||||
|
}
|
||||||
|
|
||||||
// ======= Setters =======
|
// ======= Setters =======
|
||||||
void InstanceCopyPrefs::enableCopySaves(bool b)
|
void InstanceCopyPrefs::enableCopySaves(bool b)
|
||||||
{
|
{
|
||||||
@ -173,3 +178,8 @@ void InstanceCopyPrefs::enableDontLinkSaves(bool b)
|
|||||||
{
|
{
|
||||||
dontLinkSaves = b;
|
dontLinkSaves = b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InstanceCopyPrefs::enableUseClone(bool b)
|
||||||
|
{
|
||||||
|
useClone = b;
|
||||||
|
}
|
@ -23,6 +23,7 @@ struct InstanceCopyPrefs {
|
|||||||
[[nodiscard]] bool isLinkRecursivelyEnabled() const;
|
[[nodiscard]] bool isLinkRecursivelyEnabled() const;
|
||||||
[[nodiscard]] bool isUseHardLinksEnabled() const;
|
[[nodiscard]] bool isUseHardLinksEnabled() const;
|
||||||
[[nodiscard]] bool isDontLinkSavesEnabled() const;
|
[[nodiscard]] bool isDontLinkSavesEnabled() const;
|
||||||
|
[[nodiscard]] bool isUseCloneEnabled() const;
|
||||||
// Setters
|
// Setters
|
||||||
void enableCopySaves(bool b);
|
void enableCopySaves(bool b);
|
||||||
void enableKeepPlaytime(bool b);
|
void enableKeepPlaytime(bool b);
|
||||||
@ -36,6 +37,7 @@ struct InstanceCopyPrefs {
|
|||||||
void enableLinkRecursively(bool b);
|
void enableLinkRecursively(bool b);
|
||||||
void enableUseHardLinks(bool b);
|
void enableUseHardLinks(bool b);
|
||||||
void enableDontLinkSaves(bool b);
|
void enableDontLinkSaves(bool b);
|
||||||
|
void enableUseClone(bool b);
|
||||||
|
|
||||||
protected: // data
|
protected: // data
|
||||||
bool copySaves = true;
|
bool copySaves = true;
|
||||||
@ -50,4 +52,5 @@ struct InstanceCopyPrefs {
|
|||||||
bool linkRecursively = false;
|
bool linkRecursively = false;
|
||||||
bool useHardLinks = false;
|
bool useHardLinks = false;
|
||||||
bool dontLinkSaves = false;
|
bool dontLinkSaves = false;
|
||||||
|
bool useClone = false;
|
||||||
};
|
};
|
||||||
|
@ -17,6 +17,7 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyP
|
|||||||
m_linkRecursively = prefs.isLinkRecursivelyEnabled();
|
m_linkRecursively = prefs.isLinkRecursivelyEnabled();
|
||||||
m_useHardLinks = prefs.isLinkRecursivelyEnabled() && prefs.isUseHardLinksEnabled();
|
m_useHardLinks = prefs.isLinkRecursivelyEnabled() && prefs.isUseHardLinksEnabled();
|
||||||
m_copySaves = prefs.isLinkRecursivelyEnabled() && prefs.isDontLinkSavesEnabled() && prefs.isCopySavesEnabled();
|
m_copySaves = prefs.isLinkRecursivelyEnabled() && prefs.isDontLinkSavesEnabled() && prefs.isCopySavesEnabled();
|
||||||
|
m_useClone = prefs.isUseCloneEnabled();
|
||||||
|
|
||||||
if (!filters.isEmpty())
|
if (!filters.isEmpty())
|
||||||
{
|
{
|
||||||
@ -40,7 +41,12 @@ void InstanceCopyTask::executeTask()
|
|||||||
};
|
};
|
||||||
|
|
||||||
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this, copySaves]{
|
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this, copySaves]{
|
||||||
if (m_useLinks) {
|
if (m_useClone) {
|
||||||
|
FS::clone folderClone(m_origInstance->instanceRoot(), m_stagingPath);
|
||||||
|
folderClone.matcher(m_matcher.get());
|
||||||
|
|
||||||
|
return folderClone();
|
||||||
|
} else if (m_useLinks) {
|
||||||
FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath);
|
FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath);
|
||||||
folderLink.linkRecursively(m_linkRecursively).useHardLinks(m_useHardLinks).matcher(m_matcher.get());
|
folderLink.linkRecursively(m_linkRecursively).useHardLinks(m_useHardLinks).matcher(m_matcher.get());
|
||||||
|
|
||||||
@ -83,7 +89,8 @@ void InstanceCopyTask::executeTask()
|
|||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str();
|
qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str();
|
||||||
#endif return false;
|
#endif
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_copySaves) {
|
if (m_copySaves) {
|
||||||
|
@ -34,4 +34,5 @@ private:
|
|||||||
bool m_useHardLinks = false;
|
bool m_useHardLinks = false;
|
||||||
bool m_copySaves = false;
|
bool m_copySaves = false;
|
||||||
bool m_linkRecursively = false;
|
bool m_linkRecursively = false;
|
||||||
|
bool m_useClone = false;
|
||||||
};
|
};
|
||||||
|
@ -46,6 +46,7 @@
|
|||||||
#include "icons/IconList.h"
|
#include "icons/IconList.h"
|
||||||
#include "BaseInstance.h"
|
#include "BaseInstance.h"
|
||||||
#include "InstanceList.h"
|
#include "InstanceList.h"
|
||||||
|
#include "FileSystem.h"
|
||||||
|
|
||||||
CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
|
CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
|
||||||
:QDialog(parent), ui(new Ui::CopyInstanceDialog), m_original(original)
|
:QDialog(parent), ui(new Ui::CopyInstanceDialog), m_original(original)
|
||||||
@ -90,6 +91,17 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
|
|||||||
ui->recursiveLinkCheckbox->setChecked(m_selectedOptions.isLinkRecursivelyEnabled());
|
ui->recursiveLinkCheckbox->setChecked(m_selectedOptions.isLinkRecursivelyEnabled());
|
||||||
ui->hardLinksCheckbox->setChecked(m_selectedOptions.isUseHardLinksEnabled());
|
ui->hardLinksCheckbox->setChecked(m_selectedOptions.isUseHardLinksEnabled());
|
||||||
ui->dontLinkSavesCheckbox->setChecked(m_selectedOptions.isDontLinkSavesEnabled());
|
ui->dontLinkSavesCheckbox->setChecked(m_selectedOptions.isDontLinkSavesEnabled());
|
||||||
|
|
||||||
|
auto detectedOS = FS::statFS(m_original->instanceRoot()).fsType;
|
||||||
|
m_cloneSupported = FS::canCloneOnFS(detectedOS);
|
||||||
|
|
||||||
|
if (m_cloneSupported) {
|
||||||
|
ui->cloneSupportedLabel->setText(tr("Clone / Reflink is supported on (%1)").arg(FS::getFilesystemTypeName(detectedOS)));
|
||||||
|
} else {
|
||||||
|
ui->cloneSupportedLabel->setText(tr("Clone / Reflink not supported on (%1)").arg(FS::getFilesystemTypeName(detectedOS)));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUseCloneCheckbox();
|
||||||
}
|
}
|
||||||
|
|
||||||
CopyInstanceDialog::~CopyInstanceDialog()
|
CopyInstanceDialog::~CopyInstanceDialog()
|
||||||
@ -152,6 +164,12 @@ void CopyInstanceDialog::updateSelectAllCheckbox()
|
|||||||
ui->selectAllCheckbox->blockSignals(false);
|
ui->selectAllCheckbox->blockSignals(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CopyInstanceDialog::updateUseCloneCheckbox()
|
||||||
|
{
|
||||||
|
ui->useCloneCheckbox->setEnabled(m_cloneSupported && !ui->linkFilesGroup->isChecked());
|
||||||
|
ui->useCloneCheckbox->setChecked(m_cloneSupported && m_selectedOptions.isUseCloneEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
void CopyInstanceDialog::on_iconButton_clicked()
|
void CopyInstanceDialog::on_iconButton_clicked()
|
||||||
{
|
{
|
||||||
IconPickerDialog dlg(this);
|
IconPickerDialog dlg(this);
|
||||||
@ -230,6 +248,7 @@ void CopyInstanceDialog::on_copyScreenshotsCheckbox_stateChanged(int state)
|
|||||||
void CopyInstanceDialog::on_linkFilesGroup_toggled(bool checked)
|
void CopyInstanceDialog::on_linkFilesGroup_toggled(bool checked)
|
||||||
{
|
{
|
||||||
m_selectedOptions.enableLinkFiles(checked);
|
m_selectedOptions.enableLinkFiles(checked);
|
||||||
|
updateUseCloneCheckbox();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CopyInstanceDialog::on_recursiveLinkCheckbox_stateChanged(int state)
|
void CopyInstanceDialog::on_recursiveLinkCheckbox_stateChanged(int state)
|
||||||
@ -254,3 +273,10 @@ void CopyInstanceDialog::on_dontLinkSavesCheckbox_stateChanged(int state)
|
|||||||
{
|
{
|
||||||
m_selectedOptions.enableDontLinkSaves(state == Qt::Checked);
|
m_selectedOptions.enableDontLinkSaves(state == Qt::Checked);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CopyInstanceDialog::on_useCloneCheckbox_stateChanged(int state)
|
||||||
|
{
|
||||||
|
m_selectedOptions.enableUseClone(m_cloneSupported && (state == Qt::Checked));
|
||||||
|
ui->linkFilesGroup->setEnabled(!m_selectedOptions.isUseCloneEnabled());
|
||||||
|
updateUseCloneCheckbox();
|
||||||
|
}
|
@ -16,6 +16,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
#include "BaseInstance.h"
|
||||||
#include "BaseVersion.h"
|
#include "BaseVersion.h"
|
||||||
#include "InstanceCopyPrefs.h"
|
#include "InstanceCopyPrefs.h"
|
||||||
|
|
||||||
@ -59,13 +60,17 @@ slots:
|
|||||||
void on_recursiveLinkCheckbox_stateChanged(int state);
|
void on_recursiveLinkCheckbox_stateChanged(int state);
|
||||||
void on_hardLinksCheckbox_stateChanged(int state);
|
void on_hardLinksCheckbox_stateChanged(int state);
|
||||||
void on_dontLinkSavesCheckbox_stateChanged(int state);
|
void on_dontLinkSavesCheckbox_stateChanged(int state);
|
||||||
|
void on_useCloneCheckbox_stateChanged(int state);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void checkAllCheckboxes(const bool& b);
|
void checkAllCheckboxes(const bool& b);
|
||||||
void updateSelectAllCheckbox();
|
void updateSelectAllCheckbox();
|
||||||
|
void updateUseCloneCheckbox();
|
||||||
|
|
||||||
/* data */
|
/* data */
|
||||||
Ui::CopyInstanceDialog *ui;
|
Ui::CopyInstanceDialog *ui;
|
||||||
QString InstIconKey;
|
QString InstIconKey;
|
||||||
InstancePtr m_original;
|
InstancePtr m_original;
|
||||||
InstanceCopyPrefs m_selectedOptions;
|
InstanceCopyPrefs m_selectedOptions;
|
||||||
|
bool m_cloneSupported = false;
|
||||||
};
|
};
|
||||||
|
@ -9,8 +9,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>525</width>
|
<width>531</width>
|
||||||
<height>581</height>
|
<height>640</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@ -275,6 +275,44 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="horizontalGroupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Clone / Reflink (Copy On Write) Options</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QHBoxLayout" name="useCloneLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="useCloneCheckbox">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Use Clone / Reflink</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="cloneSupportedLabel">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
|
<horstretch>1</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Clone / Reflink not supported on this filesystem</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||||
|
</property>
|
||||||
|
<property name="margin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
@ -302,7 +340,6 @@
|
|||||||
<tabstop>copyServersCheckbox</tabstop>
|
<tabstop>copyServersCheckbox</tabstop>
|
||||||
<tabstop>copyResPacksCheckbox</tabstop>
|
<tabstop>copyResPacksCheckbox</tabstop>
|
||||||
<tabstop>copyModsCheckbox</tabstop>
|
<tabstop>copyModsCheckbox</tabstop>
|
||||||
<tabstop>linkFilesGroup</tabstop>
|
|
||||||
<tabstop>recursiveLinkCheckbox</tabstop>
|
<tabstop>recursiveLinkCheckbox</tabstop>
|
||||||
<tabstop>hardLinksCheckbox</tabstop>
|
<tabstop>hardLinksCheckbox</tabstop>
|
||||||
<tabstop>dontLinkSavesCheckbox</tabstop>
|
<tabstop>dontLinkSavesCheckbox</tabstop>
|
||||||
|
Loading…
Reference in New Issue
Block a user