Skip to content

Commit d61566f

Browse files
authored
Fix read-only open semantics, Studio prompt localization, and multi-process config docs (#125)
* Initial plan * Fix read-only open behavior, Studio prompts, and multiprocess docs --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent 1cba295 commit d61566f

6 files changed

Lines changed: 68 additions & 15 deletions

File tree

README-5.0.0-preview.0.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ This feature is **opt-in** — all existing single-process behaviour is preserve
528528
```csharp
529529
var config = new PageFileConfig
530530
{
531-
EnableMultiProcessAccess = true,
531+
AllowMultiProcessAccess = true,
532532
};
533533

534534
// Process A (writer)
@@ -539,7 +539,7 @@ using var db = new AppDb("shared.db", config);
539539
```
540540

541541
> [!IMPORTANT]
542-
> Multi-process WAL requires all cooperating processes to open the database with `EnableMultiProcessAccess = true`. A process that opens with `EnableMultiProcessAccess = false` will hold `FileShare.None` and block other processes.
542+
> Multi-process WAL requires all cooperating processes to open the database with `AllowMultiProcessAccess = true`. A process that opens with `AllowMultiProcessAccess = false` will hold `FileShare.None` and block other processes.
543543
544544
---
545545

@@ -628,7 +628,7 @@ v5.0.0-preview.0 is **fully backwards-compatible** with v4.4.2 databases. No fil
628628
| Add audit trail | Call `ConfigureAudit(...)` after construction |
629629
| Add GDPR annotations | Annotate properties with `[PersonalData]` and rebuild |
630630
| Enable Strict mode | Add `HasGdprMode(GdprMode.Strict)` + configure encryption + audit |
631-
| Enable multi-process access | Set `PageFileConfig.EnableMultiProcessAccess = true` on all processes |
631+
| Enable multi-process access | Set `PageFileConfig.AllowMultiProcessAccess = true` on all processes |
632632

633633
---
634634

@@ -706,7 +706,7 @@ v5.0.0-preview.0 is **fully backwards-compatible** with v4.4.2 databases. No fil
706706
## Known limitations in this preview
707707

708708
- `RotateEncryptionKeyAsync` is implemented but not yet stress-tested under concurrent write load. Use it during a maintenance window.
709-
- Multi-process WAL (`EnableMultiProcessAccess`) is not yet supported on WASM/Browser targets (tracked in [WASM_SUPPORT.md](WASM_SUPPORT.md)).
709+
- Multi-process WAL (`AllowMultiProcessAccess`) is not yet supported on WASM/Browser targets (tracked in [WASM_SUPPORT.md](WASM_SUPPORT.md)).
710710
- Argon2id KDF is reserved for a future release. PBKDF2-SHA256 (600 000 iterations) and HKDF-SHA256 are the only supported KDFs in this preview.
711711
- Source-generator GDPR metadata emission (`PersonalDataFields`) requires .NET SDK 9+ (Roslyn 4.x). Fallback reflection path is fully functional on all supported runtimes.
712712

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ await db.VacuumAsync();
302302
A `.wal-shm` sidecar enables **N-reader / 1-writer** access across OS processes (opt-in):
303303

304304
```csharp
305-
var config = new PageFileConfig { EnableMultiProcessAccess = true };
305+
var config = new PageFileConfig { AllowMultiProcessAccess = true };
306306
using var db = new AppDb("shared.db", config); // open from multiple processes
307307
```
308308

@@ -1082,7 +1082,7 @@ We are actively building the core. Here is where we stand:
10821082
-**GDPR Compliance Primitives (v5.0.0)**: `[PersonalData]` annotation, `DataSensitivity` levels, Subject Export (`ExportSubjectDataAsync` — Art. 15/20), Database Inspection (`InspectDatabase` — Art. 30), CDC Field Masking (WP2 — `RevealPersonalData`, `IncludeOnlyFields`, `ExcludeFields`), and `GdprMode.Strict` (Art. 25 privacy-by-default orchestration).
10831083
-**Generalized Retention Policy (v5.0.0)**: `HasRetentionPolicy` now applies to any typed collection (not only `TimeSeries`). Supports `maxAge`, `maxDocumentCount`, and configurable `RetentionTrigger` (on-insert or scheduled).
10841084
-**Secure Erase & VACUUM (v5.0.0)**: `HasSecureErase(true)` zeros the storage slot on delete for GDPR Art. 17. `VacuumAsync()` compacts the database and reclaims free space.
1085-
-**Multi-Process WAL (v5.0.0)**: `.wal-shm` sidecar enables N-reader / 1-writer access across OS processes. Opt in via `PageFileConfig.EnableMultiProcessAccess = true`.
1085+
-**Multi-Process WAL (v5.0.0)**: `.wal-shm` sidecar enables N-reader / 1-writer access across OS processes. Opt in via `PageFileConfig.AllowMultiProcessAccess = true`.
10861086

10871087
## 🔮 Future Vision
10881088

@@ -1127,4 +1127,3 @@ Special thanks to the community members who helped improve BLite:
11271127

11281128
Licensed under the MIT License. Use it freely in personal and commercial projects.
11291129

1130-

src/BLite.Core/Storage/PageFile.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,10 +354,14 @@ public void Open()
354354

355355
var fileExists = File.Exists(_filePath);
356356

357+
var isReadOnlyAccess = _config.Access == MemoryMappedFileAccess.Read;
358+
var fileMode = isReadOnlyAccess ? FileMode.Open : FileMode.OpenOrCreate;
359+
var fileAccess = isReadOnlyAccess ? FileAccess.Read : FileAccess.ReadWrite;
360+
357361
_fileStream = new FileStream(
358362
_filePath,
359-
FileMode.OpenOrCreate,
360-
FileAccess.ReadWrite,
363+
fileMode,
364+
fileAccess,
361365
_config.AllowMultiProcessAccess ? FileShare.ReadWrite : FileShare.None,
362366
bufferSize: 4096,
363367
#if NET6_0_OR_GREATER

tests/BLite.Tests/MultiProcessAccessConfigTests.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,51 @@ public void StorageEngine_ForwardsAllowMultiProcessAccess_ToOwnedWal()
102102
try { Directory.Delete(dir, recursive: true); } catch { /* best-effort */ }
103103
}
104104
}
105+
106+
[Fact]
107+
public void StorageEngine_ReadOnlyReader_CanOpenAlongsideWriter_WhenMultiProcessEnabled()
108+
{
109+
var dir = Path.Combine(Path.GetTempPath(), $"blite_mpread_{Guid.NewGuid():N}");
110+
Directory.CreateDirectory(dir);
111+
try
112+
{
113+
var dbPath = Path.Combine(dir, "shared.db");
114+
var writerConfig = PageFileConfig.Default with { AllowMultiProcessAccess = true };
115+
var readerConfig = writerConfig with { Access = System.IO.MemoryMappedFiles.MemoryMappedFileAccess.Read };
116+
117+
using var writer = new StorageEngine(dbPath, writerConfig);
118+
using var reader = new StorageEngine(dbPath, readerConfig);
119+
120+
Assert.NotNull(writer.SharedMemory);
121+
Assert.NotNull(reader.SharedMemory);
122+
}
123+
finally
124+
{
125+
try { Directory.Delete(dir, recursive: true); } catch { /* best-effort */ }
126+
}
127+
}
128+
129+
[Fact]
130+
public void PageFile_ReadOnlyMode_DoesNotCreateMissingFile()
131+
{
132+
var dir = Path.Combine(Path.GetTempPath(), $"blite_readonly_{Guid.NewGuid():N}");
133+
Directory.CreateDirectory(dir);
134+
try
135+
{
136+
var dbPath = Path.Combine(dir, "missing.db");
137+
var config = PageFileConfig.Default with
138+
{
139+
Access = System.IO.MemoryMappedFiles.MemoryMappedFileAccess.Read,
140+
AllowMultiProcessAccess = true
141+
};
142+
143+
using var pageFile = new PageFile(dbPath, config);
144+
Assert.Throws<FileNotFoundException>(() => pageFile.Open());
145+
Assert.False(File.Exists(dbPath));
146+
}
147+
finally
148+
{
149+
try { Directory.Delete(dir, recursive: true); } catch { /* best-effort */ }
150+
}
151+
}
105152
}

