1
0
mirror of https://github.com/chylex/Minecraft-Phantom-Panel.git synced 2026-01-21 08:50:28 +01:00

3 Commits

Author SHA1 Message Date
65699a0331 WIP 2026-01-01 17:23:41 +01:00
ccf3ecb180 WIP 2025-12-31 17:29:36 +01:00
366735d351 Refactor Optional 2025-12-31 17:29:12 +01:00
10 changed files with 153 additions and 12 deletions

View File

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

View File

@@ -0,0 +1,5 @@
namespace Phantom.Common.Data.Web;
public class RemoteDictionary<K, V> {
}

View File

@@ -3,8 +3,47 @@
namespace Phantom.Common.Data;
[MemoryPackable]
public readonly partial record struct Optional<T>(T? Value) {
public static implicit operator Optional<T>(T? value) {
public readonly partial struct Optional<T> {
[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);
}
}

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

@@ -0,0 +1,25 @@
using System.Collections.Immutable;
using MemoryPack;
using Phantom.Common.Data.Web.Agent;
namespace Phantom.Common.Messages.Web.ToWeb;
[MemoryPackable(GenerateType.VersionTolerant)]
public sealed partial record RefreshAgentsMessage2(
[property: MemoryPackOrder(0)] ImmutableArray<RefreshAgentsMessage2.IItemAction> Actions
) : IMessageToWeb {
[MemoryPackable]
[MemoryPackUnion(tag: 0, typeof(RemoveItem))]
[MemoryPackUnion(tag: 1, typeof(SetItem))]
[MemoryPackUnion(tag: 2, typeof(UpdateItem))]
public partial interface IItemAction;
[MemoryPackable]
public sealed partial record RemoveItem(Guid AgentGuid) : IItemAction;
[MemoryPackable]
public sealed partial record SetItem(Agent Agent) : IItemAction;
[MemoryPackable]
public sealed partial record UpdateItem(Guid AgentGuid, Agent.Update Update) : IItemAction;
}

View File

@@ -42,6 +42,7 @@ public static class WebMessageRegistries {
ToController.Add<GetEventLogMessage, Result<ImmutableArray<EventLogItem>, UserActionFailure>>();
ToWeb.Add<RefreshAgentsMessage>();
ToWeb.Add<RefreshAgentsMessage2>();
ToWeb.Add<RefreshInstancesMessage>();
ToWeb.Add<InstanceOutputMessage>();
ToWeb.Add<RefreshUserSessionMessage>();

View File

@@ -1,4 +1,5 @@
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using Phantom.Common.Data.Web.Users;
using Phantom.Controller.Database;
using Phantom.Controller.Database.Entities;
@@ -9,7 +10,7 @@ namespace Phantom.Controller.Services.Users.Sessions;
sealed class AuthenticatedUserCache {
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);
}

View File

@@ -77,8 +77,13 @@ sealed class UserLoginManager {
return userGuid != null && authenticatedUserCache.TryGet(userGuid.Value, out var userInfo) ? new LoggedInUser(userInfo) : default;
}
public AuthenticatedUserInfo? GetAuthenticatedUser(Guid userGuid, ImmutableArray<byte> authToken) {
return authenticatedUserCache.TryGet(userGuid, out var userInfo) && GetSessionBucket(authToken).Contains(userGuid, authToken) ? userInfo : null;
public Optional<AuthenticatedUserInfo> GetAuthenticatedUser(Guid userGuid, ImmutableArray<byte> authToken) {
if (authenticatedUserCache.TryGet(userGuid, out var userInfo) && GetSessionBucket(authToken).Contains(userGuid, authToken)) {
return userInfo;
}
else {
return default;
}
}
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);
if (session.Value is {} userInfo) {
return new AuthenticatedUser(userInfo, authToken);
}
else {
return null;
}
return session.HasValue ? new AuthenticatedUser(session.Value, authToken) : null;
}
private void SetLoadedSession(AuthenticatedUser authenticatedUser) {

View File

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