Skip to content

Commit 5ee6c94

Browse files
authored
Merge pull request #34 from xnodeoncode/development
Windows Start Fresh Fix
2 parents c3fed5f + 15fd177 commit 5ee6c94

1 file changed

Lines changed: 47 additions & 5 deletions

File tree

2-Aquiis.Application/Services/DatabaseUnlockService.cs

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public DatabaseUnlockService(
7171
}
7272

7373
/// <summary>
74-
/// Archive encrypted database and create fresh database when password forgotten
74+
/// Archive encrypted database and create fresh database when password forgotten.
7575
/// </summary>
7676
/// <param name="databasePath">Path to encrypted database</param>
7777
/// <returns>(Success, ArchivedPath, ErrorMessage)</returns>
@@ -92,12 +92,54 @@ public DatabaseUnlockService(
9292
var timestamp = DateTime.Now.ToString("yyyyMMdd-HHmmss");
9393
var archivedPath = Path.Combine(backupsDir, $"{dbFileNameWithoutExt}.{timestamp}.encrypted.db");
9494

95-
// Move encrypted database to backups
96-
File.Move(databasePath, archivedPath);
95+
// On Windows the OS enforces mandatory file locks. Even after SqliteConnection is
96+
// disposed, the connection pool keeps the Win32 file handle open until explicitly
97+
// cleared. Clear all pools and give the GC a chance to release any lingering
98+
// handles before we attempt the file move.
99+
SqliteConnection.ClearAllPools();
100+
if (OperatingSystem.IsWindows())
101+
{
102+
GC.Collect();
103+
GC.WaitForPendingFinalizers();
104+
GC.Collect();
105+
}
106+
107+
// Move the main database file. Retry once on Windows in case a finalizer
108+
// hadn't yet released its handle on the first attempt.
109+
try
110+
{
111+
File.Move(databasePath, archivedPath);
112+
}
113+
catch (IOException) when (OperatingSystem.IsWindows())
114+
{
115+
_logger.LogWarning("File move failed on first attempt (Windows lock), retrying after 500 ms");
116+
await Task.Delay(500);
117+
File.Move(databasePath, archivedPath);
118+
}
119+
120+
// Remove WAL companion files if present. These are created when WAL mode is active
121+
// and must be cleaned up so the fresh database starts without a stale journal.
122+
// On Windows these files may also be locked; delete rather than move since the
123+
// archived backup doesn't need them.
124+
foreach (var sidecar in new[] { databasePath + "-wal", databasePath + "-shm" })
125+
{
126+
if (!File.Exists(sidecar)) continue;
127+
try
128+
{
129+
File.Delete(sidecar);
130+
_logger.LogInformation("Removed WAL companion file: {Sidecar}", sidecar);
131+
}
132+
catch (Exception ex)
133+
{
134+
// Non-fatal: a stale WAL without its main database is harmless.
135+
_logger.LogWarning("Could not remove WAL companion file {Sidecar}: {Message}", sidecar, ex.Message);
136+
}
137+
}
138+
97139
_logger.LogInformation("Encrypted database archived to: {ArchivedPath}", archivedPath);
98140

99-
// New unencrypted database will be created automatically on app restart
100-
// The app will detect no database exists and go through first-time setup
141+
// New database will be created automatically on app restart — the app detects
142+
// no database exists and runs through first-time setup / migrations.
101143
return (true, archivedPath, null);
102144
}
103145
catch (Exception ex)

0 commit comments

Comments
 (0)