A src/Extensions/NosSmooth.Extensions.SharedBinding/EventArgs/ConflictEventArgs.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/EventArgs/ConflictEventArgs.cs +60 -0
@@ 0,0 1,60 @@
+//
+// ConflictEventArgs.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Extensions.SharedBinding.Lifetime;
+
+namespace NosSmooth.Extensions.SharedBinding.EventArgs;
+
+/// <summary>
+/// Arguments containing information about a shared instance.
+/// The conflict may be resolved by setting the correct property.
+/// </summary>
+public class ConflictEventArgs : System.EventArgs
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ConflictEventArgs"/> class.
+ /// </summary>
+ /// <param name="conflictingInstances">All of the conflicting instances.</param>
+ /// <param name="defaultResolution">The default method to resolve the conflict.</param>
+ public ConflictEventArgs(IReadOnlyList<SharedInstanceInfo> conflictingInstances, ConflictResolution defaultResolution)
+ {
+ ConflictingInstances = conflictingInstances;
+ Resolve = defaultResolution;
+ }
+
+ /// <summary>
+ /// Gets the instances that are in conflict.
+ /// </summary>
+ public IReadOnlyList<SharedInstanceInfo> ConflictingInstances { get; }
+
+ /// <summary>
+ /// Gets or sets the method of resolution.
+ /// </summary>
+ public ConflictResolution Resolve { get; set; }
+
+ /// <summary>
+ /// Possible methods of resolution.
+ /// </summary>
+ public enum ConflictResolution
+ {
+ /// <summary>
+ /// Allow the new instance, keep the old ones.
+ /// </summary>
+ Allow,
+
+ /// <summary>
+ /// Do not allow the new instance, keep the old ones.
+ /// </summary>
+ Restrict,
+
+ /// <summary>
+ /// Allow the instance, try to detach the old instance.
+ /// In case the old instance cannot be detached, fall back
+ /// to <see cref="Restrict"/>.
+ /// </summary>
+ DetachOriginal
+ }
+}<
\ No newline at end of file
A src/Extensions/NosSmooth.Extensions.SharedBinding/EventArgs/InstanceEventArgs.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/EventArgs/InstanceEventArgs.cs +29 -0
@@ 0,0 1,29 @@
+//
+// InstanceEventArgs.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Extensions.SharedBinding.Lifetime;
+
+namespace NosSmooth.Extensions.SharedBinding.EventArgs;
+
+/// <summary>
+/// Arguments containing information about a shared instance.
+/// </summary>
+public class InstanceEventArgs : System.EventArgs
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="InstanceEventArgs"/> class.
+ /// </summary>
+ /// <param name="instanceInfo">The new instance.</param>
+ public InstanceEventArgs(SharedInstanceInfo instanceInfo)
+ {
+ InstanceInfo = instanceInfo;
+ }
+
+ /// <summary>
+ /// Gets the information about the new instance.
+ /// </summary>
+ public SharedInstanceInfo InstanceInfo { get; }
+}<
\ No newline at end of file
M src/Extensions/NosSmooth.Extensions.SharedBinding/Extensions/ServiceCollectionExtensions.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Extensions/ServiceCollectionExtensions.cs +4 -0
@@ 5,10 5,12 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Diagnostics;
+using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using NosSmooth.Data.NOSFiles;
using NosSmooth.Extensions.SharedBinding.Hooks;
+using NosSmooth.Extensions.SharedBinding.Lifetime;
using NosSmooth.LocalBinding;
using NosSmooth.LocalBinding.Extensions;
using NosSmooth.LocalBinding.Hooks;
@@ 94,7 96,9 @@ public static class ServiceCollectionExtensions
/// <returns>The same collection.</returns>
public static IServiceCollection ShareNosSmooth(this IServiceCollection serviceCollection)
{
+ var assembly = Assembly.GetCallingAssembly();
return serviceCollection
+ .AddSingleton(_ => new SharedInstanceInfo(assembly.GetName().FullName, assembly.GetName().Version?.ToString(), assembly))
.AddSingleton<SharedManager>(p => SharedManager.Instance)
.ShareHooks()
.TryShare<NosBrowserManager>()
A src/Extensions/NosSmooth.Extensions.SharedBinding/Lifetime/DefaultInstanceLifetime.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Lifetime/DefaultInstanceLifetime.cs +57 -0
@@ 0,0 1,57 @@
+//
+// DefaultInstanceLifetime.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Remora.Results;
+
+namespace NosSmooth.Extensions.SharedBinding.Lifetime;
+
+public class DefaultInstanceLifetime : IInstanceLifetime
+{
+ private readonly CancellationTokenSource _stoppingSource;
+ private bool _stoppingHooked;
+
+ public DefaultInstanceLifetime()
+ {
+ _stoppingSource = new CancellationTokenSource();
+ }
+
+ /// <inheritdoc/>
+ public SharedInstanceInfo Info { get; }
+
+ /// <inheritdoc />
+ public CancellationToken InstanceStopping
+ {
+ get
+ {
+ _stoppingHooked = true;
+ return _stoppingSource.Token;
+ }
+ }
+
+ /// <inheritdoc />
+ public Result RequestStop()
+ {
+ if (!_stoppingHooked)
+ { // There is no way anything was hooked to InstanceStopping, thus
+ // stop won't work for sure.
+ return new CannotRequestStop()
+ }
+
+ try
+ {
+ _stoppingSource.Cancel();
+ return Result.FromSuccess();
+ }
+ catch (TaskCanceledException)
+ {
+ return Result.FromSuccess();
+ }
+ catch (Exception e)
+ {
+ return e;
+ }
+ }
+}<
\ No newline at end of file
A src/Extensions/NosSmooth.Extensions.SharedBinding/Lifetime/IInstanceLifetime.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Lifetime/IInstanceLifetime.cs +20 -0
@@ 0,0 1,20 @@
+//
+// IInstanceLifetime.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Remora.Results;
+
+namespace NosSmooth.Extensions.SharedBinding.Lifetime;
+
+public interface IInstanceLifetime
+{
+ public SharedInstanceInfo Info { get; }
+
+ public CancellationToken InstanceStopping { get; }
+
+ public CancellationToken InstanceStopped { get; }
+
+ public Task<Result> RequestStop();
+}<
\ No newline at end of file
A src/Extensions/NosSmooth.Extensions.SharedBinding/Lifetime/SharedInstanceInfo.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Lifetime/SharedInstanceInfo.cs +11 -0
@@ 0,0 1,11 @@
+//
+// SharedInstanceInfo.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Reflection;
+
+namespace NosSmooth.Extensions.SharedBinding.Lifetime;
+
+public record SharedInstanceInfo(string Name, string? Version, Assembly Assembly);
A src/Extensions/NosSmooth.Extensions.SharedBinding/Lifetime/SharedLifetime.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Lifetime/SharedLifetime.cs +53 -0
@@ 0,0 1,53 @@
+//
+// SharedLifetime.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using NosSmooth.Extensions.SharedBinding.EventArgs;
+using Remora.Results;
+
+namespace NosSmooth.Extensions.SharedBinding.Lifetime;
+
+/// <summary>
+/// Events for shared lifetime.
+/// </summary>
+public class SharedLifetime
+{
+ /// <summary>
+ /// A new instance has been attached.
+ /// </summary>
+ public event EventHandler<InstanceEventArgs>? InstanceAttached;
+
+ /// <summary>
+ /// A new instance has been attached and a conflict was detected,
+ /// the same instance type is already attached.
+ /// </summary>
+ public event EventHandler<ConflictEventArgs>? ConflictDetected;
+
+ /// <summary>
+ /// Initialize a new shared instance.
+ /// </summary>
+ /// <param name="instanceInfo">The new instance.</param>
+ /// <returns>A result, if errorful, the new instance should not start.</returns>
+ public Result<CancellationTokenSource> Initialize(SharedInstanceInfo instanceInfo)
+ {
+ return Result<CancellationTokenSource>.FromSuccess(new CancellationTokenSource());
+ }
+
+ /// <summary>
+ /// Initialize a new shared instance without its cooperation.
+ /// In case the instance is not allowed, an exception will be thrown.
+ /// </summary>
+ /// <param name="instanceInfo">The shared instance.</param>
+ /// <exception cref="Exception">Throws an exception in case the instance cannot be attached.</exception>
+ public void ForceInitialize(SharedInstanceInfo instanceInfo)
+ {
+ var result = Initialize(instanceInfo);
+
+ if (!result.IsSuccess)
+ {
+ throw new Exception($"Initialization not allowed! {result.Error.Message}");
+ }
+ }
+}<
\ No newline at end of file
M src/Extensions/NosSmooth.Extensions.SharedBinding/SharedManager.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/SharedManager.cs +8 -0
@@ 7,6 7,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using NosSmooth.Data.NOSFiles;
+using NosSmooth.Extensions.SharedBinding.Lifetime;
using NosSmooth.LocalBinding;
using NosSmooth.PacketSerializer.Packets;
@@ 41,9 42,15 @@ public class SharedManager
private SharedManager()
{
+ Lifetime = new SharedLifetime();
}
/// <summary>
+ /// Gets the shared lifetime.
+ /// </summary>
+ public SharedLifetime Lifetime { get; }
+
+ /// <summary>
/// Get shared equivalent of the given type.
/// </summary>
/// <param name="services">The service provider.</param>
@@ 53,6 60,7 @@ public class SharedManager
public T GetShared<T>(IServiceProvider services)
where T : class
{
+ Lifetime.ForceInitialize(services.GetRequiredService<SharedInstanceInfo>());
if (!_sharedData.ContainsKey(typeof(T)))
{
_sharedData[typeof(T)] = CreateShared<T>(services);