Files
LightlessClient/LightlessSync/PlayerData/Pairs/PairManager.cs

578 lines
19 KiB
C#

using System.Diagnostics.CodeAnalysis;
using LightlessSync.API.Data;
using LightlessSync.API.Data.Enum;
using LightlessSync.API.Dto.Group;
using LightlessSync.API.Dto.User;
namespace LightlessSync.PlayerData.Pairs;
/// <summary>
/// in memory state for pairs, groups, and syncshells
/// </summary>
public sealed class PairManager
{
private readonly object _gate = new();
private readonly Dictionary<string, PairConnection> _pairs = new(StringComparer.Ordinal);
private readonly Dictionary<string, Syncshell> _groups = new(StringComparer.Ordinal);
public PairConnection? LastAddedUser { get; private set; }
public IReadOnlyDictionary<string, PairConnection> GetAllPairs()
{
lock (_gate)
{
return new Dictionary<string, PairConnection>(_pairs);
}
}
public IReadOnlyDictionary<string, Syncshell> GetAllGroups()
{
lock (_gate)
{
return new Dictionary<string, Syncshell>(_groups);
}
}
public PairConnection? GetLastAddedUser()
{
lock (_gate)
{
return LastAddedUser;
}
}
public void ClearLastAddedUser()
{
lock (_gate)
{
LastAddedUser = null;
}
}
public void ClearAll()
{
lock (_gate)
{
_pairs.Clear();
_groups.Clear();
LastAddedUser = null;
}
}
public PairOperationResult<PairConnection> GetPair(string userId)
{
lock (_gate)
{
if (_pairs.TryGetValue(userId, out var connection))
{
return PairOperationResult<PairConnection>.Ok(connection);
}
return PairOperationResult<PairConnection>.Fail($"Pair {userId} not found.");
}
}
public bool TryGetPair(string userId, [NotNullWhen(true)] out PairConnection? connection)
{
lock (_gate)
{
return _pairs.TryGetValue(userId, out connection);
}
}
public PairOperationResult<Syncshell> GetGroup(string groupId)
{
lock (_gate)
{
if (_groups.TryGetValue(groupId, out var shell))
{
return PairOperationResult<Syncshell>.Ok(shell);
}
return PairOperationResult<Syncshell>.Fail($"Group {groupId} not found.");
}
}
public IReadOnlyList<PairConnection> GetDirectPairs()
{
lock (_gate)
{
return _pairs.Values.Where(p => p.IsDirectlyPaired).ToList();
}
}
public IReadOnlyList<PairConnection> GetPairsByIdent(string ident)
{
lock (_gate)
{
return _pairs.Values
.Where(p => p.Ident is not null && string.Equals(p.Ident, ident, StringComparison.Ordinal))
.ToList();
}
}
public IReadOnlyList<Syncshell> GetOwnedOrModeratedShells(string currentUserUid)
{
lock (_gate)
{
return _groups.Values
.Where(s =>
string.Equals(s.GroupFullInfo.Owner.UID, currentUserUid, StringComparison.OrdinalIgnoreCase)
|| s.GroupFullInfo.GroupUserInfo.HasFlag(GroupPairUserInfo.IsModerator))
.ToList();
}
}
public PairOperationResult<UserPermissions> GetPairCombinedPermissions(string userId)
{
lock (_gate)
{
if (!_pairs.TryGetValue(userId, out var connection))
{
return PairOperationResult<UserPermissions>.Fail($"Pair {userId} not found.");
}
var combined = connection.SelfToOtherPermissions | connection.OtherToSelfPermissions;
return PairOperationResult<UserPermissions>.Ok(combined);
}
}
public PairOperationResult<PairRegistration> MarkOnline(OnlineUserIdentDto dto)
{
lock (_gate)
{
if (!_pairs.TryGetValue(dto.User.UID, out var connection))
{
connection = GetOrCreatePair(dto.User);
}
connection.SetOnline(dto.Ident);
return PairOperationResult<PairRegistration>.Ok(new PairRegistration(new PairUniqueIdentifier(dto.User.UID), dto.Ident));
}
}
public PairOperationResult<PairRegistration> MarkOffline(UserData user)
{
lock (_gate)
{
if (!_pairs.TryGetValue(user.UID, out var connection))
{
return PairOperationResult<PairRegistration>.Fail($"Pair {user.UID} not found.");
}
connection.SetOffline();
return PairOperationResult<PairRegistration>.Ok(new PairRegistration(new PairUniqueIdentifier(user.UID), connection.Ident));
}
}
public PairOperationResult<PairRegistration> AddOrUpdateIndividual(UserPairDto dto, bool markAsLastAddedUser = true)
{
lock (_gate)
{
var connection = GetOrCreatePair(dto.User, out var created);
connection.UpdatePermissions(dto.OwnPermissions, dto.OtherPermissions);
connection.UpdateStatus(dto.IndividualPairStatus == IndividualPairStatus.None ? null : dto.IndividualPairStatus);
if (connection.Ident is null)
{
return PairOperationResult<PairRegistration>.Ok(new PairRegistration(new PairUniqueIdentifier(dto.User.UID), null));
}
if (created && markAsLastAddedUser)
{
LastAddedUser = connection;
}
return PairOperationResult<PairRegistration>.Ok(new PairRegistration(new PairUniqueIdentifier(dto.User.UID), connection.Ident));
}
}
public PairOperationResult<PairRegistration> AddOrUpdateIndividual(UserFullPairDto dto)
{
lock (_gate)
{
var connection = GetOrCreatePair(dto.User, out _);
connection.UpdatePermissions(dto.OwnPermissions, dto.OtherPermissions);
connection.UpdateStatus(dto.IndividualPairStatus == IndividualPairStatus.None ? null : dto.IndividualPairStatus);
var removedGroups = connection.Groups.Keys.Where(k => !dto.Groups.Contains(k, StringComparer.Ordinal)).ToList();
foreach (var groupId in removedGroups)
{
connection.RemoveGroupRelationship(groupId);
if (_groups.TryGetValue(groupId, out var shell))
{
shell.Users.Remove(dto.User.UID);
}
}
foreach (var groupId in dto.Groups)
{
connection.EnsureGroupRelationship(groupId, null);
if (_groups.TryGetValue(groupId, out var shell))
{
shell.Users[dto.User.UID] = connection;
}
}
return PairOperationResult<PairRegistration>.Ok(new PairRegistration(new PairUniqueIdentifier(dto.User.UID), connection.Ident));
}
}
public PairOperationResult<PairRegistration?> RemoveIndividual(UserDto dto)
{
lock (_gate)
{
if (!_pairs.TryGetValue(dto.User.UID, out var connection))
{
return PairOperationResult<PairRegistration?>.Fail($"Pair {dto.User.UID} not found.");
}
connection.UpdateStatus(null);
var registration = TryRemovePairIfNoConnection(connection);
return PairOperationResult<PairRegistration?>.Ok(registration);
}
}
public PairOperationResult<PairRegistration> SetPairOtherToSelfPermissions(UserPermissionsDto dto)
{
lock (_gate)
{
if (!_pairs.TryGetValue(dto.User.UID, out var connection))
{
return PairOperationResult<PairRegistration>.Fail($"Pair {dto.User.UID} not found.");
}
connection.UpdatePermissions(connection.SelfToOtherPermissions, dto.Permissions);
return PairOperationResult<PairRegistration>.Ok(new PairRegistration(new PairUniqueIdentifier(dto.User.UID), connection.Ident));
}
}
public PairOperationResult<PairRegistration> SetPairSelfToOtherPermissions(UserPermissionsDto dto)
{
lock (_gate)
{
if (!_pairs.TryGetValue(dto.User.UID, out var connection))
{
return PairOperationResult<PairRegistration>.Fail($"Pair {dto.User.UID} not found.");
}
connection.UpdatePermissions(dto.Permissions, connection.OtherToSelfPermissions);
return PairOperationResult<PairRegistration>.Ok(new PairRegistration(new PairUniqueIdentifier(dto.User.UID), connection.Ident));
}
}
public PairOperationResult SetIndividualStatus(UserIndividualPairStatusDto dto)
{
lock (_gate)
{
if (!_pairs.TryGetValue(dto.User.UID, out var connection))
{
return PairOperationResult.Fail($"Pair {dto.User.UID} not found.");
}
connection.UpdateStatus(dto.IndividualPairStatus == IndividualPairStatus.None ? null : dto.IndividualPairStatus);
_ = TryRemovePairIfNoConnection(connection);
return PairOperationResult.Ok();
}
}
public PairOperationResult AddOrUpdateGroupPair(GroupPairFullInfoDto dto)
{
lock (_gate)
{
var shell = GetOrCreateShell(dto.Group);
var connection = GetOrCreatePair(dto.User);
var groupInfo = shell.GroupFullInfo.GroupPairUserInfos.GetValueOrDefault(dto.User.UID, GroupPairUserInfo.None);
connection.EnsureGroupRelationship(dto.Group.GID, groupInfo == GroupPairUserInfo.None ? null : groupInfo);
connection.UpdatePermissions(dto.SelfToOtherPermissions, dto.OtherToSelfPermissions);
shell.Users[dto.User.UID] = connection;
return PairOperationResult.Ok();
}
}
public PairOperationResult<PairRegistration?> RemoveGroupPair(GroupPairDto dto)
{
lock (_gate)
{
if (_groups.TryGetValue(dto.GID, out var shell))
{
shell.Users.Remove(dto.User.UID);
}
PairRegistration? registration = null;
if (_pairs.TryGetValue(dto.User.UID, out var connection))
{
connection.RemoveGroupRelationship(dto.GID);
registration = TryRemovePairIfNoConnection(connection);
}
return PairOperationResult<PairRegistration?>.Ok(registration);
}
}
public PairOperationResult<IReadOnlyList<PairRegistration>> RemoveGroup(string groupId)
{
lock (_gate)
{
if (!_groups.Remove(groupId, out var shell))
{
return PairOperationResult<IReadOnlyList<PairRegistration>>.Fail($"Group {groupId} not found.");
}
var removed = new List<PairRegistration>();
foreach (var connection in shell.Users.Values.ToList())
{
connection.RemoveGroupRelationship(groupId);
var registration = TryRemovePairIfNoConnection(connection);
if (registration is not null)
{
removed.Add(registration);
}
}
return PairOperationResult<IReadOnlyList<PairRegistration>>.Ok(removed);
}
}
public PairOperationResult AddGroup(GroupFullInfoDto dto)
{
lock (_gate)
{
if (!_groups.TryGetValue(dto.Group.GID, out var shell))
{
shell = new Syncshell(dto);
_groups[dto.Group.GID] = shell;
}
else
{
shell.Update(dto);
shell.Users.Clear();
}
foreach (var (userId, info) in dto.GroupPairUserInfos)
{
if (_pairs.TryGetValue(userId, out var connection))
{
connection.EnsureGroupRelationship(dto.Group.GID, info == GroupPairUserInfo.None ? null : info);
shell.Users[userId] = connection;
}
}
return PairOperationResult.Ok();
}
}
public PairOperationResult UpdateGroupInfo(GroupInfoDto dto)
{
lock (_gate)
{
if (!_groups.TryGetValue(dto.Group.GID, out var shell))
{
return PairOperationResult.Fail($"Group {dto.Group.GID} not found.");
}
var updated = new GroupFullInfoDto(
dto.Group,
dto.Owner,
dto.GroupPermissions,
shell.GroupFullInfo.GroupUserPermissions,
shell.GroupFullInfo.GroupUserInfo,
new Dictionary<string, GroupPairUserInfo>(shell.GroupFullInfo.GroupPairUserInfos, StringComparer.Ordinal),
0);
shell.Update(updated);
return PairOperationResult.Ok();
}
}
public PairOperationResult UpdateGroupPairPermissions(GroupPairUserPermissionDto dto)
{
lock (_gate)
{
if (!_groups.TryGetValue(dto.Group.GID, out var shell))
{
return PairOperationResult.Fail($"Group {dto.Group.GID} not found.");
}
var updated = shell.GroupFullInfo with { GroupUserPermissions = dto.GroupPairPermissions };
shell.Update(updated);
return PairOperationResult.Ok();
}
}
public PairOperationResult UpdateGroupPermissions(GroupPermissionDto dto)
{
lock (_gate)
{
if (!_groups.TryGetValue(dto.Group.GID, out var shell))
{
return PairOperationResult.Fail($"Group {dto.Group.GID} not found.");
}
var updated = shell.GroupFullInfo with { GroupPermissions = dto.Permissions };
shell.Update(updated);
return PairOperationResult.Ok();
}
}
public PairOperationResult UpdateGroupPairStatus(GroupPairUserInfoDto dto)
{
lock (_gate)
{
if (_pairs.TryGetValue(dto.UID, out var connection))
{
connection.EnsureGroupRelationship(dto.GID, dto.GroupUserInfo == GroupPairUserInfo.None ? null : dto.GroupUserInfo);
}
if (_groups.TryGetValue(dto.GID, out var shell))
{
var infos = new Dictionary<string, GroupPairUserInfo>(shell.GroupFullInfo.GroupPairUserInfos, StringComparer.Ordinal)
{
[dto.UID] = dto.GroupUserInfo
};
var updated = shell.GroupFullInfo with { GroupPairUserInfos = infos };
shell.Update(updated);
}
return PairOperationResult.Ok();
}
}
public PairOperationResult UpdateGroupStatus(GroupPairUserInfoDto dto)
{
lock (_gate)
{
if (!_groups.TryGetValue(dto.GID, out var shell))
{
return PairOperationResult.Fail($"Group {dto.GID} not found.");
}
var updated = shell.GroupFullInfo with { GroupUserInfo = dto.GroupUserInfo };
shell.Update(updated);
return PairOperationResult.Ok();
}
}
public PairOperationResult UpdateOtherPermissions(UserPermissionsDto dto)
{
lock (_gate)
{
if (!_pairs.TryGetValue(dto.User.UID, out var connection))
{
return PairOperationResult.Fail($"Pair {dto.User.UID} not found.");
}
connection.UpdatePermissions(connection.SelfToOtherPermissions, dto.Permissions);
return PairOperationResult.Ok();
}
}
public PairOperationResult UpdateSelfPermissions(UserPermissionsDto dto)
{
lock (_gate)
{
if (!_pairs.TryGetValue(dto.User.UID, out var connection))
{
return PairOperationResult.Fail($"Pair {dto.User.UID} not found.");
}
connection.UpdatePermissions(dto.Permissions, connection.OtherToSelfPermissions);
return PairOperationResult.Ok();
}
}
private PairConnection GetOrCreatePair(UserData user)
{
return GetOrCreatePair(user, out _);
}
private PairConnection GetOrCreatePair(UserData user, out bool created)
{
if (_pairs.TryGetValue(user.UID, out var connection))
{
created = false;
return connection;
}
connection = new PairConnection(user);
_pairs[user.UID] = connection;
created = true;
return connection;
}
private Syncshell GetOrCreateShell(GroupData group)
{
if (_groups.TryGetValue(group.GID, out var shell))
{
return shell;
}
var placeholder = new GroupFullInfoDto(
group,
new UserData(string.Empty),
GroupPermissions.NoneSet,
GroupUserPreferredPermissions.NoneSet,
GroupPairUserInfo.None,
new Dictionary<string, GroupPairUserInfo>(StringComparer.Ordinal),
0);
shell = new Syncshell(placeholder);
_groups[group.GID] = shell;
return shell;
}
private PairRegistration? TryRemovePairIfNoConnection(PairConnection connection)
{
if (connection.HasAnyConnection)
{
return null;
}
if (connection.IsOnline)
{
connection.SetOffline();
}
var userId = connection.User.UID;
_pairs.Remove(userId);
foreach (var shell in _groups.Values)
{
shell.Users.Remove(userId);
}
return new PairRegistration(new PairUniqueIdentifier(userId), connection.Ident);
}
public static PairConnection CreateFromFullData(UserFullPairDto dto)
{
var connection = new PairConnection(dto.User);
connection.UpdatePermissions(dto.OwnPermissions, dto.OtherPermissions);
connection.UpdateStatus(dto.IndividualPairStatus == IndividualPairStatus.None ? null : dto.IndividualPairStatus);
foreach (var groupId in dto.Groups)
{
connection.EnsureGroupRelationship(groupId, null);
}
return connection;
}
public static PairConnection CreateFromPartialData(UserPairDto dto)
{
var connection = new PairConnection(dto.User);
connection.UpdatePermissions(dto.OwnPermissions, dto.OtherPermissions);
connection.UpdateStatus(dto.IndividualPairStatus == IndividualPairStatus.None ? null : dto.IndividualPairStatus);
return connection;
}
public static GroupPairRelationship CreateGroupPairRelationshipFromFullInfo(string userUid, GroupFullInfoDto fullInfo)
{
return new GroupPairRelationship(fullInfo.Group.GID,
fullInfo.GroupPairUserInfos.TryGetValue(userUid, out var info) && info != GroupPairUserInfo.None
? info
: null);
}
}