diff --git a/cmake/NSIS.template.in b/cmake/NSIS.template.in index 3359b9803..956b3349a 100644 --- a/cmake/NSIS.template.in +++ b/cmake/NSIS.template.in @@ -383,13 +383,15 @@ Function BackupUserDataBeforeUninstall ${If} $OldInstallDir == "" StrCpy $OldInstallDir $INSTDIR ${EndIf} -; No wipe of $TEMP\CockatriceDataBackup here: %TEMP% survives across installer -; runs, so the previous run's safety copy stays on disk until this run overwrites -; it. Stale content can never leak into this run because restore is gated on the -; per-run $DataBackupDone / $LooseDecksBackupDone flags below, not on what happens -; to be on disk. CopyFiles merges/overwrites, so calling this twice in sequence -; (32-bit then 64-bit uninstall) can only add safety, never delete the first -; call's copy. +; %TEMP% survives across installer runs, so a previous run's safety copy would +; otherwise merge into this run: CopyFiles merges/overwrites, resurrecting files +; the user deleted since (e.g. deck A from run 1 reappearing alongside deck B +; from run 2) or restoring stale content after a failed copy. Wipe once per +; installer run - $DataBackupDir is empty until the first backup of this run, +; so the second call in sequence (32-bit then 64-bit uninstall) still merges. +${If} $DataBackupDir == "" + RMDir /r "$TEMP\CockatriceDataBackup" +${EndIf} ${If} ${FileExists} "$OldInstallDir\portable.dat" ${AndIf} ${FileExists} "$OldInstallDir\data" StrCpy $DataBackupDir "$TEMP\CockatriceDataBackup" @@ -485,7 +487,7 @@ StrCmp $DataBackupDir "" done StrCmp $OldInstallDir "" done StrCmp $OldInstallDir $INSTDIR done ${If} $DataBackupDone == "1" -${AndIf} ${FileExists} "$OldInstallDir\data\*.*" +${AndIf} ${FileExists} "$OldInstallDir\data" !insertmacro CockLog "relocate: copying restored data $OldInstallDir\data -> $INSTDIR\data" CreateDirectory "$INSTDIR\data" CopyFiles /SILENT "$OldInstallDir\data\*.*" "$INSTDIR\data\" diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp index c8c782b99..53b4fb35d 100644 --- a/cockatrice/src/interface/window_main.cpp +++ b/cockatrice/src/interface/window_main.cpp @@ -674,8 +674,12 @@ void MainWindow::warnAboutDataPathsInProgramDir() } backupSources.append(offender); } + // A fresh portable install has no data on disk yet, but the user still + // needs to know their data will live where the 3.1.0/3.1.1 uninstaller + // deletes it - so fall through to the warning with an empty backup + // instead of returning silently. if (backupSources.isEmpty()) { - return; + qCInfo(MainWindowLog) << "Portable install with no existing data files, warning without backup"; } offenders.clear(); } else { @@ -718,7 +722,11 @@ void MainWindow::warnAboutDataPathsInProgramDir() bool recentUsable = false; if (!recentSession.isEmpty()) { recentLocations = UserDataBackup::listSessionBackupLocations(recentSession); - recentUsable = !recentLocations.isEmpty() && UserDataBackup::isBackupSessionComplete(recentSession) && + // An empty source set must never reuse a session: sessionCoversSources is + // vacuously true for it, which would claim an unrelated recent backup + // covers paths that were never backed up. + recentUsable = !sourcesToBackUp.isEmpty() && !recentLocations.isEmpty() && + UserDataBackup::isBackupSessionComplete(recentSession) && UserDataBackup::sessionCoversSources(recentSession, sourcesToBackUp); } if (recentUsable) { @@ -747,10 +755,13 @@ void MainWindow::warnAboutDataPathsInProgramDir() UserDataBackup::pruneOldBackupSessions(backupRoot, 5); if (backupLocations.isEmpty() && !recentLocations.isEmpty()) { // Fresh copy failed entirely: fall back to the recent session so the - // dialog still points at something, flagged incomplete below. - sessionDir = recentSession; - backupLocations = recentLocations; - backupComplete = false; + // dialog still points at something, flagged incomplete below - but + // only when it actually covers the current sources. + if (!recentSession.isEmpty() && UserDataBackup::sessionCoversSources(recentSession, sourcesToBackUp)) { + sessionDir = recentSession; + backupLocations = recentLocations; + backupComplete = false; + } } } } @@ -788,6 +799,11 @@ void MainWindow::warnAboutDataPathsInProgramDir() "before uninstalling or updating.\n\nReplays and downloaded pictures would not have been " "included in that backup either.") .arg(sizeInfo); + } else if (sourcesToBackUp.isEmpty()) { + 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\nNone of your data files exist on disk yet, so there was nothing to back up. " + "Files created there later are still at risk until you have updated once."); } else { 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 "