From 7419c6f907cf50ba0595dd9f978c1722505a1a79 Mon Sep 17 00:00:00 2001 From: Anton Tarasenko Date: Sat, 26 Nov 2022 04:21:44 +0700 Subject: [PATCH] Add scheduler support to users feature --- sources/Config/AcediaConfig.uc | 27 +++++-- sources/Users/UserAPI.uc | 36 +++++++++ sources/Users/Users_Feature.uc | 135 ++++++++++++++++++++++++++++++--- 3 files changed, 180 insertions(+), 18 deletions(-) diff --git a/sources/Config/AcediaConfig.uc b/sources/Config/AcediaConfig.uc index 69a3b89..b3deefa 100644 --- a/sources/Config/AcediaConfig.uc +++ b/sources/Config/AcediaConfig.uc @@ -67,6 +67,8 @@ class AcediaConfig extends AcediaObject // was detected in config, but not yet loaded. // Only its default value is ever used. var private HashTable existingConfigs; +// TODO: comment and add static cleanup +var private array clearQueue; // Stores name of the config where settings are to be stored. // Must correspond to value in `config(...)` modifier in class definition. @@ -174,7 +176,7 @@ public final static function bool NewConfig(BaseText name) new(none, NameToStorageVersion(name.ToString())) default.class; newConfig._ = __(); newConfig.DefaultIt(); - newConfig.SaveConfig(); + __().scheduler.RequestDiskAccess(newConfig).connect = StaticSaveConfig; default.existingConfigs.SetItem(name, newConfig); name.FreeSelf(); return true; @@ -212,14 +214,17 @@ public final static function bool Exists(BaseText name) */ public final static function DeleteConfig(BaseText name) { - local AcediaObject value; + local AcediaConfig value; + if (name == none) return; if (default.existingConfigs == none) return; name = name.LowerCopy(); - value = default.existingConfigs.TakeItem(name); - if (value != none) { - value.ClearConfig(); + value = AcediaConfig(default.existingConfigs.TakeItem(name)); + if (value != none) + { + __().scheduler.RequestDiskAccess(value).connect = HandleClearQueue; + default.clearQueue[default.clearQueue.length] = value; } __().memory.Free(name); } @@ -317,8 +322,18 @@ public final static function SaveData(BaseText name, HashTable data) if (requiredConfig != none) { requiredConfig.FromData(data); - requiredConfig.SaveConfig(); + __().scheduler.RequestDiskAccess(requiredConfig).connect = + StaticSaveConfig; + } +} + +private final static function HandleClearQueue() +{ + if (default.clearQueue.length <= 0) { + return; } + default.clearQueue[0].ClearConfig(); + default.clearQueue.Remove(0, 1); } defaultproperties diff --git a/sources/Users/UserAPI.uc b/sources/Users/UserAPI.uc index 330c2e5..a34b0d1 100644 --- a/sources/Users/UserAPI.uc +++ b/sources/Users/UserAPI.uc @@ -18,6 +18,7 @@ * along with Acedia. If not, see . */ class UserAPI extends AcediaObject + dependson(Users_Feature) config(AcediaSystem); var private config string userdataDBLink; @@ -676,6 +677,9 @@ public final function array GetGroupsForUser(User user) /** * Returns `UserID`s of all users that belong into the group named `groupName`. * + * @see For more information alongside `UserID`s use + * `GetAnnotatedGroupMembers()`. + * * Will only work if `Users_Feature` is active. * In case active config of `Users_Feature` is set up to load user groups * from a database (either local or remote), the returned value is a locally @@ -699,6 +703,38 @@ public final function array GetGroupMembers(BaseText groupName) return emptyResult; } +/** + * Returns annotated `UserID`s of all users that belong into the group named + * `groupName`. `UserID`s aren't necessarily human-readable (e.g. SteamID) + * and to help organize configs they can be annotated with a `Text` name. + * This method returns `UserID` alongside such annotation, if it exists. + * + * @see For just `UserID`s use `GetGroupMembers()`. + * + * Will only work if `Users_Feature` is active. + * In case active config of `Users_Feature` is set up to load user groups + * from a database (either local or remote), the returned value is a locally + * cached one. This helps us avoid having to query database each time we want + * to check something about user groups, but it also means we might have + * an outdated information. + * + * @param groupName Name of the group. Case-insensitive. + * @return Array with `UserID`s for every user in the user group named + * `groupName`. All array elements are guaranteed to be not-`none` and + * correspond to unique players. + * If data wasn't yet loaded - returns empty array. + */ +public final function array GetAnnotatedGroupMembers( + BaseText groupName) +{ + local array emptyResult; + + if (usersFeature != none) { + return usersFeature.GetAnnotatedGroupMembers(groupName); + } + return emptyResult; +} + /** * Checks whether user given by the `UserID` belongs to the group named * `groupName`. diff --git a/sources/Users/Users_Feature.uc b/sources/Users/Users_Feature.uc index c5f1a2b..df0bbf4 100644 --- a/sources/Users/Users_Feature.uc +++ b/sources/Users/Users_Feature.uc @@ -26,6 +26,19 @@ var private /*config*/ bool useDatabase; var private /*config*/ string databaseLink; var private /*config*/ array availableUserGroups; +var private bool diskSaveScheduled; + +struct AnnotatedUserID +{ + var public UserID id; + var public Text annotation; +}; + +struct IDAnnotationPair +{ + var Text id, annotation; +}; + // List of all available user groups for current config var private array loadedUserGroups; // `HashTable` (with group name keys) that stores `HashTable`s used as @@ -105,11 +118,12 @@ private final function LoadLocalGroup( BaseText groupName, optional bool localGroupIsExpected) { - local int i; - local Text newSteamID, lowerCaseGroupName; - local HashTable newPlayerSet; - local UserGroup groupConfig; - local array groupUserArray; + local int i; + local Text lowerCaseGroupName; + local HashTable newPlayerSet; + local UserGroup groupConfig; + local IDAnnotationPair nextUserPair; + local array groupUserArray; if (groupName == none) { return; @@ -129,9 +143,10 @@ private final function LoadLocalGroup( groupUserArray = groupConfig.user; for (i = 0; i < groupUserArray.length; i += 1) { - newSteamID = _.text.FromString(groupUserArray[i]); - newPlayerSet.SetItem(newSteamID, none); - newSteamID.FreeSelf(); + nextUserPair = ParseConfigUserName(groupUserArray[i]); + newPlayerSet.SetItem(nextUserPair.id, nextUserPair.annotation); + _.memory.Free(nextUserPair.id); + _.memory.Free(nextUserPair.annotation); } lowerCaseGroupName = groupName.LowerCopy(); loadedGroupToUsersMap.SetItem(lowerCaseGroupName, newPlayerSet); @@ -140,6 +155,29 @@ private final function LoadLocalGroup( groupConfig.FreeSelf(); } +private final function IDAnnotationPair ParseConfigUserName( + string configUserName) +{ + local Parser parser; + local MutableText parsingResult; + local IDAnnotationPair result; + local Text.Character slashSeparator; + + parser = _.text.ParseString(configUserName); + slashSeparator = _.text.GetCharacter("/"); + if (parser.MUntil(parsingResult, slashSeparator).Match(P("/")).Ok()) { + result.annotation = parser.GetRemainderM().IntoText(); + } + result.id = parsingResult.IntoText(); + if (result.annotation != none && result.annotation.IsEmpty()) + { + result.annotation.FreeSelf(); + result.annotation = none; + } + parser.FreeSelf(); + return result; +} + private final function SaveLocalData() { local Text nextGroup, activeConfigName; @@ -178,6 +216,15 @@ private final function SaveLocalData() _.memory.Free(activeConfigName); } +private final function ScheduleConfigSave() +{ + if (diskSaveScheduled) { + return; + } + _.scheduler.RequestDiskAccess(self).connect = SaveLocalData; + diskSaveScheduled = true; +} + /** * Returns names of all available groups that users can belong to. * @@ -237,6 +284,7 @@ public final function bool AddGroup(BaseText groupName) loadedUserGroups[loadedUserGroups.length] = lowerCaseGroupName; // Try loading local `UserGroup`? LoadLocalGroup(lowerCaseGroupName); + ScheduleConfigSave(); return true; } @@ -284,6 +332,7 @@ public final function bool RemoveGroup(BaseText groupName) // Try loading local `UserGroup`? loadedGroupToUsersMap.RemoveItem(lowerCaseGroupName); lowerCaseGroupName.FreeSelf(); + ScheduleConfigSave(); return true; } @@ -347,6 +396,7 @@ public final function bool AddSteamIDToGroup( } groupUsers.SetItem(steamID, none); groupUsers.FreeSelf(); + ScheduleConfigSave(); return true; } @@ -402,7 +452,7 @@ public final function bool AddUserIDToGroup( if (groupName == none) return false; if (id == none) return false; - steamID = id.GetSteamID64String(); + steamID = id.GetUniqueID(); if (steamID == none) return false; result = AddSteamIDToGroup(steamID, groupName); @@ -472,6 +522,7 @@ public final function bool RemoveSteamIDFromGroup( hadUser = groupUsers.HasKey(steamID); groupUsers.RemoveItem(steamID); groupUsers.FreeSelf(); + ScheduleConfigSave(); return hadUser; } @@ -527,7 +578,7 @@ public final function bool RemoveUserIDFromGroup( if (groupName == none) return false; if (id == none) return false; - steamID = id.GetSteamID64String(); + steamID = id.GetUniqueID(); if (steamID == none) return false; result = RemoveSteamIDFromGroup(steamID, groupName); @@ -666,7 +717,7 @@ public final function array GetGroupsForUserID(UserID id) local array result; if (id == none) return result; - steamID = id.GetSteamID64String(); + steamID = id.GetUniqueID(); if (steamID == none) return result; result = GetGroupsForSteamID(steamID); @@ -709,6 +760,9 @@ public final function array GetGroupsForUser(User user) /** * Returns `UserID`s of all users that belong into the group named `groupName`. * + * @see For more information alongside `UserID`s use + * `GetAnnotatedGroupMembers()`. + * * In case this feature is configured to load user groups from a database * (either local or remote), the returned value is a locally cached one. * This helps us avoid having to query database each time we want to check @@ -755,6 +809,63 @@ public final function array GetGroupMembers(BaseText groupName) return result; } +/** + * Returns annotated `UserID`s of all users that belong into the group named + * `groupName`. `UserID`s aren't necessarily human-readable (e.g. SteamID) + * and to help organize configs they can be annotated with a `Text` name. + * This method returns `UserID` alongside such annotation, if it exists. + * + * @see For just `UserID`s use `GetGroupMembers()`. + * + * In case this feature is configured to load user groups from a database + * (either local or remote), the returned value is a locally cached one. + * This helps us avoid having to query database each time we want to check + * something about user groups, but it also means we might have an outdated + * information. + * + * @param groupName Name of the group. Case-insensitive. + * @return Array with `UserID`s for every user in the user group named + * `groupName`. All array elements are guaranteed to be not-`none` and + * correspond to unique players. + * If data wasn't yet loaded - returns empty array. + */ +public final function array GetAnnotatedGroupMembers( + BaseText groupName) +{ + local int i; + local Text lowerCaseGroupName; + local HashTable groupUsers; + local array groupUsersNames; + local AnnotatedUserID nextRecord; + local array result; + + if (loadedGroupToUsersMap == none) return result; + if (groupName == none) return result; + + lowerCaseGroupName = groupName.LowerCopy(); + groupUsers = loadedGroupToUsersMap.GetHashTable(lowerCaseGroupName); + lowerCaseGroupName.FreeSelf(); + if (groupUsers == none) { + groupUsersNames = groupUsers.GetTextKeys(); + } + for (i = 0; i < groupUsersNames.length; i += 1) + { + nextRecord.id = UserID(_.memory.Allocate(class'UserID')); + nextRecord.id.Initialize(groupUsersNames[i]); + if (nextRecord.id.IsInitialized()) + { + nextRecord.annotation = groupUsers.GetText(groupUsersNames[i]); + result[result.length] = nextRecord; + } + else { + nextRecord.id.FreeSelf(); + } + } + _.memory.FreeMany(groupUsersNames); + _.memory.Free(groupUsers); + return result; +} + /** * Checks whether user given by `UserID` belongs to the group named * `groupName`. @@ -781,7 +892,7 @@ public final function bool IsUserIDInGroup(UserID id, Text groupName) if (loadedGroupToUsersMap == none) return false; if (groupName == none) return false; if (id == none) return false; - steamID = id.GetSteamID64String(); + steamID = id.GetUniqueID(); if (steamID == none) return false; lowerGroupName = groupName.LowerCopy();