580 lines
19 KiB
C#
580 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.");
|
|
}
|
|
|
|
var ident = connection.Ident;
|
|
connection.SetOffline();
|
|
return PairOperationResult<PairRegistration>.Ok(new PairRegistration(new PairUniqueIdentifier(user.UID), 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;
|
|
}
|
|
|
|
var ident = connection.Ident;
|
|
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), 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);
|
|
}
|
|
}
|