Browse Source

Add dirty changes made thus far

pull/8/head
Anton Tarasenko 2 years ago
parent
commit
86648239f9
  1. 25
      config/AcediaUserGroups.ini
  2. 17
      config/AcediaUsers.ini
  3. 2
      sources/Data/Database/Local/LocalDatabaseInstance.uc
  4. 103
      sources/Users/User.uc
  5. 163
      sources/Users/UserAPI.uc
  6. 8
      sources/Users/UserGroup.uc
  7. 87
      sources/Users/Users.uc
  8. 251
      sources/Users/Users_Feature.uc

25
config/AcediaUserGroups.ini

@ -1,25 +0,0 @@
; In this config you can add several different user groups by adding
; `[<group_name> 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=

17
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?

2
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;

103
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<Text> userGroups; // user groups loaded from database
var private array<Text> 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()
{
@ -414,44 +420,89 @@ private function bool SetupDatabaseVariables()
{
local Text userDataLink;
local Text userTextID;
local HashTable emptyObject, skeletonObject;
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;
}
persistentSettingsPointer = _.db.GetPointer(userDataLink);
// Try making skeleton database
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);
userDataLink = _.users.GetPersistentDataLink();
persistentSettingsPointer = _.db.GetPointer(userDataLink);
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
{
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.")
errNoUserDataDatabase = (l=LOG_Error,m="Failed to load persistent user database instance given by link \"%1\".")
}

163
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\".")
}

8
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<string> 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
}

87
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 <https://www.gnu.org/licenses/>.
*/
class Users extends FeatureConfig
perobjectconfig
config(AcediaUsers);
var public config bool useDatabase;
var public config string databaseLink;
var public config array<string> 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"
}

251
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 <https://www.gnu.org/licenses/>.
*/
class Users_Feature extends Feature;
var private /*config*/ bool useDatabase;
var private /*config*/ string databaseLink;
var private /*config*/ array<string> userGroup;
// Defines order
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).
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<string> 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<Text> GetGroupsForUserID(UserID user)
{
return GetLocalGroupsForUserID(user);
}
private final function array<Text> GetLocalGroupsForUserID(UserID id)
{
local Text steamID;
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;
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<Text> GetGroupsForUser(User user)
{
return GetLocalGroupsForUser(user);
}
private final function array<Text> GetLocalGroupsForUser(User user)
{
local UserID id;
local array<Text> result;
if (user == none) {
return result;
}
id = user.GetID();
result = GetLocalGroupsForUserID(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)
{
local int i;
local Text lowerCaseGroupName;
local HashTable groupUsers;
local array<Text> groupUserNames;
local UserID nextUserID;
local array<UserID> 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'
}
Loading…
Cancel
Save