-
Notifications
You must be signed in to change notification settings - Fork 1
Windows Start Fresh Fix #34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -71,7 +71,7 @@ public DatabaseUnlockService( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Archive encrypted database and create fresh database when password forgotten | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Archive encrypted database and create fresh database when password forgotten. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="databasePath">Path to encrypted database</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <returns>(Success, ArchivedPath, ErrorMessage)</returns> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -92,12 +92,54 @@ public DatabaseUnlockService( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var timestamp = DateTime.Now.ToString("yyyyMMdd-HHmmss"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var archivedPath = Path.Combine(backupsDir, $"{dbFileNameWithoutExt}.{timestamp}.encrypted.db"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Move encrypted database to backups | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| File.Move(databasePath, archivedPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // On Windows the OS enforces mandatory file locks. Even after SqliteConnection is | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // disposed, the connection pool keeps the Win32 file handle open until explicitly | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // cleared. Clear all pools and give the GC a chance to release any lingering | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // handles before we attempt the file move. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SqliteConnection.ClearAllPools(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (OperatingSystem.IsWindows()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| GC.Collect(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| GC.WaitForPendingFinalizers(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| GC.Collect(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Move the main database file. Retry once on Windows in case a finalizer | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // hadn't yet released its handle on the first attempt. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| File.Move(databasePath, archivedPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| catch (IOException) when (OperatingSystem.IsWindows()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _logger.LogWarning("File move failed on first attempt (Windows lock), retrying after 500 ms"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await Task.Delay(500); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| File.Move(databasePath, archivedPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Remove WAL companion files if present. These are created when WAL mode is active | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // and must be cleaned up so the fresh database starts without a stale journal. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // On Windows these files may also be locked; delete rather than move since the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // archived backup doesn't need them. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| foreach (var sidecar in new[] { databasePath + "-wal", databasePath + "-shm" }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!File.Exists(sidecar)) continue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| File.Delete(sidecar); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _logger.LogInformation("Removed WAL companion file: {Sidecar}", sidecar); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| catch (Exception ex) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Non-fatal: a stale WAL without its main database is harmless. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _logger.LogWarning("Could not remove WAL companion file {Sidecar}: {Message}", sidecar, ex.Message); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _logger.LogWarning("Could not remove WAL companion file {Sidecar}: {Message}", sidecar, ex.Message); | |
| _logger.LogWarning(ex, "Could not remove WAL companion file {Sidecar}", sidecar); |
Copilot
AI
Feb 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The app explicitly enables SQLite WAL mode (see SqlCipherConnectionInterceptor.cs:60-63), so the -wal file can contain committed transactions not yet checkpointed into the main DB. Deleting databasePath-wal/-shm here can make the archived backup incomplete/unrestorable. Instead, archive the sidecar files alongside the moved DB (e.g., move/rename to ${archivedPath}-wal and ${archivedPath}-shm), and only delete them from the original location after they’ve been safely moved (or at least avoid deleting if the move fails).
| // Remove WAL companion files if present. These are created when WAL mode is active | |
| // and must be cleaned up so the fresh database starts without a stale journal. | |
| // On Windows these files may also be locked; delete rather than move since the | |
| // archived backup doesn't need them. | |
| foreach (var sidecar in new[] { databasePath + "-wal", databasePath + "-shm" }) | |
| { | |
| if (!File.Exists(sidecar)) continue; | |
| try | |
| { | |
| File.Delete(sidecar); | |
| _logger.LogInformation("Removed WAL companion file: {Sidecar}", sidecar); | |
| } | |
| catch (Exception ex) | |
| { | |
| // Non-fatal: a stale WAL without its main database is harmless. | |
| _logger.LogWarning("Could not remove WAL companion file {Sidecar}: {Message}", sidecar, ex.Message); | |
| // Move WAL companion files alongside the archived database if present. These are | |
| // created when WAL mode is active and may contain committed transactions that have | |
| // not yet been checkpointed into the main database file. For the backup to be | |
| // complete and restorable, we archive these sidecar files together with the main DB. | |
| var sidecars = new[] | |
| { | |
| new { Source = databasePath + "-wal", Destination = archivedPath + "-wal" }, | |
| new { Source = databasePath + "-shm", Destination = archivedPath + "-shm" } | |
| }; | |
| foreach (var sidecar in sidecars) | |
| { | |
| if (!File.Exists(sidecar.Source)) continue; | |
| try | |
| { | |
| if (File.Exists(sidecar.Destination)) | |
| { | |
| File.Delete(sidecar.Destination); | |
| } | |
| File.Move(sidecar.Source, sidecar.Destination); | |
| _logger.LogInformation("Archived WAL companion file from {Source} to {Destination}", sidecar.Source, sidecar.Destination); | |
| } | |
| catch (Exception ex) | |
| { | |
| // Non-fatal: if we can't move the sidecar, log and continue. The main | |
| // database has already been moved, and leaving the original WAL/SHM | |
| // next to the soon-to-be-recreated database is safer than deleting it. | |
| _logger.LogWarning( | |
| "Could not archive WAL companion file {Source} to {Destination}: {Message}", | |
| sidecar.Source, | |
| sidecar.Destination, | |
| ex.Message); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the Windows retry path you catch
IOExceptionbut don’t log the exception details, which makes it harder to diagnose persistent lock issues in the field. Consider capturing the exception (catch (IOException ex)) and logging it (e.g.,_logger.LogWarning(ex, ...)) before the retry.