add some default types to de-duplicate similar implmentations

This commit is contained in:
Pilzinsel64
2025-03-28 10:40:48 +01:00
parent 9a652a343c
commit 8de643b7d1
17 changed files with 555 additions and 7 deletions

View File

@@ -1,5 +1,6 @@
using Castle.Core.Logging;
using Pilz.Extensions.Reflection;
using Pilz.Net.Data;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Reflection;
@@ -12,8 +13,12 @@ namespace Pilz.Net.Api;
public class ApiServer : IApiServer
{
protected record struct ThreadHolder(Thread? Thread);
public class MissingDataManagerException : Exception { }
protected readonly List<PrivateMessageHandler> handlers = [];
protected readonly Dictionary<Type, IApiMessageSerializer> serializers = [];
protected readonly Dictionary<ThreadHolder, IDataManager> managers = [];
protected HttpListener httpListener;
protected int restartAttempts = 0;
protected DateTime lastRestartAttempt;
@@ -24,6 +29,8 @@ public class ApiServer : IApiServer
public event OnCheckAuthenticationEventHandler? OnCheckAuthentication;
public event OnCheckContextEventHandler? OnCheckContext;
public event OnCheckContextEventHandler? OnCheckContextCompleted;
public event OnGetNewDataManagerEventHandler? OnGetNewDataManager;
public event DataManagerEventHandler? OnResetDataManager;
protected record PrivateParameterInfo(string Name, int Index);
@@ -51,6 +58,10 @@ public class ApiServer : IApiServer
public int MaxConcurentConnections { get; set; } = 5;
public IDataManager Manager => GetManager();
public bool ThreadedDataManager { get; set; }
public ApiServer(string apiUrl) : this(apiUrl, null)
{
}
@@ -61,16 +72,38 @@ public class ApiServer : IApiServer
this.httpListener = httpListener ?? CreateDefaultHttpListener();
}
private IDataManager GetManager()
{
var curThread = ThreadedDataManager ? Thread.CurrentThread : null;
var threadHolder = new ThreadHolder(curThread);
if (managers.TryGetValue(threadHolder, out var mgr))
return mgr;
if (OnGetNewDataManager?.Invoke(this, EventArgs.Empty) is not IDataManager manager)
throw new MissingDataManagerException();
managers.Add(threadHolder, manager);
return manager;
}
public virtual void ResetManager()
{
if (managers.Remove(new(ThreadedDataManager ? Thread.CurrentThread : null), out var manager))
OnResetDataManager?.Invoke(this, new(manager));
}
protected virtual HttpListener CreateDefaultHttpListener()
{
var httpListener = new HttpListener();
httpListener.TimeoutManager.IdleConnection = new TimeSpan(0, 10, 0);
httpListener.TimeoutManager.IdleConnection = new TimeSpan(0, 2, 0);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
httpListener.TimeoutManager.RequestQueue = new TimeSpan(0, 10, 0);
httpListener.TimeoutManager.HeaderWait = new TimeSpan(0, 10, 0);
httpListener.TimeoutManager.RequestQueue = new TimeSpan(0, 2, 0);
httpListener.TimeoutManager.HeaderWait = new TimeSpan(0, 2, 0);
}
return httpListener;
@@ -296,21 +329,27 @@ public class ApiServer : IApiServer
if (context is not null)
{
Log.Info("Request retrived for " + context.Request.RawUrl);
ResetManager();
OnCheckContext?.Invoke(this, new(context));
try
{
CheckContext(context);
success = true;
}
catch (MissingDataManagerException mdmex)
{
Log.Error("DataManager is not supported by this server instnace!", mdmex);
if (DebugMode) throw;
}
catch (Exception ex)
{
Log.Error("Error checking context", ex);
if (DebugMode)
throw;
if (DebugMode) throw;
}
finally
{
OnCheckContextCompleted?.Invoke(this, new(context));
ResetManager();
}
}

View File

@@ -0,0 +1,34 @@
using Pilz.Net.Api.Messages;
using Pilz.Net.Data;
namespace Pilz.Net.Api.Client;
public abstract class BaseChildItemClient<T>(IApiClient client) : BaseClient<T>(client), IBaseChildItemClient<T> where T : IDataObject
{
public virtual async Task<IEnumerable<T>> GetAll(int parentId)
{
return (await client.SendRequest<GeneralItemMessages<T>.Items>(ApiEndpoint, HttpMethod.Get, new ApiParameterCollection
{
["parent"] = parentId,
})).EnsureOk().Items;
}
public override async Task<T> Save(T item)
{
if (item.Id == 0)
throw new NullReferenceException("Item has no parent yet!");
return await base.Save(item);
}
public virtual async Task<T> Save(T item, int parentId)
{
return (await client.SendRequest<GeneralItemMessages<T>.Item>(
ApiEndpoint,
HttpMethod.Post,
GenerateUpdateMessage(item, true),
new ApiParameterCollection
{
["parent"] = parentId,
})).EnsureOk().Item;
}
}

