From 6cbbea9b96db551e5c0ac26f0ace3d4c3d82a276 Mon Sep 17 00:00:00 2001
From: Matthias Nott <mnott@mnsoft.org>
Date: Mon, 06 Apr 2026 15:02:33 +0200
Subject: [PATCH] fix: single pailot/out topic, per-session file locks, merge protection, resume reconnect
---
lib/services/message_store.dart | 27 ++++++++++++++++++++++++---
1 files changed, 24 insertions(+), 3 deletions(-)
diff --git a/lib/services/message_store.dart b/lib/services/message_store.dart
index f45e0d0..1723b70 100644
--- a/lib/services/message_store.dart
+++ b/lib/services/message_store.dart
@@ -15,6 +15,8 @@
static Directory? _baseDir;
static Timer? _debounceTimer;
static final Map<String, List<Message>> _pendingSaves = {};
+ // Per-session lock to prevent concurrent read/write on the same file
+ static final Map<String, Completer<void>> _locks = {};
static const _backupChannel =
MethodChannel('com.mnsoft.pailot/backup');
@@ -60,10 +62,25 @@
/// Write directly to disk, bypassing debounce. For critical saves.
static Future<void> writeDirect(String sessionId, List<Message> messages) async {
- // Cancel ALL pending debounce to prevent race with frozen iOS timers
_debounceTimer?.cancel();
_pendingSaves.remove(sessionId);
- await _writeSession(sessionId, messages);
+ await _withLock(sessionId, () => _writeSession(sessionId, messages));
+ }
+
+ /// Acquire a per-session lock, run the operation, release.
+ static Future<T> _withLock<T>(String sessionId, Future<T> Function() fn) async {
+ // Wait for any existing operation on this session to finish
+ while (_locks.containsKey(sessionId)) {
+ await _locks[sessionId]!.future;
+ }
+ final completer = Completer<void>();
+ _locks[sessionId] = completer;
+ try {
+ return await fn();
+ } finally {
+ _locks.remove(sessionId);
+ completer.complete();
+ }
}
/// Immediately flush all pending saves.
@@ -77,7 +94,7 @@
_pendingSaves.clear();
for (final entry in entries.entries) {
- await _writeSession(entry.key, entry.value);
+ await _withLock(entry.key, () => _writeSession(entry.key, entry.value));
}
}
@@ -128,6 +145,10 @@
/// Load all messages for a session (no pagination).
static Future<List<Message>> loadAll(String sessionId) async {
+ return _withLock(sessionId, () => _loadAllImpl(sessionId));
+ }
+
+ static Future<List<Message>> _loadAllImpl(String sessionId) async {
try {
final dir = await _getBaseDir();
final file = File('${dir.path}/${_fileForSession(sessionId)}');
--
Gitblit v1.3.1