Browse Source

Refactor group API

pull/8/head
Anton Tarasenko 2 years ago
parent
commit
356b1d6cbf
  1. 3
      sources/Manifest.uc
  2. 6
      sources/Users/User.uc
  3. 254
      sources/Users/UserAPI.uc
  4. 262
      sources/Users/Users_Feature.uc

3
sources/Manifest.uc

@ -24,7 +24,8 @@ defaultproperties
{
features(0) = class'Aliases_Feature'
features(1) = class'Commands_Feature'
features(2) = class'Avarice_Feature'
features(2) = class'Users_Feature'
features(3) = class'Avarice_Feature'
testCases(0) = class'TEST_Base'
testCases(1) = class'TEST_ActorService'
testCases(2) = class'TEST_Boxes'

6
sources/Users/User.uc

@ -77,11 +77,11 @@ public final function Initialize(UserID initID, int initKey)
if (initID != none) {
initID.NewRef();
}
LoadLocalGroups();
groupsReadingTask = ReadPersistentData(P("Acedia"), P("UserGroups"));
//LoadLocalGroups();
/*groupsReadingTask = ReadPersistentData(P("Acedia"), P("UserGroups"));
if (groupsReadingTask != none) {
groupsReadingTask.connect = LoadDBGroups;
}
}*/
}
/**

254
sources/Users/UserAPI.uc

@ -230,6 +230,260 @@ public final function User FetchByKey(int userKey)
return result;
}
/**
* Returns names of all groups available for the user with a SteamID given by
* `steamID`.
*
* 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.
*
* @see `GetGroupsForUserID()` / `GetGroupsForUser()`.
*
* @param steamID SteamID of the user.
* Must be specified in a SteamID64 format, e.g. "76561197960287930".
* @return Array with names of the groups of the specified user.
* All array elements are guaranteed to be not-`none`, unique and in
* lower case.
* If passed SteamID is `none` or data wasn't yet loaded - returns empty
* array.
*/
public final /*unreal*/ function array<Text> GetGroupsForSteamID(
BaseText steamID)
{
local Users_Feature usersFeature;
local array<Text> result;
usersFeature = Users_Feature(class'Users_Feature'.static
.GetEnabledInstance());
if (usersFeature == none) {
return result;
}
result = usersFeature.GetGroupsForSteamID(steamID);
usersFeature.FreeSelf();
return result;
}
/**
* Returns names of all groups available for the user with a SteamID given by
* `steamID`.
*
* 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.
*
* @see `GetGroupsForUserID()` / `GetGroupsForUser()`.
*
* @param steamID SteamID of the user.
* Must be specified in a SteamID64 format, e.g. "76561197960287930".
* @return Array with names of the groups of the specified user.
* All array elements are guaranteed to be not-`none`, unique and in
* lower case.
* If data wasn't yet loaded - returns empty array.
*/
public final /*unreal*/ function array<Text> GetGroupsForSteamID_S(
string steamID)
{
local Users_Feature usersFeature;
local array<Text> result;
usersFeature = Users_Feature(class'Users_Feature'.static
.GetEnabledInstance());
if (usersFeature == none) {
return result;
}
result = usersFeature.GetGroupsForSteamID_S(steamID);
usersFeature.FreeSelf();
return result;
}
/**
* Returns names of all groups available for the user with an ID given by `id`.
*
* 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.
*
* @see `GetGroupsForSteamID()` / `GetGroupsForUser()`.
*
* @param id ID of the user.
* @return Array with names of the groups of the specified user.
* All array elements are guaranteed to be not-`none`, unique and in
* lower case.
* If data wasn't yet loaded - returns empty array.
*/
public final function array<Text> GetGroupsForUserID(UserID id)
{
local Users_Feature usersFeature;
local array<Text> result;
usersFeature = Users_Feature(class'Users_Feature'.static
.GetEnabledInstance());
if (usersFeature == none) {
return result;
}
result = usersFeature.GetGroupsForUserID(id);
usersFeature.FreeSelf();
return result;
}
/**
* Returns names of all groups available for the user given by `user`.
*
* 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.
*
* @see `GetGroupsForSteamID()` / `GetGroupsForUserID()`.
*
* @param user Reference to `User` object that represent the user we are to
* find groups for.
* @return Array with names of the groups of the specified user.
* All array elements are guaranteed to be not-`none`, unique and in
* lower case.
* If data wasn't yet loaded - returns empty array.
*/
public final function array<Text> GetGroupsForUser(User user)
{
local Users_Feature usersFeature;
local array<Text> result;
usersFeature = Users_Feature(class'Users_Feature'.static
.GetEnabledInstance());
if (usersFeature == none) {
return result;
}
result = usersFeature.GetGroupsForUser(user);
usersFeature.FreeSelf();
return result;
}
/**
* Returns `UserID`s of all users that belong into the group named `groupName`.
*
* 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<UserID> GetGroupMembers(Text groupName)
{
local Users_Feature usersFeature;
local array<UserID> result;
usersFeature = Users_Feature(class'Users_Feature'.static
.GetEnabledInstance());
if (usersFeature == none) {
return result;
}
result = usersFeature.GetGroupMembers(groupName);
usersFeature.FreeSelf();
return result;
}
/**
* Checks whether user with an ID given by `id` belongs to the group named
* `groupName`.
*
* 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 id ID of the user.
* @param groupName Name of the group. Case-insensitive.
* @return `true` if user with an ID given by `id` belongs to the group named
* `groupName` and false if: it does not, either of the parameters is
* invalid or group data wasn't yet properly loaded.
*/
public final function bool IsUserIDInGroup(UserID id, Text groupName)
{
local Users_Feature usersFeature;
local bool result;
usersFeature = Users_Feature(class'Users_Feature'.static
.GetEnabledInstance());
if (usersFeature == none) {
return result;
}
result = usersFeature.IsUserIDInGroup(id, groupName);
usersFeature.FreeSelf();
return result;
}
/**
* Checks whether user given by `user` belongs to the group named `groupName`.
*
* 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 user Reference to `User` object that represent the user we are to
* check for belonging to the group.
* @param groupName Name of the group. Case-insensitive.
* @return `true` if user with an ID given by `id` belongs to the group named
* `groupName` and false if: it does not, either of the parameters is
* invalid or group data wasn't yet properly loaded.
*/
public final function bool IsUserInGroup(User user, Text groupName)
{
local Users_Feature usersFeature;
local bool result;
usersFeature = Users_Feature(class'Users_Feature'.static
.GetEnabledInstance());
if (usersFeature == none) {
return result;
}
result = usersFeature.IsUserInGroup(user, groupName);
usersFeature.FreeSelf();
return result;
}
/**
* Checks whether user groups' data was already loaded from the source
* (either config file or local/remote database).
*
* Data loaded once is cached and this method returning `true` does not
* guarantee that is isn't outdated. Additional, asynchronous queries must be
* made to check for that.
*
* @return `true` if user groups' data was loaded and `false` otherwise.
*/
public final function bool IsUserGroupDataLoaded()
{
local Users_Feature usersFeature;
local bool result;
usersFeature = Users_Feature(class'Users_Feature'.static
.GetEnabledInstance());
if (usersFeature == none) {
return result;
}
result = usersFeature.IsUserGroupDataLoaded();
usersFeature.FreeSelf();
return result;
}
defaultproperties
{
userdataDBLink = "[local]database:/users"

262
sources/Users/Users_Feature.uc

@ -1,5 +1,8 @@
/**
* ???
* Feature for managing Acedia's user groups. Supports both config- and
* database-defined information about group sources. An instance of this
* feature is necessary for functioning of Acedia's `UserAPI` methods related
* to user groups.
* Copyright 2022 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
@ -21,9 +24,9 @@ class Users_Feature extends Feature;
var private /*config*/ bool useDatabase;
var private /*config*/ string databaseLink;
var private /*config*/ array<string> userGroup;
var private /*config*/ array<string> availableUserGroups;
// Defines order
// List of all available user groups for current config
var private array<Text> 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).
@ -37,9 +40,10 @@ protected function SwapConfig(FeatureConfig config)
if (newConfig == none) {
return;
}
useDatabase = newConfig.useDatabase;
databaseLink = newConfig.databaseLink;
userGroup = newConfig.localUserGroup;
useDatabase = newConfig.useDatabase;
databaseLink = newConfig.databaseLink;
availableUserGroups = newConfig.localUserGroup;
LoadLocalData();
}
private final function LoadLocalData()
@ -56,10 +60,10 @@ private final function LoadLocalGroupNames()
_.memory.FreeMany(loadedUserGroups);
loadedUserGroups.length = 0;
for (i = 0; i < userGroup.length; i += 1)
for (i = 0; i < availableUserGroups.length; i += 1)
{
isDuplicate = false;
nextUserGroup = _.text.FromString(userGroup[i]);
nextUserGroup = _.text.FromString(availableUserGroups[i]);
for(j = 0; j < loadedUserGroups.length; j += 1)
{
if (loadedUserGroups[j].Compare(nextUserGroup, SCASE_INSENSITIVE))
@ -123,14 +127,15 @@ private final function SaveLocalData()
if (useDatabase) return;
if (loadedGroupToUsersMap == none) return;
userGroup.length = 0;
availableUserGroups.length = 0;
iter = HashTableIterator(loadedGroupToUsersMap.Iterate());
while (!iter.HasFinished())
{
nextGroup = Text(iter.GetKey());
if (nextGroup != none)
{
userGroup[userGroup.length] = nextGroup.ToString();
availableUserGroups[availableUserGroups.length] =
nextGroup.ToString();
nextGroup.FreeSelf();
}
iter.Next();
@ -144,31 +149,46 @@ private final function SaveLocalData()
}
if (currentConfig != none)
{
currentConfig.localUserGroup = userGroup;
currentConfig.localUserGroup = availableUserGroups;
// !!! save config !!!
}
_.memory.Free(currentConfig);
_.memory.Free(activeConfigName);
}
public final function array<Text> GetGroupsForUserID(UserID user)
{
return GetLocalGroupsForUserID(user);
}
private final function array<Text> GetLocalGroupsForUserID(UserID id)
/**
* Returns names of all groups available for the user with a SteamID given by
* `steamID`.
*
* 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.
*
* @see `GetGroupsForUserID()` / `GetGroupsForUser()`.
*
* @param steamID SteamID of the user.
* Must be specified in a SteamID64 format, e.g. "76561197960287930".
* @return Array with names of the groups of the specified user.
* All array elements are guaranteed to be not-`none`, unique and in
* lower case.
* If passed SteamID is `none` or data wasn't yet loaded - returns empty
* array.
*/
public final /*unreal*/ function array<Text> GetGroupsForSteamID(
BaseText steamID)
{
local Text steamID;
local Text immutableSteamID;
local array<Text> 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;
immutableSteamID = steamID.LowerCopy();
iter = HashTableIterator(loadedGroupToUsersMap.Iterate());
while (!iter.HasFinished())
{
@ -183,16 +203,91 @@ private final function array<Text> GetLocalGroupsForUserID(UserID id)
_.memory.Free(nextGroup);
_.memory.Free(nextGroupUsers);
}
steamID.FreeSelf();
immutableSteamID.FreeSelf();
return result;
}
public final function array<Text> GetGroupsForUser(User user)
/**
* Returns names of all groups available for the user with a SteamID given by
* `steamID`.
*
* 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.
*
* @see `GetGroupsForUserID()` / `GetGroupsForUser()`.
*
* @param steamID SteamID of the user.
* Must be specified in a SteamID64 format, e.g. "76561197960287930".
* @return Array with names of the groups of the specified user.
* All array elements are guaranteed to be not-`none`, unique and in
* lower case.
* If data wasn't yet loaded - returns empty array.
*/
public final /*unreal*/ function array<Text> GetGroupsForSteamID_S(
string steamID)
{
return GetLocalGroupsForUser(user);
local array<Text> result;
local MutableText wrapper;
wrapper = _.text.FromStringM(steamID);
result = GetGroupsForSteamID(wrapper);
wrapper.FreeSelf();
return result;
}
private final function array<Text> GetLocalGroupsForUser(User user)
/**
* Returns names of all groups available for the user with an ID given by `id`.
*
* 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.
*
* @see `GetGroupsForSteamID()` / `GetGroupsForUser()`.
*
* @param id ID of the user.
* @return Array with names of the groups of the specified user.
* All array elements are guaranteed to be not-`none`, unique and in
* lower case.
* If data wasn't yet loaded - returns empty array.
*/
public final function array<Text> GetGroupsForUserID(UserID id)
{
local Text steamID;
local array<Text> result;
if (id == none) return result;
steamID = id.GetSteamID64String();
if (steamID == none) return result;
result = GetGroupsForSteamID(steamID);
steamID.FreeSelf();
return result;
}
/**
* Returns names of all groups available for the user given by `user`.
*
* 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.
*
* @see `GetGroupsForSteamID()` / `GetGroupsForUserID()`.
*
* @param user Reference to `User` object that represent the user we are to
* find groups for.
* @return Array with names of the groups of the specified user.
* All array elements are guaranteed to be not-`none`, unique and in
* lower case.
* If data wasn't yet loaded - returns empty array.
*/
public final function array<Text> GetGroupsForUser(User user)
{
local UserID id;
local array<Text> result;
@ -201,22 +296,32 @@ private final function array<Text> GetLocalGroupsForUser(User user)
return result;
}
id = user.GetID();
result = GetLocalGroupsForUserID(id);
result = GetGroupsForUserID(id);
_.memory.Free(id);
return result;
}
public final function array<UserID> GetUserIDsInGroup(Text groupName)
{
return GetUserIDsInLocalGroup(groupName);
}
private final function array<UserID> GetUserIDsInLocalGroup(Text groupName)
/**
* Returns `UserID`s of all users that belong into the group named `groupName`.
*
* 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<UserID> GetGroupMembers(Text groupName)
{
local int i;
local Text lowerCaseGroupName;
local HashTable groupUsers;
local array<Text> groupUserNames;
local array<Text> groupUsersNames;
local UserID nextUserID;
local array<UserID> result;
@ -227,13 +332,13 @@ private final function array<UserID> GetUserIDsInLocalGroup(Text groupName)
groupUsers = loadedGroupToUsersMap.GetHashTable(lowerCaseGroupName);
lowerCaseGroupName.FreeSelf();
if (groupUsers == none) {
groupUserNames = groupUsers.GetTextKeys();
groupUsersNames = groupUsers.GetTextKeys();
}
_.memory.Free(groupUsers);
for (i = 0; i < groupUserNames.length; i += 1)
for (i = 0; i < groupUsersNames.length; i += 1)
{
nextUserID = UserID(_.memory.Allocate(class'UserID'));
nextUserID.Initialize(groupUserNames[i]);
nextUserID.Initialize(groupUsersNames[i]);
if (nextUserID.IsInitialized()) {
result[result.length] = nextUserID;
}
@ -241,10 +346,95 @@ private final function array<UserID> GetUserIDsInLocalGroup(Text groupName)
nextUserID.FreeSelf();
}
}
_.memory.FreeMany(groupUserNames);
_.memory.FreeMany(groupUsersNames);
return result;
}
/**
* Checks whether user with an ID given by `id` belongs to the group named
* `groupName`.
*
* 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 id ID of the user.
* @param groupName Name of the group. Case-insensitive.
* @return `true` if user with an ID given by `id` belongs to the group named
* `groupName` and false if: it does not, either of the parameters is
* invalid or group data wasn't yet properly loaded.
*/
public final function bool IsUserIDInGroup(UserID id, Text groupName)
{
local bool result;
local Text steamID;
local Text lowerGroupName;
local HashTable nextGroupUsers;
if (loadedGroupToUsersMap == none) return false;
if (groupName == none) return false;
if (id == none) return false;
steamID = id.GetSteamID64String();
if (steamID == none) return false;
lowerGroupName = groupName.LowerCopy();
nextGroupUsers = loadedGroupToUsersMap.GetHashTable(lowerGroupName);
lowerGroupName.FreeSelf();
if (nextGroupUsers != none) {
result = nextGroupUsers.HasKey(steamID);
}
_.memory.Free(nextGroupUsers);
steamID.FreeSelf();
return result;
}
/**
* Checks whether user given by `user` belongs to the group named `groupName`.
*
* 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 user Reference to `User` object that represent the user we are to
* check for belonging to the group.
* @param groupName Name of the group. Case-insensitive.
* @return `true` if user with an ID given by `id` belongs to the group named
* `groupName` and false if: it does not, either of the parameters is
* invalid or group data wasn't yet properly loaded.
*/
public final function bool IsUserInGroup(User user, Text groupName)
{
local UserID id;
local bool result;
if (user == none) {
return false;
}
id = user.GetID();
result = IsUserIDInGroup(id, groupName);
_.memory.Free(id);
return result;
}
/**
* Checks whether user groups' data was already loaded from the source
* (either config file or local/remote database).
*
* Data loaded once is cached and this method returning `true` does not
* guarantee that is isn't outdated. Additional, asynchronous queries must be
* made to check for that.
*
* @return `true` if user groups' data was loaded and `false` otherwise.
*/
public final function bool IsUserGroupDataLoaded()
{
return true;
}
defaultproperties
{
configClass = class'Users'

Loading…
Cancel
Save