mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-06 10:03:26 -08:00
157 lines
7.3 KiB
C++
157 lines
7.3 KiB
C++
/**
|
|
* @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>
|
|
#include <QStringList>
|
|
|
|
namespace UserDataBackup
|
|
{
|
|
|
|
/** @brief Above this total, the client skips the automatic startup backup and asks for a manual copy. */
|
|
constexpr qint64 MAX_AUTO_BACKUP_BYTES = 500LL * 1024 * 1024;
|
|
|
|
/**
|
|
* @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).
|
|
* Symlinks (file or directory) are copied best-effort via QFile::copy without
|
|
* dereferencing them; a dangling link fails that entry only.
|
|
*/
|
|
bool copyDirectoryRecursively(const QString &sourceDir, const QString &destDir);
|
|
|
|
/**
|
|
* @brief Creates a fresh timestamped session directory under @p backupRoot.
|
|
* @param backupRoot directory OUTSIDE the install tree that will hold timestamped backups.
|
|
* @param sessionDirOut receives the created directory on success.
|
|
* @returns true on success. Millisecond timestamps plus a numeric suffix keep rapid
|
|
* successive calls (e.g. one per installer phase) from sharing a directory.
|
|
*/
|
|
bool createBackupSessionDir(const QString &backupRoot, QString *sessionDirOut);
|
|
|
|
/**
|
|
* @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 whenever a target
|
|
* was created, even when the copy was only partial (i.e. also on failure with a partial copy).
|
|
* Cleared when nothing was salvaged.
|
|
* @returns true when the whole source was copied, false when the source is missing or
|
|
* any entry failed. A false return with a non-empty @p backupLocationOut means a partial
|
|
* backup exists at that location.
|
|
*/
|
|
bool backupAtRiskPath(const QString &sourcePath, const QString &backupRoot, QString *backupLocationOut = nullptr);
|
|
|
|
/**
|
|
* @brief Copies several at-risk paths into a single timestamped session directory.
|
|
* @param sourcePaths files or directories holding user data; missing entries are skipped.
|
|
* @param backupRoot directory OUTSIDE the install tree that will hold timestamped backups.
|
|
* @param backupLocationsOut receives one entry per source that produced a backup target
|
|
* (including partial copies).
|
|
* @param sessionDirOut optionally receives the shared session directory; set to empty
|
|
* when nothing was backed up (all sources missing, or every copy failed outright).
|
|
* @returns true when every existing source was copied completely.
|
|
* A false return with a non-empty @p backupLocationsOut means a partial backup exists.
|
|
* When nothing at all could be salvaged the session directory is removed again, so a
|
|
* failed attempt never leaves an empty session behind that would block retries.
|
|
* Completeness is recorded with a marker file; see isBackupSessionComplete().
|
|
*/
|
|
bool backupAtRiskPaths(const QStringList &sourcePaths,
|
|
const QString &backupRoot,
|
|
QStringList *backupLocationsOut,
|
|
QString *sessionDirOut = nullptr);
|
|
|
|
/**
|
|
* @brief Estimates the total bytes that backing up @p sourcePaths would copy.
|
|
* @param capBytes stop counting once the total exceeds this; the returned value is then
|
|
* at least @p capBytes (used to skip the scan early on huge trees).
|
|
* Missing entries contribute 0. Best effort only.
|
|
*/
|
|
qint64 estimateBackupSize(const QStringList &sourcePaths, qint64 capBytes = MAX_AUTO_BACKUP_BYTES);
|
|
|
|
/**
|
|
* @brief Default backup root guaranteed to live outside the install directory.
|
|
*/
|
|
QString defaultBackupRoot();
|
|
|
|
/**
|
|
* @brief Lists existing backup session directories under @p backupRoot, newest first.
|
|
*/
|
|
QStringList listBackupSessions(const QString &backupRoot);
|
|
|
|
/**
|
|
* @brief Returns the newest non-empty session directory younger than @p maxAgeHours, or empty.
|
|
*
|
|
* Used to throttle startup safety backups so dismissing the warning without
|
|
* "Don't show again" does not create a full duplicate on every launch.
|
|
* Empty sessions (failed attempts) are skipped so they never block a retry.
|
|
* Every session is age-checked individually; no early exit assumes filename
|
|
* order matches mtime order (clock adjustments or externally touched dirs
|
|
* can break that assumption).
|
|
*/
|
|
QString findRecentBackupSession(const QString &backupRoot, int maxAgeHours = 24);
|
|
|
|
/**
|
|
* @brief Returns the newest session that is non-empty, complete and covering @p sourcePaths, or empty.
|
|
*
|
|
* Unlike findRecentBackupSession(), an incomplete newest session (partial failure) or one
|
|
* covering a different source set does not block reuse of an older good session.
|
|
*/
|
|
QString findRecentUsableBackupSession(const QString &backupRoot, const QStringList &sourcePaths, int maxAgeHours = 24);
|
|
|
|
/**
|
|
* @brief Returns the newest non-empty session covering @p sourcePaths, regardless of completeness.
|
|
*
|
|
* Used as a fallback pointer when a fresh copy fails entirely, so the dialog can still
|
|
* point at salvaged content. Completeness is left to the caller to report.
|
|
*/
|
|
QString
|
|
findRecentCoveringBackupSession(const QString &backupRoot, const QStringList &sourcePaths, int maxAgeHours = 24);
|
|
|
|
/**
|
|
* @brief Reports whether @p sessionDir holds a complete backup.
|
|
* @returns true when the session carries the complete marker, or when it predates
|
|
* markers but holds backup content (legacy sessions); false for partial backups
|
|
* and for empty/missing sessions.
|
|
*/
|
|
bool isBackupSessionComplete(const QString &sessionDir);
|
|
|
|
/**
|
|
* @brief Deletes oldest sessions keeping only the newest @p keepCount ones.
|
|
* Empty sessions are junk from failed attempts and are always removed, so they
|
|
* can never evict a good session from the kept set.
|
|
*/
|
|
void pruneOldBackupSessions(const QString &backupRoot, int keepCount = 5);
|
|
|
|
/**
|
|
* @brief Lists the backup targets inside an existing session directory.
|
|
* @returns absolute paths of the entries, for reuse messages without a new copy.
|
|
* Completeness marker and source manifest files are internal bookkeeping and are never listed.
|
|
*/
|
|
QStringList listSessionBackupLocations(const QString &sessionDir);
|
|
|
|
/**
|
|
* @brief Reports whether @p sessionDir was recorded as covering all of @p sourcePaths.
|
|
* @returns true when the session's source manifest lists every entry of @p sourcePaths
|
|
* (normalized via QDir::cleanPath, case-insensitive on Windows/macOS). Legacy sessions
|
|
* without a manifest return false so a changed source set always triggers a fresh copy
|
|
* instead of reusing a backup that may not cover it.
|
|
*/
|
|
bool sessionCoversSources(const QString &sessionDir, const QStringList &sourcePaths);
|
|
|
|
} // namespace UserDataBackup
|
|
|
|
#endif // USER_DATA_BACKUP_H
|