tools/BLite.Studio/ViewModels/MainWindowViewModel.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -262,10 +262,13 @@ private async Task TryOpen(string path, int presetValue, bool readOnly)
262262
if (string.IsNullOrEmpty(EncryptionPassphrase))
263263
throw new InvalidOperationException("A passphrase is required when encryption is enabled.");
264264

265+
var access = IsReadOnly
266+
? MemoryMappedFileAccess.Read
267+
: MemoryMappedFileAccess.ReadWrite;
265268
var crypto = new CryptoOptions(EncryptionPassphrase);
266-
var baseConfig = new PageFileConfig { AllowMultiProcessAccess = true };
269+
var baseConfig = new PageFileConfig { AllowMultiProcessAccess = true, Access = access };
267270
_engine = new BLiteEngine(path, crypto, baseConfig: baseConfig);
268-
_openedConfig = PageFileConfig.Default with { AllowMultiProcessAccess = true };
271+
_openedConfig = PageFileConfig.Default with { AllowMultiProcessAccess = true, Access = access };
269272
}
270273
else
271274
{
@@ -308,7 +311,7 @@ private async Task TryOpen(string path, int presetValue, bool readOnly)
308311
}
309312
catch (Exception ex)
310313
{
311-
StatusMessage = $"Errore: {ex.Message}";
314+
StatusMessage = $"Error: {ex.Message}";
312315
IsDatabaseOpen = false;
313316
}
314317
}

tools/BLite.Studio/Views/MainWindow.axaml.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ private async void Browse_Click(object? sender, RoutedEventArgs e)
2020

2121
var files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
2222
{
23-
Title = "Apri database BLite",
23+
Title = "Open BLite database",
2424
AllowMultiple = false,
25-
// Nessun filtro: qualsiasi estensione è accettata
25+
// Keep broad filters so users can open existing files with custom extensions.
2626
FileTypeFilter =
2727
[
2828
new FilePickerFileType("Database BLite") { Patterns = ["*.db", "*.blite", "*.blt"] },
29-
new FilePickerFileType("Tutti i file") { Patterns = ["*.*"] },
29+
new FilePickerFileType("All files") { Patterns = ["*.*"] },
3030
]
3131
});
3232

0 commit comments

Comments
 (0)