1
0
mirror of https://github.com/chylex/Minecraft-Phantom-Panel.git synced 2026-01-01 05:55:20 +01:00

2 Commits

Author SHA1 Message Date
ccf3ecb180 WIP 2025-12-31 17:29:36 +01:00
366735d351 Refactor Optional 2025-12-31 17:29:12 +01:00
7 changed files with 124 additions and 12 deletions

View File

@@ -15,4 +15,32 @@ public sealed partial record Agent(
) { ) {
[MemoryPackIgnore] [MemoryPackIgnore]
public RamAllocationUnits? AvailableMemory => RuntimeInfo.MaxMemory - Stats?.RunningInstanceMemory; public RamAllocationUnits? AvailableMemory => RuntimeInfo.MaxMemory - Stats?.RunningInstanceMemory;
public Agent With(Update update) => new (
update.AgentGuid.Or(AgentGuid),
update.Configuration.Or(Configuration),
update.ConnectionKey.Or(ConnectionKey),
update.RuntimeInfo.Or(RuntimeInfo),
update.Stats.Or(Stats),
update.ConnectionStatus.Or(ConnectionStatus)
);
[MemoryPackable(GenerateType.VersionTolerant)]
public sealed partial record Update(
[property: MemoryPackOrder(0)] Optional<Guid> AgentGuid,
[property: MemoryPackOrder(1)] Optional<AgentConfiguration> Configuration,
[property: MemoryPackOrder(2)] Optional<ImmutableArray<byte>> ConnectionKey,
[property: MemoryPackOrder(3)] Optional<AgentRuntimeInfo> RuntimeInfo,
[property: MemoryPackOrder(4)] OptionalNullable<AgentStats> Stats,
[property: MemoryPackOrder(5)] Optional<IAgentConnectionStatus> ConnectionStatus
) {
public Update Merge(Update newer) => new (
newer.AgentGuid.Or(AgentGuid),
newer.Configuration.Or(Configuration),
newer.ConnectionKey.Or(ConnectionKey),
newer.RuntimeInfo.Or(RuntimeInfo),
newer.Stats.Or(Stats),
newer.ConnectionStatus.Or(ConnectionStatus)
);
}
} }

View File

@@ -3,8 +3,47 @@
namespace Phantom.Common.Data; namespace Phantom.Common.Data;
[MemoryPackable] [MemoryPackable]
public readonly partial record struct Optional<T>(T? Value) { public readonly partial struct Optional<T> {
public static implicit operator Optional<T>(T? value) { [MemoryPackOrder(0)]
public bool HasValue { get; }
[MemoryPackOrder(1)]
public T Value {
get {
if (HasValue) {
return field!;
}
else {
throw new InvalidOperationException();
}
}
}
[MemoryPackIgnore]
public T? ValueOrDefault => HasValue ? Value : default;
public Optional() : this(hasValue: false, value: default) {}
public Optional(T value) : this(hasValue: true, value) {}
[MemoryPackConstructor]
private Optional(bool hasValue, T? value) {
this.HasValue = hasValue;
this.Value = value;
}
public Optional<R> Map<R>(Func<T, R> func) {
return HasValue ? new Optional<R>(func(Value)) : new Optional<R>();
}
public T Or(T fallbackValue) {
return HasValue ? Value : fallbackValue;
}
public Optional<T> Or(Optional<T> fallbackOptional) {
return HasValue ? Value : fallbackOptional;
}
public static implicit operator Optional<T>(T value) {
return new Optional<T>(value); return new Optional<T>(value);
} }
} }

View File

