zio fork 1.8.2b-zio11: reloading admins removes deleted and demoted admins
Some checks are pending
Build and Publish / build (push) Waiting to run
Build and Publish / publish (push) Blocked by required conditions

CSS's admin loaders only add, and the per-admin cleanup in CreateAdminsJsonFile
reset AdminCache once per admin, so it only ever tracked the last one and a
reload never took anyone else out of memory. ReloadAdmins now removes every
admin it loaded last time and loads the new set in one tick. admins.json is
keyed by SteamID instead of name, so admins sharing a name aren't merged, and a
failed admins query keeps the loaded admins instead of removing them.
This commit is contained in:
Astra 2026-10-02 21:21:09 +01:00
parent f46ccf18ce
commit d042aab1c0
3 changed files with 63 additions and 103 deletions

View file

@ -17,8 +17,8 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
/// <summary>
/// Retrieves all players' flags and associated data asynchronously.
/// </summary>
/// <returns>A list of tuples containing player SteamID, name, flags, immunity, and expiration time.</returns>
private async Task<List<(ulong, string ,List<string>, int, DateTime?)>> GetAllPlayersFlags()
/// <returns>A list of tuples containing player SteamID, name, flags, immunity, and expiration time, or null if the database query failed.</returns>
private async Task<List<(ulong, string ,List<string>, int, DateTime?)>?> GetAllPlayersFlags()
{
if (databaseProvider == null)
return new List<(ulong, string, List<string>, int, DateTime?)>();
@ -68,7 +68,8 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError("Unable to load admins from database! {exception}", ex.Message);
return [];
// zio: null rather than empty, so ReloadAdmins keeps the loaded admins instead of removing them all.
return null;
}
}
@ -150,96 +151,50 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
/// <summary>
/// Creates a JSON file containing admins data asynchronously.
/// </summary>
/// <returns>zio: every admin row loaded, for ReloadAdmins to rebuild AdminCache from, or null if the database query failed (the file is then left alone).</returns>
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
public async Task CreateAdminsJsonFile()
public async Task<List<(SteamID SteamId, DateTime? Ends, List<string> Flags)>?> CreateAdminsJsonFile()
{
List<(ulong identity, string name, List<string> flags, int immunity, DateTime? ends)> allPlayers = await GetAllPlayersFlags();
var allPlayers = await GetAllPlayersFlags();
if (allPlayers == null) return null;
var validPlayers = allPlayers
.Where(player => SteamID.TryParse(player.identity.ToString(), out _))
.Select(player => (player, steamId: SteamID.TryParse(player.Item1.ToString(), out var steamId) ? steamId : null))
.Where(p => p.steamId != null)
.ToList();
var jsonData = validPlayers
.GroupBy(player => player.name)
.ToDictionary(
group => group.Key,
object (group) =>
{
var consolidatedData = group.Aggregate(
new
{
identity = string.Empty,
immunity = 0,
flags = new List<string>(),
groups = new List<string>()
},
(acc, player) =>
{
if (string.IsNullOrEmpty(acc.identity) && !string.IsNullOrEmpty(player.identity.ToString()))
{
acc = acc with { identity = player.identity.ToString() };
}
// zio: keyed by SteamID, not name: CSS ignores the key, and two admins with the same name were
// merged into one entry under the first one's SteamID. Upstream also cleared and refilled
// AdminCache here once per admin, so it only ever kept the last admin and a reload never
// removed anyone else; ReloadAdmins now does that once, from the list returned here.
var jsonData = validPlayers
.GroupBy(p => p.steamId!.SteamId64)
.ToDictionary(
group => group.Key.ToString(),
object (group) =>
{
var rows = group.Select(p => p.player).ToList();
var flags = rows.SelectMany(r => r.Item3).Distinct().ToList();
return new
{
identity = group.Key.ToString(),
immunity = rows.Max(r => r.Item4),
flags = flags.Where(flag => flag.StartsWith($"@")).ToList(),
groups = flags.Where(flag => flag.StartsWith($"#")).ToList()
};
});
acc = acc with { immunity = Math.Max(acc.immunity, player.immunity) };
var options = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
acc = acc with
{
flags = acc.flags.Concat(player.flags.Where(flag => flag.StartsWith($"@"))).Distinct().ToList(),
groups = acc.groups.Concat(player.flags.Where(flag => flag.StartsWith($"#"))).Distinct().ToList()
};
return acc;
});
Server.NextWorldUpdate(() =>
{
var keysToRemove = new List<SteamID>();
foreach (var steamId in AdminCache.Keys.ToList())
{
var data = AdminManager.GetPlayerAdminData(steamId);
if (data != null)
{
var flagsArray = AdminCache[steamId].Flags.ToArray();
AdminManager.RemovePlayerPermissions(steamId, flagsArray);
AdminManager.RemovePlayerFromGroup(steamId, true, flagsArray);
}
keysToRemove.Add(steamId);
}
foreach (var steamId in keysToRemove)
{
if (!AdminCache.TryRemove(steamId, out _)) continue;
var data = AdminManager.GetPlayerAdminData(steamId);
if (data == null) continue;
if (data.Flags.Count != 0 && data.Groups.Count != 0) continue;
AdminManager.ClearPlayerPermissions(steamId);
AdminManager.RemovePlayerAdminData(steamId);
}
foreach (var player in group)
{
if (SteamID.TryParse(player.identity.ToString(), out var steamId) && steamId != null)
{
AdminCache.TryAdd(steamId, (player.ends, player.flags));
}
}
});
return consolidatedData;
});
var options = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var json = JsonSerializer.Serialize(jsonData, options);
var json = JsonSerializer.Serialize(jsonData, options);
var filePath = Path.Combine(CS2_SimpleAdmin.Instance.ModuleDirectory, "data", "admins.json");
await File.WriteAllTextAsync(filePath, json);
return validPlayers.Select(p => (p.steamId!, p.player.Item5, p.player.Item3)).ToList();
}
/// <summary>