Skip to content

Commit

Permalink
remove ProjectContext.cs and use stateful CurrentProjectService.cs in…
Browse files Browse the repository at this point in the history
…stead
  • Loading branch information
hahn-kev committed Dec 16, 2024
1 parent 465f98e commit 94fd1bd
Show file tree
Hide file tree
Showing 20 changed files with 118 additions and 145 deletions.
4 changes: 2 additions & 2 deletions backend/FwHeadless/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ static async Task<Results<Ok<SyncResult>, NotFound, ProblemHttpResult>> ExecuteM
}

static async Task<Results<Ok<ProjectSyncStatus>, NotFound>> GetMergeStatus(
ProjectContext projectContext,
CurrentProjectService projectContext,
ProjectLookupService projectLookupService,
SyncJobStatusService syncJobStatusService,
IServiceProvider services,
Expand All @@ -139,7 +139,7 @@ static async Task<Results<Ok<ProjectSyncStatus>, NotFound>> GetMergeStatus(
{
var jobStatus = syncJobStatusService.SyncStatus(projectId);
if (jobStatus == SyncJobStatus.Running) return TypedResults.Ok(ProjectSyncStatus.Syncing);
var project = projectContext.Project;
var project = projectContext.MaybeProject;
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
Expand Down
3 changes: 1 addition & 2 deletions backend/FwHeadless/Services/ProjectContextFromIdService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ public async Task PopulateProjectContext(HttpContext context, Func<Task> next)
if (File.Exists(crdtFile))
{
var project = new CrdtProject("crdt", crdtFile);
projectsService.SetProjectScope(project);
await context.RequestServices.GetRequiredService<CurrentProjectService>().PopulateProjectDataCache();
await context.RequestServices.GetRequiredService<CurrentProjectService>().SetupProjectContext(project);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,8 @@ public static IServiceCollection AddSyncServices(this IServiceCollection service
return services.AddLcmCrdtClient()
.AddTestFwDataBridge(mockFwProjectLoader)
.AddFwLiteProjectSync()
.AddSingleton<ProjectContext>(new MockProjectContext(null))
.Configure<FwDataBridgeConfig>(c => c.ProjectsFolder = Path.Combine(".", projectName, "FwData"))
.Configure<LcmCrdtConfig>(c => c.ProjectPath = Path.Combine(".", projectName, "LcmCrdt"))
.AddLogging(builder => builder.AddDebug());
}

public class MockProjectContext(CrdtProject? project) : ProjectContext
{
public override CrdtProject? Project { get; set; } = project;
}

}
2 changes: 1 addition & 1 deletion backend/FwLite/FwLiteProjectSync.Tests/SyncTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public static async Task SyncFailsWithMismatchedProjectIds()
var newFwProjectId = Guid.NewGuid();
await fixture.Services.GetRequiredService<LcmCrdtDbContext>().ProjectData.
ExecuteUpdateAsync(updates => updates.SetProperty(p => p.FwProjectId, newFwProjectId));
await fixture.Services.GetRequiredService<CurrentProjectService>().PopulateProjectDataCache(force: true);
await fixture.Services.GetRequiredService<CurrentProjectService>().RefreshProjectData();

Func<Task> syncTask = async () => await fixture.SyncService.Sync(crdtApi, fwdataApi);
await syncTask.Should().ThrowAsync<InvalidOperationException>();
Expand Down
14 changes: 3 additions & 11 deletions backend/FwLite/FwLiteShared/ChangeEventBus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace FwLiteShared;

public class ChangeEventBus(ProjectContext projectContext)
public class ChangeEventBus
: IDisposable
{

Expand All @@ -20,18 +20,10 @@ public IObservable<Entry> OnProjectEntryUpdated(CrdtProject project)
.Where(n => n.ProjectName == projectName)
.Select(n => n.Entry);
}
public IObservable<Entry> OnEntryUpdated
{
get
{
return OnProjectEntryUpdated(projectContext.Project ?? throw new InvalidOperationException("Not in a project"));
}
}

public void NotifyEntryUpdated(Entry entry)
public void NotifyEntryUpdated(Entry entry, CrdtProject project)
{
_entryUpdated.OnNext(new ChangeNotification(entry,
projectContext.Project?.Name ?? throw new InvalidOperationException("Not in a project")));
_entryUpdated.OnNext(new ChangeNotification(entry, project.Name));
}

public void Dispose()
Expand Down
4 changes: 2 additions & 2 deletions backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ public async Task SetService(DotnetService service, object? serviceInstance)

public async Task<IAsyncDisposable> InjectCrdtProject(string projectName)
{
var project = crdtProjectsService.SetActiveProject(projectName);
var projectData = await serviceProvider.GetRequiredService<CurrentProjectService>().PopulateProjectDataCache();
var project = crdtProjectsService.GetProject(projectName) ?? throw new InvalidOperationException($"Crdt Project {projectName} not found");
var projectData = await serviceProvider.GetRequiredService<CurrentProjectService>().SetupProjectContext(project);
await lexboxProjectService.ListenForProjectChanges(projectData, CancellationToken.None);
var entryUpdatedSubscription = changeEventBus.OnProjectEntryUpdated(project).Subscribe(entry =>
{
Expand Down
10 changes: 3 additions & 7 deletions backend/FwLite/FwLiteShared/Sync/BackgroundSyncService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ namespace FwLiteShared.Sync;

public class BackgroundSyncService(
CrdtProjectsService crdtProjectsService,
ProjectContext projectContext,
ILogger<BackgroundSyncService> logger,
IMemoryCache memoryCache,
IServiceProvider serviceProvider,
IHostApplicationLifetime? applicationLifetime = null) : BackgroundService
{
private readonly Channel<CrdtProject> _syncResultsChannel = Channel.CreateUnbounded<CrdtProject>();
Expand Down Expand Up @@ -41,10 +41,6 @@ public void TriggerSync(Guid projectId, Guid? ignoredClientId = null)

TriggerSync(crdtProject);
}
public void TriggerSync()
{
TriggerSync(projectContext.Project ?? throw new InvalidOperationException("No project selected"));
}

public void TriggerSync(CrdtProject crdtProject)
{
Expand Down Expand Up @@ -83,8 +79,8 @@ private async Task<SyncResults> SyncProject(CrdtProject crdtProject)
{
try
{
await using var serviceScope = crdtProjectsService.CreateProjectScope(crdtProject);
await serviceScope.ServiceProvider.GetRequiredService<CurrentProjectService>().PopulateProjectDataCache();
await using var serviceScope = serviceProvider.CreateAsyncScope();
await serviceScope.ServiceProvider.GetRequiredService<CurrentProjectService>().SetupProjectContext(crdtProject);
var syncService = serviceScope.ServiceProvider.GetRequiredService<SyncService>();
return await syncService.ExecuteSync();
}
Expand Down
2 changes: 1 addition & 1 deletion backend/FwLite/FwLiteShared/Sync/SyncService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ private async Task SendNotifications(SyncResults syncResults)
var entry = await lexboxApi.GetEntry(entryId.Value);
if (entry is not null)
{
changeEventBus.NotifyEntryUpdated(entry);
changeEventBus.NotifyEntryUpdated(entry, currentProjectService.Project);
}
else
{
Expand Down
13 changes: 7 additions & 6 deletions backend/FwLite/LcmCrdt.Tests/DataModelSnapshotTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using FluentAssertions.Execution;
using LcmCrdt.Objects;
using LcmCrdt.Tests.Mocks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -25,14 +24,14 @@ public class DataModelSnapshotTests : IAsyncLifetime
protected readonly AsyncServiceScope _services;
private readonly LcmCrdtDbContext _crdtDbContext;
private CrdtConfig _crdtConfig;
private CrdtProject _crdtProject;

public DataModelSnapshotTests()
{

_crdtProject = new CrdtProject("sena-3", $"sena-3-{Guid.NewGuid()}.sqlite");
var services = new ServiceCollection()
.AddLcmCrdtClient()
.AddTestLcmCrdtClient(_crdtProject)
.AddLogging(builder => builder.AddDebug())
.RemoveAll(typeof(ProjectContext))
.AddSingleton<ProjectContext>(new MockProjectContext(new CrdtProject("sena-3", ":memory:")))
.BuildServiceProvider();
_services = services.CreateAsyncScope();
_crdtDbContext = _services.ServiceProvider.GetRequiredService<LcmCrdtDbContext>();
Expand All @@ -45,11 +44,13 @@ public async Task InitializeAsync()
//can't use ProjectsService.CreateProject because it opens and closes the db context, this would wipe out the in memory db.
await CrdtProjectsService.InitProjectDb(_crdtDbContext,
new ProjectData("Sena 3", Guid.NewGuid(), null, Guid.NewGuid()));
await _services.ServiceProvider.GetRequiredService<CurrentProjectService>().PopulateProjectDataCache();
await _services.ServiceProvider.GetRequiredService<CurrentProjectService>().SetupProjectContext(_crdtProject);
}

public async Task DisposeAsync()
{
await _crdtDbContext.Database.CloseConnectionAsync();
await _crdtDbContext.Database.EnsureDeletedAsync();
await _services.DisposeAsync();
}

Expand Down
24 changes: 24 additions & 0 deletions backend/FwLite/LcmCrdt.Tests/LcmCrdtTestsKernel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace LcmCrdt.Tests;

public static class LcmCrdtTestsKernel
{
public static IServiceCollection AddTestLcmCrdtClient(this IServiceCollection services, CrdtProject? project = null)
{
services.TryAddSingleton<IConfiguration>(new ConfigurationRoot([]));
services.AddLcmCrdtClient();
if (project is not null)
{
services.AddSingleton<CurrentProjectService>(provider =>
{
var currentProjectService = ActivatorUtilities.CreateInstance<CurrentProjectService>(provider);
currentProjectService.SetupProjectContextForNewDb(project);
return currentProjectService;
});
}
return services;
}
}
11 changes: 5 additions & 6 deletions backend/FwLite/LcmCrdt.Tests/MiniLcmApiFixture.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using LcmCrdt.Tests.Mocks;
using Meziantou.Extensions.Logging.Xunit;
using Meziantou.Extensions.Logging.Xunit;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
Expand All @@ -26,22 +26,21 @@ public MiniLcmApiFixture()

public async Task InitializeAsync()
{
var crdtProject = new CrdtProject("sena-3", ":memory:");
var services = new ServiceCollection()
.AddLcmCrdtClient()
.AddTestLcmCrdtClient(crdtProject)
.AddLogging(builder => builder.AddDebug()
.AddProvider(new LateXUnitLoggerProvider(this))
.AddFilter("LinqToDB", LogLevel.Trace)
.SetMinimumLevel(LogLevel.Error))
.RemoveAll(typeof(ProjectContext))
.AddSingleton<ProjectContext>(new MockProjectContext(new CrdtProject("sena-3", ":memory:")))
.BuildServiceProvider();
_services = services.CreateAsyncScope();
_crdtDbContext = _services.ServiceProvider.GetRequiredService<LcmCrdtDbContext>();
await _crdtDbContext.Database.OpenConnectionAsync();
//can't use ProjectsService.CreateProject because it opens and closes the db context, this would wipe out the in memory db.
await CrdtProjectsService.InitProjectDb(_crdtDbContext,
new ProjectData("Sena 3", Guid.NewGuid(), null, Guid.NewGuid()));
await _services.ServiceProvider.GetRequiredService<CurrentProjectService>().PopulateProjectDataCache();
await _services.ServiceProvider.GetRequiredService<CurrentProjectService>().RefreshProjectData();

await Api.CreateWritingSystem(WritingSystemType.Vernacular,
new WritingSystem()
Expand Down
6 changes: 0 additions & 6 deletions backend/FwLite/LcmCrdt.Tests/Mocks/MockProjectContext.cs

This file was deleted.

6 changes: 3 additions & 3 deletions backend/FwLite/LcmCrdt.Tests/OpenProjectTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ public async Task OpeningAProjectWorks()
var sqliteConnectionString = "OpeningAProjectWorks.sqlite";
if (File.Exists(sqliteConnectionString)) File.Delete(sqliteConnectionString);
var builder = Host.CreateEmptyApplicationBuilder(null);
builder.Services.AddLcmCrdtClient();
builder.Services.AddTestLcmCrdtClient();
using var host = builder.Build();
var services = host.Services;
var asyncScope = services.CreateAsyncScope();
await asyncScope.ServiceProvider.GetRequiredService<CrdtProjectsService>()
.CreateProject(new(Name: "OpeningAProjectWorks", Path: "", SeedNewProjectData: true));
var crdtProjectsService = asyncScope.ServiceProvider.GetRequiredService<CrdtProjectsService>();
await crdtProjectsService.CreateProject(new(Name: "OpeningAProjectWorks", Path: "", SeedNewProjectData: true));

var miniLcmApi = (CrdtMiniLcmApi)await asyncScope.ServiceProvider.OpenCrdtProject(new CrdtProject("OpeningAProjectWorks", sqliteConnectionString));
miniLcmApi.ProjectData.Name.Should().Be("OpeningAProjectWorks");
Expand Down
28 changes: 5 additions & 23 deletions backend/FwLite/LcmCrdt/CrdtProjectsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

namespace LcmCrdt;

public partial class CrdtProjectsService(IServiceProvider provider, ProjectContext projectContext, ILogger<CrdtProjectsService> logger, IOptions<LcmCrdtConfig> config, IMemoryCache memoryCache)
public partial class CrdtProjectsService(IServiceProvider provider, ILogger<CrdtProjectsService> logger, IOptions<LcmCrdtConfig> config, IMemoryCache memoryCache)
{
public Task<CrdtProject[]> ListProjects()
{
Expand Down Expand Up @@ -56,7 +56,9 @@ public async Task<CrdtProject> CreateProject(CreateProjectRequest request)
var sqliteFile = Path.Combine(request.Path ?? config.Value.ProjectPath, $"{name}.sqlite");
if (File.Exists(sqliteFile)) throw new InvalidOperationException("Project already exists");
var crdtProject = new CrdtProject(name, sqliteFile);
await using var serviceScope = CreateProjectScope(crdtProject);
await using var serviceScope = provider.CreateAsyncScope();
var currentProjectService = serviceScope.ServiceProvider.GetRequiredService<CurrentProjectService>();
currentProjectService.SetupProjectContextForNewDb(crdtProject);
var db = serviceScope.ServiceProvider.GetRequiredService<LcmCrdtDbContext>();
try
{
Expand All @@ -65,7 +67,7 @@ public async Task<CrdtProject> CreateProject(CreateProjectRequest request)
ProjectData.GetOriginDomain(request.Domain),
Guid.NewGuid(), request.FwProjectId);
await InitProjectDb(db, projectData);
await serviceScope.ServiceProvider.GetRequiredService<CurrentProjectService>().PopulateProjectDataCache();
await currentProjectService.RefreshProjectData();
if (request.SeedNewProjectData)
await SeedSystemData(serviceScope.ServiceProvider.GetRequiredService<DataModel>(), projectData.ClientId);
await (request.AfterCreate?.Invoke(serviceScope.ServiceProvider, crdtProject) ?? Task.CompletedTask);
Expand Down Expand Up @@ -93,26 +95,6 @@ internal static async Task SeedSystemData(DataModel dataModel, Guid clientId)
await PreDefinedData.PredefinedSemanticDomains(dataModel, clientId);
}

public AsyncServiceScope CreateProjectScope(CrdtProject crdtProject)
{
//todo make this helper method call `CurrentProjectService.PopulateProjectDataCache`
var serviceScope = provider.CreateAsyncScope();
SetProjectScope(crdtProject);
return serviceScope;
}

public void SetProjectScope(CrdtProject crdtProject)
{
projectContext.Project = crdtProject;
}

public CrdtProject SetActiveProject(string name)
{
var project = GetProject(name) ?? throw new InvalidOperationException($"Crdt Project {name} not found");
SetProjectScope(project);
return project;
}

[GeneratedRegex("^[a-zA-Z0-9][a-zA-Z0-9-_]+$")]
public static partial Regex ProjectName();

Expand Down
Loading

0 comments on commit 94fd1bd

Please sign in to comment.