-
-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathCrdtFwdataProjectSyncService.cs
More file actions
179 lines (150 loc) · 9.33 KB
/
Copy pathCrdtFwdataProjectSyncService.cs
File metadata and controls
179 lines (150 loc) · 9.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
using System.Diagnostics;
using FwDataMiniLcmBridge.Api;
using LcmCrdt;
using LexCore.Sync;
using Microsoft.Extensions.Logging;
using MiniLcm;
using MiniLcm.SyncHelpers;
using MiniLcm.Validators;
namespace FwLiteProjectSync;
public class CrdtFwdataProjectSyncService(MiniLcmImport miniLcmImport,
ILogger<CrdtFwdataProjectSyncService> logger,
MiniLcmApiValidationWrapperFactory validationWrapperFactory,
IgnoreNotFoundMiniLcmApiFactory ignoreNotFoundWrapperFactory)
{
public record DryRunSyncResult(
int CrdtChanges,
int FwdataChanges,
List<DryRunMiniLcmApi.DryRunRecord> CrdtDryRunRecords,
List<DryRunMiniLcmApi.DryRunRecord> FwDataDryRunRecords) : SyncResult(CrdtChanges, FwdataChanges);
public async Task<DryRunSyncResult> SyncDryRun(IMiniLcmApi crdtApi, FwDataMiniLcmApi fwdataApi, ProjectSnapshot projectSnapshot)
{
return (DryRunSyncResult)await Sync(crdtApi, fwdataApi, projectSnapshot, true);
}
public virtual async Task<SyncResult> Sync(IMiniLcmApi crdtApi, FwDataMiniLcmApi fwdataApi, ProjectSnapshot projectSnapshot, bool dryRun = false)
{
return await SyncOrImportInternal(crdtApi, fwdataApi, dryRun, projectSnapshot);
}
public async Task<DryRunSyncResult> ImportDryRun(IMiniLcmApi crdtApi, FwDataMiniLcmApi fwdataApi)
{
return (DryRunSyncResult)await Import(crdtApi, fwdataApi, true);
}
public virtual async Task<SyncResult> Import(IMiniLcmApi crdtApi, FwDataMiniLcmApi fwdataApi, bool dryRun = false)
{
return await SyncOrImportInternal(crdtApi, fwdataApi, dryRun, projectSnapshot: null);
}
private async Task<SyncResult> SyncOrImportInternal(IMiniLcmApi crdtApi, IMiniLcmApi fwdataApi, bool dryRun, ProjectSnapshot? projectSnapshot)
{
using var activity = FwLiteProjectSyncActivitySource.Value.StartActivity();
if (crdtApi is not CrdtMiniLcmApi crdt) // maybe the argument type should be changed?
throw new InvalidOperationException("CrdtApi must be of type CrdtMiniLcmApi to sync.");
if (fwdataApi is not FwDataMiniLcmApi fwdata) // maybe the argument type should be changed?
throw new InvalidOperationException("FwdataApi must be of type FwDataMiniLcmApi to sync.");
if (crdt.ProjectData.FwProjectId != fwdata.ProjectId)
{
activity?.SetStatus(ActivityStatusCode.Error, $"Project id mismatch, CRDT Id: {crdt.ProjectData.FwProjectId}, FWData Id: {fwdata.ProjectId}");
throw new InvalidOperationException($"Project id mismatch, CRDT Id: {crdt.ProjectData.FwProjectId}, FWData Id: {fwdata.ProjectId}");
}
// Project snapshot logic/handling is done outside of this class so that Sync vs Import is explicit.
// We still choose to explicitly verify a consistent state to avoid accidental misuse.
var hasSyncedSuccessfully = ProjectSnapshotService.HasSyncedSuccessfully(fwdata.Project);
if (hasSyncedSuccessfully != (projectSnapshot is not null))
{
activity?.SetStatus(ActivityStatusCode.Error, "Project sync state does not match presence of snapshot.");
throw new InvalidOperationException("Project sync state does not match presence of snapshot.");
}
// No write normalization: Data is already normalised on both sides.
// No query normalization: The sync doesn't do any querying.
crdtApi = validationWrapperFactory.Create(crdtApi);
fwdataApi = validationWrapperFactory.Create(fwdataApi);
// An object deleted in the CRDT but still edited/referenced from FwData (since the last sync) makes
// the diff try to write to an object that no longer exists in the CRDT. Tolerate the resulting
// NotFound; the reverse diff then propagates the deletion to FwData. Scoped to the CRDT side (that's
// the direction issue #2361 reported), and to sync only: a fresh import has no deletion races, so a
// NotFound there is a real bug that must surface, not be swallowed.
IgnoreNotFoundMiniLcmApi? ignoreNotFound = null;
if (projectSnapshot is not null)
{
ignoreNotFound = ignoreNotFoundWrapperFactory.Create(crdtApi);
crdtApi = ignoreNotFound;
}
if (dryRun)
{
crdtApi = new DryRunMiniLcmApi(crdtApi);
fwdataApi = new DryRunMiniLcmApi(fwdataApi);
}
if (projectSnapshot is not null)
{
// Repair any missing translation IDs before doing the full sync, so the sync doesn't have to deal with them
var syncedIdCount = await CrdtRepairs.SyncMissingTranslationIds(projectSnapshot.Entries, fwdata, crdt, dryRun);
// Patch legacy snapshots that were created before morph-type support.
// After seeding, the CRDT has morph-types but the snapshot still has [].
// Without this patch, the diff would see all morph-types as "new" and try to re-add them.
if (projectSnapshot.MorphTypes is null or [])
{
var currentCrdtMorphTypes = await crdt.GetMorphTypes().ToArrayAsync();
if (currentCrdtMorphTypes.Length > 0)
{
projectSnapshot = projectSnapshot with { MorphTypes = currentCrdtMorphTypes };
}
}
}
var syncResult = projectSnapshot is null
? await ImportInternal(crdtApi, fwdataApi, fwdata.EntryCount)
: await SyncInternal(crdtApi, fwdataApi, projectSnapshot);
if (ignoreNotFound is { IgnoredWriteCount: > 0 })
logger.LogInformation("Sync ignored {Count} write(s) to objects deleted in the CRDT", ignoreNotFound.IgnoredWriteCount);
if (!dryRun)
{
fwdata.Save();
return syncResult;
}
LogDryRun(crdtApi, "crdt");
LogDryRun(fwdataApi, "fwdata");
return new DryRunSyncResult(syncResult.CrdtChanges, syncResult.FwdataChanges,
GetDryRunRecords(crdtApi), GetDryRunRecords(fwdataApi));
}
private async Task<SyncResult> ImportInternal(IMiniLcmApi crdtApi, IMiniLcmApi fwdataApi, int entryCount)
{
await miniLcmImport.ImportProject(crdtApi, fwdataApi, entryCount);
return new SyncResult(entryCount, 0);
}
private async Task<SyncResult> SyncInternal(IMiniLcmApi crdtApi, IMiniLcmApi fwdataApi, ProjectSnapshot projectSnapshot)
{
var currentFwDataWritingSystems = await fwdataApi.GetWritingSystems();
var crdtChanges = await WritingSystemSync.Sync(projectSnapshot.WritingSystems, currentFwDataWritingSystems, crdtApi);
var fwdataChanges = await WritingSystemSync.Sync(currentFwDataWritingSystems, await crdtApi.GetWritingSystems(), fwdataApi);
var currentFwDataPublications = await fwdataApi.GetPublications().ToArrayAsync();
crdtChanges += await PublicationSync.Sync(projectSnapshot.Publications, currentFwDataPublications, crdtApi);
fwdataChanges += await PublicationSync.Sync(currentFwDataPublications, await crdtApi.GetPublications().ToArrayAsync(), fwdataApi);
var currentFwDataPartsOfSpeech = await fwdataApi.GetPartsOfSpeech().ToArrayAsync();
crdtChanges += await PartOfSpeechSync.Sync(projectSnapshot.PartsOfSpeech, currentFwDataPartsOfSpeech, crdtApi);
fwdataChanges += await PartOfSpeechSync.Sync(currentFwDataPartsOfSpeech, await crdtApi.GetPartsOfSpeech().ToArrayAsync(), fwdataApi);
var currentFwDataSemanticDomains = await fwdataApi.GetSemanticDomains().ToArrayAsync();
crdtChanges += await SemanticDomainSync.Sync(projectSnapshot.SemanticDomains, currentFwDataSemanticDomains, crdtApi);
fwdataChanges += await SemanticDomainSync.Sync(currentFwDataSemanticDomains, await crdtApi.GetSemanticDomains().ToArrayAsync(), fwdataApi);
var currentFwDataComplexFormTypes = await fwdataApi.GetComplexFormTypes().ToArrayAsync();
crdtChanges += await ComplexFormTypeSync.Sync(projectSnapshot.ComplexFormTypes, currentFwDataComplexFormTypes, crdtApi);
fwdataChanges += await ComplexFormTypeSync.Sync(currentFwDataComplexFormTypes, await crdtApi.GetComplexFormTypes().ToArrayAsync(), fwdataApi);
var currentFwDataMorphTypes = await fwdataApi.GetMorphTypes().ToArrayAsync();
crdtChanges += await MorphTypeSync.Sync(projectSnapshot.MorphTypes, currentFwDataMorphTypes, crdtApi);
fwdataChanges += await MorphTypeSync.Sync(currentFwDataMorphTypes, await crdtApi.GetMorphTypes().ToArrayAsync(), fwdataApi);
var currentFwDataEntries = await fwdataApi.GetAllEntries().ToArrayAsync();
crdtChanges += await EntrySync.SyncFull(projectSnapshot.Entries, currentFwDataEntries, crdtApi);
fwdataChanges += await EntrySync.SyncFull(currentFwDataEntries, await crdtApi.GetAllEntries().ToArrayAsync(), fwdataApi);
return new SyncResult(crdtChanges, fwdataChanges);
}
private void LogDryRun(IMiniLcmApi api, string type)
{
if (api is not DryRunMiniLcmApi dryRunApi) return;
foreach (var dryRunRecord in dryRunApi.DryRunRecords)
{
logger.LogInformation($"Dry run {type} record: {dryRunRecord.Method} {dryRunRecord.Description}");
}
logger.LogInformation($"Dry run {type} changes: {dryRunApi.DryRunRecords.Count}");
}
private List<DryRunMiniLcmApi.DryRunRecord> GetDryRunRecords(IMiniLcmApi api)
{
return ((DryRunMiniLcmApi)api).DryRunRecords;
}
}