-
-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add merge status API to fw-headless #1294
Changes from 10 commits
61712c8
db1fdc0
e6a371f
937f54e
641567c
2649b68
71c3d3c
03c175b
84624cc
c934db3
e81d560
0ce3d27
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
using FwHeadless; | ||
using FwHeadless.Services; | ||
using FwDataMiniLcmBridge; | ||
using FwDataMiniLcmBridge.Api; | ||
using FwLiteProjectSync; | ||
|
@@ -10,6 +11,10 @@ | |
using Microsoft.Extensions.Options; | ||
using MiniLcm; | ||
using Scalar.AspNetCore; | ||
using LexCore.Utils; | ||
using LinqToDB; | ||
using SIL.Harmony.Core; | ||
using SIL.Harmony; | ||
|
||
var builder = WebApplication.CreateBuilder(args); | ||
|
||
|
@@ -36,6 +41,13 @@ | |
await next(); | ||
}); | ||
|
||
// Load project ID from request | ||
app.Use((context, next) => | ||
{ | ||
var renameThisService = context.RequestServices.GetRequiredService<ProjectContextFromIdService>(); | ||
return renameThisService.PopulateProjectContext(context, next); | ||
}); | ||
|
||
// Configure the HTTP request pipeline. | ||
if (app.Environment.IsDevelopment()) | ||
{ | ||
|
@@ -49,6 +61,7 @@ | |
app.MapHealthChecks("/api/healthz"); | ||
|
||
app.MapPost("/api/crdt-sync", ExecuteMergeRequest); | ||
app.MapGet("/api/crdt-sync-status", GetMergeStatus); | ||
|
||
app.Run(); | ||
|
||
|
@@ -60,6 +73,7 @@ static async Task<Results<Ok<SyncResult>, NotFound, ProblemHttpResult>> ExecuteM | |
FwDataFactory fwDataFactory, | ||
CrdtProjectsService projectsService, | ||
ProjectLookupService projectLookupService, | ||
ProjectSyncStatusService syncStatusService, | ||
CrdtFwdataProjectSyncService syncService, | ||
CrdtHttpSyncService crdtHttpSyncService, | ||
IHttpClientFactory httpClientFactory, | ||
|
@@ -73,6 +87,9 @@ static async Task<Results<Ok<SyncResult>, NotFound, ProblemHttpResult>> ExecuteM | |
return TypedResults.Ok(new SyncResult(0, 0)); | ||
} | ||
|
||
syncStatusService.StartSyncing(projectId); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤔 It would be pretty easy to put the number of changes that are being synced in the sync status, so that we can provide that bonus status info during a sync as well. |
||
using var stopSyncing = Defer.Action(() => syncStatusService.StopSyncing(projectId)); | ||
|
||
var projectCode = await projectLookupService.GetProjectCode(projectId); | ||
if (projectCode is null) | ||
{ | ||
|
@@ -103,7 +120,6 @@ static async Task<Results<Ok<SyncResult>, NotFound, ProblemHttpResult>> ExecuteM | |
var crdtSyncService = services.GetRequiredService<CrdtSyncService>(); | ||
await crdtSyncService.Sync(); | ||
|
||
|
||
var result = await syncService.Sync(miniLcmApi, fwdataApi, dryRun); | ||
logger.LogInformation("Sync result, CrdtChanges: {CrdtChanges}, FwdataChanges: {FwdataChanges}", result.CrdtChanges, result.FwdataChanges); | ||
|
||
|
@@ -113,6 +129,29 @@ static async Task<Results<Ok<SyncResult>, NotFound, ProblemHttpResult>> ExecuteM | |
return TypedResults.Ok(result); | ||
} | ||
|
||
static async Task<Results<Ok<ProjectSyncStatus>, NotFound>> GetMergeStatus( | ||
ProjectContext projectContext, | ||
ProjectLookupService projectLookupService, | ||
ProjectSyncStatusService syncStatusService, | ||
IServiceProvider services, | ||
LexBoxDbContext lexBoxDb, | ||
Guid projectId) | ||
{ | ||
var status = syncStatusService.SyncStatus(projectId); | ||
hahn-kev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (status is not null) return TypedResults.Ok(new ProjectSyncStatus(status.Value, 0)); | ||
var project = projectContext.Project; | ||
if (project is null) | ||
{ | ||
// 404 only means "project doesn't exist"; if we don't know the status, then it hasn't synced before and is therefore ready to sync | ||
if (await projectLookupService.ProjectExists(projectId)) return TypedResults.Ok(ProjectSyncStatus.NeverSynced); | ||
else return TypedResults.NotFound(); | ||
} | ||
var commitsOnServer = await lexBoxDb.Set<ServerCommit>().CountAsync(c => c.ProjectId == projectId); | ||
var lcmCrdtDbContext = services.GetRequiredService<LcmCrdtDbContext>(); // TODO: This *cannot* be right, can it? | ||
var localCommits = await lcmCrdtDbContext.Set<Commit>().CountAsync(); | ||
hahn-kev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return TypedResults.Ok(ProjectSyncStatus.ReadyToSync(localCommits - commitsOnServer)); | ||
} | ||
|
||
static async Task<FwDataMiniLcmApi> SetupFwData(FwDataProject fwDataProject, | ||
SendReceiveService srService, | ||
string projectCode, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
namespace FwHeadless.Services; | ||
|
||
public static class HttpHelpers | ||
{ | ||
public static Guid? GetProjectId(this HttpContext? context) | ||
{ | ||
if (context is null) return null; | ||
if (context.Request.Query.TryGetValue("projectId", out var projectIds) && projectIds.FirstOrDefault() is string idStr) | ||
{ | ||
if (Guid.TryParse(idStr, out var projectId)) return projectId; | ||
} | ||
return null; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
using LcmCrdt; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace FwHeadless.Services; | ||
|
||
// TODO: Pick better name | ||
public class ProjectContextFromIdService(IOptions<FwHeadlessConfig> config, CrdtProjectsService projectsService, ProjectLookupService projectLookupService) | ||
hahn-kev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
public async Task PopulateProjectContext(HttpContext context, Func<Task> next) | ||
{ | ||
if (context.GetProjectId() is Guid projectId) | ||
{ | ||
var projectCode = await projectLookupService.GetProjectCode(projectId); | ||
if (projectCode is not null) | ||
{ | ||
var projectFolder = Path.Join(config.Value.ProjectStorageRoot, $"{projectCode}-{projectId}"); | ||
var crdtFile = Path.Join(projectFolder, "crdt.sqlite"); | ||
if (File.Exists(crdtFile)) | ||
{ | ||
var project = new CrdtProject("crdt", crdtFile); | ||
projectsService.SetProjectScope(project); | ||
await context.RequestServices.GetRequiredService<CurrentProjectService>().PopulateProjectDataCache(); | ||
} | ||
} | ||
} | ||
await next(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,43 @@ | ||||||||
using System.Collections.Concurrent; | ||||||||
|
||||||||
namespace FwHeadless.Services; | ||||||||
|
||||||||
public class ProjectSyncStatusService() | ||||||||
{ | ||||||||
private ConcurrentDictionary<Guid, ProjectSyncStatusEnum> Status { get; init; } = new(); | ||||||||
|
||||||||
public void StartSyncing(Guid projectId) | ||||||||
{ | ||||||||
Status.AddOrUpdate(projectId, (_) => ProjectSyncStatusEnum.Syncing, (_, _) => ProjectSyncStatusEnum.Syncing); | ||||||||
} | ||||||||
|
||||||||
public void StopSyncing(Guid projectId) | ||||||||
{ | ||||||||
Status.Remove(projectId, out var _); | ||||||||
} | ||||||||
|
||||||||
public ProjectSyncStatusEnum? SyncStatus(Guid projectId) | ||||||||
{ | ||||||||
return Status.TryGetValue(projectId, out var status) ? status : null; | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
public enum ProjectSyncStatusEnum | ||||||||
{ | ||||||||
NeverSynced, | ||||||||
ReadyToSync, | ||||||||
Syncing, | ||||||||
} | ||||||||
|
||||||||
// TODO: Bikeshed this name | ||||||||
public record ProjectSyncStatus( | ||||||||
Comment on lines
+38
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's perfect 👌
Suggested change
|
||||||||
ProjectSyncStatusEnum status, | ||||||||
int ChangesAvailable) | ||||||||
{ | ||||||||
public static ProjectSyncStatus NeverSynced => new(ProjectSyncStatusEnum.NeverSynced, 0); | ||||||||
public static ProjectSyncStatus Syncing => new(ProjectSyncStatusEnum.Syncing, 0); | ||||||||
public static ProjectSyncStatus ReadyToSync(int changes) | ||||||||
{ | ||||||||
return new(ProjectSyncStatusEnum.ReadyToSync, changes); | ||||||||
} | ||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔧 Or just delete it. But, I believe the comment is inaccurate/out-of-date