@@ -0,0 +1,42 @@
using MemoryPack;
namespace Phantom.Common.Data;
[MemoryPackable]
public readonly partial struct OptionalNullable<T> {
[MemoryPackOrder(0)]
public bool HasValue { get; }
[MemoryPackOrder(1)]
public T? Value {
get {
if (HasValue) {
return field;
}
else {
throw new InvalidOperationException();
}
}
}
public OptionalNullable() : this(hasValue: false, value: default) {}
public OptionalNullable(T? value) : this(hasValue: true, value) {}
[MemoryPackConstructor]
private OptionalNullable(bool hasValue, T? value) {
this.HasValue = hasValue;
this.Value = value;
}
public T? Or(T? fallbackValue) {
return HasValue ? Value : fallbackValue;
}
public OptionalNullable<T> Or(OptionalNullable<T> fallbackOptional) {
return HasValue ? Value : fallbackOptional;
}
public static implicit operator OptionalNullable<T>(T? value) {
return new OptionalNullable<T>(value);
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using Phantom.Common.Data.Web.Users; using Phantom.Common.Data.Web.Users;
using Phantom.Controller.Database; using Phantom.Controller.Database;
using Phantom.Controller.Database.Entities; using Phantom.Controller.Database.Entities;
@@ -9,7 +10,7 @@ namespace Phantom.Controller.Services.Users.Sessions;
sealed class AuthenticatedUserCache { sealed class AuthenticatedUserCache {
private readonly ConcurrentDictionary<Guid, AuthenticatedUserInfo> authenticatedUsersByGuid = new (); private readonly ConcurrentDictionary<Guid, AuthenticatedUserInfo> authenticatedUsersByGuid = new ();
public bool TryGet(Guid userGuid, out AuthenticatedUserInfo? userInfo) { public bool TryGet(Guid userGuid, [NotNullWhen(true)] out AuthenticatedUserInfo? userInfo) {
return authenticatedUsersByGuid.TryGetValue(userGuid, out userInfo); return authenticatedUsersByGuid.TryGetValue(userGuid, out userInfo);
} }

View File

@@ -77,8 +77,13 @@ sealed class UserLoginManager {
return userGuid != null && authenticatedUserCache.TryGet(userGuid.Value, out var userInfo) ? new LoggedInUser(userInfo) : default; return userGuid != null && authenticatedUserCache.TryGet(userGuid.Value, out var userInfo) ? new LoggedInUser(userInfo) : default;
} }
public AuthenticatedUserInfo? GetAuthenticatedUser(Guid userGuid, ImmutableArray<byte> authToken) { public Optional<AuthenticatedUserInfo> GetAuthenticatedUser(Guid userGuid, ImmutableArray<byte> authToken) {
return authenticatedUserCache.TryGet(userGuid, out var userInfo) && GetSessionBucket(authToken).Contains(userGuid, authToken) ? userInfo : null; if (authenticatedUserCache.TryGet(userGuid, out var userInfo) && GetSessionBucket(authToken).Contains(userGuid, authToken)) {
return userInfo;
}
else {
return default;
}
} }
private sealed class UserSessionBucket { private sealed class UserSessionBucket {

View File

@@ -87,12 +87,7 @@ public sealed class CustomAuthenticationStateProvider : ServerAuthenticationStat
} }
var session = await controllerConnection.Send<GetAuthenticatedUser, Optional<AuthenticatedUserInfo>>(new GetAuthenticatedUser(userGuid, authToken), TimeSpan.FromSeconds(30), cancellationToken); var session = await controllerConnection.Send<GetAuthenticatedUser, Optional<AuthenticatedUserInfo>>(new GetAuthenticatedUser(userGuid, authToken), TimeSpan.FromSeconds(30), cancellationToken);
if (session.Value is {} userInfo) { return session.HasValue ? new AuthenticatedUser(session.Value, authToken) : null;
return new AuthenticatedUser(userInfo, authToken);
}
else {
return null;
}
} }
private void SetLoadedSession(AuthenticatedUser authenticatedUser) { private void SetLoadedSession(AuthenticatedUser authenticatedUser) {

View File

@@ -19,10 +19,12 @@ public sealed class UserLoginManager(Navigation navigation, UserSessionBrowserSt
return false; return false;
} }
if (result.Value is not var (userInfo, authToken)) { if (!result.HasValue) {
return false; return false;
} }
var (userInfo, authToken) = result.Value;
Logger.Information("Successfully logged in {Username}.", username); Logger.Information("Successfully logged in {Username}.", username);
authenticationStateProvider.SetUnloadedSession(); authenticationStateProvider.SetUnloadedSession();