View File

@@ -0,0 +1,30 @@
using Pilz.Net.Api.Messages;
using Pilz.Net.Data;
namespace Pilz.Net.Api.Client;
public abstract class BaseClient<T>(IApiClient client) : IBaseClient<T> where T : IDataObject
{
public abstract string ApiEndpoint { get; }
public virtual async Task<T> Get(int id)
{
return (await client.SendRequest<GeneralItemMessages<T>.Item>($"{ApiEndpoint}/{id}", HttpMethod.Get)).EnsureOk().Item;
}
public virtual async Task Delete(int id)
{
(await client.SendRequest($"{ApiEndpoint}/{id}", HttpMethod.Delete)).EnsureOk();
}
public virtual async Task<T> Save(T item)
{
var generateNew = item.Id == 0;
return (await client.SendRequest<GeneralItemMessages<T>.Item>(
generateNew ? ApiEndpoint : $"{ApiEndpoint}/{item.Id}",
generateNew ? HttpMethod.Post : HttpMethod.Put,
GenerateUpdateMessage(item, generateNew))).EnsureOk().Item;
}
protected abstract ApiMessage GenerateUpdateMessage(T item, bool generateNew);
}

View File

@@ -0,0 +1,12 @@
using Pilz.Net.Api.Messages;
using Pilz.Net.Data;
namespace Pilz.Net.Api.Client;
public abstract class BaseItemClient<T>(IApiClient client) : BaseClient<T>(client), IBaseItemClient<T> where T : IDataObject
{
public virtual async Task<IEnumerable<T>> GetAll()
{
return (await client.SendRequest<GeneralItemMessages<T>.Items>(ApiEndpoint, HttpMethod.Get)).EnsureOk().Items;
}
}

View File

@@ -0,0 +1,9 @@
using Pilz.Net.Data;
namespace Pilz.Net.Api.Client;
public interface IBaseChildItemClient<T> : IBaseClient<T> where T : IDataObject
{
Task<IEnumerable<T>> GetAll(int parentId);
Task<T> Save(T item, int parentId);
}

View File

@@ -0,0 +1,10 @@
using Pilz.Net.Data;
namespace Pilz.Net.Api.Client;
public interface IBaseClient<T> where T : IDataObject
{
Task Delete(int id);
Task<T> Get(int id);
Task<T> Save(T item);
}

View File

@@ -0,0 +1,8 @@
using Pilz.Net.Data;
namespace Pilz.Net.Api.Client;
public interface IBaseItemClient<T> : IBaseClient<T> where T : IDataObject
{
Task<IEnumerable<T>> GetAll();
}

View File

