Skip to content

Commit 624ea9c

Browse files
authored
Only regenerate snapshot if Send/Receive works (#2069)
* Only regenerate snapshot if Send/Receive works * Only sync harmony after regenerating snapshot * Pull snapshot handling out of CrdtFwdataProjectSyncService * Reference interfaces instead of classes * Add project snapshot tests * Block project when rollback detected
1 parent a43a321 commit 624ea9c

36 files changed

Lines changed: 1200 additions & 264 deletions

backend/FwHeadless/FwHeadlessKernel.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ public static IServiceCollection AddFwHeadless(this IServiceCollection services)
2121
.BindConfiguration("FwHeadlessConfig")
2222
.ValidateDataAnnotations()
2323
.ValidateOnStart();
24-
services.AddSingleton<SyncJobStatusService>();
25-
services.AddScoped<SendReceiveService>();
26-
services.AddScoped<ProjectLookupService>();
24+
services.AddSingleton<ISyncJobStatusService, SyncJobStatusService>();
25+
services.AddScoped<ISendReceiveService, SendReceiveService>();
26+
services.AddScoped<IProjectLookupService, ProjectLookupService>();
2727
services.AddScoped<ProjectDeletionService>();
2828
services.AddScoped<LogSanitizerService>();
2929
services.AddScoped<SafeLoggingProgress>();
30-
services.AddScoped<ProjectMetadataService>();
30+
services.AddScoped<IProjectMetadataService, ProjectMetadataService>();
3131
services
3232
.AddLcmCrdtClientCore()
3333
.AddFwDataBridge(ServiceLifetime.Scoped)

backend/FwHeadless/Media/MediaFileService.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System.Security.Cryptography;
22
using FwHeadless.Services;
3-
using LcmCrdt;
43
using LcmCrdt.MediaServer;
54
using LexCore.Entities;
65
using LexCore.Exceptions;
@@ -13,11 +12,11 @@
1312

1413
namespace FwHeadless.Media;
1514

16-
public class MediaFileService(LexBoxDbContext dbContext, IOptions<FwHeadlessConfig> config, SendReceiveService sendReceiveService)
15+
public class MediaFileService(LexBoxDbContext dbContext, IOptions<FwHeadlessConfig> config, ISendReceiveService sendReceiveService)
1716
{
1817
public record MediaFileSyncResult(List<MediaFile> Added, List<MediaFile> Removed);
1918
// TODO: This assumes FieldWorks is the source of truth, which is not true when FWL starts adding/deleting files
20-
public async Task<MediaFileSyncResult> SyncMediaFiles(LcmCache cache)
19+
public virtual async Task<MediaFileSyncResult> SyncMediaFiles(LcmCache cache)
2120
{
2221
var result = new MediaFileSyncResult([], []);
2322
var projectId = config.Value.LexboxProjectId(cache);
@@ -107,7 +106,7 @@ public string FilePath(MediaFile mediaFile)
107106
return Path.Join(config.Value.GetFwDataFolder(mediaFile.ProjectId), mediaFile.Filename);
108107
}
109108

110-
public async Task SyncMediaFiles(Guid projectId, LcmMediaService lcmMediaService)
109+
public virtual async Task SyncMediaFiles(Guid projectId, LcmMediaService lcmMediaService)
111110
{
112111
var lcmResources = (await lcmMediaService.AllResources()).ToDictionary(r => r.Id);
113112
var existingDbFiles = dbContext.Files.Where(p => p.ProjectId == projectId).AsAsyncEnumerable();

backend/FwHeadless/Routes/MergeRoutes.cs

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ public static IEndpointConventionBuilder MapMergeRoutes(this WebApplication app)
3434

3535
static async Task<Results<Ok, ProblemHttpResult>> ExecuteMergeRequest(
3636
SyncHostedService syncHostedService,
37-
ProjectLookupService projectLookupService,
38-
ProjectMetadataService metadataService,
37+
IProjectLookupService projectLookupService,
38+
IProjectMetadataService metadataService,
3939
ILogger<Program> logger,
4040
CrdtHttpSyncService crdtHttpSyncService,
4141
IHttpClientFactory httpClientFactory,
@@ -64,7 +64,7 @@ static async Task<Results<Ok, ProblemHttpResult>> ExecuteMergeRequest(
6464
}
6565

6666
// Check if project is blocked from syncing
67-
var blockInfo = await metadataService.GetSyncBlockInfoAsync(projectId);
67+
var blockInfo = await metadataService.GetSyncBlockedInfoAsync(projectId);
6868
if (blockInfo?.IsBlocked == true)
6969
{
7070
logger.LogInformation("Project {projectId} is blocked from syncing. Reason: {Reason}", projectId, blockInfo.Reason);
@@ -79,7 +79,7 @@ static async Task<Results<Ok, ProblemHttpResult>> ExecuteMergeRequest(
7979

8080
static async Task<Results<Ok, NotFound<string>>> SyncHarmonyProject(
8181
Guid projectId,
82-
ProjectLookupService projectLookupService,
82+
IProjectLookupService projectLookupService,
8383
CrdtSyncService crdtSyncService,
8484
IServiceProvider services,
8585
CancellationToken stoppingToken
@@ -104,8 +104,8 @@ CancellationToken stoppingToken
104104
static async Task<Results<Ok, NotFound<string>>> RegenerateProjectSnapshot(
105105
Guid projectId,
106106
CurrentProjectService projectContext,
107-
ProjectLookupService projectLookupService,
108-
CrdtFwdataProjectSyncService syncService,
107+
IProjectLookupService projectLookupService,
108+
ProjectSnapshotService projectSnapshotService,
109109
SnapshotAtCommitService snapshotAtCommitService,
110110
IOptions<FwHeadlessConfig> config,
111111
HttpContext context,
@@ -132,25 +132,25 @@ 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, keepBackup: true);
144144
}
145145
return TypedResults.Ok();
146146
}
147147

148148
static async Task<Results<Ok<ProjectSyncStatus>, NotFound>> GetMergeStatus(
149149
CurrentProjectService projectContext,
150-
ProjectLookupService projectLookupService,
151-
SendReceiveService srService,
150+
IProjectLookupService projectLookupService,
151+
ISendReceiveService srService,
152152
IOptions<FwHeadlessConfig> config,
153-
SyncJobStatusService syncJobStatusService,
153+
ISyncJobStatusService syncJobStatusService,
154154
IServiceProvider services,
155155
LexBoxDbContext lexBoxDb,
156156
SyncHostedService syncHostedService,
@@ -199,7 +199,7 @@ static async Task<Results<Ok<ProjectSyncStatus>, NotFound>> GetMergeStatus(
199199

200200
static async Task<SyncJobResult> AwaitSyncFinished(
201201
SyncHostedService syncHostedService,
202-
SyncJobStatusService syncJobStatusService,
202+
ISyncJobStatusService syncJobStatusService,
203203
CancellationToken cancellationToken,
204204
Guid projectId)
205205
{
@@ -241,8 +241,8 @@ static async Task<SyncJobResult> AwaitSyncFinished(
241241
}
242242

243243
static async Task<Results<Ok, NotFound, BadRequest<string>>> BlockProject(
244-
ProjectLookupService projectLookupService,
245-
ProjectMetadataService metadataService,
244+
IProjectLookupService projectLookupService,
245+
IProjectMetadataService metadataService,
246246
ILogger<Program> logger,
247247
Guid projectId,
248248
string? reason = null)
@@ -273,8 +273,8 @@ static async Task<Results<Ok, NotFound, BadRequest<string>>> BlockProject(
273273
}
274274

275275
static async Task<Results<Ok, NotFound, BadRequest<string>>> UnblockProject(
276-
ProjectLookupService projectLookupService,
277-
ProjectMetadataService metadataService,
276+
IProjectLookupService projectLookupService,
277+
IProjectMetadataService metadataService,
278278
ILogger<Program> logger,
279279
Guid projectId)
280280
{
@@ -304,8 +304,8 @@ static async Task<Results<Ok, NotFound, BadRequest<string>>> UnblockProject(
304304
}
305305

306306
static async Task<Results<Ok<SyncBlockStatus>, NotFound, BadRequest<string>>> GetBlockStatus(
307-
ProjectLookupService projectLookupService,
308-
ProjectMetadataService metadataService,
307+
IProjectLookupService projectLookupService,
308+
IProjectMetadataService metadataService,
309309
ILogger<Program> logger,
310310
Guid projectId)
311311
{
@@ -319,7 +319,7 @@ static async Task<Results<Ok<SyncBlockStatus>, NotFound, BadRequest<string>>> Ge
319319
return TypedResults.NotFound();
320320
}
321321

322-
var blockInfo = await metadataService.GetSyncBlockInfoAsync(projectId);
322+
var blockInfo = await metadataService.GetSyncBlockedInfoAsync(projectId);
323323

324324
activity?.SetStatus(ActivityStatusCode.Ok, $"Block status retrieved: {(blockInfo?.IsBlocked == true ? "blocked" : "unblocked")}");
325325
return TypedResults.Ok(new SyncBlockStatus

backend/FwHeadless/Services/CrdtSyncService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public class CrdtSyncService(
1111
DataModel dataModel,
1212
ILogger<CrdtSyncService> logger)
1313
{
14-
public async Task SyncHarmonyProject()
14+
public virtual async Task SyncHarmonyProject()
1515
{
1616
using var activity = FwHeadlessActivitySource.Value.StartActivity();
1717
activity?.SetTag("app.project_id", currentProjectService.ProjectData.Id);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace FwHeadless.Services;
2+
3+
public interface IProjectLookupService
4+
{
5+
ValueTask<string?> GetProjectCode(Guid projectId);
6+
Task<Guid?> GetProjectId(string projectCode);
7+
Task<bool> ProjectExists(Guid projectId);
8+
Task<bool> IsCrdtProject(Guid projectId);
9+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace FwHeadless.Services;
2+
3+
public interface IProjectMetadataService
4+
{
5+
Task BlockFromSyncAsync(Guid projectId, string? reason = null);
6+
Task UnblockFromSyncAsync(Guid projectId);
7+
Task<SyncBlockedInfo?> GetSyncBlockedInfoAsync(Guid projectId);
8+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using FwDataMiniLcmBridge;
2+
3+
namespace FwHeadless.Services;
4+
5+
public interface ISendReceiveService
6+
{
7+
Task<SendReceiveHelpers.LfMergeBridgeResult> SendReceive(FwDataProject project, string? projectCode, string? commitMessage = null);
8+
Task<SendReceiveHelpers.LfMergeBridgeResult> Clone(FwDataProject project, string? projectCode);
9+
Task<int> PendingCommitCount(FwDataProject project, string? projectCode);
10+
Task CommitFile(string filePath, string commitMessage);
11+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace FwHeadless.Services;
2+
3+
public interface ISyncJobStatusService
4+
{
5+
void StartSyncing(Guid projectId);
6+
void StopSyncing(Guid projectId);
7+
SyncJobStatus SyncStatus(Guid projectId);
8+
}

backend/FwHeadless/Services/ProjectContextFromIdService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
namespace FwHeadless.Services;
77

88
// TODO: Pick better name
9-
public class ProjectContextFromIdService(IOptions<FwHeadlessConfig> config, ProjectLookupService projectLookupService, ProjectDataCache projectDataCache)
9+
public class ProjectContextFromIdService(IOptions<FwHeadlessConfig> config, IProjectLookupService projectLookupService, ProjectDataCache projectDataCache)
1010
{
1111
public async Task PopulateProjectContext(HttpContext context, Func<Task> next)
1212
{

backend/FwHeadless/Services/ProjectDeletionService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace FwHeadless.Services;
55

66
public class ProjectDeletionService(
77
IOptions<FwHeadlessConfig> config,
8-
ProjectLookupService projectLookupService,
8+
IProjectLookupService projectLookupService,
99
SyncHostedService syncHostedService,
1010
ILogger<ProjectDeletionService> logger)
1111
{

0 commit comments

Comments
 (0)