mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-12 04:50:01 -08:00
[Installer][Client] Fix review findings: subdir backup, portable warning, backup API
NSIS: - Back up unknown top-level install subdirs (e.g. <installdir>\decks), not just loose deck files, so the 3.1.1 -> new upgrade no longer loses data folders the old RMDir /r deletes before the client ever runs. Payload dirs (plugins/translations/sounds/themes) stay in place to avoid restoring stale DLLs; restore + relocate handle the new backup set and the one-time notice covers both decks and folders. - Restart the %TEMP% backup when the 32/64-bit passes point at different dirs instead of merging (which restored one dir's files into the other); document that only the last pass relocates and the %TEMP% copy is not a durable second copy. Client: - Drop the portable at-risk warning: portable installs never shipped an uninstaller and portable updates preserve data/, so every portable user got a spurious dialog plus daily host-machine copies. - AtRiskDataWarning is now a plain class with Q_DECLARE_TR_FUNCTIONS instead of QObject+Q_OBJECT; logging category renamed to at_risk_data_warning; MainWindow wrapper inlined into startupConfigCheck. - Lower the synchronous startup backup cap 500 -> 200 MB. Utility/tests: - Remove test-only backupAtRiskPath()/findRecentBackupSession() and the no-marker/no-manifest legacy paths; isBackupSessionComplete() now requires the complete marker; tests updated to the plural API.
This commit is contained in:
parent
202c0d6561
commit
a23e02b5a4
8 changed files with 229 additions and 345 deletions
|
|
@ -33,8 +33,8 @@ bool pathsEqual(const QString &a, const QString &b)
|
|||
|
||||
void writeCompletionMarker(const QString &sessionDir, bool complete)
|
||||
{
|
||||
// Best effort: a missing marker only means isBackupSessionComplete() falls
|
||||
// back to the legacy content check below.
|
||||
// Best effort: a missing marker only means isBackupSessionComplete()
|
||||
// reports the session as incomplete.
|
||||
QFile marker(QDir(sessionDir).absoluteFilePath(complete ? COMPLETE_MARKER : INCOMPLETE_MARKER));
|
||||
if (marker.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
marker.write(complete ? "complete\n" : "incomplete\n");
|
||||
|
|
@ -132,20 +132,6 @@ bool createBackupSessionDir(const QString &backupRoot, QString *sessionDirOut)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool backupAtRiskPath(const QString &sourcePath, const QString &backupRoot, QString *backupLocationOut)
|
||||
{
|
||||
QStringList locations;
|
||||
const bool ok = backupAtRiskPaths({sourcePath}, backupRoot, &locations, nullptr);
|
||||
if (backupLocationOut) {
|
||||
if (!locations.isEmpty()) {
|
||||
*backupLocationOut = locations.first();
|
||||
} else {
|
||||
backupLocationOut->clear();
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool backupAtRiskPaths(const QStringList &sourcePaths,
|
||||
const QString &backupRoot,
|
||||
QStringList *backupLocationsOut,
|
||||
|
|
@ -204,7 +190,7 @@ bool backupAtRiskPaths(const QStringList &sourcePaths,
|
|||
}
|
||||
if (locations.isEmpty()) {
|
||||
// Nothing salvaged: remove the session again so the empty directory
|
||||
// neither blocks a retry via findRecentBackupSession() nor evicts a
|
||||
// neither blocks a retry via the recent-session reuse nor evicts a
|
||||
// good session in pruneOldBackupSessions().
|
||||
QDir(sessionDir).removeRecursively();
|
||||
if (backupLocationsOut) {
|
||||
|
|
@ -287,29 +273,6 @@ QStringList listBackupSessions(const QString &backupRoot)
|
|||
return sessions;
|
||||
}
|
||||
|
||||
QString findRecentBackupSession(const QString &backupRoot, int maxAgeHours)
|
||||
{
|
||||
const QStringList sessions = listBackupSessions(backupRoot);
|
||||
const QDateTime now = QDateTime::currentDateTime();
|
||||
for (const QString &session : sessions) {
|
||||
const QFileInfo info(session);
|
||||
if (!info.exists()) {
|
||||
continue;
|
||||
}
|
||||
// No early break: filename order is not guaranteed to match mtime order
|
||||
// (clock adjustments, externally touched dirs), and session counts are tiny.
|
||||
if (info.lastModified().secsTo(now) >= static_cast<qint64>(maxAgeHours) * 3600) {
|
||||
continue;
|
||||
}
|
||||
// Skip empty sessions from failed attempts: they must never block a retry.
|
||||
if (listSessionBackupLocations(session).isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
return session;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QString findRecentCoveringBackupSession(const QString &backupRoot, const QStringList &sourcePaths, int maxAgeHours)
|
||||
{
|
||||
const QStringList sessions = listBackupSessions(backupRoot);
|
||||
|
|
@ -368,15 +331,7 @@ bool isBackupSessionComplete(const QString &sessionDir)
|
|||
if (!session.exists()) {
|
||||
return false;
|
||||
}
|
||||
if (QFileInfo::exists(session.absoluteFilePath(COMPLETE_MARKER))) {
|
||||
return true;
|
||||
}
|
||||
if (QFileInfo::exists(session.absoluteFilePath(INCOMPLETE_MARKER))) {
|
||||
return false;
|
||||
}
|
||||
// Legacy sessions from before markers existed: content means the copy
|
||||
// reported success at the time, so treat them as complete.
|
||||
return !listSessionBackupLocations(session.absolutePath()).isEmpty();
|
||||
return QFileInfo::exists(session.absoluteFilePath(COMPLETE_MARKER));
|
||||
}
|
||||
|
||||
void pruneOldBackupSessions(const QString &backupRoot, int keepCount)
|
||||
|
|
@ -421,8 +376,8 @@ bool sessionCoversSources(const QString &sessionDir, const QStringList &sourcePa
|
|||
{
|
||||
QFile manifest(QDir(sessionDir).absoluteFilePath(SOURCES_MANIFEST));
|
||||
if (!manifest.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
// Legacy sessions predate the manifest: coverage cannot be verified,
|
||||
// so report not-covered and let the caller take a fresh copy.
|
||||
// No readable manifest (e.g. the write failed): coverage cannot be
|
||||
// verified, so report not-covered and let the caller take a fresh copy.
|
||||
return false;
|
||||
}
|
||||
QStringList recorded;
|
||||
|
|
|
|||
|
|
@ -22,8 +22,9 @@
|
|||
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 Above this total, the client skips the automatic startup backup and asks for a manual copy.
|
||||
* Kept small on purpose: the copy runs synchronously on the UI thread at startup. */
|
||||
constexpr qint64 MAX_AUTO_BACKUP_BYTES = 200LL * 1024 * 1024;
|
||||
|
||||
/**
|
||||
* @brief Recursively copies @p sourceDir into @p destDir, creating it as needed.
|
||||
|
|
@ -42,19 +43,6 @@ bool copyDirectoryRecursively(const QString &sourceDir, const QString &destDir);
|
|||
*/
|
||||
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.
|
||||
|
|
@ -92,23 +80,11 @@ QString defaultBackupRoot();
|
|||
*/
|
||||
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.
|
||||
* 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);
|
||||
|
||||
|
|
@ -123,9 +99,8 @@ findRecentCoveringBackupSession(const QString &backupRoot, const QStringList &so
|
|||
|
||||
/**
|
||||
* @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.
|
||||
* @returns true when the session carries the complete marker, false otherwise
|
||||
* (partial backups and empty/missing sessions).
|
||||
*/
|
||||
bool isBackupSessionComplete(const QString &sessionDir);
|
||||
|
||||
|
|
@ -146,9 +121,9 @@ 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.
|
||||
* (normalized via QDir::cleanPath, case-insensitive on Windows/macOS). Sessions
|
||||
* without a readable 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);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue