From 4409d47b18ce0b1632ec685aa03a90fdcd3f4c1e Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Fri, 15 Nov 2024 12:20:48 +0700 Subject: [PATCH] Implement UpdateWritingSystem(before, after) Note that FwDataMiniLcmApi hasn't implemented the UpdateWritingSystem overload with (id, type, update), which is the (before, after) method is going to end up calling. So that still needs to happen. --- .../Api/FwDataMiniLcmApi.cs | 18 +++++- .../FwLiteProjectSync/DryRunMiniLcmApi.cs | 6 ++ backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs | 6 ++ backend/FwLite/MiniLcm/IMiniLcmWriteApi.cs | 3 +- .../MiniLcm/SyncHelpers/SimpleStringDiff.cs | 16 +++++ .../MiniLcm/SyncHelpers/WritingSystemSync.cs | 59 +++++++++++++++++++ 6 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 backend/FwLite/MiniLcm/SyncHelpers/SimpleStringDiff.cs create mode 100644 backend/FwLite/MiniLcm/SyncHelpers/WritingSystemSync.cs diff --git a/backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs b/backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs index 8e132b2e6..fcba2d259 100644 --- a/backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs +++ b/backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs @@ -132,6 +132,11 @@ private WritingSystem FromLcmWritingSystem(CoreWritingSystemDefinition ws, int i }; } + public Task GetWritingSystem(WritingSystemId id, WritingSystemType type) + { + throw new NotImplementedException(); + } + internal void CompleteExemplars(WritingSystems writingSystems) { var wsExemplars = writingSystems.Vernacular.Concat(writingSystems.Analysis) @@ -185,7 +190,18 @@ public Task CreateWritingSystem(WritingSystemType type, WritingSy public Task UpdateWritingSystem(WritingSystemId id, WritingSystemType type, UpdateObjectInput update) { - throw new NotImplementedException(); + throw new NotImplementedException(); // TODO: This needs to be implemented now, because UpdateWritingSystem(before, after) needs to call this + } + + public async Task UpdateWritingSystem(WritingSystem before, WritingSystem after) + { + await Cache.DoUsingNewOrCurrentUOW("Update WritingSystem", + "Revert WritingSystem", + async () => + { + await WritingSystemSync.Sync(after, before, this); + }); + return await GetWritingSystem(after.WsId, after.Type) ?? throw new NullReferenceException("unable to find writing system with id " + after.Id); } public IAsyncEnumerable GetPartsOfSpeech() diff --git a/backend/FwLite/FwLiteProjectSync/DryRunMiniLcmApi.cs b/backend/FwLite/FwLiteProjectSync/DryRunMiniLcmApi.cs index 25c317670..75cf73886 100644 --- a/backend/FwLite/FwLiteProjectSync/DryRunMiniLcmApi.cs +++ b/backend/FwLite/FwLiteProjectSync/DryRunMiniLcmApi.cs @@ -34,6 +34,12 @@ public async Task UpdateWritingSystem(WritingSystemId id, }).First(w => w.WsId == id); } + public Task UpdateWritingSystem(WritingSystem before, WritingSystem after) + { + DryRunRecords.Add(new DryRunRecord(nameof(UpdateEntry), $"Update {after.Type} writing system {after.Id}")); + return Task.FromResult(after); + } + public IAsyncEnumerable GetPartsOfSpeech() { return api.GetPartsOfSpeech(); diff --git a/backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs b/backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs index 241c8daad..4468f8d16 100644 --- a/backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs +++ b/backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs @@ -56,6 +56,12 @@ public async Task UpdateWritingSystem(WritingSystemId id, Writing return await dataModel.GetLatest(ws.Id) ?? throw new NullReferenceException(); } + public async Task UpdateWritingSystem(WritingSystem before, WritingSystem after) + { + await WritingSystemSync.Sync(after, before, this); + return await GetWritingSystem(after.WsId, after.Type) ?? throw new NullReferenceException("unable to find writing system with id " + after.WsId); + } + private WritingSystem? _defaultVernacularWs; private WritingSystem? _defaultAnalysisWs; private async Task GetWritingSystem(WritingSystemId id, WritingSystemType type) diff --git a/backend/FwLite/MiniLcm/IMiniLcmWriteApi.cs b/backend/FwLite/MiniLcm/IMiniLcmWriteApi.cs index 4685f6d37..6608c6335 100644 --- a/backend/FwLite/MiniLcm/IMiniLcmWriteApi.cs +++ b/backend/FwLite/MiniLcm/IMiniLcmWriteApi.cs @@ -11,7 +11,8 @@ public interface IMiniLcmWriteApi Task UpdateWritingSystem(WritingSystemId id, WritingSystemType type, UpdateObjectInput update); - + Task UpdateWritingSystem(WritingSystem before, WritingSystem after); + // Note there's no Task DeleteWritingSystem(Guid id) because deleting writing systems needs careful consideration, as it can cause a massive cascade of data deletion #region PartOfSpeech Task CreatePartOfSpeech(PartOfSpeech partOfSpeech); diff --git a/backend/FwLite/MiniLcm/SyncHelpers/SimpleStringDiff.cs b/backend/FwLite/MiniLcm/SyncHelpers/SimpleStringDiff.cs new file mode 100644 index 000000000..2708a6b23 --- /dev/null +++ b/backend/FwLite/MiniLcm/SyncHelpers/SimpleStringDiff.cs @@ -0,0 +1,16 @@ +using SystemTextJsonPatch.Operations; + +namespace MiniLcm.SyncHelpers; + +public static class SimpleStringDiff +{ + public static IEnumerable> GetStringDiff(string path, + string before, + string after) where T : class + { + if (before == after) yield break; + if (after is null) yield return new Operation("remove", $"/{path}", null); + else if (before is null) yield return new Operation("add", $"/{path}", null); + else yield return new Operation("replace", $"/{path}", null, after); + } +} diff --git a/backend/FwLite/MiniLcm/SyncHelpers/WritingSystemSync.cs b/backend/FwLite/MiniLcm/SyncHelpers/WritingSystemSync.cs new file mode 100644 index 000000000..cb5983907 --- /dev/null +++ b/backend/FwLite/MiniLcm/SyncHelpers/WritingSystemSync.cs @@ -0,0 +1,59 @@ +using MiniLcm; +using MiniLcm.Models; +using MiniLcm.SyncHelpers; +using SystemTextJsonPatch; + +public static class WritingSystemSync +{ + public static async Task Sync(WritingSystem[] currentWritingSystems, + WritingSystem[] previousWritingSystems, + IMiniLcmApi api) + { + return await DiffCollection.Diff(api, + previousWritingSystems, + currentWritingSystems, + ws => ws.Id, + async (api, currentWs) => + { + await api.CreateWritingSystem(currentWs.Type, currentWs); + return 1; + }, + async (api, previousWs) => + { + // await api.DeleteWritingSystem(previousWs.Id); // Deleting writing systems is dangerous as it causes cascading data deletion. Needs careful thought. + // TODO: should we throw an exception? + return 0; + }, + async (api, previousWs, currentWs) => + { + return await Sync(currentWs, previousWs, api); + }); + } + + public static async Task Sync(WritingSystem afterWs, WritingSystem beforeWs, IMiniLcmApi api) + { + var updateObjectInput = WritingSystemDiffToUpdate(beforeWs, afterWs); + if (updateObjectInput is not null) await api.UpdateWritingSystem(afterWs.WsId, afterWs.Type, updateObjectInput); + return updateObjectInput is null ? 0 : 1; + } + + public static UpdateObjectInput? WritingSystemDiffToUpdate(WritingSystem previousWritingSystem, WritingSystem currentWritingSystem) + { + JsonPatchDocument patchDocument = new(); + patchDocument.Operations.AddRange(SimpleStringDiff.GetStringDiff(nameof(WritingSystem.WsId), + previousWritingSystem.WsId, + currentWritingSystem.WsId)); + patchDocument.Operations.AddRange(SimpleStringDiff.GetStringDiff(nameof(WritingSystem.Name), + previousWritingSystem.Name, + currentWritingSystem.Name)); + patchDocument.Operations.AddRange(SimpleStringDiff.GetStringDiff(nameof(WritingSystem.Abbreviation), + previousWritingSystem.Abbreviation, + currentWritingSystem.Abbreviation)); + patchDocument.Operations.AddRange(SimpleStringDiff.GetStringDiff(nameof(WritingSystem.Font), + previousWritingSystem.Font, + currentWritingSystem.Font)); + // TODO: Exemplars, Order, and do we need DeletedAt? + if (patchDocument.Operations.Count == 0) return null; + return new UpdateObjectInput(patchDocument); + } +}