mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 17:43:23 -08:00
[Installer][Client] Back up user data before old uninstallers can delete it
This commit is contained in:
parent
a712553c81
commit
d33542db59
7 changed files with 367 additions and 12 deletions
|
|
@ -15,6 +15,7 @@ Var ReinstallMode
|
||||||
Var AllowForceClose
|
Var AllowForceClose
|
||||||
Var GuardDir
|
Var GuardDir
|
||||||
Var OldUninstallString
|
Var OldUninstallString
|
||||||
|
Var DataBackupDir
|
||||||
Var StaleFindHandle
|
Var StaleFindHandle
|
||||||
Var StaleFindName
|
Var StaleFindName
|
||||||
Var IsUpgrade
|
Var IsUpgrade
|
||||||
|
|
@ -317,7 +318,9 @@ ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatr
|
||||||
!insertmacro CockLog "uninstall (32-bit): '$R0 _?=$INSTDIR'"
|
!insertmacro CockLog "uninstall (32-bit): '$R0 _?=$INSTDIR'"
|
||||||
StrCmp $R0 "" done32
|
StrCmp $R0 "" done32
|
||||||
DetailPrint "Removing previous version (32-bit)..."
|
DetailPrint "Removing previous version (32-bit)..."
|
||||||
|
Call BackupUserDataBeforeUninstall
|
||||||
ExecWait '$R0 _?=$INSTDIR'
|
ExecWait '$R0 _?=$INSTDIR'
|
||||||
|
Call RestoreUserDataAfterUninstall
|
||||||
|
|
||||||
done32:
|
done32:
|
||||||
|
|
||||||
|
|
@ -329,13 +332,67 @@ ${If} ${RunningX64}
|
||||||
!insertmacro CockLog "uninstall (64-bit): '$R0 _?=$INSTDIR'"
|
!insertmacro CockLog "uninstall (64-bit): '$R0 _?=$INSTDIR'"
|
||||||
StrCmp $R0 "" done64
|
StrCmp $R0 "" done64
|
||||||
DetailPrint "Removing previous version (64-bit)..."
|
DetailPrint "Removing previous version (64-bit)..."
|
||||||
|
Call BackupUserDataBeforeUninstall
|
||||||
ExecWait '$R0 _?=$INSTDIR'
|
ExecWait '$R0 _?=$INSTDIR'
|
||||||
|
Call RestoreUserDataAfterUninstall
|
||||||
|
|
||||||
done64:
|
done64:
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
FunctionEnd
|
FunctionEnd
|
||||||
|
|
||||||
|
; BackupUserDataBeforeUninstall / RestoreUserDataAfterUninstall
|
||||||
|
;
|
||||||
|
; The uninstallers shipped in 3.1.0/3.1.1 run a blanket "RMDir /r $INSTDIR".
|
||||||
|
; For a portable install that deletes $INSTDIR\data (decks, settings, pictures)
|
||||||
|
; along with the payload, and for a normal install it deletes anything the user
|
||||||
|
; pointed into $INSTDIR. The fixed uninstaller preserves foreign files, but the
|
||||||
|
; destructive one is already on disk on affected machines, so stash a copy
|
||||||
|
; outside $INSTDIR before invoking it and put it back afterwards. Best effort
|
||||||
|
; only: a failed backup must never abort the install.
|
||||||
|
Function BackupUserDataBeforeUninstall
|
||||||
|
StrCpy $DataBackupDir "$TEMP\CockatriceDataBackup"
|
||||||
|
${If} ${FileExists} "$INSTDIR\portable.dat"
|
||||||
|
${AndIf} ${FileExists} "$INSTDIR\data\*.*"
|
||||||
|
!insertmacro CockLog "pre-uninstall backup: copying $INSTDIR\data"
|
||||||
|
DetailPrint "Backing up portable user data before removing previous version..."
|
||||||
|
CreateDirectory "$DataBackupDir\data"
|
||||||
|
CopyFiles /SILENT "$INSTDIR\data\*.*" "$DataBackupDir\data\"
|
||||||
|
DetailPrint "Portable user data backup complete."
|
||||||
|
${EndIf}
|
||||||
|
${If} ${FileExists} "$INSTDIR\*.cod"
|
||||||
|
!insertmacro CockLog "pre-uninstall backup: copying loose deck files"
|
||||||
|
DetailPrint "Backing up deck files stored in the install directory..."
|
||||||
|
CreateDirectory "$DataBackupDir\loose_decks"
|
||||||
|
CopyFiles /SILENT "$INSTDIR\*.cod" "$DataBackupDir\loose_decks\"
|
||||||
|
${EndIf}
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
Function RestoreUserDataAfterUninstall
|
||||||
|
StrCmp $DataBackupDir "" done
|
||||||
|
${If} ${FileExists} "$DataBackupDir\data\*.*"
|
||||||
|
${If} ${FileExists} "$INSTDIR\data\*.*"
|
||||||
|
!insertmacro CockLog "post-uninstall restore: user data survived, backup kept"
|
||||||
|
DetailPrint "Previous user data survived the uninstall; backup kept at $DataBackupDir\data."
|
||||||
|
${Else}
|
||||||
|
!insertmacro CockLog "post-uninstall restore: restoring $INSTDIR\data"
|
||||||
|
CreateDirectory "$INSTDIR\data"
|
||||||
|
CopyFiles /SILENT "$DataBackupDir\data\*.*" "$INSTDIR\data\"
|
||||||
|
DetailPrint "Restored portable user data from pre-uninstall backup."
|
||||||
|
${EndIf}
|
||||||
|
${EndIf}
|
||||||
|
${If} ${FileExists} "$DataBackupDir\loose_decks\*.cod"
|
||||||
|
!insertmacro CockLog "post-uninstall restore: restoring loose deck files"
|
||||||
|
CreateDirectory "$INSTDIR"
|
||||||
|
CopyFiles /SILENT "$DataBackupDir\loose_decks\*.cod" "$INSTDIR\"
|
||||||
|
DetailPrint "Restored deck files stored in the install directory."
|
||||||
|
${IfNot} ${Silent}
|
||||||
|
MessageBox MB_OK|MB_ICONINFORMATION "Your decks were stored inside the Cockatrice install folder, which the previous version's uninstaller deletes.$\n$\nThey have been restored from a backup. Please move them out of the install folder (Settings -> General -> Paths) so a future uninstall cannot delete them again."
|
||||||
|
${EndIf}
|
||||||
|
${EndIf}
|
||||||
|
done:
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
; --- Running instance handling ---
|
; --- Running instance handling ---
|
||||||
; Cockatrice, Oracle and Servatrice must not be running while files are
|
; Cockatrice, Oracle and Servatrice must not be running while files are
|
||||||
; replaced or deleted. A still-running process holds locks on its .exe and
|
; replaced or deleted. A still-running process holds locks on its .exe and
|
||||||
|
|
@ -1041,7 +1098,9 @@ ${If} $PortableMode = 0
|
||||||
!insertmacro CockLog "componentsPagePre: old 32-bit uninstall '$OldUninstallString _?=$INSTDIR'"
|
!insertmacro CockLog "componentsPagePre: old 32-bit uninstall '$OldUninstallString _?=$INSTDIR'"
|
||||||
Call EnsureOldInstallNotRunning
|
Call EnsureOldInstallNotRunning
|
||||||
ClearErrors
|
ClearErrors
|
||||||
|
Call BackupUserDataBeforeUninstall
|
||||||
ExecWait "$OldUninstallString _?=$INSTDIR"
|
ExecWait "$OldUninstallString _?=$INSTDIR"
|
||||||
|
Call RestoreUserDataAfterUninstall
|
||||||
|
|
||||||
done32:
|
done32:
|
||||||
|
|
||||||
|
|
@ -1064,7 +1123,9 @@ ${If} $PortableMode = 0
|
||||||
!insertmacro CockLog "componentsPagePre: old 64-bit uninstall '$OldUninstallString _?=$INSTDIR'"
|
!insertmacro CockLog "componentsPagePre: old 64-bit uninstall '$OldUninstallString _?=$INSTDIR'"
|
||||||
Call EnsureOldInstallNotRunning
|
Call EnsureOldInstallNotRunning
|
||||||
ClearErrors
|
ClearErrors
|
||||||
|
Call BackupUserDataBeforeUninstall
|
||||||
ExecWait "$OldUninstallString _?=$INSTDIR"
|
ExecWait "$OldUninstallString _?=$INSTDIR"
|
||||||
|
Call RestoreUserDataAfterUninstall
|
||||||
|
|
||||||
done64:
|
done64:
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
#include <QDir>
|
||||||
#include <QEvent>
|
#include <QEvent>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
|
|
@ -110,6 +111,7 @@
|
||||||
#include <libcockatrice/settings/tabs_settings.h>
|
#include <libcockatrice/settings/tabs_settings.h>
|
||||||
#include <libcockatrice/settings/updates_settings.h>
|
#include <libcockatrice/settings/updates_settings.h>
|
||||||
#include <libcockatrice/utility/local_server_name.h>
|
#include <libcockatrice/utility/local_server_name.h>
|
||||||
|
#include <libcockatrice/utility/user_data_backup.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
#include <qnamespace.h>
|
#include <qnamespace.h>
|
||||||
|
|
@ -609,23 +611,82 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
void MainWindow::warnAboutDataPathsInProgramDir()
|
void MainWindow::warnAboutDataPathsInProgramDir()
|
||||||
{
|
{
|
||||||
auto &paths = SettingsCache::instance().paths();
|
auto &paths = SettingsCache::instance().paths();
|
||||||
if (SettingsCache::instance().getIsPortableBuild() || paths.getAppDirWarningAcknowledged()) {
|
if (paths.getAppDirWarningAcknowledged()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QStringList offenders = paths.pathsInsideDir(QCoreApplication::applicationDirPath());
|
const QString appDir = QCoreApplication::applicationDirPath();
|
||||||
if (offenders.isEmpty()) {
|
const bool isPortable = SettingsCache::instance().getIsPortableBuild();
|
||||||
return;
|
|
||||||
|
// Sources to back up outside the install directory before warning. The uninstallers
|
||||||
|
// shipped in 3.1.0/3.1.1 delete the whole install tree, so a safety copy is kept
|
||||||
|
// even though the fixed uninstaller preserves foreign files - the destructive one
|
||||||
|
// is already on disk on affected machines.
|
||||||
|
QStringList backupSources;
|
||||||
|
QStringList offenders;
|
||||||
|
if (isPortable) {
|
||||||
|
// Portable data lives inside the install directory by design, so there is
|
||||||
|
// nothing to reconfigure. Back up the small irreplaceable data (decks, custom
|
||||||
|
// sets, settings) instead. Pictures are deliberately excluded: they are large
|
||||||
|
// and can always be re-downloaded.
|
||||||
|
const QString dataDir = SettingsCache::instance().getDataPath();
|
||||||
|
for (const QString &sub : {QStringLiteral("decks"), QStringLiteral("customsets"), QStringLiteral("settings")}) {
|
||||||
|
const QString candidate = QDir(dataDir).absoluteFilePath(sub);
|
||||||
|
if (QFileInfo::exists(candidate)) {
|
||||||
|
backupSources.append(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (backupSources.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
offenders = paths.pathsInsideDir(appDir);
|
||||||
|
if (offenders.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
backupSources = offenders;
|
||||||
}
|
}
|
||||||
|
|
||||||
QMessageBox box(QMessageBox::Warning, tr("Data paths inside the program directory"),
|
QString backupRoot = UserDataBackup::defaultBackupRoot();
|
||||||
tr("These paths are configured inside the Cockatrice program directory:\n\n%1\n\n"
|
if (PathsSettings::isInsideDir(backupRoot, appDir)) {
|
||||||
"Cockatrice keeps its own data outside the program directory by default. Files "
|
backupRoot = QDir::temp().absoluteFilePath(QStringLiteral("CockatriceBackups"));
|
||||||
"stored inside it can be deleted by an update or by uninstalling Cockatrice. "
|
}
|
||||||
"Change them in the Settings dialog - most are on the General page - to keep "
|
QStringList backupLocations;
|
||||||
"them safe.")
|
for (const QString &source : backupSources) {
|
||||||
.arg(offenders.join(QLatin1Char('\n'))),
|
QString backupLocation;
|
||||||
QMessageBox::Ok, this);
|
if (UserDataBackup::backupAtRiskPath(source, backupRoot, &backupLocation)) {
|
||||||
|
qCInfo(MainWindowLog) << "Backed up at-risk user data" << source << "to" << backupLocation;
|
||||||
|
backupLocations.append(backupLocation);
|
||||||
|
} else {
|
||||||
|
qCWarning(MainWindowLog) << "Failed to back up at-risk user data:" << source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString message;
|
||||||
|
if (isPortable) {
|
||||||
|
message = tr("This portable install keeps its data inside the program directory, where the "
|
||||||
|
"uninstallers shipped in Cockatrice 3.1.0/3.1.1 delete everything on uninstall or "
|
||||||
|
"update.\n\nA safety backup of your decks, custom sets and settings has been created "
|
||||||
|
"at:\n%1\n\nUninstalling with the old uninstaller still deletes the program "
|
||||||
|
"directory, so keep your own copy of that backup until you have updated once.")
|
||||||
|
.arg(backupLocations.isEmpty() ? backupRoot : backupLocations.join(QLatin1Char('\n')));
|
||||||
|
} else {
|
||||||
|
message = tr("These paths are configured inside the Cockatrice program directory:\n\n%1\n\n"
|
||||||
|
"Cockatrice keeps its own data outside the program directory by default. Files "
|
||||||
|
"stored inside it can be deleted by an update or by uninstalling Cockatrice. "
|
||||||
|
"Change them in the Settings dialog - most are on the General page - to keep "
|
||||||
|
"them safe.")
|
||||||
|
.arg(offenders.join(QLatin1Char('\n')));
|
||||||
|
if (!backupLocations.isEmpty()) {
|
||||||
|
message += tr("\n\nA safety backup has been created at:\n%1").arg(backupLocations.join(QLatin1Char('\n')));
|
||||||
|
} else {
|
||||||
|
message += tr("\n\nThe automatic safety backup failed; please back up these folders manually.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QMessageBox box(QMessageBox::Warning,
|
||||||
|
isPortable ? tr("Portable data at risk on uninstall")
|
||||||
|
: tr("Data paths inside the program directory"),
|
||||||
|
message, QMessageBox::Ok, this);
|
||||||
box.setTextFormat(Qt::PlainText);
|
box.setTextFormat(Qt::PlainText);
|
||||||
auto *dontShowAgain = new QCheckBox(tr("Don't show this again"));
|
auto *dontShowAgain = new QCheckBox(tr("Don't show this again"));
|
||||||
box.setCheckBox(dontShowAgain);
|
box.setCheckBox(dontShowAgain);
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ set(UTILITY_SOURCES
|
||||||
libcockatrice/utility/report_categories.cpp
|
libcockatrice/utility/report_categories.cpp
|
||||||
libcockatrice/utility/server_rate_limiter.cpp
|
libcockatrice/utility/server_rate_limiter.cpp
|
||||||
libcockatrice/utility/translation_loader.cpp
|
libcockatrice/utility/translation_loader.cpp
|
||||||
|
libcockatrice/utility/user_data_backup.cpp
|
||||||
libcockatrice/utility/warning_categories.cpp
|
libcockatrice/utility/warning_categories.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -35,6 +36,7 @@ set(UTILITY_HEADERS
|
||||||
libcockatrice/utility/days_years_between.h
|
libcockatrice/utility/days_years_between.h
|
||||||
libcockatrice/utility/server_rate_limiter.h
|
libcockatrice/utility/server_rate_limiter.h
|
||||||
libcockatrice/utility/translation_loader.h
|
libcockatrice/utility/translation_loader.h
|
||||||
|
libcockatrice/utility/user_data_backup.h
|
||||||
libcockatrice/utility/warning_categories.h
|
libcockatrice/utility/warning_categories.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
#include "user_data_backup.h"
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QDirIterator>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
|
||||||
|
namespace UserDataBackup
|
||||||
|
{
|
||||||
|
|
||||||
|
bool copyDirectoryRecursively(const QString &sourceDir, const QString &destDir)
|
||||||
|
{
|
||||||
|
QDir source(sourceDir);
|
||||||
|
if (!source.exists()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!QDir().mkpath(destDir)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ok = true;
|
||||||
|
QDirIterator it(sourceDir, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden,
|
||||||
|
QDirIterator::Subdirectories);
|
||||||
|
while (it.hasNext()) {
|
||||||
|
it.next();
|
||||||
|
QString entryRel = source.relativeFilePath(it.filePath());
|
||||||
|
QString target = QDir(destDir).absoluteFilePath(entryRel);
|
||||||
|
if (it.fileInfo().isDir()) {
|
||||||
|
if (!QDir().mkpath(target)) {
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (QFile::exists(target) && !QFile::remove(target)) {
|
||||||
|
ok = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!QFile::copy(it.filePath(), target)) {
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool backupAtRiskPath(const QString &sourcePath, const QString &backupRoot, QString *backupLocationOut)
|
||||||
|
{
|
||||||
|
QFileInfo source(sourcePath);
|
||||||
|
if (!source.exists()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss");
|
||||||
|
QString backupDir = QDir(backupRoot).absoluteFilePath("CockatriceDataBackup-" + timestamp);
|
||||||
|
if (!QDir().mkpath(backupDir)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ok;
|
||||||
|
QString backupLocation;
|
||||||
|
if (source.isDir()) {
|
||||||
|
backupLocation = QDir(backupDir).absoluteFilePath(source.fileName());
|
||||||
|
ok = copyDirectoryRecursively(sourcePath, backupLocation);
|
||||||
|
} else {
|
||||||
|
backupLocation = QDir(backupDir).absoluteFilePath(source.fileName());
|
||||||
|
if (QFile::exists(backupLocation)) {
|
||||||
|
QFile::remove(backupLocation);
|
||||||
|
}
|
||||||
|
ok = QFile::copy(sourcePath, backupLocation);
|
||||||
|
}
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (backupLocationOut) {
|
||||||
|
*backupLocationOut = backupLocation;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString defaultBackupRoot()
|
||||||
|
{
|
||||||
|
QString documents = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
|
||||||
|
if (!documents.isEmpty()) {
|
||||||
|
return QDir(documents).absoluteFilePath("CockatriceBackups");
|
||||||
|
}
|
||||||
|
QString appData = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
|
||||||
|
if (!appData.isEmpty()) {
|
||||||
|
return QDir(appData).absoluteFilePath("../CockatriceBackups");
|
||||||
|
}
|
||||||
|
return QDir::temp().absoluteFilePath("CockatriceBackups");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace UserDataBackup
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
/**
|
||||||
|
* @file user_data_backup.h
|
||||||
|
* @ingroup Utility
|
||||||
|
* @brief Best-effort safety copies of user data that lives inside the install directory.
|
||||||
|
*
|
||||||
|
* Background: Windows uninstallers shipped in 3.1.0/3.1.1 ran a blanket
|
||||||
|
* "RMDir /r $INSTDIR". For a portable install that deletes $INSTDIR\data
|
||||||
|
* (decks, settings, pictures) along with the payload, and for a normal install
|
||||||
|
* it deletes anything the user pointed into $INSTDIR. The fixed uninstaller
|
||||||
|
* preserves foreign files, but the destructive one is already on disk on
|
||||||
|
* affected machines, so the client keeps a safety copy outside the install
|
||||||
|
* directory before warning the user. Detection of at-risk paths lives in
|
||||||
|
* PathsSettings::pathsInsideDir; this helper only copies.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef USER_DATA_BACKUP_H
|
||||||
|
#define USER_DATA_BACKUP_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
namespace UserDataBackup
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Recursively copies @p sourceDir into @p destDir, creating it as needed.
|
||||||
|
* @returns true when every file was copied (or there was nothing to copy).
|
||||||
|
*/
|
||||||
|
bool copyDirectoryRecursively(const QString &sourceDir, const QString &destDir);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copies an at-risk path (file or directory) to a timestamped folder under @p backupRoot.
|
||||||
|
* @param sourcePath file or directory holding user data (must exist).
|
||||||
|
* @param backupRoot directory OUTSIDE the install tree that will hold timestamped backups.
|
||||||
|
* @param backupLocationOut receives the created backup file or directory on success.
|
||||||
|
* @returns true on success, false when the source is missing or nothing could be copied.
|
||||||
|
*/
|
||||||
|
bool backupAtRiskPath(const QString &sourcePath, const QString &backupRoot, QString *backupLocationOut = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Default backup root guaranteed to live outside the install directory.
|
||||||
|
*/
|
||||||
|
QString defaultBackupRoot();
|
||||||
|
|
||||||
|
} // namespace UserDataBackup
|
||||||
|
|
||||||
|
#endif // USER_DATA_BACKUP_H
|
||||||
|
|
@ -174,6 +174,7 @@ endfunction()
|
||||||
cockatrice_add_test(NAME expression_test SOURCES expression_test.cpp LIBS libcockatrice_utility)
|
cockatrice_add_test(NAME expression_test SOURCES expression_test.cpp LIBS libcockatrice_utility)
|
||||||
cockatrice_add_test(NAME clamped_arithmetic_test SOURCES clamped_arithmetic_test.cpp LIBS libcockatrice_utility)
|
cockatrice_add_test(NAME clamped_arithmetic_test SOURCES clamped_arithmetic_test.cpp LIBS libcockatrice_utility)
|
||||||
cockatrice_add_test(NAME test_age_formatting SOURCES test_age_formatting.cpp LIBS libcockatrice_utility)
|
cockatrice_add_test(NAME test_age_formatting SOURCES test_age_formatting.cpp LIBS libcockatrice_utility)
|
||||||
|
cockatrice_add_test(NAME user_data_backup_test SOURCES user_data_backup_test.cpp LIBS libcockatrice_utility)
|
||||||
cockatrice_add_test(NAME password_hash_test SOURCES password_hash_test.cpp LIBS libcockatrice_utility)
|
cockatrice_add_test(NAME password_hash_test SOURCES password_hash_test.cpp LIBS libcockatrice_utility)
|
||||||
cockatrice_add_test(NAME report_categories_test SOURCES report_categories_test.cpp LIBS libcockatrice_utility)
|
cockatrice_add_test(NAME report_categories_test SOURCES report_categories_test.cpp LIBS libcockatrice_utility)
|
||||||
cockatrice_add_test(NAME warning_categories_test SOURCES warning_categories_test.cpp LIBS libcockatrice_utility)
|
cockatrice_add_test(NAME warning_categories_test SOURCES warning_categories_test.cpp LIBS libcockatrice_utility)
|
||||||
|
|
|
||||||
91
tests/user_data_backup_test.cpp
Normal file
91
tests/user_data_backup_test.cpp
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QTemporaryDir>
|
||||||
|
#include <libcockatrice/utility/user_data_backup.h>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
TEST(UserDataBackup, CopyDirectoryKeepsNestedStructure)
|
||||||
|
{
|
||||||
|
QTemporaryDir sourceDir;
|
||||||
|
QTemporaryDir destParent;
|
||||||
|
ASSERT_TRUE(sourceDir.isValid());
|
||||||
|
ASSERT_TRUE(destParent.isValid());
|
||||||
|
QDir source(sourceDir.path());
|
||||||
|
ASSERT_TRUE(source.mkpath("decks/sub"));
|
||||||
|
QFile deck(source.absoluteFilePath("decks/sub/mydeck.cod"));
|
||||||
|
ASSERT_TRUE(deck.open(QIODevice::WriteOnly | QIODevice::Text));
|
||||||
|
deck.write("<cockatrice_deck version=\"1\"/>\n");
|
||||||
|
deck.close();
|
||||||
|
|
||||||
|
QString dest = QDir(destParent.path()).absoluteFilePath("copy");
|
||||||
|
EXPECT_TRUE(UserDataBackup::copyDirectoryRecursively(sourceDir.path(), dest));
|
||||||
|
EXPECT_TRUE(QFile::exists(dest + "/decks/sub/mydeck.cod"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UserDataBackup, CopyMissingDirectoryFails)
|
||||||
|
{
|
||||||
|
QTemporaryDir destParent;
|
||||||
|
ASSERT_TRUE(destParent.isValid());
|
||||||
|
EXPECT_FALSE(
|
||||||
|
UserDataBackup::copyDirectoryRecursively("/nonexistent-cockatrice-path-xyz", destParent.path() + "/copy"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UserDataBackup, BackupDirectoryCreatesTimestampedCopy)
|
||||||
|
{
|
||||||
|
QTemporaryDir sourceDir;
|
||||||
|
QTemporaryDir backupRoot;
|
||||||
|
ASSERT_TRUE(sourceDir.isValid());
|
||||||
|
ASSERT_TRUE(backupRoot.isValid());
|
||||||
|
QFile deck(QDir(sourceDir.path()).absoluteFilePath("mydeck.cod"));
|
||||||
|
ASSERT_TRUE(deck.open(QIODevice::WriteOnly | QIODevice::Text));
|
||||||
|
deck.write("<cockatrice_deck version=\"1\"/>\n");
|
||||||
|
deck.close();
|
||||||
|
|
||||||
|
QString backupLocation;
|
||||||
|
EXPECT_TRUE(UserDataBackup::backupAtRiskPath(sourceDir.path(), backupRoot.path(), &backupLocation));
|
||||||
|
EXPECT_TRUE(QFile::exists(backupLocation + "/mydeck.cod"));
|
||||||
|
EXPECT_TRUE(backupLocation.startsWith(QDir(backupRoot.path()).absolutePath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UserDataBackup, BackupSingleFile)
|
||||||
|
{
|
||||||
|
QTemporaryDir sourceDir;
|
||||||
|
QTemporaryDir backupRoot;
|
||||||
|
ASSERT_TRUE(sourceDir.isValid());
|
||||||
|
ASSERT_TRUE(backupRoot.isValid());
|
||||||
|
QString cardDb = QDir(sourceDir.path()).absoluteFilePath("cards.xml");
|
||||||
|
QFile db(cardDb);
|
||||||
|
ASSERT_TRUE(db.open(QIODevice::WriteOnly | QIODevice::Text));
|
||||||
|
db.write("<cockatrice_card_database/>\n");
|
||||||
|
db.close();
|
||||||
|
|
||||||
|
QString backupLocation;
|
||||||
|
EXPECT_TRUE(UserDataBackup::backupAtRiskPath(cardDb, backupRoot.path(), &backupLocation));
|
||||||
|
EXPECT_TRUE(QFile::exists(backupLocation));
|
||||||
|
EXPECT_TRUE(backupLocation.endsWith("cards.xml"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UserDataBackup, BackupMissingSourceFails)
|
||||||
|
{
|
||||||
|
QTemporaryDir backupRoot;
|
||||||
|
ASSERT_TRUE(backupRoot.isValid());
|
||||||
|
EXPECT_FALSE(UserDataBackup::backupAtRiskPath("/nonexistent-cockatrice-path-xyz", backupRoot.path(), nullptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UserDataBackup, DefaultBackupRootIsUsable)
|
||||||
|
{
|
||||||
|
QString root = UserDataBackup::defaultBackupRoot();
|
||||||
|
EXPECT_FALSE(root.isEmpty());
|
||||||
|
EXPECT_TRUE(QDir(root).isAbsolute());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
|
return RUN_ALL_TESTS();
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue