Minimale Liste im Tab "Synchronisation" - Entitaet, Zeitpunkt, welche Seite ConflictResolver gewaehlt hat, mit "Gesehen"-Aktion. Kein Feld-Diff fuer v1: die Payloads sind clientseitig verschluesselt, ein Diff wuerde ohnehin nur rohes JSON zeigen. Neu EventQueue.MarkReviewed(id) - bisher gab es AddConflict/ GetUnreviewed/ConflictCount, aber keinen Weg, einen Konflikt als gesehen zu markieren. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
106 lines
4.1 KiB
C#
106 lines
4.1 KiB
C#
using LiteDB;
|
|
using LehrerApp.Sync.Models;
|
|
|
|
namespace LehrerApp.Sync;
|
|
|
|
/// <summary>
|
|
/// Lokale Event-Queue in LiteDB. Puffert Events bis sie
|
|
/// erfolgreich zum Server gepusht wurden.
|
|
/// </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 readonly ILiteCollection<PendingAttachmentUpload> _attachmentUploads;
|
|
private long _currentSeq;
|
|
|
|
public EventQueue(string path)
|
|
{
|
|
_db = new LiteDatabase(path);
|
|
_queue = _db.GetCollection<SyncEvent>("queue");
|
|
_meta = _db.GetCollection<SyncMeta>("meta");
|
|
_conflicts = _db.GetCollection<ConflictEntry>("conflicts");
|
|
_attachmentUploads = _db.GetCollection<PendingAttachmentUpload>("attachment_uploads");
|
|
_attachmentUploads.EnsureIndex(x => x.StorageId, unique: true);
|
|
_queue.EnsureIndex(x => x.SequenceNr);
|
|
_currentSeq = _meta.FindById("seq")?.Value ?? 0;
|
|
}
|
|
|
|
public SyncEvent Enqueue(string deviceId, DeviceType deviceType,
|
|
string entityType, string entityId, string operation, string payload)
|
|
{
|
|
var evt = new SyncEvent
|
|
{
|
|
DeviceId = deviceId,
|
|
DeviceType = deviceType,
|
|
Timestamp = DateTime.UtcNow,
|
|
SequenceNr = ++_currentSeq,
|
|
EntityType = entityType,
|
|
EntityId = entityId,
|
|
Operation = operation,
|
|
Payload = payload,
|
|
};
|
|
_queue.Insert(evt);
|
|
_meta.Upsert(new SyncMeta { Id = "seq", Value = _currentSeq });
|
|
return evt;
|
|
}
|
|
|
|
public List<SyncEvent> GetPending(int max = 200) =>
|
|
_queue.Find(Query.All(nameof(SyncEvent.SequenceNr))).Take(max).ToList();
|
|
public int PendingCount() => _queue.Count();
|
|
public void Acknowledge(IEnumerable<Guid> ids) { foreach (var id in ids) _queue.Delete(id); }
|
|
public long GetLastServerSeq() => _meta.FindById("serverSeq")?.Value ?? 0;
|
|
public void SetLastServerSeq(long nr) => _meta.Upsert(new SyncMeta { Id = "serverSeq", Value = nr });
|
|
public DateTime? GetLastSyncAt() => _meta.FindById("lastSync")?.Timestamp;
|
|
public void SetLastSyncAt(DateTime dt) =>
|
|
_meta.Upsert(new SyncMeta { Id = "lastSync", Value = 0, Timestamp = dt });
|
|
public void AddConflict(ConflictEntry c) => _conflicts.Insert(c);
|
|
public List<ConflictEntry> GetUnreviewed() => _conflicts.Find(c => !c.Reviewed).ToList();
|
|
public int ConflictCount() => _conflicts.Count(c => !c.Reviewed);
|
|
public void MarkReviewed(Guid id)
|
|
{
|
|
var conflict = _conflicts.FindById(id);
|
|
if (conflict is null) return;
|
|
conflict.Reviewed = true;
|
|
_conflicts.Update(conflict);
|
|
}
|
|
|
|
// ── Anhang-Warteliste (getrennt von der JSON-Ereignis-Outbox, siehe AttachmentSyncer) ────
|
|
public void QueueAttachmentUpload(string storageId)
|
|
{
|
|
if (!_attachmentUploads.Exists(a => a.StorageId == storageId))
|
|
_attachmentUploads.Insert(new PendingAttachmentUpload { StorageId = storageId });
|
|
}
|
|
public List<string> GetPendingAttachmentUploads() =>
|
|
_attachmentUploads.FindAll().Select(a => a.StorageId).ToList();
|
|
public void MarkAttachmentUploaded(string storageId) =>
|
|
_attachmentUploads.DeleteMany(a => a.StorageId == storageId);
|
|
|
|
public void Dispose() => _db.Dispose();
|
|
}
|
|
|
|
public class ConflictEntry
|
|
{
|
|
public Guid Id { get; init; } = Guid.NewGuid();
|
|
public DateTime DetectedAt { get; init; } = DateTime.UtcNow;
|
|
public SyncEvent LocalEvent { get; init; } = null!;
|
|
public SyncEvent RemoteEvent { get; init; } = null!;
|
|
public string Resolution { get; init; } = "";
|
|
public bool Reviewed { get; set; }
|
|
}
|
|
|
|
internal class SyncMeta
|
|
{
|
|
public string Id { get; set; } = "";
|
|
public long Value { get; set; }
|
|
public DateTime? Timestamp { get; set; }
|
|
}
|
|
|
|
internal class PendingAttachmentUpload
|
|
{
|
|
public ObjectId Id { get; set; } = ObjectId.NewObjectId();
|
|
public string StorageId { get; set; } = "";
|
|
}
|