switched to xunit

This commit is contained in:
2024-05-03 18:49:51 -06:00
parent aab38c3e9b
commit 26bf2bbbd1
34 changed files with 643 additions and 298 deletions

View File

@@ -7,6 +7,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Akka" Version="1.5.20" />
<PackageReference Include="Akka.DependencyInjection" Version="1.5.20" />
<PackageReference Include="Markdig" Version="0.31.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Common" Version="8.0.3" />
@@ -16,4 +18,7 @@
<PackageReference Include="YamlDotNet" Version="13.1.1" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,9 @@
using Akka.Actor;
using Management.Services.Canvas;
public class CanvasQueue(IActorRef canvasQueueActor)
{
private readonly IActorRef canvasQueueActor = canvasQueueActor;
}

View File

@@ -0,0 +1,18 @@
using Akka.Actor;
using Microsoft.Extensions.DependencyInjection;
public class CanvasQueueActor : ReceiveActor
{
private readonly IServiceProvider serviceProvider;
private readonly IServiceScope scope;
private readonly ILogger<CanvasQueueActor> logger;
public CanvasQueueActor(IServiceProvider serviceProviderArg)
{
serviceProvider = serviceProviderArg;
scope = serviceProvider.CreateScope();
logger = scope.ServiceProvider.GetRequiredService<ILogger<CanvasQueueActor>>();
}
}

View File

@@ -0,0 +1,61 @@
using Akka.Actor;
using LocalModels;
using Microsoft.Extensions.DependencyInjection;
public class LocalStorageActor : ReceiveActor
{
private readonly IServiceProvider serviceProvider;
private readonly IServiceScope scope;
private readonly ILogger<CanvasQueueActor> logger;
private readonly FileStorageManager storage;
private DateTime? cacheTime { get; set; } = null;
private IEnumerable<LocalCourse>? cachedCourses { get; set; } = null;
private readonly int cacheSeconds = 2;
public LocalStorageActor(IServiceProvider serviceProviderArg)
{
serviceProvider = serviceProviderArg;
scope = serviceProvider.CreateScope();
logger = scope.ServiceProvider.GetRequiredService<ILogger<CanvasQueueActor>>();
storage = scope.ServiceProvider.GetRequiredService<FileStorageManager>();
Receive<EmptyDirectoryAsk>(m =>
{
storage
.GetEmptyDirectories()
.PipeTo(Sender);
});
ReceiveAsync<SavedCoursesAsk>(async m =>
{
var secondsFromLastLoad = (DateTime.Now - cacheTime)?.Seconds;
if (cachedCourses != null && secondsFromLastLoad < cacheSeconds)
{
logger.LogInformation("returning cached courses from file");
Sender.Tell(cachedCourses);
return;
}
cachedCourses = await storage.LoadSavedCourses();
cacheTime = DateTime.Now;
Sender.Tell(cachedCourses);
});
ReceiveAsync<SaveCoursesRequest>(async m =>
{
cacheTime = null;
cachedCourses = null;
await storage.SaveCourseAsync(m.Course, m.PreviouslyStoredCourse);
});
}
}
public record EmptyDirectoryAsk();
public record SavedCoursesAsk();
public record SaveCoursesRequest(LocalCourse Course, LocalCourse? PreviouslyStoredCourse);
public record SaveCoursesResponseSuccess();

View File

@@ -0,0 +1,23 @@
using Akka.Actor;
using LocalModels;
public class LocalStorageCache(IActorRef storageActor) : IFileStorageManager
{
private readonly IActorRef storageActor = storageActor;
public async Task<IEnumerable<string>> GetEmptyDirectories()
{
return await storageActor.Ask<IEnumerable<string>>(new EmptyDirectoryAsk());
}
public async Task<IEnumerable<LocalCourse>> LoadSavedCourses()
{
return await storageActor.Ask<IEnumerable<LocalCourse>>(new SavedCoursesAsk());
}
public async Task SaveCourseAsync(LocalCourse course, LocalCourse? previouslyStoredCourse)
{
await storageActor.Ask<SaveCoursesResponseSuccess>(new SaveCoursesRequest(course, previouslyStoredCourse));
}
}

View File

