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. 258
      sources/Users/Users_Feature.uc

3
sources/Manifest.uc

@ -24,7 +24,8 @@ defaultproperties
{ {
features(0) = class'Aliases_Feature' features(0) = class'Aliases_Feature'
features(1) = class'Commands_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(0) = class'TEST_Base'
testCases(1) = class'TEST_ActorService' testCases(1) = class'TEST_ActorService'
testCases(2) = class'TEST_Boxes' 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) { if (initID != none) {
initID.NewRef(); initID.NewRef();
} }
LoadLocalGroups(); //LoadLocalGroups();
groupsReadingTask = ReadPersistentData(P("Acedia"), P("UserGroups")); /*groupsReadingTask = ReadPersistentData(P("Acedia"), P("UserGroups"));
if (groupsReadingTask != none) { if (groupsReadingTask != none) {
groupsReadingTask.connect = LoadDBGroups; groupsReadingTask.connect = LoadDBGroups;
} }*/
} }
/** /**

254
sources/Users/UserAPI.uc

@ -230,6 +230,260 @@ public final function User FetchByKey(int userKey)
return result; 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 defaultproperties
{ {
userdataDBLink = "[local]database:/users" userdataDBLink = "[local]database:/users"

258
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 * Copyright 2022 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
@ -21,9 +24,9 @@ class Users_Feature extends Feature;
var private /*config*/ bool useDatabase; var private /*config*/ bool useDatabase;
var private /*config*/ string databaseLink; 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; var private array<Text> loadedUserGroups;
// `HashTable` (with group name keys) that stores `HashTable`s used as // `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). // a set data structure (has user id as keys and always `none` as a value).
@ -39,7 +42,8 @@ protected function SwapConfig(FeatureConfig config)
} }
useDatabase = newConfig.useDatabase; useDatabase = newConfig.useDatabase;
databaseLink = newConfig.databaseLink; databaseLink = newConfig.databaseLink;
userGroup = newConfig.localUserGroup; availableUserGroups = newConfig.localUserGroup;
LoadLocalData();
} }
private final function LoadLocalData() private final function LoadLocalData()
@ -56,10 +60,10 @@ private final function LoadLocalGroupNames()
_.memory.FreeMany(loadedUserGroups); _.memory.FreeMany(loadedUserGroups);
loadedUserGroups.length = 0; loadedUserGroups.length = 0;
for (i = 0; i < userGroup.length; i += 1) for (i = 0; i < availableUserGroups.length; i += 1)
{ {
isDuplicate = false; isDuplicate = false;
nextUserGroup = _.text.FromString(userGroup[i]); nextUserGroup = _.text.FromString(availableUserGroups[i]);
for(j = 0; j < loadedUserGroups.length; j += 1) for(j = 0; j < loadedUserGroups.length; j += 1)
{ {
if (loadedUserGroups[j].Compare(nextUserGroup, SCASE_INSENSITIVE)) if (loadedUserGroups[j].Compare(nextUserGroup, SCASE_INSENSITIVE))
@ -123,14 +127,15 @@ private final function SaveLocalData()
if (useDatabase) return; if (useDatabase) return;
if (loadedGroupToUsersMap == none) return; if (loadedGroupToUsersMap == none) return;
userGroup.length = 0; availableUserGroups.length = 0;
iter = HashTableIterator(loadedGroupToUsersMap.Iterate()); iter = HashTableIterator(loadedGroupToUsersMap.Iterate());
while (!iter.HasFinished()) while (!iter.HasFinished())
{ {
nextGroup = Text(iter.GetKey()); nextGroup = Text(iter.GetKey());
if (nextGroup != none) if (nextGroup != none)
{ {
userGroup[userGroup.length] = nextGroup.ToString(); availableUserGroups[availableUserGroups.length] =
nextGroup.ToString();
nextGroup.FreeSelf(); nextGroup.FreeSelf();
} }
iter.Next(); iter.Next();
@ -144,31 +149,46 @@ private final function SaveLocalData()
} }
if (currentConfig != none) if (currentConfig != none)
{ {
currentConfig.localUserGroup = userGroup; currentConfig.localUserGroup = availableUserGroups;
// !!! save config !!! // !!! save config !!!
} }
_.memory.Free(currentConfig); _.memory.Free(currentConfig);
_.memory.Free(activeConfigName); _.memory.Free(activeConfigName);
} }
public final function array<Text> GetGroupsForUserID(UserID user) /**
{ * Returns names of all groups available for the user with a SteamID given by
return GetLocalGroupsForUserID(user); * `steamID`.
} *
* In case this feature is configured to load user groups from a database
private final function array<Text> GetLocalGroupsForUserID(UserID id) * (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 array<Text> result;
local HashTableIterator iter; local HashTableIterator iter;
local Text nextGroup; local Text nextGroup;
local HashTable nextGroupUsers; local HashTable nextGroupUsers;
if (loadedGroupToUsersMap == none) return result; if (loadedGroupToUsersMap == none) return result;
if (id == none) return result;
steamID = id.GetSteamID64String();
if (steamID == none) return result; if (steamID == none) return result;
immutableSteamID = steamID.LowerCopy();
iter = HashTableIterator(loadedGroupToUsersMap.Iterate()); iter = HashTableIterator(loadedGroupToUsersMap.Iterate());
while (!iter.HasFinished()) while (!iter.HasFinished())
{ {
@ -183,16 +203,91 @@ private final function array<Text> GetLocalGroupsForUserID(UserID id)
_.memory.Free(nextGroup); _.memory.Free(nextGroup);
_.memory.Free(nextGroupUsers); _.memory.Free(nextGroupUsers);
} }
steamID.FreeSelf(); immutableSteamID.FreeSelf();
return result; 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)
{
local array<Text> result;
local MutableText wrapper;
wrapper = _.text.FromStringM(steamID);
result = GetGroupsForSteamID(wrapper);
wrapper.FreeSelf();
return result;
}
/**
* 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)
{ {
return GetLocalGroupsForUser(user); 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;
} }
private final function array<Text> GetLocalGroupsForUser(User user) /**
* 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 UserID id;
local array<Text> result; local array<Text> result;
@ -201,22 +296,32 @@ private final function array<Text> GetLocalGroupsForUser(User user)
return result; return result;
} }
id = user.GetID(); id = user.GetID();
result = GetLocalGroupsForUserID(id); result = GetGroupsForUserID(id);
_.memory.Free(id); _.memory.Free(id);
return result; return result;
} }
public final function array<UserID> GetUserIDsInGroup(Text groupName) /**
{ * Returns `UserID`s of all users that belong into the group named `groupName`.
return GetUserIDsInLocalGroup(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.
private final function array<UserID> GetUserIDsInLocalGroup(Text groupName) * 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 int i;
local Text lowerCaseGroupName; local Text lowerCaseGroupName;
local HashTable groupUsers; local HashTable groupUsers;
local array<Text> groupUserNames; local array<Text> groupUsersNames;
local UserID nextUserID; local UserID nextUserID;
local array<UserID> result; local array<UserID> result;
@ -227,13 +332,13 @@ private final function array<UserID> GetUserIDsInLocalGroup(Text groupName)
groupUsers = loadedGroupToUsersMap.GetHashTable(lowerCaseGroupName); groupUsers = loadedGroupToUsersMap.GetHashTable(lowerCaseGroupName);
lowerCaseGroupName.FreeSelf(); lowerCaseGroupName.FreeSelf();
if (groupUsers == none) { if (groupUsers == none) {
groupUserNames = groupUsers.GetTextKeys(); groupUsersNames = groupUsers.GetTextKeys();
} }
_.memory.Free(groupUsers); _.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 = UserID(_.memory.Allocate(class'UserID'));
nextUserID.Initialize(groupUserNames[i]); nextUserID.Initialize(groupUsersNames[i]);
if (nextUserID.IsInitialized()) { if (nextUserID.IsInitialized()) {
result[result.length] = nextUserID; result[result.length] = nextUserID;
} }
@ -241,10 +346,95 @@ private final function array<UserID> GetUserIDsInLocalGroup(Text groupName)
nextUserID.FreeSelf(); 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; 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 defaultproperties
{ {
configClass = class'Users' configClass = class'Users'

Loading…
Cancel
Save