From 86648239f9d38c5b7ccda56f6e8ede469d33f13b Mon Sep 17 00:00:00 2001 From: Anton Tarasenko Date: Sat, 12 Nov 2022 04:14:57 +0700 Subject: [PATCH] Add dirty changes made thus far --- config/AcediaUserGroups.ini | 25 -- config/AcediaUsers.ini | 17 ++ .../Database/Local/LocalDatabaseInstance.uc | 2 +- sources/Users/User.uc | 111 +++++--- sources/Users/UserAPI.uc | 163 +++++++++--- sources/Users/UserGroup.uc | 8 +- sources/Users/Users.uc | 87 ++++++ sources/Users/Users_Feature.uc | 251 ++++++++++++++++++ 8 files changed, 571 insertions(+), 93 deletions(-) delete mode 100644 config/AcediaUserGroups.ini create mode 100644 config/AcediaUsers.ini create mode 100644 sources/Users/Users.uc create mode 100644 sources/Users/Users_Feature.uc diff --git a/config/AcediaUserGroups.ini b/config/AcediaUserGroups.ini deleted file mode 100644 index d51292d..0000000 --- a/config/AcediaUserGroups.ini +++ /dev/null @@ -1,25 +0,0 @@ -; In this config you can add several different user groups by adding -; `[ UserGroup]` section for rach group. Every user can belong to -; several different groups. -; `priority` describes how important the group is. For example, if a user -; belongs to two different groups and both groups have different access rights -; for a certain command - the one with the highest priority will be chosen by -; default. -; You can specify several `user` entries with players stead id to add user to -; the certain group. - -[admin UserGroup] -priority=400 -;user= - -[moderator UserGroup] -priority=200 -;user= - -[trusted UserGroup] -priority=100 -;user= - -[wanted UserGroup] -priority=0 -;user= \ No newline at end of file diff --git a/config/AcediaUsers.ini b/config/AcediaUsers.ini new file mode 100644 index 0000000..6b910d7 --- /dev/null +++ b/config/AcediaUsers.ini @@ -0,0 +1,17 @@ +[default Users] +useDatabase=false +databaseLink="[local]database:/users" +userGroup=admin +userGroup=moderator +userGroup=trusted + +[admin UserGroup] +;user= + +[moderator UserGroup] +;user= + +[trusted UserGroup] +;user= + +; ?wanted, banned? \ No newline at end of file diff --git a/sources/Data/Database/Local/LocalDatabaseInstance.uc b/sources/Data/Database/Local/LocalDatabaseInstance.uc index cc36ef0..07aafb0 100644 --- a/sources/Data/Database/Local/LocalDatabaseInstance.uc +++ b/sources/Data/Database/Local/LocalDatabaseInstance.uc @@ -103,7 +103,7 @@ private final function CompleteAllTasks( optional float dilationCoefficient) { if (lastTask != none && lastTask.GetLifeVersion() == lastTaskLifeVersion) { - lastTask.TryCompleting(); + lastTask.TryCompleting(self); } lastTask = none; lastTaskLifeVersion = -1; diff --git a/sources/Users/User.uc b/sources/Users/User.uc index beecfbd..e5675d8 100644 --- a/sources/Users/User.uc +++ b/sources/Users/User.uc @@ -27,6 +27,10 @@ var private UserID id; // an easy reference in console commands var private int key; +// If we failed to create user database skeleton - set this to `true`, +// this will prevent us from making changes that might mess up database due to +// misconfiguration +var private bool failedToCreateDatabaseSkeleton; // Database where user's persistent data is stored var private Database persistentDatabase; // Pointer to this user's "settings" data in particular @@ -40,8 +44,10 @@ var private JSONPointer persistentSettingsPointer; // Group names are stored in the lower register. var private array userGroups; // user groups loaded from database var private array localUserGroups; // user groups loaded from local files -var private LoggerAPI.Definition errNoUserDataDatabase, errCannotReadDB; -var private LoggerAPI.Definition errInvalidUserGroups; +var private LoggerAPI.Definition warnNoPersistentDatabase; +var private LoggerAPI.Definition infoPersistentDatabaseLoaded; +var private LoggerAPI.Definition errCannotCreateSkeletonFor; +var private LoggerAPI.Definition errCannotReadDB, errInvalidUserGroups; protected function Finalizer() { @@ -412,46 +418,91 @@ public final function DBWriteTask WritePersistentData( // and `false` otherwise. private function bool SetupDatabaseVariables() { - local Text userDataLink; - local Text userTextID; - local HashTable emptyObject, skeletonObject; + local Text userDataLink; + local Text userTextID; - if ( persistentDatabase != none && persistentSettingsPointer != none - && persistentDatabase.IsAllocated()) - { - return true; - } - if (id == none || !id.IsInitialized()) { - return false; - } - _.memory.Free(persistentSettingsPointer); - userDataLink = _.users.GetUserDataLink(); - persistentDatabase = _.db.Load(userDataLink); + if (failedToCreateDatabaseSkeleton) return false; + if (persistentDatabase != none) return true; + if (id == none || !id.IsInitialized()) return false; + + // Check if database was even specified + persistentDatabase = _.users.GetPersistentDatabase(); if (persistentDatabase == none) { - _.logger.Auto(errNoUserDataDatabase).Arg(userDataLink); + _.logger.Auto(warnNoPersistentDatabase); return false; } + // Try making skeleton database + userTextID = id.GetSteamID64String(); + userDataLink = _.users.GetPersistentDataLink(); persistentSettingsPointer = _.db.GetPointer(userDataLink); - userTextID = id.GetSteamID64String(); - skeletonObject = _.collections.EmptyHashTable(); - skeletonObject.SetItem(P("statistics"), _.collections.EmptyHashTable()); - skeletonObject.SetItem(P("settings"), _.collections.EmptyHashTable()); - emptyObject = _.collections.EmptyHashTable(); - persistentDatabase.IncrementData(persistentSettingsPointer, emptyObject); + persistentSettingsPointer.Push(P("PerUserData")); persistentSettingsPointer.Push(userTextID); - persistentDatabase.IncrementData(persistentSettingsPointer, skeletonObject); + MakeSkeletonUserDatabase(userTextID, persistentSettingsPointer); persistentSettingsPointer.Push(P("settings")); - _.memory.Free(userTextID); + userTextID.FreeSelf(); _.memory.Free(userDataLink); - _.memory.Free(skeletonObject); - _.memory.Free(emptyObject); return true; } +private function MakeSkeletonUserDatabase( + Text userTextID, + JSONPointer userDataPointer) +{ + local HashTable skeleton, emptyObject; + + // Construct skeleton object + skeleton = _.collections.EmptyHashTable(); + emptyObject = _.collections.EmptyHashTable(); + skeleton.SetItem(P("Settings"), emptyObject); + skeleton.SetItem(P("Statistics"), emptyObject); + // Try adding the skeleton object + persistentDatabase + .IncrementData(userDataPointer, skeleton) + .connect = ReportSkeletonCreationResult; + // Release skeleton objects + skeleton.FreeSelf(); + emptyObject.FreeSelf(); +} + +private function ReportSkeletonCreationResult( + Database.DBQueryResult result, + Database source) +{ + local Text userTextID; + local Text userDataLink; + + userTextID = id.GetSteamID64String(); + userDataLink = _.users.GetPersistentDataLink(); + if (result == DBR_Success) + { + _.logger.Auto(infoPersistentDatabaseLoaded) + .Arg(userTextID) + .Arg(userDataLink); + } + else + { + _.logger.Auto(errCannotCreateSkeletonFor) + .Arg(userTextID) + .Arg(userDataLink); + failedToCreateDatabaseSkeleton = true; + _.memory.Free(persistentDatabase); + _.memory.Free(persistentSettingsPointer); + persistentDatabase = none; + persistentSettingsPointer = none; + } + _.memory.Free(userTextID); + _.memory.Free(userDataLink); +} + +// Load groups from db data only, inside the `UserAPI` +// Get rid of the "AcediaUserGroups.ini" +// Make command for editing user groups defaultproperties { - errCannotReadDB = (l=LOG_Error,m="Failed to read user groups from persistent user database.") - errInvalidUserGroups = (l=LOG_Error,m="Invalid data is written as user groups array inside persistent user database.") - errNoUserDataDatabase = (l=LOG_Error,m="Failed to load persistent user database instance given by link \"%1\".") + warnNoPersistentDatabase = (l=LOG_Error,m="No persistent user database available.") + infoPersistentDatabaseLoaded = (l=LOG_Info,m="Persistent user database was setup for user \"%1\" (using database link \"%2\").") + errCannotCreateSkeletonFor = (l=LOG_Error,m="Failed to create persistent user database skeleton for user \"%1\" (using database link \"%2\"). User data functionality won't function properly.") + errCannotReadDB = (l=LOG_Error,m="Failed to read user groups from persistent user database.") + errInvalidUserGroups = (l=LOG_Error,m="Invalid data is written as user groups array inside persistent user database.") } \ No newline at end of file diff --git a/sources/Users/UserAPI.uc b/sources/Users/UserAPI.uc index 35fd4d0..23faeaa 100644 --- a/sources/Users/UserAPI.uc +++ b/sources/Users/UserAPI.uc @@ -1,6 +1,6 @@ /** * API that allows easy access to `User` persistent data and `UserID`s. - * Copyright 2020-2021 Anton Tarasenko + * Copyright 2020-2022 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * @@ -20,12 +20,90 @@ class UserAPI extends AcediaObject config(AcediaSystem); -var private config string userDataDBLink; +var private config string userdataDBLink; + +// Database where user's data (persistent data and user groups) is stored +var private Database persistentDatabase; + +var private LoggerAPI.Definition warnNoPersistentDatabaseLink; +var private LoggerAPI.Definition errNoPersistentDatabase; +var private LoggerAPI.Definition errCannotCreateSkeletonFor; +var private LoggerAPI.Definition infoPersistentDatabaseLoaded; + +protected function Constructor() +{ + SetupUserDataDatabase(); +} + +// Loads persistent user database, specified by the AcediaCore's config and +// creates a basic skeleton for storing its data +private function SetupUserDataDatabase() +{ + local Text persistentDataLink; + local JSONPointer persistentDataPointer; + local HashTable skeleton, emptyObject; + + if (persistentDatabase != none) { + return; + } + // Check if database was even specified + persistentDataLink = GetPersistentDataLink(); + if (persistentDataLink.IsEmpty()) + { + _.logger.Auto(warnNoPersistentDatabaseLink); + persistentDataLink.FreeSelf(); + return; + } + // If link was specified - try loading database from it + persistentDatabase = _.db.Load(persistentDataLink); + if (persistentDatabase == none) + { + _.logger.Auto(errNoPersistentDatabase).Arg(persistentDataLink); + persistentDataLink.FreeSelf(); + return; + } + // Write skeleton database's skeleton + skeleton = _.collections.EmptyHashTable(); + emptyObject = _.collections.EmptyHashTable(); + skeleton.SetItem(P("Groups"), emptyObject); + skeleton.SetItem(P("PerUserData"), emptyObject); + persistentDataPointer = _.db.GetPointer(persistentDataLink); + persistentDatabase + .IncrementData(persistentDataPointer, skeleton) + .connect = ReportSkeletonCreationResult; + skeleton.FreeSelf(); + emptyObject.FreeSelf(); + persistentDataLink.FreeSelf(); + _.memory.Free(persistentDataPointer); +} + +private function ReportSkeletonCreationResult( + Database.DBQueryResult result, + Database source) +{ + local Text persistentDataLink; + + persistentDataLink = GetPersistentDataLink(); + if (result == DBR_Success) { + _.logger.Auto(infoPersistentDatabaseLoaded).Arg(persistentDataLink); + } + else + { + _.logger.Auto(errCannotCreateSkeletonFor).Arg(persistentDataLink); + _.memory.Free(persistentDatabase); + persistentDatabase = none; + } + _.memory.Free(persistentDataLink); +} /** * Returns reference to the database of user records that Acedia was * set up to use. * + * `UserDatabase` is for storing a set of users that joined the game during + * the session, for database that stores persistent user data + * @see `GetPersistentDatabase()`. + * * @return Main `UserDatabase` that Acedia currently uses to load and * store user information. Guaranteed to be a valid non-`none` reference. */ @@ -34,6 +112,53 @@ public final function UserDatabase GetDatabase() return class'UserDatabase'.static.GetInstance(); } +/** + * Returns reference to the database of user records that Acedia was + * set up to use. + * + * `Database` returned by this method stores persistent user data, for + * the database of users that joined during the current game session + * @see `GetDatabase()`. + * + * @return Main `UserDatabase` that Acedia currently uses to load and + * store user information. Guaranteed to be a valid non-`none` reference. + */ +public final function Database GetPersistentDatabase() +{ + if (persistentDatabase != none) { + persistentDatabase.NewRef(); + } + return persistentDatabase; +} + +/** + * Returns configured database link to the JSON object in which users' data + * is stored. + * + * @return Database link to the JSON object in which users' data is stored. + * Guaranteed to not be `none`. + */ +public final function Text GetPersistentDataLink() +{ + return _.text.FromString(userdataDBLink); +} + +/** + * Checks whether database setup to store users' persistent data was configured + * and actually exists. + * + * This does not check for whether that database is properly configured. + * If sub-object set to store users' data was not created inside it, then + * Acedia will not be able to make use of users' persistent storage. + * + * @return `true` if database for users' persistent data storage exists and + * `false` otherwise. + */ +public final function bool PersistentStorageExists() +{ + return (persistentDatabase != none); +} + /** * Fetches `User` object that stores persistent data for a given `userID`. * @@ -105,35 +230,11 @@ public final function User FetchByKey(int userKey) return result; } -/** - * Returns configured database link to the JSON object in which users' data - * is stored. - * - * @return Database link to the JSON object in which users' data is stored. - * Guaranteed to not be `none`. - */ -public final function Text GetUserDataLink() -{ - return P(userDataDBLink).Copy(); -} - -/** - * Checks whether database setup to store users' persistent data was configured - * and actually exists. - * - * This does not check for whether that database is properly configured. - * If sub-object set to store users' data was not created inside it, then - * Acedia will not be able to make use of users' persistent storage. - * - * @return `true` if database for users' persistent data storage exists and - * `false` otherwise. - */ -public final function bool PersistentStorageExists() -{ - return _.db.ExistsLocal(P(userDataDBLink)); -} - defaultproperties { - userDataDBLink = "[local]database:/users" + userdataDBLink = "[local]database:/users" + warnNoPersistentDatabaseLink = (l=LOG_Warning,m="No persistent user database link is setup. No persistent user data or user groups will be available. Setup `userDataDBLink` inside \"AcediaSystem.ini\".") + errCannotCreateSkeletonFor = (l=LOG_Error,m="Failed to create persistent database skeleton for connected database with link \"%1\". User data functionality won't function properly.") + errNoPersistentDatabase = (l=LOG_Error,m="Failed to connect to persistent user database with link \"%1\").") + infoPersistentDatabaseLoaded = (l=LOG_Info,m="Connected to persistent user database with link \"%1\".") } \ No newline at end of file diff --git a/sources/Users/UserGroup.uc b/sources/Users/UserGroup.uc index 5a66fe8..ff7bc7d 100644 --- a/sources/Users/UserGroup.uc +++ b/sources/Users/UserGroup.uc @@ -19,9 +19,8 @@ */ class UserGroup extends AcediaConfig perobjectconfig - config(AcediaUserGroups); + config(AcediaUsers); -var public config int priority; var public config array user; protected function HashTable ToData() @@ -31,7 +30,6 @@ protected function HashTable ToData() local ArrayList wrappedUserArray; data = __().collections.EmptyHashTable(); - data.SetInt(P("priority"), priority); wrappedUserArray = __().collections.EmptyArrayList(); for (i = 0; i < user.length; i += 1) { wrappedUserArray.AddString(user[i]); @@ -50,7 +48,6 @@ protected function FromData(HashTable source) if (source == none) { return; } - priority = source.GetInt(P("priority"), 0); wrappedUserArray = source.GetArrayList(P("user")); if (wrappedUserArray == none) { return; @@ -63,12 +60,11 @@ protected function FromData(HashTable source) protected function DefaultIt() { - priority = 0; user.length = 0; } defaultproperties { - configName = "AcediaUserGroups" + configName = "AcediaUsers" supportsDataConversion = true } \ No newline at end of file diff --git a/sources/Users/Users.uc b/sources/Users/Users.uc new file mode 100644 index 0000000..f1bfa10 --- /dev/null +++ b/sources/Users/Users.uc @@ -0,0 +1,87 @@ +/** + * Config object for `Users_Feature`. + * Copyright 2022 Anton Tarasenko + *------------------------------------------------------------------------------ + * This file is part of Acedia. + * + * Acedia is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, or + * (at your option) any later version. + * + * Acedia is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Acedia. If not, see . + */ +class Users extends FeatureConfig + perobjectconfig + config(AcediaUsers); + +var public config bool useDatabase; +var public config string databaseLink; +var public config array localUserGroup; + +protected function HashTable ToData() +{ + local int i; + local HashTable data; + local ArrayList userGroupList; + + data = __().collections.EmptyHashTable(); + data.SetBool(P("useDatabase"), useDatabase, false); + data.SetString(P("databaseLink"), databaseLink); + userGroupList = _.collections.EmptyArrayList(); + for (i = 0; i < localUserGroup.length; i += 1) { + userGroupList.AddString(localUserGroup[i]); + } + data.SetItem(P("userGroups"), userGroupList); + userGroupList.FreeSelf(); + return data; +} + +protected function FromData(HashTable source) +{ + local int i; + local ArrayList userGroupList; + + if (source == none) { + return; + } + useDatabase = source.GetBool(P("useDatabase")); + databaseLink = source.GetString( + P("databaseLink"), + "[local]database:/users"); + userGroupList = source.GetArrayList(P("userGroups")); + localUserGroup.length = 0; + if (userGroupList == none) { + return; + } + for (i = 0; i < userGroupList.GetLength(); i += 1) { + localUserGroup[localUserGroup.length] = userGroupList.GetString(i); + } + userGroupList.FreeSelf(); +} + +protected function DefaultIt() +{ + useDatabase = false; + databaseLink = "[local]database:/users"; + localUserGroup.length = 0; + localUserGroup[0] = "admin"; + localUserGroup[1] = "moderator"; + localUserGroup[2] = "trusted"; +} + +defaultproperties +{ + configName = "AcediaUsers" + useDatabase = false + databaseLink = "[local]database:/users" + localUserGroup(0) = "admin" + localUserGroup(1) = "moderator" + localUserGroup(2) = "trusted" +} \ No newline at end of file diff --git a/sources/Users/Users_Feature.uc b/sources/Users/Users_Feature.uc new file mode 100644 index 0000000..7b8513d --- /dev/null +++ b/sources/Users/Users_Feature.uc @@ -0,0 +1,251 @@ +/** + * ??? + * Copyright 2022 Anton Tarasenko + *------------------------------------------------------------------------------ + * This file is part of Acedia. + * + * Acedia is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, or + * (at your option) any later version. + * + * Acedia is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Acedia. If not, see . + */ +class Users_Feature extends Feature; + +var private /*config*/ bool useDatabase; +var private /*config*/ string databaseLink; +var private /*config*/ array userGroup; + +// Defines order +var private array loadedUserGroups; +// `HashTable` (with group name keys) that stores `HashTable`s used as +// a set data structure (has user id as keys and always `none` as a value). +var private HashTable loadedGroupToUsersMap; + +protected function SwapConfig(FeatureConfig config) +{ + local Users newConfig; + + newConfig = Users(config); + if (newConfig == none) { + return; + } + useDatabase = newConfig.useDatabase; + databaseLink = newConfig.databaseLink; + userGroup = newConfig.localUserGroup; +} + +private final function LoadLocalData() +{ + LoadLocalGroupNames(); + LoadLocalGroupToUserMap(); +} + +private final function LoadLocalGroupNames() +{ + local int i, j; + local bool isDuplicate; + local Text nextUserGroup; + + _.memory.FreeMany(loadedUserGroups); + loadedUserGroups.length = 0; + for (i = 0; i < userGroup.length; i += 1) + { + isDuplicate = false; + nextUserGroup = _.text.FromString(userGroup[i]); + for(j = 0; j < loadedUserGroups.length; j += 1) + { + if (loadedUserGroups[j].Compare(nextUserGroup, SCASE_INSENSITIVE)) + { + isDuplicate = true; + break; + } + } + if (!isDuplicate) + { + loadedUserGroups[loadedUserGroups.length] = + nextUserGroup.LowerCopy(); + } + nextUserGroup.FreeSelf(); + } +} + +private final function LoadLocalGroupToUserMap() +{ + local int i, j; + local Text newSteamID; + local HashTable newPlayerSet; + local UserGroup nextGroupConfig; + local array nextGroupUserArray; + + _.memory.Free(loadedGroupToUsersMap); + loadedGroupToUsersMap = _.collections.EmptyHashTable(); + class'UserGroup'.static.Initialize(); + // Go over every group + for (i = 0; i < loadedUserGroups.length; i += 1) + { + nextGroupConfig = UserGroup( + class'UserGroup'.static.GetConfigInstance(loadedUserGroups[i])); + if (nextGroupConfig == none) + { + // !!! Log missing group + continue; + } + // Copy player IDs from `string` array into `HashTable` + // that is serving as a set data structure + newPlayerSet = _.collections.EmptyHashTable(); + nextGroupUserArray = nextGroupConfig.user; + for (j = 0; j < nextGroupUserArray.length; j += 1) + { + newSteamID = _.text.FromString(nextGroupUserArray[j]); + newPlayerSet.SetItem(newSteamID, none); + newSteamID.FreeSelf(); + } + loadedGroupToUsersMap.SetItem(loadedUserGroups[i], newPlayerSet); + newPlayerSet.FreeSelf(); + nextGroupConfig.FreeSelf(); + } +} + +private final function SaveLocalData() +{ + local Text nextGroup, activeConfigName; + local Users currentConfig; + local HashTableIterator iter; + + if (useDatabase) return; + if (loadedGroupToUsersMap == none) return; + + userGroup.length = 0; + iter = HashTableIterator(loadedGroupToUsersMap.Iterate()); + while (!iter.HasFinished()) + { + nextGroup = Text(iter.GetKey()); + if (nextGroup != none) + { + userGroup[userGroup.length] = nextGroup.ToString(); + nextGroup.FreeSelf(); + } + iter.Next(); + } + iter.FreeSelf(); + activeConfigName = GetCurrentConfig(); + if (activeConfigName != none) + { + currentConfig = Users(class'Users'.static + .GetConfigInstance(activeConfigName)); + } + if (currentConfig != none) + { + currentConfig.localUserGroup = userGroup; + // !!! save config !!! + } + _.memory.Free(currentConfig); + _.memory.Free(activeConfigName); +} + +public final function array GetGroupsForUserID(UserID user) +{ + return GetLocalGroupsForUserID(user); +} + +private final function array GetLocalGroupsForUserID(UserID id) +{ + local Text steamID; + local array result; + local HashTableIterator iter; + local Text nextGroup; + local HashTable nextGroupUsers; + + if (loadedGroupToUsersMap == none) return result; + if (id == none) return result; + steamID = id.GetSteamID64String(); + if (steamID == none) return result; + + iter = HashTableIterator(loadedGroupToUsersMap.Iterate()); + while (!iter.HasFinished()) + { + nextGroup = Text(iter.GetKey()); + nextGroupUsers = HashTable(iter.Get()); + if ( nextGroup != none && nextGroupUsers != none + && nextGroupUsers.HasKey(steamID)) + { + result[result.length] = nextGroup.Copy(); + } + iter.Next(); + _.memory.Free(nextGroup); + _.memory.Free(nextGroupUsers); + } + steamID.FreeSelf(); + return result; +} + +public final function array GetGroupsForUser(User user) +{ + return GetLocalGroupsForUser(user); +} + +private final function array GetLocalGroupsForUser(User user) +{ + local UserID id; + local array result; + + if (user == none) { + return result; + } + id = user.GetID(); + result = GetLocalGroupsForUserID(id); + _.memory.Free(id); + return result; +} + +public final function array GetUserIDsInGroup(Text groupName) +{ + return GetUserIDsInLocalGroup(groupName); +} + +private final function array GetUserIDsInLocalGroup(Text groupName) +{ + local int i; + local Text lowerCaseGroupName; + local HashTable groupUsers; + local array groupUserNames; + local UserID nextUserID; + 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) { + groupUserNames = groupUsers.GetTextKeys(); + } + _.memory.Free(groupUsers); + for (i = 0; i < groupUserNames.length; i += 1) + { + nextUserID = UserID(_.memory.Allocate(class'UserID')); + nextUserID.Initialize(groupUserNames[i]); + if (nextUserID.IsInitialized()) { + result[result.length] = nextUserID; + } + else { + nextUserID.FreeSelf(); + } + } + _.memory.FreeMany(groupUserNames); + return result; +} + +defaultproperties +{ + configClass = class'Users' +} \ No newline at end of file