@@ -0,0 +1,60 @@
using Akka.Actor;
using Akka.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
namespace Management.Services;
public class AkkaService(
IServiceProvider serviceProvider,
IHostApplicationLifetime appLifetime,
IConfiguration configuration
) : IHostedService
{
private ActorSystem? actorSystem;
private readonly IConfiguration configuration = configuration;
private readonly IServiceProvider serviceProvider = serviceProvider;
private readonly IHostApplicationLifetime applicationLifetime = appLifetime;
public IActorRef? CanvasQueueActor { get; private set; }
public IActorRef? StorageActor { get; private set; }
public Task StartAsync(CancellationToken cancellationToken)
{
var bootstrap = BootstrapSetup.Create();
var dependencyInjectionSetup = DependencyResolverSetup.Create(serviceProvider);
var mergedSystemSetup = bootstrap.And(dependencyInjectionSetup);
actorSystem = ActorSystem.Create("canavas-management-actor-system", mergedSystemSetup);
var canvasQueueProps = DependencyResolver.For(actorSystem).Props<CanvasQueueActor>();
CanvasQueueActor = actorSystem.ActorOf(canvasQueueProps, "canvasQueue");
var localStorageProps = DependencyResolver.For(actorSystem).Props<LocalStorageActor>();
StorageActor = actorSystem.ActorOf(localStorageProps, "localStorage");
// crash if the actor system crashes, awaiting never returns...
actorSystem.WhenTerminated.ContinueWith(tr =>
{
applicationLifetime.StopApplication();
});
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
// public void Tell(object message)
// {
// userSessionSupervisor?.Tell(message);
// }
// public Task<T> Ask<T>(object message)
// {
// return userSessionSupervisor.Ask<T>(message);
// }
}

View File

@@ -4,7 +4,6 @@ using CanvasModel.Courses;
using CanvasModel.EnrollmentTerms;
using CanvasModel.Modules;
using CanvasModel.Pages;
using Microsoft.Extensions.Logging;
using RestSharp;
namespace Management.Services.Canvas;
@@ -35,7 +34,7 @@ public class CanvasService(
ICanvasQuizService Quizzes,
ICanvasCoursePageService Pages,
MyLogger<ICanvasService> logger
):ICanvasService
) : ICanvasService
{
private readonly IWebRequestor webRequestor = webRequestor;
private readonly CanvasServiceUtils utils = utils;

View File

@@ -1,7 +1,7 @@
using LocalModels;
using Management.Services;
public class FileStorageManager : IFileStorageManager
public class FileStorageManager
{
private readonly MyLogger<FileStorageManager> logger;
private readonly CourseMarkdownLoader _courseMarkdownLoader;
@@ -39,7 +39,7 @@ public class FileStorageManager : IFileStorageManager
return await _courseMarkdownLoader.LoadSavedCourses();
}
public IEnumerable<string> GetEmptyDirectories()
public async Task<IEnumerable<string>> GetEmptyDirectories()
{
if (!Directory.Exists(_basePath))
throw new DirectoryNotFoundException($"Cannot get empty directories, {_basePath} does not exist");

View File

@@ -1,50 +1,50 @@
using System.Diagnostics.CodeAnalysis;
using LocalModels;
// using System.Diagnostics.CodeAnalysis;
// using LocalModels;
public class FileStorageManagerCached : IFileStorageManager
{
private readonly FileStorageManager manager;
// public class FileStorageManagerCached : IFileStorageManager
// {
// private readonly FileStorageManager manager;
private readonly object cacheLock = new object(); // Lock object for synchronization
// private readonly object cacheLock = new object(); // Lock object for synchronization
private DateTime? cacheTime { get; set; } = null;
private IEnumerable<LocalCourse>? cachedCourses { get; set; } = null;
private ILogger<FileStorageManagerCached> logger { get; }
// private DateTime? cacheTime { get; set; } = null;
// private IEnumerable<LocalCourse>? cachedCourses { get; set; } = null;
// private ILogger<FileStorageManagerCached> logger { get; }
private readonly int cacheSeconds = 2;
public FileStorageManagerCached(FileStorageManager manager, ILogger<FileStorageManagerCached> logger)
{
this.manager = manager;
this.logger = logger;
}
public IEnumerable<string> GetEmptyDirectories()
{
return manager.GetEmptyDirectories();
}
// private readonly int cacheSeconds = 2;
// public FileStorageManagerCached(FileStorageManager manager, ILogger<FileStorageManagerCached> logger)
// {
// this.manager = manager;
// this.logger = logger;
// }
// public Task<IEnumerable<string>> GetEmptyDirectories()
// {
// return manager.GetEmptyDirectories();
// }
public async Task<IEnumerable<LocalCourse>> LoadSavedCourses()
{
// public async Task<IEnumerable<LocalCourse>> LoadSavedCourses()
// {
var secondsFromLastLoad = (DateTime.Now - cacheTime)?.Seconds;
// var secondsFromLastLoad = (DateTime.Now - cacheTime)?.Seconds;
if (cachedCourses != null && secondsFromLastLoad < cacheSeconds)
{
logger.LogInformation("returning cached courses from file");
return cachedCourses;
}
// if (cachedCourses != null && secondsFromLastLoad < cacheSeconds)
// {
// logger.LogInformation("returning cached courses from file");
// return cachedCourses;
// }
cachedCourses = await manager.LoadSavedCourses();
cacheTime = DateTime.Now;
return cachedCourses;
}
// cachedCourses = await manager.LoadSavedCourses();
// cacheTime = DateTime.Now;
// return cachedCourses;
// }
public async Task SaveCourseAsync(LocalCourse course, LocalCourse? previouslyStoredCourse)
{
// race condition...
cacheTime = null;
cachedCourses = null;
await manager.SaveCourseAsync(course, previouslyStoredCourse);
}
}
// public async Task SaveCourseAsync(LocalCourse course, LocalCourse? previouslyStoredCourse)
// {
// // race condition...
// cacheTime = null;
// cachedCourses = null;
// await manager.SaveCourseAsync(course, previouslyStoredCourse);
// }
// }

View File

@@ -4,5 +4,5 @@ public interface IFileStorageManager
{
Task SaveCourseAsync(LocalCourse course, LocalCourse? previouslyStoredCourse);
Task<IEnumerable<LocalCourse>> LoadSavedCourses();
IEnumerable<string> GetEmptyDirectories();
Task<IEnumerable<string>> GetEmptyDirectories();
}