@@ -1,4 +1,5 @@
using Castle.Core.Logging;
using Pilz.Net.Data;
namespace Pilz.Net.Api;
@@ -6,10 +7,15 @@ public interface IApiServer
{
public delegate void OnCheckAuthenticationEventHandler(object sender, ApiAuthCheckEventArgs e);
public delegate void OnCheckContextEventHandler(object sender, ApiContextEventArgs e);
public delegate IDataManager? OnGetNewDataManagerEventHandler(object sender, EventArgs e);
event OnCheckAuthenticationEventHandler? OnCheckAuthentication;
event OnCheckContextEventHandler OnCheckContext;
event OnCheckContextEventHandler OnCheckContextCompleted;
event OnCheckContextEventHandler? OnCheckContext;
event OnCheckContextEventHandler? OnCheckContextCompleted;
event OnGetNewDataManagerEventHandler? OnGetNewDataManager;
event DataManagerEventHandler? OnResetDataManager;
IDataManager Manager { get; }
string ApiUrl { get; }

View File

@@ -0,0 +1,21 @@
using System.ComponentModel;
namespace Pilz.Net.Api.Messages;
public static class GeneralItemMessages<T>
{
public class Item(T item) : ObjectMessage(item);
public class Items(List<T> items) : ObjectsMessage(items);
[EditorBrowsable(EditorBrowsableState.Never)]
public class ObjectMessage(T item) : ApiMessage
{
public T Item { get; } = item;
}
[EditorBrowsable(EditorBrowsableState.Never)]
public class ObjectsMessage(List<T> items) : ApiMessage
{
public List<T> Items { get; } = items;
}
}

View File

@@ -0,0 +1,52 @@
using Pilz.Extensions.Reflection;
using Pilz.Net.Data;
using Pilz.Net.Extensions;
using System.Diagnostics;
namespace Pilz.Net.Api.Server;
public abstract class BaseChildItemHandler<TEntity, TParent, TUpdateMsg>(IApiServer server)
: BaseHandler<TEntity, TUpdateMsg>(server)
where TEntity : IDataObject
where TParent : IDataObject
where TUpdateMsg : ApiMessage
{
protected virtual bool RegisterGetAll => true;
protected virtual bool RegisterPost => true;
public override void Initialize(IApiServer server)
{
base.Initialize(server);
var t = GetType();
if (RegisterGetAll)
server.RegisterHandler(t.GetMethod(nameof(GetAll))!.CreateDelegate(this), new(Route, "GET"), Debugger.IsAttached);
if (RegisterPost)
server.RegisterHandler(t.GetMethod(nameof(Post))!.CreateDelegate(this), new(Route, "POST"), Debugger.IsAttached);
}
public virtual ApiResult GetAll(int parent)
{
IQueryable<TEntity> list;
if (parent != 0 && server.Manager.Find(parent, out TParent? parentEntity))
list = GetChilds(parentEntity).AsQueryable();
else
list = server.Manager.Get<TEntity>();
return list.ToList().Select(ToClient).ToItemsResult();
}
public virtual ApiResult Post(int parent, TUpdateMsg msg)
{
if (!server.Manager.Find(parent, out TParent? parentEntity))
return ApiResult.NotFound();
var entity = CreateNewEntity();
if (UpdateEntity(entity, msg) is ApiResult result)
return result;
GetChilds(parentEntity).Add(entity);
server.Manager.Save(parentEntity, true);
return ToClient(entity).ToItemResult();
}
public abstract IList<TEntity> GetChilds(TParent parent);
}

View File

@@ -0,0 +1,63 @@
using Pilz.Extensions.Reflection;
using Pilz.Net.Data;
using Pilz.Net.Extensions;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace Pilz.Net.Api.Server;
public abstract class BaseHandler<TEntity, TUpdateMsg>(IApiServer server)
: IApiHandlerInitializer
where TEntity : IDataObject
where TUpdateMsg : ApiMessage
{
public abstract string Route { get; }
protected virtual bool RegisterGet => true;
protected virtual bool RegisterPut => true;
protected virtual bool RegisterDelete => true;
public virtual void Initialize(IApiServer server)
{
var t = GetType();
if (RegisterGet)
server.RegisterHandler(t.GetMethod(nameof(Get))!.CreateDelegate(this), new(Route + "/{id}", "GET"), Debugger.IsAttached);
if (RegisterPut)
server.RegisterHandler(t.GetMethod(nameof(Put))!.CreateDelegate(this), new(Route + "/{id}", "PUT"), Debugger.IsAttached);
if (RegisterDelete)
server.RegisterHandler(t.GetMethod(nameof(Delete))!.CreateDelegate(this), new(Route + "/{id}", "DELETE"), Debugger.IsAttached);
}
public virtual ApiResult Get(int id)
{
if (!server.Manager.Find(id, out TEntity? entity))
return ApiResult.NotFound();
return ToClient(entity).ToItemResult();
}
public virtual ApiResult Put(int id, TUpdateMsg msg)
{
if (!server.Manager.Find(id, out TEntity? entity))
return ApiResult.NotFound();
if (UpdateEntity(entity, msg) is ApiResult result)
return result;
server.Manager.Save(entity, true);
return ToClient(entity).ToItemResult();
}
public virtual ApiResult Delete(int id)
{
server.Manager.Delete<TEntity>(id, true);
return ApiResult.Ok();
}
protected virtual TEntity CreateNewEntity()
{
return Activator.CreateInstance<TEntity>();
}
protected abstract ApiResult? UpdateEntity(TEntity entity, TUpdateMsg msg);
protected abstract IDataObject ToClient(TEntity entity);
}

View File

@@ -0,0 +1,39 @@
using Pilz.Extensions.Reflection;
using Pilz.Net.Data;
using Pilz.Net.Extensions;
using System.Diagnostics;
namespace Pilz.Net.Api.Server;
public abstract class BaseItemHandler<TEntity, TUpdateMsg>(IApiServer server)
: BaseHandler<TEntity, TUpdateMsg>(server)
where TEntity : IDataObject
where TUpdateMsg : ApiMessage
{
protected virtual bool RegisterGetAll => true;
protected virtual bool RegisterPost => true;
public override void Initialize(IApiServer server)
{
base.Initialize(server);
var t = GetType();
if (RegisterGetAll)
server.RegisterHandler(t.GetMethod(nameof(GetAll))!.CreateDelegate(this), new(Route, "GET"), Debugger.IsAttached);
if (RegisterPost)
server.RegisterHandler(t.GetMethod(nameof(Put))!.CreateDelegate(this), new(Route, "POST"), Debugger.IsAttached);
}
public virtual ApiResult GetAll()
{
return server.Manager.Get<TEntity>().ToList().Select(ToClient).ToItemsResult();
}
public virtual ApiResult Post(TUpdateMsg msg)
{
var entity = CreateNewEntity();
if (UpdateEntity(entity, msg) is ApiResult result)
return result;
server.Manager.Save(entity, true);
return entity.ToItemResult();
}
}