This commit is contained in:
2026-03-29 23:47:31 +02:00
commit 216d5d2280
75 changed files with 5702 additions and 0 deletions

View File

@@ -0,0 +1,129 @@
using LiteDB;
using LehrerApp.Sync.Models;
namespace LehrerApp.Sync;
/// <summary>
/// Lokale Event-Queue in LiteDB.
/// Jede Datenänderung erzeugt einen Event dieser wird hier gepuffert
/// bis er erfolgreich zum Server gepusht wurde.
/// </summary>
public class EventQueue : IDisposable
{
private readonly LiteDatabase _db;
private readonly ILiteCollection<SyncEvent> _queue;
private readonly ILiteCollection<SyncMeta> _meta;
private readonly ILiteCollection<ConflictEntry> _conflicts;
private long _currentSequenceNr;
public EventQueue(string queueDbPath)
{
_db = new LiteDatabase(queueDbPath);
_queue = _db.GetCollection<SyncEvent>("queue");
_meta = _db.GetCollection<SyncMeta>("meta");
_conflicts = _db.GetCollection<ConflictEntry>("conflicts");
_queue.EnsureIndex(x => x.SequenceNr);
_queue.EnsureIndex(x => x.Timestamp);
// Letzte SequenceNr wiederherstellen
var meta = _meta.FindById("seq");
_currentSequenceNr = meta?.Value ?? 0;
}
// ── Events einreihen ──────────────────────────────────────────────────────
public SyncEvent Enqueue(
string entityType,
string entityId,
string operation,
string encryptedPayload,
string deviceId,
DeviceType deviceType)
{
var evt = new SyncEvent
{
DeviceId = deviceId,
DeviceType = deviceType,
Timestamp = DateTime.UtcNow,
SequenceNr = ++_currentSequenceNr,
EntityType = entityType,
EntityId = entityId,
Operation = operation,
Payload = encryptedPayload,
};
_queue.Insert(evt);
_meta.Upsert(new SyncMeta { Id = "seq", Value = _currentSequenceNr });
return evt;
}
// ── Ausstehende Events ────────────────────────────────────────────────────
public List<SyncEvent> GetPending(int maxBatch = 100) =>
_queue.Find(Query.All(nameof(SyncEvent.SequenceNr)))
.Take(maxBatch)
.ToList();
public int PendingCount() => _queue.Count();
// ── Bestätigung nach erfolgreichem Push ───────────────────────────────────
public void Acknowledge(IEnumerable<Guid> eventIds)
{
foreach (var id in eventIds)
_queue.Delete(id);
}
// ── Letzte Sync-Metadaten ─────────────────────────────────────────────────
public long GetLastServerSequenceNr() =>
_meta.FindById("serverSeq")?.Value ?? 0;
public void SetLastServerSequenceNr(long nr) =>
_meta.Upsert(new SyncMeta { Id = "serverSeq", Value = nr });
public DateTime? GetLastSyncAt()
{
var meta = _meta.FindById("lastSync");
return meta?.Timestamp;
}
public void SetLastSyncAt(DateTime timestamp) =>
_meta.Upsert(new SyncMeta
{
Id = "lastSync",
Value = 0,
Timestamp = timestamp
});
// ── Konflikte ─────────────────────────────────────────────────────────────
public void AddConflict(ConflictEntry conflict) =>
_conflicts.Insert(conflict);
public List<ConflictEntry> GetUnreviewedConflicts() =>
_conflicts.Find(c => !c.Reviewed).ToList();
public int ConflictCount() =>
_conflicts.Count(c => !c.Reviewed);
public void MarkConflictReviewed(Guid id)
{
var conflict = _conflicts.FindById(id);
if (conflict is null) return;
conflict.Reviewed = true;
_conflicts.Update(conflict);
}
public void Dispose() => _db.Dispose();
}
// Internes Hilfsdokument für Metadaten
internal class SyncMeta
{
public string Id { get; set; } = "";
public long Value { get; set; }
public DateTime? Timestamp { get; set; }
}