Skip to content

Commit 3e9c79d

Browse files
committed
Checkpoint
1 parent 6809777 commit 3e9c79d

File tree

14 files changed

+342
-187
lines changed

14 files changed

+342
-187
lines changed

backend/FwHeadless/Routes/MergeRoutes.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ static async Task<Results<Ok, NotFound<string>>> RegenerateProjectSnapshot(
105105
Guid projectId,
106106
CurrentProjectService projectContext,
107107
IProjectLookupService projectLookupService,
108-
CrdtFwdataProjectSyncService syncService,
108+
ProjectSnapshotService projectSnapshotService,
109109
SnapshotAtCommitService snapshotAtCommitService,
110110
IOptions<FwHeadlessConfig> config,
111111
HttpContext context,
@@ -132,15 +132,15 @@ static async Task<Results<Ok, NotFound<string>>> RegenerateProjectSnapshot(
132132
var fwDataProject = config.Value.GetFwDataProject(projectId);
133133
if (commitId.HasValue)
134134
{
135-
if (!await syncService.RegenerateProjectSnapshotAtCommit(snapshotAtCommitService, fwDataProject, commitId.Value, preserveAllFieldWorksCommits))
135+
if (!await projectSnapshotService.RegenerateProjectSnapshotAtCommit(snapshotAtCommitService, fwDataProject, commitId.Value, preserveAllFieldWorksCommits))
136136
{
137137
return TypedResults.NotFound($"Commit {commitId} not found");
138138
}
139139
}
140140
else
141141
{
142142
var miniLcmApi = context.RequestServices.GetRequiredService<IMiniLcmApi>();
143-
await syncService.RegenerateProjectSnapshot(miniLcmApi, fwDataProject);
143+
await projectSnapshotService.RegenerateProjectSnapshot(miniLcmApi, fwDataProject);
144144
}
145145
return TypedResults.Ok();
146146
}

backend/FwHeadless/Services/SyncHostedService.cs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ public class SyncWorker(
109109
IProjectLookupService projectLookupService,
110110
ISyncJobStatusService syncStatusService,
111111
CrdtFwdataProjectSyncService syncService,
112+
ProjectSnapshotService projectSnapshotService,
112113
CrdtHttpSyncService crdtHttpSyncService,
113114
IHttpClientFactory httpClientFactory,
114115
MediaFileService mediaFileService,
@@ -156,6 +157,7 @@ public async Task<SyncJobResult> ExecuteSync(CancellationToken stoppingToken, bo
156157
if (!Directory.Exists(projectFolder)) Directory.CreateDirectory(projectFolder);
157158

158159
var crdtFile = config.Value.GetCrdtFile(projectCode, projectId);
160+
var crdtFileExists = File.Exists(crdtFile);
159161
var fwDataProject = config.Value.GetFwDataProject(projectCode, projectId);
160162
logger.LogDebug("crdtFile: {crdtFile}", crdtFile);
161163
logger.LogDebug("fwDataFile: {fwDataFile}", fwDataProject.FilePath);
@@ -193,7 +195,7 @@ public async Task<SyncJobResult> ExecuteSync(CancellationToken stoppingToken, bo
193195
var crdtSyncService = services.GetRequiredService<CrdtSyncService>();
194196

195197
// If the last merge was successful, we can sync the Harmony project, otherwise we risk pushing a partial sync
196-
if (CrdtFwdataProjectSyncService.HasSyncedSuccessfully(fwDataProject) || onlyHarmony)
198+
if (ProjectSnapshotService.HasSyncedSuccessfully(fwDataProject) || onlyHarmony)
197199
{
198200
await crdtSyncService.SyncHarmonyProject();
199201
}
@@ -206,7 +208,25 @@ public async Task<SyncJobResult> ExecuteSync(CancellationToken stoppingToken, bo
206208
return new SyncJobResult(SyncJobStatusEnum.Success, "Only Harmony sync requested, skipping Mercurial/Crdt sync");
207209
}
208210

209-
var projectSnapshot = await syncService.GetProjectSnapshot(fwdataApi.Project);
211+
var projectSnapshot = await projectSnapshotService.GetProjectSnapshot(fwdataApi.Project);
212+
if (projectSnapshot is null)
213+
{
214+
if (!crdtFileExists)
215+
{
216+
logger.LogInformation("No snapshot found and no CRDT database detected; importing project");
217+
var importResult = await syncService.Import(miniLcmApi, fwdataApi);
218+
logger.LogInformation("Import result, CrdtChanges: {CrdtChanges}, FwdataChanges: {FwdataChanges}",
219+
importResult.CrdtChanges,
220+
importResult.FwdataChanges);
221+
await crdtSyncService.SyncHarmonyProject();
222+
activity?.SetStatus(ActivityStatusCode.Ok, "Import finished");
223+
return new SyncJobResult(importResult);
224+
}
225+
226+
activity?.SetStatus(ActivityStatusCode.Error, "Snapshot missing for existing CRDT project");
227+
return new SyncJobResult(SyncJobStatusEnum.UnableToSync, "Project snapshot missing for existing CRDT project");
228+
}
229+
210230
var result = await syncService.Sync(miniLcmApi, fwdataApi, projectSnapshot);
211231
logger.LogInformation("Sync result, CrdtChanges: {CrdtChanges}, FwdataChanges: {FwdataChanges}",
212232
result.CrdtChanges,
@@ -246,7 +266,7 @@ public async Task<SyncJobResult> ExecuteSync(CancellationToken stoppingToken, bo
246266
//note we are now using the crdt API, this avoids issues where some data isn't synced yet
247267
//later when we add the ability to sync that data we need the snapshot to reflect the synced state, not what was in the FW project
248268
//related to https://github.com/sillsdev/languageforge-lexbox/issues/1912
249-
await syncService.RegenerateProjectSnapshot(miniLcmApi, fwdataApi.Project, keepBackup: false);
269+
await projectSnapshotService.RegenerateProjectSnapshot(miniLcmApi, fwdataApi.Project, keepBackup: false);
250270
}
251271
else
252272
{

backend/FwLite/FwLiteProjectSync.Tests/CrdtRepairTests.cs

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public class CrdtRepairTests(SyncFixture fixture) : IClassFixture<SyncFixture>,
1616
private CrdtMiniLcmApi CrdtApi => _fixture.CrdtApi;
1717
private FwDataMiniLcmApi FwDataApi => _fixture.FwDataApi;
1818
private CrdtFwdataProjectSyncService SyncService => _fixture.SyncService;
19+
private ProjectSnapshotService SnapshotService => _fixture.Services.GetRequiredService<ProjectSnapshotService>();
1920
private static Entry TestEntry()
2021
{
2122
return new()
@@ -44,7 +45,7 @@ private static Entry TestEntry()
4445

4546
public async Task InitializeAsync()
4647
{
47-
await SyncService.Sync(CrdtApi, FwDataApi);
48+
await SyncService.Import(CrdtApi, FwDataApi);
4849
}
4950

5051
public async Task DisposeAsync()
@@ -89,8 +90,9 @@ public async Task CrdtEntryMissingTranslationId_NoChanges_FullSync()
8990
var fwTranslationId = fwEntry.SingleTranslation().Id;
9091

9192
// act
92-
await SyncService.Sync(CrdtApi, FwDataApi);
93-
await SyncService.RegenerateProjectSnapshot(CrdtApi, FwDataApi.Project);
93+
var projectSnapshot = await GetSnapshot();
94+
await SyncService.Sync(CrdtApi, FwDataApi, projectSnapshot);
95+
await SnapshotService.RegenerateProjectSnapshot(CrdtApi, FwDataApi.Project);
9496

9597
// assert - the fwdata ID is now used everywhere
9698
var updatedFwEntry = await FwDataApi.GetEntry(crdtEntry.Id);
@@ -116,7 +118,8 @@ public async Task CrdtEntryMissingTranslationId_FwTranslationChanged_FullSync()
116118
var translationUpdate = new UpdateObjectInput<Translation>().Set(t => t.Text["en"], new RichString("changed translation", "en"));
117119
await FwDataApi.UpdateTranslation(fwEntry.Id, fwEntry.Senses.Single().Id, fwEntry.SingleExampleSentence().Id,
118120
fwTranslationId, translationUpdate);
119-
await SyncService.Sync(CrdtApi, FwDataApi);
121+
var projectSnapshot = await GetSnapshot();
122+
await SyncService.Sync(CrdtApi, FwDataApi, projectSnapshot);
120123

121124
// assert
122125
var crdtEntryAfter = await CrdtApi.GetEntry(crdtEntry.Id);
@@ -135,7 +138,8 @@ public async Task CrdtEntryMissingTranslationId_CrdtTranslationChanged_FullSync(
135138
var translationUpdate = new UpdateObjectInput<Translation>().Set(t => t.Text["en"], new RichString("changed translation", "en"));
136139
await CrdtApi.UpdateTranslation(crdtEntry.Id, crdtEntry.Senses.Single().Id, crdtEntry.SingleExampleSentence().Id,
137140
crdtEntry.SingleTranslation().Id, translationUpdate);
138-
await SyncService.Sync(CrdtApi, FwDataApi);
141+
var projectSnapshot = await GetSnapshot();
142+
await SyncService.Sync(CrdtApi, FwDataApi, projectSnapshot);
139143

140144
// assert
141145
var fwEntryAfter = await FwDataApi.GetEntry(fwEntry.Id);
@@ -156,8 +160,9 @@ public async Task CrdtEntryMissingTranslationId_MultipleFwTranslations_FullSync(
156160
fwEntry = await FwDataApi.GetEntry(fwEntry.Id);
157161

158162
// act
159-
await SyncService.Sync(CrdtApi, FwDataApi);
160-
await SyncService.RegenerateProjectSnapshot(CrdtApi, FwDataApi.Project);
163+
var projectSnapshot = await GetSnapshot();
164+
await SyncService.Sync(CrdtApi, FwDataApi, projectSnapshot);
165+
await SnapshotService.RegenerateProjectSnapshot(CrdtApi, FwDataApi.Project);
161166

162167
// assert - the fwdata ID is now used everywhere
163168
var updatedFwEntry = await FwDataApi.GetEntry(crdtEntry.Id);
@@ -208,7 +213,8 @@ public async Task CrdtEntryMissingTranslationId_FwTranslationRemoved_FullSync()
208213

209214
// act
210215
await FwDataApi.RemoveTranslation(entryId, senseId, exampleSentenceId, fwTranslationId);
211-
var result = await SyncService.Sync(CrdtApi, FwDataApi);
216+
var projectSnapshot = await GetSnapshot();
217+
var result = await SyncService.Sync(CrdtApi, FwDataApi, projectSnapshot);
212218
result.CrdtChanges.Should().Be(1, "the crdt translation was removed");
213219

214220
// assert - the crdt translation was also removed
@@ -227,7 +233,8 @@ public async Task CrdtEntryMissingTranslationId_FwExampleSentenceRemoved_FullSyn
227233

228234
// act
229235
await FwDataApi.DeleteExampleSentence(entryId, senseId, exampleSentenceId);
230-
var result = await SyncService.Sync(CrdtApi, FwDataApi);
236+
var projectSnapshot = await GetSnapshot();
237+
var result = await SyncService.Sync(CrdtApi, FwDataApi, projectSnapshot);
231238
result.CrdtChanges.Should().Be(1, "the crdt example-sentence was removed");
232239

233240
// assert - the crdt translation was also removed
@@ -269,7 +276,8 @@ public async Task CrdtEntryMissingTranslationId_CrdtTranslationRemoved_FullSync(
269276

270277
// act
271278
await CrdtApi.RemoveTranslation(entryId, senseId, exampleSentenceId, fwExample.DefaultFirstTranslationId);
272-
await SyncService.Sync(CrdtApi, FwDataApi);
279+
var projectSnapshot = await GetSnapshot();
280+
await SyncService.Sync(CrdtApi, FwDataApi, projectSnapshot);
273281

274282
// assert - the fw translation was also removed
275283
var updatedFwEntry = await FwDataApi.GetEntry(crdtEntry.Id);
@@ -287,7 +295,8 @@ public async Task CrdtEntryMissingTranslationId_CrdtExampleSentenceRemoved_FullS
287295

288296
// act
289297
await CrdtApi.DeleteExampleSentence(entryId, senseId, exampleSentenceId);
290-
await SyncService.Sync(CrdtApi, FwDataApi);
298+
var projectSnapshot = await GetSnapshot();
299+
await SyncService.Sync(CrdtApi, FwDataApi, projectSnapshot);
291300

292301
// assert - the fw translation was also removed
293302
var updatedFwEntry = await FwDataApi.GetEntry(crdtEntry.Id);
@@ -321,7 +330,8 @@ public async Task CrdtEntryMissingTranslationId_NewCrdtEntry_FullSync()
321330
var crdtEntry = await CrdtApi.CreateEntry(entry);
322331

323332
// act
324-
await SyncService.Sync(CrdtApi, FwDataApi);
333+
var projectSnapshot = await GetSnapshot();
334+
await SyncService.Sync(CrdtApi, FwDataApi, projectSnapshot);
325335

326336
// assert - the default ID was used in the new fw translation
327337
var fwEntry = await FwDataApi.GetEntry(crdtEntry.Id);
@@ -341,7 +351,7 @@ public async Task CrdtEntryMissingTranslationId_NewCrdtEntry_FullSync()
341351
var snapshot = await CrdtApi.TakeProjectSnapshot();
342352
var snapshotEntry = snapshot.Entries.Single(e => e.Id == crdtEntry.Id);
343353
snapshotEntry.SingleTranslation().Id = Translation.MissingTranslationId;
344-
await CrdtFwdataProjectSyncService.SaveProjectSnapshot(FwDataApi.Project, snapshot);
354+
await ProjectSnapshotService.SaveProjectSnapshot(FwDataApi.Project, snapshot);
345355

346356
// assert
347357
// snapshot reflects the missing ID
@@ -367,10 +377,16 @@ public async Task CrdtEntryMissingTranslationId_NewCrdtEntry_FullSync()
367377

368378
private async Task<Entry[]> GetSnapshotEntries()
369379
{
370-
var snapshot = await SyncService.GetProjectSnapshot(FwDataApi.Project);
380+
var snapshot = await SnapshotService.GetProjectSnapshot(FwDataApi.Project);
371381
return snapshot?.Entries ?? [];
372382
}
373383

384+
private async Task<ProjectSnapshot> GetSnapshot()
385+
{
386+
return await SnapshotService.GetProjectSnapshot(FwDataApi.Project)
387+
?? throw new InvalidOperationException("Expected snapshot to exist");
388+
}
389+
374390
}
375391
#pragma warning restore CS0618 // Type or member is obsolete
376392

backend/FwLite/FwLiteProjectSync.Tests/Fixtures/SyncFixture.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ public async Task DisposeAsync()
118118

119119
public void DeleteSyncSnapshot()
120120
{
121-
var snapshotPath = CrdtFwdataProjectSyncService.SnapshotPath(FwDataApi.Project);
121+
var snapshotPath = ProjectSnapshotService.SnapshotPath(FwDataApi.Project);
122122
if (File.Exists(snapshotPath)) File.Delete(snapshotPath);
123123
}
124124
}

backend/FwLite/FwLiteProjectSync.Tests/ProjectSnapshotSerializationTests.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ namespace FwLiteProjectSync.Tests;
1010
public class ProjectSnapshotSerializationTests
1111
{
1212

13-
private readonly CrdtFwdataProjectSyncService syncService;
13+
private readonly ProjectSnapshotService snapshotService;
1414

1515
public ProjectSnapshotSerializationTests()
1616
{
1717
var crdtServices = new ServiceCollection()
1818
.AddSyncServices(nameof(ProjectSnapshotSerializationTests));
19-
syncService = crdtServices.BuildServiceProvider()
20-
.GetRequiredService<CrdtFwdataProjectSyncService>();
19+
snapshotService = crdtServices.BuildServiceProvider()
20+
.GetRequiredService<ProjectSnapshotService>();
2121
}
2222

2323
public static IEnumerable<object[]> GetSena3SnapshotNames()
@@ -41,11 +41,11 @@ public async Task AssertSena3Snapshots(string sourceSnapshotName)
4141
nameof(AssertSena3Snapshots),
4242
Path.Combine(".", nameof(ProjectSnapshotSerializationTests)));
4343

44-
var snapshotPath = CrdtFwdataProjectSyncService.SnapshotPath(fwDataProject);
44+
var snapshotPath = ProjectSnapshotService.SnapshotPath(fwDataProject);
4545
File.Copy(RelativePath($"Snapshots\\{sourceSnapshotName}"), snapshotPath, overwrite: true);
4646

4747
// act - read the current snapshot
48-
var snapshot = await syncService.GetProjectSnapshot(fwDataProject)
48+
var snapshot = await snapshotService.GetProjectSnapshot(fwDataProject)
4949
?? throw new InvalidOperationException("Failed to load verified snapshot");
5050

5151
// assert - whatever about the snapshot (i.e. ensure deserialization does what we think)
@@ -90,13 +90,13 @@ private async Task<string> GetRoundTrippedIndentedSnapshot(string sourceSnapshot
9090
fwDataProjectName,
9191
Path.Combine(".", nameof(ProjectSnapshotSerializationTests)));
9292

93-
var snapshotPath = CrdtFwdataProjectSyncService.SnapshotPath(fwDataProject);
93+
var snapshotPath = ProjectSnapshotService.SnapshotPath(fwDataProject);
9494
Directory.CreateDirectory(Path.GetDirectoryName(snapshotPath) ?? throw new InvalidOperationException("Could not get directory of snapshot path"));
9595
File.Copy(sourceSnapshotPath, snapshotPath, overwrite: true);
9696

97-
var snapshot = await syncService.GetProjectSnapshot(fwDataProject)
97+
var snapshot = await snapshotService.GetProjectSnapshot(fwDataProject)
9898
?? throw new InvalidOperationException("Failed to load verified snapshot");
99-
await CrdtFwdataProjectSyncService.SaveProjectSnapshot(fwDataProject, snapshot);
99+
await ProjectSnapshotService.SaveProjectSnapshot(fwDataProject, snapshot);
100100

101101
using var stream = File.OpenRead(snapshotPath);
102102
var node = JsonNode.Parse(stream, null, new()

0 commit comments

Comments
 (0)