diff --git a/Hyperbar.Windows.MediaController/MediaControllerFactory.cs b/Hyperbar.Windows.MediaController/MediaControllerFactory.cs index 4c78801..78b6aa2 100644 --- a/Hyperbar.Windows.MediaController/MediaControllerFactory.cs +++ b/Hyperbar.Windows.MediaController/MediaControllerFactory.cs @@ -2,8 +2,7 @@ namespace Hyperbar.Windows.MediaController; -public class MediaControllerFactory(IMediator mediator, - IServiceScopeFactory serviceScopeFactory) : +public class MediaControllerFactory(IServiceScopeFactory serviceScopeFactory) : IFactory { public MediaController? Create(GlobalSystemMediaTransportControlsSession value) @@ -11,22 +10,6 @@ public class MediaControllerFactory(IMediator mediator, if (serviceScopeFactory.Create(value) is MediaController mediaController) { return mediaController; - - //if (serviceScope.ServiceProvider.GetService() is IServiceFactory serviceFactory) - //{ - // if (serviceFactory.Create(value) is MediaController mediaController) - // { - // //if (serviceScope.ServiceProvider.GetService>() - // // is IFactory factory) - // //{ - // // if (factory.Create() is MediaControllerViewModel mediaControllerViewModel) - // // { - // // _ = await mediator.PublishAsync(new Created(mediaControllerViewModel)); - // // } - // //} - - // } - //} } return default; diff --git a/Hyperbar.Windows.MediaController/MediaControllerManager.cs b/Hyperbar.Windows.MediaController/MediaControllerManager.cs index 5b91658..70571b1 100644 --- a/Hyperbar.Windows.MediaController/MediaControllerManager.cs +++ b/Hyperbar.Windows.MediaController/MediaControllerManager.cs @@ -6,7 +6,7 @@ public class MediaControllerManager(IMediator mediator, IFactory factory) : IInitializer { - private readonly ConcurrentDictionary cachedSessions = []; + private readonly List> cachedSessions = []; public async Task InitializeAsync() { @@ -28,7 +28,7 @@ public class MediaControllerManager(IMediator mediator, if (factory.Create(session) is MediaController mediaController) { await mediator.PublishAsync(new Created(mediaController)); - cachedSessions.TryAdd(session, mediaController); + cachedSessions.Add(new KeyValuePair(session, mediaController)); } } @@ -39,17 +39,20 @@ public class MediaControllerManager(IMediator mediator, sender.GetSessions(); foreach (KeyValuePair session in - cachedSessions) + cachedSessions.ToList()) { - if (!sessions.Contains(session.Key)) + if (!sessions.Any(x => x.SourceAppUserModelId == session.Key.SourceAppUserModelId)) { - cachedSessions.TryRemove(session); + cachedSessions.Remove(session); } } foreach (GlobalSystemMediaTransportControlsSession session in sessions) { - await InitializeSessionAsync(session); + if (!cachedSessions.Any(x => x.Key.SourceAppUserModelId == session.SourceAppUserModelId)) + { + await InitializeSessionAsync(session); + } } } } diff --git a/Hyperbar.Windows.MediaController/MediaControllerWidgetProvider.cs b/Hyperbar.Windows.MediaController/MediaControllerWidgetProvider.cs index a2c1838..0634958 100644 --- a/Hyperbar.Windows.MediaController/MediaControllerWidgetProvider.cs +++ b/Hyperbar.Windows.MediaController/MediaControllerWidgetProvider.cs @@ -13,10 +13,11 @@ public class MediaControllerWidgetProvider : .AddSingleton() .AddTransient, ServiceScopeFactory>() .AddTransient, ServiceScopeProvider>() - .AddSingleton>() + .AddCache() .AddTransient, MediaControllerFactory>() .AddHandler() .AddTransient, MediaControllerViewModelFactory>() + .AddCache() .AddContentTemplate() .AddContentTemplate(); } \ No newline at end of file diff --git a/Hyperbar.Windows.Primary/PrimaryWidgetProvider.cs b/Hyperbar.Windows.Primary/PrimaryWidgetProvider.cs index 7ece5f8..ef7f0ce 100644 --- a/Hyperbar.Windows.Primary/PrimaryWidgetProvider.cs +++ b/Hyperbar.Windows.Primary/PrimaryWidgetProvider.cs @@ -8,7 +8,7 @@ public class PrimaryWidgetProvider : { public void Create(HostBuilderContext comtext, IServiceCollection services) => services.AddConfiguration() - .AddSingleton, Cache>() + .AddCache() .AddTransient, WidgetComponentViewModelFactory>() .AddTransient, WidgetComponentViewModelEnumerator>() .AddWidgetTemplate() diff --git a/Hyperbar.Windows/App.xaml.cs b/Hyperbar.Windows/App.xaml.cs index 2350969..1289177 100644 --- a/Hyperbar.Windows/App.xaml.cs +++ b/Hyperbar.Windows/App.xaml.cs @@ -38,6 +38,7 @@ public partial class App : services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddHostedService(); services.AddConfiguration(); diff --git a/Hyperbar.Windows/Lifecycles/Dispatcher.cs b/Hyperbar.Windows/Lifecycles/Dispatcher.cs new file mode 100644 index 0000000..e772e7d --- /dev/null +++ b/Hyperbar.Windows/Lifecycles/Dispatcher.cs @@ -0,0 +1,14 @@ +using CommunityToolkit.WinUI; +using Microsoft.UI.Dispatching; + +namespace Hyperbar.Windows; + +public class Dispatcher : + IDispatcher +{ + private DispatcherQueue dispatcherQueue; + + public Dispatcher() => dispatcherQueue = DispatcherQueue.GetForCurrentThread(); + + public async Task InvokeAsync(Action action) => await dispatcherQueue.EnqueueAsync(action.Invoke); +} diff --git a/Hyperbar.Windows/Lifecycles/IServiceCollectionExtensions.cs b/Hyperbar.Windows/Lifecycles/IServiceCollectionExtensions.cs index d80641b..c4323d9 100644 --- a/Hyperbar.Windows/Lifecycles/IServiceCollectionExtensions.cs +++ b/Hyperbar.Windows/Lifecycles/IServiceCollectionExtensions.cs @@ -34,8 +34,10 @@ namespace Hyperbar.Windows isolatedServices.AddHostedService(); isolatedServices.AddTransient(); + isolatedServices.AddScoped(); isolatedServices.AddScoped(); + isolatedServices.AddSingleton(); isolatedServices.AddScoped(); diff --git a/Hyperbar/Extensions/IServiceCollectionExtensions.cs b/Hyperbar/Extensions/IServiceCollectionExtensions.cs index b5fdc0a..e5f41f3 100644 --- a/Hyperbar/Extensions/IServiceCollectionExtensions.cs +++ b/Hyperbar/Extensions/IServiceCollectionExtensions.cs @@ -9,22 +9,31 @@ namespace Hyperbar; public static class IServiceCollectionExtensions { + public static IServiceCollection AddCache(this IServiceCollection services) + where TKey : + notnull + { + services.AddSingleton, Cache>(); + services.AddTransient(provider => provider.GetService>()!.Select(x => x.Value)); + + return services; + } + + public static IServiceCollection AddCache(this IServiceCollection services) + { + services.AddSingleton, Cache>(); + services.AddTransient(provider => provider.GetService>()!.Select(x => x)); + + return services; + } + public static IServiceCollection AddConfiguration(this IServiceCollection services) - where TConfiguration : + where TConfiguration : class, new() { return services.AddConfiguration(typeof(TConfiguration).Name, "Settings.json", null); } - public static IServiceCollection AddNotificationPipeline(this IServiceCollection services) - where TFromNotification : - INotification - where TToNotification : - INotification, new() - { - return services.AddHandler>(); - } - public static IServiceCollection AddConfiguration(this IServiceCollection services, TConfiguration? defaults = null) where TConfiguration : @@ -65,12 +74,12 @@ public static class IServiceCollectionExtensions serializerDelegate.Invoke(defaultSerializer); } - return new ConfigurationReader(provider.GetRequiredService>(), + return new ConfigurationReader(provider.GetRequiredService>(), defaultSerializer); }); services.AddSingleton>(provider => - { + { JsonSerializerOptions? defaultSerializer = null; if (serializerDelegate is not null) { @@ -78,7 +87,7 @@ public static class IServiceCollectionExtensions serializerDelegate.Invoke(defaultSerializer); } - return new ConfigurationWriter(provider.GetRequiredService>(), + return new ConfigurationWriter(provider.GetRequiredService>(), defaultSerializer); }); @@ -115,7 +124,7 @@ public static class IServiceCollectionExtensions } public static IServiceCollection AddHandler(this IServiceCollection services, - ServiceLifetime lifetime = ServiceLifetime.Transient) + ServiceLifetime lifetime = ServiceLifetime.Transient) where THandler : IHandler { @@ -134,31 +143,40 @@ public static class IServiceCollectionExtensions provider => provider.GetRequiredService(), lifetime)); } } - } - } + if (contract.Name == typeof(IHandler<,>).Name) + { + if (contract.GetGenericArguments() is { Length: 2 } arguments) + { + Type requestType = arguments[0]; + Type responseType = arguments[1]; - if (typeof(THandler).GetInterface(typeof(IHandler<,>).Name) is { } requestContract) - { - if (requestContract.GetGenericArguments() is { Length: 2 } arguments) - { - Type requestType = arguments[0]; - Type responseType = arguments[1]; + Type wrapperType = typeof(RequestClassHandlerWrapper<,>).MakeGenericType(requestType, responseType); - Type wrapperType = typeof(RequestClassHandlerWrapper<,>).MakeGenericType(requestType, responseType); - - services.TryAdd(new ServiceDescriptor(typeof(THandler), typeof(THandler), lifetime)); - services.Add(new ServiceDescriptor(wrapperType, - provider => provider.GetService()?.Create(wrapperType, - provider.GetRequiredService(), - provider.GetServices(typeof(IPipelineBehavior<,>).MakeGenericType(requestType, responseType)))!, - lifetime - )); + services.TryAdd(new ServiceDescriptor(typeof(THandler), typeof(THandler), lifetime)); + services.Add(new ServiceDescriptor(wrapperType, + provider => provider.GetService()?.Create(wrapperType, + provider.GetRequiredService(), + provider.GetServices(typeof(IPipelineBehavior<,>).MakeGenericType(requestType, responseType)))!, + lifetime + )); + } + } } + } return services; } + + public static IServiceCollection AddNotificationPipeline(this IServiceCollection services) + where TFromNotification : + INotification + where TToNotification : + INotification, new() + { + return services.AddHandler>(); + } public static IServiceCollection AddWidgetTemplate(this IServiceCollection services) where TWidgetContent : IWidgetViewModel diff --git a/Hyperbar/Lifecycles/Cache.cs b/Hyperbar/Lifecycles/Cache.cs index a2e8f75..c9015fb 100644 --- a/Hyperbar/Lifecycles/Cache.cs +++ b/Hyperbar/Lifecycles/Cache.cs @@ -1,83 +1,68 @@ using System.Collections; -using System.Diagnostics.CodeAnalysis; +using System.Collections.Concurrent; using System.Reactive.Disposables; namespace Hyperbar; -public class Cache(IDisposer disposer) : - ICache - where TKey : notnull +public class Cache(IDisposer disposer) : + ICache { - private readonly IDictionary cache = new Dictionary(); + private readonly List cache = []; - public TService this[TKey key] + public void Add(TValue value) { - get => cache[key]; - set => cache[key] = value; + disposer.Add(value!, Disposable.Create(() => + { + Remove(value); + })); + + cache.Add(value); } - public ICollection Keys => cache.Keys; + public void Clear() => cache.Clear(); - public ICollection Values => cache.Values; + public IEnumerator GetEnumerator() => cache.GetEnumerator(); - public int Count => cache.Count; + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public bool IsReadOnly => false; + public bool Remove(TValue value) => cache.Remove(value); +} - public void Add(TKey key, TService value) +public class Cache(IDisposer disposer) : + ICache + where TKey : notnull +{ + private readonly ConcurrentDictionary cache = new(); + + public void Add(TKey key, + TValue value) { disposer.Add(value!, Disposable.Create(() => { Remove(key); })); - cache.Add(key, value); - } - - public void Add(KeyValuePair item) - { - cache.Add(item); + cache.TryAdd(key, value); } public void Clear() => cache.Clear(); - public bool Contains(KeyValuePair item) - { - return cache.Contains(item); - } + public bool ContainsKey(TKey key) => cache.ContainsKey(key); - public bool ContainsKey(TKey key) - { - return cache.ContainsKey(key); - } + public IEnumerator> GetEnumerator() => cache.GetEnumerator(); - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - cache.CopyTo(array, arrayIndex); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public IEnumerator> GetEnumerator() - { - return cache.GetEnumerator(); - } + public bool Remove(TKey key) => cache.Remove(key, out _); - public bool Remove(TKey key) + public bool TryGetValue(TKey key, out TValue? value) { - return cache.Remove(key); - } + if (cache.TryGetValue(key, out value)) + { + return true; + } - public bool Remove(KeyValuePair item) - { - return cache.Remove(item); - } - - public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TService value) - { - return cache.TryGetValue(key, out value); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return cache.GetEnumerator(); + value = default; + return false; } } \ No newline at end of file diff --git a/Hyperbar/Lifecycles/ICache.cs b/Hyperbar/Lifecycles/ICache.cs index 7416019..d4dc3ce 100644 --- a/Hyperbar/Lifecycles/ICache.cs +++ b/Hyperbar/Lifecycles/ICache.cs @@ -1,8 +1,29 @@ -namespace Hyperbar; +using Microsoft.Extensions.DependencyInjection; -public interface ICache : - IDictionary +namespace Hyperbar; + +public interface ICache : + IEnumerable +{ + void Add(TValue value); + + void Clear(); + + bool Remove(TValue value); +} + +public interface ICache : + IEnumerable> where TKey : notnull { + void Add(TKey key, + TValue value); + void Clear(); + + bool ContainsKey(TKey key); + + bool Remove(TKey key); + + bool TryGetValue(TKey key, out TValue? value); } diff --git a/Hyperbar/Lifecycles/ServiceScopeFactory.cs b/Hyperbar/Lifecycles/ServiceScopeFactory.cs index 167fc9f..391f978 100644 --- a/Hyperbar/Lifecycles/ServiceScopeFactory.cs +++ b/Hyperbar/Lifecycles/ServiceScopeFactory.cs @@ -1,10 +1,9 @@ using Microsoft.Extensions.DependencyInjection; -using System.Collections.Concurrent; namespace Hyperbar; public class ServiceScopeFactory(IServiceScopeFactory serviceScopeFactory, - ConcurrentDictionary services) : + ICache cache) : IServiceScopeFactory where TService : notnull { @@ -16,7 +15,7 @@ public class ServiceScopeFactory(IServiceScopeFactory serviceScopeFact { if (serviceFactory.Create(parameters) is TService service) { - services.TryAdd(service, serviceScope); + cache.Add(service, serviceScope); return service; } } diff --git a/Hyperbar/Lifecycles/ServiceScopeProvider.cs b/Hyperbar/Lifecycles/ServiceScopeProvider.cs index 7f6f8b2..3fcaae7 100644 --- a/Hyperbar/Lifecycles/ServiceScopeProvider.cs +++ b/Hyperbar/Lifecycles/ServiceScopeProvider.cs @@ -1,16 +1,15 @@ using Microsoft.Extensions.DependencyInjection; -using System.Collections.Concurrent; namespace Hyperbar; -public class ServiceScopeProvider(ConcurrentDictionary services) : +public class ServiceScopeProvider(ICache cache) : IServiceScopeProvider where TService : notnull { public bool TryGet(TService service, out IServiceScope? serviceScope) { - if (services.TryGetValue(service, out IServiceScope? value)) + if (cache.TryGetValue(service, out IServiceScope? value)) { serviceScope = value; return true; diff --git a/Hyperbar/Mediators/Mediator.cs b/Hyperbar/Mediators/Mediator.cs index dbe0514..8beb121 100644 --- a/Hyperbar/Mediators/Mediator.cs +++ b/Hyperbar/Mediators/Mediator.cs @@ -1,16 +1,23 @@ using Microsoft.Extensions.DependencyInjection; using System.Collections.Concurrent; +using System.Reactive.Concurrency; namespace Hyperbar; -public class Mediator(IServiceProvider provider) : +public interface IDispatcher +{ + Task InvokeAsync(Action action); +} + +public class Mediator(IServiceProvider provider, + IDispatcher dispatcher) : IMediator { private readonly SynchronizationContext? context = SynchronizationContext.Current; private readonly ConcurrentDictionary> subjects = []; - public ValueTask PublishAsync(TNotification notification, + public async ValueTask PublishAsync(TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification @@ -31,10 +38,8 @@ public class Mediator(IServiceProvider provider) : foreach (INotificationHandler handler in handlers) { - context?.Post(async state => await handler.Handle(notification, cancellationToken), null); + await dispatcher.InvokeAsync(async () => await handler.Handle(notification, cancellationToken)); } - - return ValueTask.CompletedTask; } public ValueTask SendAsync(IRequest request, diff --git a/Hyperbar/Views/ObservableCollectionViewModel.cs b/Hyperbar/Views/ObservableCollectionViewModel.cs index 521631b..bee55df 100644 --- a/Hyperbar/Views/ObservableCollectionViewModel.cs +++ b/Hyperbar/Views/ObservableCollectionViewModel.cs @@ -283,6 +283,7 @@ public partial class ObservableCollectionViewModel : return true; } + void IList.Remove(object? value) { if (IsCompatibleObject(value))