diff --git a/backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs b/backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs index f2256115b5..fd51dd14e7 100644 --- a/backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs +++ b/backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs @@ -2003,6 +2003,22 @@ public async Task SaveFile(Stream stream, LcmFileMetadata me } } + #region Submit (result-less write variants) + // Nothing to skip here: liblcm holds the object already, and a genuinely missing one should still throw. + public async Task SubmitUpdateEntry(Guid id, UpdateObjectInput update) => await UpdateEntry(id, update); + public async Task SubmitCreateComplexFormComponent(ComplexFormComponent complexFormComponent, BetweenPosition? position = null) => await CreateComplexFormComponent(complexFormComponent, position); + public async Task SubmitMoveComplexFormComponent(ComplexFormComponent complexFormComponent, BetweenPosition between) => await MoveComplexFormComponent(complexFormComponent, between); + public async Task SubmitCreateSense(Guid entryId, Sense sense, BetweenPosition? position = null) => await CreateSense(entryId, sense, position); + public async Task SubmitUpdateSense(Guid entryId, Guid senseId, UpdateObjectInput update) => await UpdateSense(entryId, senseId, update); + public async Task SubmitCreateExampleSentence(Guid entryId, Guid senseId, ExampleSentence exampleSentence, BetweenPosition? position = null) => await CreateExampleSentence(entryId, senseId, exampleSentence, position); + public async Task SubmitUpdateExampleSentence(Guid entryId, Guid senseId, Guid exampleSentenceId, UpdateObjectInput update) => await UpdateExampleSentence(entryId, senseId, exampleSentenceId, update); + public async Task SubmitUpdatePartOfSpeech(Guid id, UpdateObjectInput update) => await UpdatePartOfSpeech(id, update); + public async Task SubmitUpdatePicture(Guid entryId, Guid senseId, Guid pictureId, UpdateObjectInput update) => await UpdatePicture(entryId, senseId, pictureId, update); + public async Task SubmitUpdatePublication(Guid id, UpdateObjectInput update) => await UpdatePublication(id, update); + public async Task SubmitUpdateSemanticDomain(Guid id, UpdateObjectInput update) => await UpdateSemanticDomain(id, update); + public async Task SubmitUpdateComplexFormType(Guid id, UpdateObjectInput update) => await UpdateComplexFormType(id, update); + #endregion + private string TypeToLinkedFolder(string mimeType) { return mimeType switch diff --git a/backend/FwLite/FwLiteProjectSync/RecordingMiniLcmApi.cs b/backend/FwLite/FwLiteProjectSync/RecordingMiniLcmApi.cs index 522b57fe87..9ab93a431f 100644 --- a/backend/FwLite/FwLiteProjectSync/RecordingMiniLcmApi.cs +++ b/backend/FwLite/FwLiteProjectSync/RecordingMiniLcmApi.cs @@ -4,6 +4,10 @@ namespace FwLiteProjectSync; +/// +/// Records every write a dry-run sync would make. The records are the run's only output, so each one must +/// identify its object by id: headwords, glosses and names are not unique. +/// public partial class RecordingMiniLcmApi(IMiniLcmApi api) : IMiniLcmApi { @@ -25,7 +29,7 @@ public void Dispose() public async Task CreateWritingSystem(WritingSystem writingSystem, BetweenPosition? position = null) { RunRecords.Add(new RunRecord(nameof(CreateWritingSystem), - $"Create writing system {writingSystem.Type} between {position?.Previous} and {position?.Next}")); + $"Create {writingSystem.Type} writing system {writingSystem.WsId} ({writingSystem.Name}) {Position(position)}")); return await _api.CreateWritingSystem(writingSystem, position); } @@ -34,7 +38,7 @@ public async Task UpdateWritingSystem(WritingSystemId id, UpdateObjectInput update) { RunRecords.Add(new RunRecord(nameof(UpdateWritingSystem), - $"Update writing system {type}, changes: {update.Summarize()}")); + $"Update {type} writing system {id}, changes: {update.Summarize()}")); return await _api.UpdateWritingSystem(id, type, update); } @@ -46,19 +50,19 @@ public async Task UpdateWritingSystem(WritingSystem before, Writi public async Task MoveWritingSystem(WritingSystemId id, WritingSystemType type, BetweenPosition between) { - RunRecords.Add(new RunRecord(nameof(MoveWritingSystem), $"Move writing system {id} between {between.Previous} and {between.Next}")); + RunRecords.Add(new RunRecord(nameof(MoveWritingSystem), $"Move {type} writing system {id} {Position(between)}")); await _api.MoveWritingSystem(id, type, between); } public async Task CreatePartOfSpeech(PartOfSpeech partOfSpeech) { - RunRecords.Add(new RunRecord(nameof(CreatePartOfSpeech), $"Create part of speech {partOfSpeech.Name}")); + RunRecords.Add(new RunRecord(nameof(CreatePartOfSpeech), $"Create part of speech {partOfSpeech.Name} ({partOfSpeech.Id})")); return await _api.CreatePartOfSpeech(partOfSpeech); } public async Task UpdatePartOfSpeech(Guid id, UpdateObjectInput update) { - RunRecords.Add(new RunRecord(nameof(UpdatePartOfSpeech), $"Update part of speech {id}")); + RunRecords.Add(new RunRecord(nameof(UpdatePartOfSpeech), $"Update part of speech {id}, changes: {update.Summarize()}")); return await _api.UpdatePartOfSpeech(id, update); } @@ -77,13 +81,13 @@ public async Task DeletePartOfSpeech(Guid id) public async Task CreateSemanticDomain(SemanticDomain semanticDomain) { RunRecords.Add(new RunRecord(nameof(CreateSemanticDomain), - $"Create semantic domain {semanticDomain.Name}")); + $"Create semantic domain {semanticDomain.Code} {semanticDomain.Name} ({semanticDomain.Id})")); return await _api.CreateSemanticDomain(semanticDomain); } public async Task UpdateSemanticDomain(Guid id, UpdateObjectInput update) { - RunRecords.Add(new RunRecord(nameof(UpdateSemanticDomain), $"Update semantic domain {id}")); + RunRecords.Add(new RunRecord(nameof(UpdateSemanticDomain), $"Update semantic domain {id}, changes: {update.Summarize()}")); return await _api.UpdateSemanticDomain(id, update); } @@ -102,13 +106,13 @@ public async Task DeleteSemanticDomain(Guid id) public async Task CreateComplexFormType(ComplexFormType complexFormType) { RunRecords.Add(new RunRecord(nameof(CreateComplexFormType), - $"Create complex form type {complexFormType.Name}")); + $"Create complex form type {complexFormType.Name} ({complexFormType.Id})")); return await _api.CreateComplexFormType(complexFormType); } public async Task UpdateComplexFormType(Guid id, UpdateObjectInput update) { - RunRecords.Add(new RunRecord(nameof(UpdateComplexFormType), $"Update complex form type {id}")); + RunRecords.Add(new RunRecord(nameof(UpdateComplexFormType), $"Update complex form type {id}, changes: {update.Summarize()}")); return await _api.UpdateComplexFormType(id, update); } @@ -132,7 +136,7 @@ public async Task CreateMorphType(MorphType morphType) public async Task UpdateMorphType(Guid id, UpdateObjectInput update) { - RunRecords.Add(new RunRecord(nameof(UpdateMorphType), $"Update morph type {id}")); + RunRecords.Add(new RunRecord(nameof(UpdateMorphType), $"Update morph type {id}, changes: {update.Summarize()}")); return await _api.UpdateMorphType(id, update); } @@ -144,19 +148,20 @@ public async Task UpdateMorphType(MorphType before, MorphType after, public async Task CreateEntry(Entry entry, CreateEntryOptions? options = null) { - RunRecords.Add(new RunRecord(nameof(CreateEntry), $"Create entry {entry.Headword()} ({options ?? new CreateEntryOptions()})")); + RunRecords.Add(new RunRecord(nameof(CreateEntry), + $"Create entry {entry.Headword()} ({entry.Id}), options: {options ?? new CreateEntryOptions()}")); return await _api.CreateEntry(entry, options); } public async Task UpdateEntry(Guid id, UpdateObjectInput update) { - RunRecords.Add(new RunRecord(nameof(UpdateEntry), $"Update entry {id}")); + RunRecords.Add(new RunRecord(nameof(UpdateEntry), $"Update entry {id}, changes: {update.Summarize()}")); return await _api.UpdateEntry(id, update); } public async Task UpdateEntry(Entry before, Entry after, IMiniLcmApi? api) { - RunRecords.Add(new RunRecord(nameof(UpdateEntry), $"Update entry {after.Id}")); + RunRecords.Add(new RunRecord(nameof(UpdateEntry), $"Update entry {after.Headword()} ({after.Id})")); return await _api.UpdateEntry(before, after, api); } @@ -168,63 +173,68 @@ public async Task DeleteEntry(Guid id) public async Task RemoveComplexFormType(Guid entryId, Guid complexFormTypeId) { - RunRecords.Add(new RunRecord(nameof(RemoveComplexFormType), $"Remove complex form type {complexFormTypeId}, from entry {entryId}")); + RunRecords.Add(new RunRecord(nameof(RemoveComplexFormType), $"Remove complex form type {complexFormTypeId} from entry {entryId}")); await _api.RemoveComplexFormType(entryId, complexFormTypeId); } public async Task CreateSense(Guid entryId, Sense sense, BetweenPosition? position = null) { - RunRecords.Add(new RunRecord(nameof(CreateSense), $"Create sense {sense.Gloss} between {position?.Previous} and {position?.Next}")); + RunRecords.Add(new RunRecord(nameof(CreateSense), + $"Create sense {sense.Gloss} ({sense.Id}) in entry {entryId} {Position(position)}")); return await _api.CreateSense(entryId, sense, position); } public async Task UpdateSense(Guid entryId, Guid senseId, UpdateObjectInput update) { RunRecords.Add(new RunRecord(nameof(UpdateSense), - $"Update sense {senseId}, changes: {update.Summarize()}")); + $"Update sense {senseId} in entry {entryId}, changes: {update.Summarize()}")); return await _api.UpdateSense(entryId, senseId, update); } public async Task UpdateSense(Guid entryId, Sense before, Sense after, IMiniLcmApi? api) { RunRecords.Add(new RunRecord(nameof(UpdateSense), - $"Update sense {after.Id}")); + $"Update sense {after.Gloss} ({after.Id}) in entry {entryId}")); return await _api.UpdateSense(entryId, before, after, api); } public async Task MoveSense(Guid entryId, Guid senseId, BetweenPosition between) { - RunRecords.Add(new RunRecord(nameof(MoveSense), $"Move sense {senseId} between {between.Previous} and {between.Next}")); + RunRecords.Add(new RunRecord(nameof(MoveSense), $"Move sense {senseId} in entry {entryId} {Position(between)}")); await _api.MoveSense(entryId, senseId, between); } public async Task DeleteSense(Guid entryId, Guid senseId) { - RunRecords.Add(new RunRecord(nameof(DeleteSense), $"Delete sense {senseId}")); + RunRecords.Add(new RunRecord(nameof(DeleteSense), $"Delete sense {senseId} in entry {entryId}")); await _api.DeleteSense(entryId, senseId); } public async Task AddSemanticDomainToSense(Guid senseId, SemanticDomain semanticDomain) { - RunRecords.Add(new RunRecord(nameof(AddSemanticDomainToSense), $"Add semantic domain {semanticDomain.Name}")); + RunRecords.Add(new RunRecord(nameof(AddSemanticDomainToSense), + $"Add semantic domain {semanticDomain.Code} {semanticDomain.Name} ({semanticDomain.Id}) to sense {senseId}")); await _api.AddSemanticDomainToSense(senseId, semanticDomain); } public async Task RemoveSemanticDomainFromSense(Guid senseId, Guid semanticDomainId) { - RunRecords.Add(new RunRecord(nameof(RemoveSemanticDomainFromSense), $"Remove semantic domain {semanticDomainId}")); + RunRecords.Add(new RunRecord(nameof(RemoveSemanticDomainFromSense), + $"Remove semantic domain {semanticDomainId} from sense {senseId}")); await _api.RemoveSemanticDomainFromSense(senseId, semanticDomainId); } public async Task SetSensePartOfSpeech(Guid senseId, Guid? partOfSpeechId) { - RunRecords.Add(new RunRecord(nameof(SetSensePartOfSpeech), $"Set part of speech {partOfSpeechId}")); + RunRecords.Add(new RunRecord(nameof(SetSensePartOfSpeech), + $"Set part of speech {OrNull(partOfSpeechId)} on sense {senseId}")); await _api.SetSensePartOfSpeech(senseId, partOfSpeechId); } public async Task CreateExampleSentence(Guid entryId, Guid senseId, ExampleSentence exampleSentence, BetweenPosition? position = null) { - RunRecords.Add(new RunRecord(nameof(CreateExampleSentence), $"Create example sentence {exampleSentence.Sentence} between {position?.Previous} and {position?.Next}")); + RunRecords.Add(new RunRecord(nameof(CreateExampleSentence), + $"Create example sentence {exampleSentence.Sentence} ({exampleSentence.Id}) in sense {senseId} {Position(position)}")); return await _api.CreateExampleSentence(entryId, senseId, exampleSentence, position); } @@ -234,7 +244,7 @@ public async Task UpdateExampleSentence(Guid entryId, UpdateObjectInput update) { RunRecords.Add(new RunRecord(nameof(UpdateExampleSentence), - $"Update example sentence {exampleSentenceId}, changes: {update.Summarize()}")); + $"Update example sentence {exampleSentenceId} in sense {senseId}, changes: {update.Summarize()}")); return await _api.UpdateExampleSentence(entryId, senseId, exampleSentenceId, update); } @@ -244,31 +254,36 @@ public async Task UpdateExampleSentence(Guid entryId, ExampleSentence after, IMiniLcmApi? api) { - RunRecords.Add(new RunRecord(nameof(UpdateExampleSentence), $"Update example sentence {after.Id}")); + RunRecords.Add(new RunRecord(nameof(UpdateExampleSentence), + $"Update example sentence {after.Sentence} ({after.Id}) in sense {senseId}")); return await _api.UpdateExampleSentence(entryId, senseId, before, after, api); } public async Task MoveExampleSentence(Guid entryId, Guid senseId, Guid exampleId, BetweenPosition between) { - RunRecords.Add(new RunRecord(nameof(MoveExampleSentence), $"Move example sentence {exampleId} between {between.Previous} and {between.Next}")); + RunRecords.Add(new RunRecord(nameof(MoveExampleSentence), + $"Move example sentence {exampleId} in sense {senseId} {Position(between)}")); await _api.MoveExampleSentence(entryId, senseId, exampleId, between); } public async Task DeleteExampleSentence(Guid entryId, Guid senseId, Guid exampleSentenceId) { - RunRecords.Add(new RunRecord(nameof(DeleteExampleSentence), $"Delete example sentence {exampleSentenceId}")); + RunRecords.Add(new RunRecord(nameof(DeleteExampleSentence), + $"Delete example sentence {exampleSentenceId} in sense {senseId}")); await _api.DeleteExampleSentence(entryId, senseId, exampleSentenceId); } public async Task AddTranslation(Guid entryId, Guid senseId, Guid exampleSentenceId, Translation translation) { - RunRecords.Add(new RunRecord(nameof(AddTranslation), $"Add translation {translation.Id} to example sentence {exampleSentenceId}")); + RunRecords.Add(new RunRecord(nameof(AddTranslation), + $"Add translation {translation.Text} ({translation.Id}) to example sentence {exampleSentenceId}")); await _api.AddTranslation(entryId, senseId, exampleSentenceId, translation); } public async Task RemoveTranslation(Guid entryId, Guid senseId, Guid exampleSentenceId, Guid translationId) { - RunRecords.Add(new RunRecord(nameof(RemoveTranslation), $"Remove translation {translationId} from example sentence {exampleSentenceId}")); + RunRecords.Add(new RunRecord(nameof(RemoveTranslation), + $"Remove translation {translationId} from example sentence {exampleSentenceId}")); await _api.RemoveTranslation(entryId, senseId, exampleSentenceId, translationId); } @@ -278,14 +293,16 @@ public async Task UpdateTranslation(Guid entryId, Guid translationId, UpdateObjectInput update) { - RunRecords.Add(new RunRecord(nameof(UpdateTranslation), $"Update translation {translationId} in example sentence {exampleSentenceId}")); + RunRecords.Add(new RunRecord(nameof(UpdateTranslation), + $"Update translation {translationId} in example sentence {exampleSentenceId}, changes: {update.Summarize()}")); await _api.UpdateTranslation(entryId, senseId, exampleSentenceId, translationId, update); } public async Task CreatePicture(Guid entryId, Guid senseId, Picture picture, BetweenPosition? position = null) { - RunRecords.Add(new RunRecord(nameof(CreatePicture), $"Create picture {picture.Caption} between {position?.Previous} and {position?.Next}")); + RunRecords.Add(new RunRecord(nameof(CreatePicture), + $"Create picture {picture.Caption} ({picture.Id}) in sense {senseId} {Position(position)}")); return await _api.CreatePicture(entryId, senseId, picture, position); } @@ -295,7 +312,7 @@ public async Task UpdatePicture(Guid entryId, UpdateObjectInput update) { RunRecords.Add(new RunRecord(nameof(UpdatePicture), - $"Update picture {pictureId}, changes: {update.Summarize()}")); + $"Update picture {pictureId} in sense {senseId}, changes: {update.Summarize()}")); return await _api.UpdatePicture(entryId, senseId, pictureId, update); } @@ -305,69 +322,64 @@ public async Task UpdatePicture(Guid entryId, Picture after, IMiniLcmApi? api) { - RunRecords.Add(new RunRecord(nameof(UpdatePicture), $"Update picture {after.Id}")); + RunRecords.Add(new RunRecord(nameof(UpdatePicture), $"Update picture {after.Caption} ({after.Id}) in sense {senseId}")); return await _api.UpdatePicture(entryId, senseId, before, after, api); } - public async Task MovePicture(Guid entryId, Guid senseId, Guid exampleId, BetweenPosition between) + public async Task MovePicture(Guid entryId, Guid senseId, Guid pictureId, BetweenPosition between) { - RunRecords.Add(new RunRecord(nameof(MovePicture), $"Move picture {exampleId} between {between.Previous} and {between.Next}")); - await _api.MovePicture(entryId, senseId, exampleId, between); + RunRecords.Add(new RunRecord(nameof(MovePicture), $"Move picture {pictureId} in sense {senseId} {Position(between)}")); + await _api.MovePicture(entryId, senseId, pictureId, between); } public async Task DeletePicture(Guid entryId, Guid senseId, Guid pictureId) { - RunRecords.Add(new RunRecord(nameof(DeletePicture), $"Delete picture {pictureId}")); + RunRecords.Add(new RunRecord(nameof(DeletePicture), $"Delete picture {pictureId} in sense {senseId}")); await _api.DeletePicture(entryId, senseId, pictureId); } public async Task CreateComplexFormComponent(ComplexFormComponent complexFormComponent, BetweenPosition? between = null) { - var complexFormName = ComplexFormName(complexFormComponent); - var componentName = ComplexFormComponentName(complexFormComponent); - var previous = ComplexFormComponentName(between?.Previous); - var next = ComplexFormComponentName(between?.Next); - RunRecords.Add(new RunRecord(nameof(CreateComplexFormComponent), $"Create complex form component complex entry: {complexFormName}, component entry: {componentName}, between {previous} and {next}")); + RunRecords.Add(new RunRecord(nameof(CreateComplexFormComponent), + $"Create complex form component {ComplexFormLink(complexFormComponent)} {Position(between)}")); return await _api.CreateComplexFormComponent(complexFormComponent, between); } public async Task MoveComplexFormComponent(ComplexFormComponent complexFormComponent, BetweenPosition between) { - var componentName = ComplexFormComponentName(complexFormComponent); - var previous = ComplexFormComponentName(between.Previous); - var next = ComplexFormComponentName(between.Next); - RunRecords.Add(new RunRecord(nameof(MoveComplexFormComponent), $"Move complex form component {componentName} between {previous} and {next}")); + RunRecords.Add(new RunRecord(nameof(MoveComplexFormComponent), + $"Move complex form component {ComplexFormLink(complexFormComponent)} {Position(between)}")); await _api.MoveComplexFormComponent(complexFormComponent, between); } public async Task DeleteComplexFormComponent(ComplexFormComponent complexFormComponent) { - var componentName = ComplexFormComponentName(complexFormComponent); - RunRecords.Add(new RunRecord(nameof(DeleteComplexFormComponent), $"Delete complex form component: {componentName}")); + RunRecords.Add(new RunRecord(nameof(DeleteComplexFormComponent), + $"Delete complex form component {ComplexFormLink(complexFormComponent)}")); await _api.DeleteComplexFormComponent(complexFormComponent); } public async Task AddComplexFormType(Guid entryId, Guid complexFormTypeId) { - RunRecords.Add(new RunRecord(nameof(AddComplexFormType), $"Add complex form type {complexFormTypeId}, to entry {entryId}")); + RunRecords.Add(new RunRecord(nameof(AddComplexFormType), $"Add complex form type {complexFormTypeId} to entry {entryId}")); await _api.AddComplexFormType(entryId, complexFormTypeId); } public async Task CreatePublication(Publication pub) { - RunRecords.Add(new RunRecord(nameof(CreatePublication), $"Create publication {pub.Id}")); + RunRecords.Add(new RunRecord(nameof(CreatePublication), $"Create publication {pub.Name} ({pub.Id})")); return await _api.CreatePublication(pub); } public async Task UpdatePublication(Guid id, UpdateObjectInput update) { - RunRecords.Add(new RunRecord(nameof(UpdatePublication), $"Update publication {id}")); + RunRecords.Add(new RunRecord(nameof(UpdatePublication), $"Update publication {id}, changes: {update.Summarize()}")); return await _api.UpdatePublication(id, update); } public async Task UpdatePublication(Publication before, Publication after, IMiniLcmApi? api = null) { - RunRecords.Add(new RunRecord(nameof(UpdatePublication), $"Update publication {before.Id}")); + RunRecords.Add(new RunRecord(nameof(UpdatePublication), $"Update publication {after.Id}")); return await _api.UpdatePublication(before, after, api); } @@ -390,78 +402,119 @@ public async Task RemovePublication(Guid entryId, Guid publicationId) } #region Submit (sync's result-less write variants) - // Submit* are writes, so they're implemented here to record and forward. Any not listed falls back to the - // interface default, which routes to the recording Update*/Move* above. public async Task SubmitUpdateEntry(Guid id, UpdateObjectInput update) { - RunRecords.Add(new RunRecord(nameof(SubmitUpdateEntry), $"Update entry {id}")); + RunRecords.Add(new RunRecord(nameof(SubmitUpdateEntry), $"Update entry {id}, changes: {update.Summarize()}")); await _api.SubmitUpdateEntry(id, update); } public async Task SubmitUpdateSense(Guid entryId, Guid senseId, UpdateObjectInput update) { - RunRecords.Add(new RunRecord(nameof(SubmitUpdateSense), $"Update sense {senseId}, changes: {update.Summarize()}")); + RunRecords.Add(new RunRecord(nameof(SubmitUpdateSense), + $"Update sense {senseId} in entry {entryId}, changes: {update.Summarize()}")); await _api.SubmitUpdateSense(entryId, senseId, update); } public async Task SubmitUpdateExampleSentence(Guid entryId, Guid senseId, Guid exampleSentenceId, UpdateObjectInput update) { - RunRecords.Add(new RunRecord(nameof(SubmitUpdateExampleSentence), $"Update example sentence {exampleSentenceId}, changes: {update.Summarize()}")); + RunRecords.Add(new RunRecord(nameof(SubmitUpdateExampleSentence), + $"Update example sentence {exampleSentenceId} in sense {senseId}, changes: {update.Summarize()}")); await _api.SubmitUpdateExampleSentence(entryId, senseId, exampleSentenceId, update); } + public async Task SubmitUpdatePicture(Guid entryId, Guid senseId, Guid pictureId, UpdateObjectInput update) + { + RunRecords.Add(new RunRecord(nameof(SubmitUpdatePicture), + $"Update picture {pictureId} in sense {senseId}, changes: {update.Summarize()}")); + await _api.SubmitUpdatePicture(entryId, senseId, pictureId, update); + } + public async Task SubmitCreateSense(Guid entryId, Sense sense, BetweenPosition? position = null) { - RunRecords.Add(new RunRecord(nameof(SubmitCreateSense), $"Create sense {sense.Gloss}")); + RunRecords.Add(new RunRecord(nameof(SubmitCreateSense), + $"Create sense {sense.Gloss} ({sense.Id}) in entry {entryId} {Position(position)}")); await _api.SubmitCreateSense(entryId, sense, position); } public async Task SubmitCreateExampleSentence(Guid entryId, Guid senseId, ExampleSentence exampleSentence, BetweenPosition? position = null) { - RunRecords.Add(new RunRecord(nameof(SubmitCreateExampleSentence), $"Create example sentence {exampleSentence.Sentence}")); + RunRecords.Add(new RunRecord(nameof(SubmitCreateExampleSentence), + $"Create example sentence {exampleSentence.Sentence} ({exampleSentence.Id}) in sense {senseId} {Position(position)}")); await _api.SubmitCreateExampleSentence(entryId, senseId, exampleSentence, position); } public async Task SubmitCreateComplexFormComponent(ComplexFormComponent complexFormComponent, BetweenPosition? position = null) { - RunRecords.Add(new RunRecord(nameof(SubmitCreateComplexFormComponent), $"Create complex form component {ComplexFormComponentName(complexFormComponent)}")); + RunRecords.Add(new RunRecord(nameof(SubmitCreateComplexFormComponent), + $"Create complex form component {ComplexFormLink(complexFormComponent)} {Position(position)}")); await _api.SubmitCreateComplexFormComponent(complexFormComponent, position); } + public async Task SubmitMoveComplexFormComponent(ComplexFormComponent complexFormComponent, BetweenPosition between) + { + RunRecords.Add(new RunRecord(nameof(SubmitMoveComplexFormComponent), + $"Move complex form component {ComplexFormLink(complexFormComponent)} {Position(between)}")); + await _api.SubmitMoveComplexFormComponent(complexFormComponent, between); + } + public async Task SubmitUpdatePartOfSpeech(Guid id, UpdateObjectInput update) { - RunRecords.Add(new RunRecord(nameof(SubmitUpdatePartOfSpeech), $"Update part of speech {id}")); + RunRecords.Add(new RunRecord(nameof(SubmitUpdatePartOfSpeech), $"Update part of speech {id}, changes: {update.Summarize()}")); await _api.SubmitUpdatePartOfSpeech(id, update); } public async Task SubmitUpdatePublication(Guid id, UpdateObjectInput update) { - RunRecords.Add(new RunRecord(nameof(SubmitUpdatePublication), $"Update publication {id}")); + RunRecords.Add(new RunRecord(nameof(SubmitUpdatePublication), $"Update publication {id}, changes: {update.Summarize()}")); await _api.SubmitUpdatePublication(id, update); } public async Task SubmitUpdateSemanticDomain(Guid id, UpdateObjectInput update) { - RunRecords.Add(new RunRecord(nameof(SubmitUpdateSemanticDomain), $"Update semantic domain {id}")); + RunRecords.Add(new RunRecord(nameof(SubmitUpdateSemanticDomain), $"Update semantic domain {id}, changes: {update.Summarize()}")); await _api.SubmitUpdateSemanticDomain(id, update); } public async Task SubmitUpdateComplexFormType(Guid id, UpdateObjectInput update) { - RunRecords.Add(new RunRecord(nameof(SubmitUpdateComplexFormType), $"Update complex form type {id}")); + RunRecords.Add(new RunRecord(nameof(SubmitUpdateComplexFormType), $"Update complex form type {id}, changes: {update.Summarize()}")); await _api.SubmitUpdateComplexFormType(id, update); } #endregion - private string ComplexFormComponentName(ComplexFormComponent? component) + // One component is joined to several complex forms, so it takes both entries to identify a link + // (fwdata components carry no link id). + private static string ComplexFormLink(ComplexFormComponent component) + { + return $"complex form {ComplexFormName(component)}, component {ComplexFormComponentName(component)} (link {OrNull(component.MaybeId)})"; + } + + private static string ComplexFormComponentName(ComplexFormComponent? component) { if (component == null) return "null"; - return $"{component.ComponentHeadword} ({component.ComponentEntryId}:{component.ComponentSenseId})"; + return $"{component.ComponentHeadword} (entry {component.ComponentEntryId}, sense {OrNull(component.ComponentSenseId)})"; } - private string ComplexFormName(ComplexFormComponent? component) + private static string ComplexFormName(ComplexFormComponent? component) { if (component == null) return "null"; return $"{component.ComplexFormHeadword} ({component.ComplexFormEntryId})"; } + + private static string Position(BetweenPosition? position) + { + if (position is null) return "at the end"; + return $"between {OrNull(position.Previous)} and {OrNull(position.Next)}"; + } + + private static string Position(BetweenPosition? position) + { + if (position is null) return "at the end"; + return $"between {ComplexFormComponentName(position.Previous)} and {ComplexFormComponentName(position.Next)}"; + } + + private static string OrNull(T? value) + { + return value is null ? "null" : value.ToString() ?? "null"; + } } diff --git a/backend/FwLite/FwLiteProjectSync/WriteIgnoringMiniLcmApi.cs b/backend/FwLite/FwLiteProjectSync/WriteIgnoringMiniLcmApi.cs index e7d903a445..4bec207de7 100644 --- a/backend/FwLite/FwLiteProjectSync/WriteIgnoringMiniLcmApi.cs +++ b/backend/FwLite/FwLiteProjectSync/WriteIgnoringMiniLcmApi.cs @@ -327,8 +327,6 @@ public Task RemovePublication(Guid entryId, Guid publicationId) } #region Submit (sync's result-less write variants) - // Explicit, not the interface default: that routes to the returning Update* and re-reads the object, which - // throws when a dry run hits an object the other side already deleted. public Task SubmitUpdateEntry(Guid id, UpdateObjectInput update) { return Task.CompletedTask; @@ -359,6 +357,16 @@ public Task SubmitCreateComplexFormComponent(ComplexFormComponent complexFormCom return Task.CompletedTask; } + public Task SubmitMoveComplexFormComponent(ComplexFormComponent complexFormComponent, BetweenPosition between) + { + return Task.CompletedTask; + } + + public Task SubmitUpdatePicture(Guid entryId, Guid senseId, Guid pictureId, UpdateObjectInput update) + { + return Task.CompletedTask; + } + public Task SubmitUpdatePartOfSpeech(Guid id, UpdateObjectInput update) { return Task.CompletedTask; diff --git a/backend/FwLite/MiniLcm/IMiniLcmWriteApi.cs b/backend/FwLite/MiniLcm/IMiniLcmWriteApi.cs index a09f955573..4cc8f2fae5 100644 --- a/backend/FwLite/MiniLcm/IMiniLcmWriteApi.cs +++ b/backend/FwLite/MiniLcm/IMiniLcmWriteApi.cs @@ -140,23 +140,23 @@ Task UpdatePicture(Guid entryId, #region Submit (fire-and-forget write variants for sync) // Result-less write variants the sync uses instead of the returning Update/Create methods above. The CRDT - // overrides them to submit the change without fetching the result, so applying to an object the other side - // deleted leaves it deleted (delete wins) rather than throwing. The defaults forward to the returning - // method (correct for FwData, which still surfaces a genuinely-missing object). - Task SubmitUpdateEntry(Guid id, UpdateObjectInput update) => UpdateEntry(id, update); - Task SubmitCreateComplexFormComponent(ComplexFormComponent complexFormComponent, BetweenPosition? position = null) => CreateComplexFormComponent(complexFormComponent, position); - Task SubmitMoveComplexFormComponent(ComplexFormComponent complexFormComponent, BetweenPosition between) => MoveComplexFormComponent(complexFormComponent, between); - Task SubmitCreateSense(Guid entryId, Sense sense, BetweenPosition? position = null) => CreateSense(entryId, sense, position); - Task SubmitUpdateSense(Guid entryId, Guid senseId, UpdateObjectInput update) => UpdateSense(entryId, senseId, update); - Task SubmitCreateExampleSentence(Guid entryId, Guid senseId, ExampleSentence exampleSentence, BetweenPosition? position = null) => CreateExampleSentence(entryId, senseId, exampleSentence, position); - Task SubmitUpdateExampleSentence(Guid entryId, Guid senseId, Guid exampleSentenceId, UpdateObjectInput update) => UpdateExampleSentence(entryId, senseId, exampleSentenceId, update); + // implements them to submit the change without fetching the result, so applying to an object the other side + // deleted leaves it deleted (delete wins) rather than throwing. + // No defaults on purpose: a wrapper that has to see every write can't then inherit one silently. + Task SubmitUpdateEntry(Guid id, UpdateObjectInput update); + Task SubmitCreateComplexFormComponent(ComplexFormComponent complexFormComponent, BetweenPosition? position = null); + Task SubmitMoveComplexFormComponent(ComplexFormComponent complexFormComponent, BetweenPosition between); + Task SubmitCreateSense(Guid entryId, Sense sense, BetweenPosition? position = null); + Task SubmitUpdateSense(Guid entryId, Guid senseId, UpdateObjectInput update); + Task SubmitCreateExampleSentence(Guid entryId, Guid senseId, ExampleSentence exampleSentence, BetweenPosition? position = null); + Task SubmitUpdateExampleSentence(Guid entryId, Guid senseId, Guid exampleSentenceId, UpdateObjectInput update); // Dependency types too (they sync before entries, outside EntrySync's try/catch). WritingSystem is omitted // (its update resolves the entity id, so it can't be a blind submit); MorphType is omitted (not deletable). - Task SubmitUpdatePartOfSpeech(Guid id, UpdateObjectInput update) => UpdatePartOfSpeech(id, update); - Task SubmitUpdatePicture(Guid entryId, Guid senseId, Guid pictureId, UpdateObjectInput update) => UpdatePicture(entryId, senseId, pictureId, update); - Task SubmitUpdatePublication(Guid id, UpdateObjectInput update) => UpdatePublication(id, update); - Task SubmitUpdateSemanticDomain(Guid id, UpdateObjectInput update) => UpdateSemanticDomain(id, update); - Task SubmitUpdateComplexFormType(Guid id, UpdateObjectInput update) => UpdateComplexFormType(id, update); + Task SubmitUpdatePartOfSpeech(Guid id, UpdateObjectInput update); + Task SubmitUpdatePicture(Guid entryId, Guid senseId, Guid pictureId, UpdateObjectInput update); + Task SubmitUpdatePublication(Guid id, UpdateObjectInput update); + Task SubmitUpdateSemanticDomain(Guid id, UpdateObjectInput update); + Task SubmitUpdateComplexFormType(Guid id, UpdateObjectInput update); #endregion #region CustomView diff --git a/backend/FwLite/MiniLcm/JsonPatchExtensions.cs b/backend/FwLite/MiniLcm/JsonPatchExtensions.cs index 66de5cd5c9..6c50bd01c5 100644 --- a/backend/FwLite/MiniLcm/JsonPatchExtensions.cs +++ b/backend/FwLite/MiniLcm/JsonPatchExtensions.cs @@ -1,4 +1,3 @@ -using System.Text; using SystemTextJsonPatch; namespace MiniLcm; @@ -31,14 +30,9 @@ public static bool TryGetPropertyChange(this UpdateObjectInput upd } return false; } + /// One line: a multi-line summary breaks per-line grep of the logs it ends up in. public static string Summarize(this JsonPatchDocument document) where T : class { - var sb = new StringBuilder(); - sb.AppendLine($"Update: {typeof(T).Name}"); - foreach (var op in document.Operations) - { - sb.AppendLine($"{op.OperationType} {op.Path}: {op.Value}"); - } - return sb.ToString(); + return string.Join(", ", document.Operations.Select(op => $"{op.OperationType} {op.Path}: {op.Value}")); } } diff --git a/backend/FwLite/MiniLcm/Models/RichString.cs b/backend/FwLite/MiniLcm/Models/RichString.cs index 02e755d5c1..b7c7d18764 100644 --- a/backend/FwLite/MiniLcm/Models/RichString.cs +++ b/backend/FwLite/MiniLcm/Models/RichString.cs @@ -46,6 +46,8 @@ public string GetPlainText() return string.Join("", Spans.Select(s => s.Text)); } + public override string ToString() => GetPlainText(); + public void EnsureWs(WritingSystemId ws) { foreach (var span in Spans) diff --git a/backend/FwLite/MiniLcm/Normalization/MiniLcmApiWriteNormalizationWrapper.cs b/backend/FwLite/MiniLcm/Normalization/MiniLcmApiWriteNormalizationWrapper.cs index a43847bb50..2da2e0e965 100644 --- a/backend/FwLite/MiniLcm/Normalization/MiniLcmApiWriteNormalizationWrapper.cs +++ b/backend/FwLite/MiniLcm/Normalization/MiniLcmApiWriteNormalizationWrapper.cs @@ -89,6 +89,11 @@ public Task UpdatePartOfSpeech(Guid id, UpdateObjectInput update) + { + await _api.SubmitUpdatePartOfSpeech(id, NormalizePatch(update)); + } + public async Task UpdatePartOfSpeech(PartOfSpeech before, PartOfSpeech after, IMiniLcmApi? api = null) { @@ -121,6 +126,11 @@ public Task UpdatePublication(Guid id, UpdateObjectInput update) + { + await _api.SubmitUpdatePublication(id, NormalizePatch(update)); + } + public async Task UpdatePublication(Publication before, Publication after, IMiniLcmApi? api = null) { @@ -153,6 +163,11 @@ public Task UpdateSemanticDomain(Guid id, UpdateObjectInput update) + { + await _api.SubmitUpdateSemanticDomain(id, NormalizePatch(update)); + } + public async Task UpdateSemanticDomain(SemanticDomain before, SemanticDomain after, IMiniLcmApi? api = null) { @@ -186,6 +201,11 @@ public Task UpdateComplexFormType(Guid id, UpdateObjectInput update) + { + await _api.SubmitUpdateComplexFormType(id, NormalizePatch(update)); + } + public async Task UpdateComplexFormType(ComplexFormType before, ComplexFormType after, IMiniLcmApi? api = null) { @@ -250,6 +270,11 @@ public Task UpdateEntry(Guid id, UpdateObjectInput update) return _api.UpdateEntry(id, NormalizePatch(update)); } + public async Task SubmitUpdateEntry(Guid id, UpdateObjectInput update) + { + await _api.SubmitUpdateEntry(id, NormalizePatch(update)); + } + public async Task UpdateEntry(Entry before, Entry after, IMiniLcmApi? api = null) { @@ -266,11 +291,21 @@ public async Task CreateComplexFormComponent(ComplexFormCo return await _api.CreateComplexFormComponent(NormalizeComplexFormComponent(complexFormComponent), position); } + public async Task SubmitCreateComplexFormComponent(ComplexFormComponent complexFormComponent, BetweenPosition? position = null) + { + await _api.SubmitCreateComplexFormComponent(NormalizeComplexFormComponent(complexFormComponent), position); + } + public Task MoveComplexFormComponent(ComplexFormComponent complexFormComponent, BetweenPosition between) { return _api.MoveComplexFormComponent(complexFormComponent, between); } + public async Task SubmitMoveComplexFormComponent(ComplexFormComponent complexFormComponent, BetweenPosition between) + { + await _api.SubmitMoveComplexFormComponent(complexFormComponent, between); + } + public Task DeleteComplexFormComponent(ComplexFormComponent complexFormComponent) { return _api.DeleteComplexFormComponent(complexFormComponent); @@ -328,11 +363,21 @@ public async Task CreateSense(Guid entryId, Sense sense, BetweenPosition? return await _api.CreateSense(entryId, NormalizeSense(sense), position); } + public async Task SubmitCreateSense(Guid entryId, Sense sense, BetweenPosition? position = null) + { + await _api.SubmitCreateSense(entryId, NormalizeSense(sense), position); + } + public Task UpdateSense(Guid entryId, Guid senseId, UpdateObjectInput update) { return _api.UpdateSense(entryId, senseId, NormalizePatch(update)); } + public async Task SubmitUpdateSense(Guid entryId, Guid senseId, UpdateObjectInput update) + { + await _api.SubmitUpdateSense(entryId, senseId, NormalizePatch(update)); + } + public async Task UpdateSense(Guid entryId, Sense before, Sense after, IMiniLcmApi? api = null) { @@ -385,11 +430,21 @@ public async Task CreateExampleSentence(Guid entryId, Guid sens return await _api.CreateExampleSentence(entryId, senseId, NormalizeExampleSentence(exampleSentence), position); } + public async Task SubmitCreateExampleSentence(Guid entryId, Guid senseId, ExampleSentence exampleSentence, BetweenPosition? position = null) + { + await _api.SubmitCreateExampleSentence(entryId, senseId, NormalizeExampleSentence(exampleSentence), position); + } + public Task UpdateExampleSentence(Guid entryId, Guid senseId, Guid exampleSentenceId, UpdateObjectInput update) { return _api.UpdateExampleSentence(entryId, senseId, exampleSentenceId, NormalizePatch(update)); } + public async Task SubmitUpdateExampleSentence(Guid entryId, Guid senseId, Guid exampleSentenceId, UpdateObjectInput update) + { + await _api.SubmitUpdateExampleSentence(entryId, senseId, exampleSentenceId, NormalizePatch(update)); + } + public async Task UpdateExampleSentence(Guid entryId, Guid senseId, ExampleSentence before, ExampleSentence after, IMiniLcmApi? api = null) { @@ -461,9 +516,9 @@ public Task UpdatePicture(Guid entryId, Guid senseId, Guid pictureId, U return _api.UpdatePicture(entryId, senseId, pictureId, NormalizePatch(update)); } - public Task SubmitUpdatePicture(Guid entryId, Guid senseId, Guid pictureId, UpdateObjectInput update) + public async Task SubmitUpdatePicture(Guid entryId, Guid senseId, Guid pictureId, UpdateObjectInput update) { - return _api.SubmitUpdatePicture(entryId, senseId, pictureId, NormalizePatch(update)); + await _api.SubmitUpdatePicture(entryId, senseId, pictureId, NormalizePatch(update)); } public async Task UpdatePicture(Guid entryId, Guid senseId, Picture before, Picture after, IMiniLcmApi? api = null)