/** * API that allows easy access to `User` persistent data and `UserID`s. * Copyright 2020-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 UserAPI extends AcediaObject config(AcediaSystem); 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. */ 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`. * * @param userID ID for which to fetch a persistent data storage. * @return `User` object for a given `UserID`. Guaranteed to be a valid * non-`none` reference if passed `userID` is not `none` and initialized * (which is guaranteed unless you manually created it). */ public final function User Fetch(UserID userID) { local User result; local UserDatabase userDB; userDB = class'UserDatabase'.static.GetInstance(); if (userDB == none) { return none; } result = userDB.FetchUser(userID); userDB.FreeSelf(); return result; } /** * Fetches appropriate `User` object for a player, given his id as a `Text`. * * @param idHash `Text` representation of someone's id. * @return Corresponding `User` object. Guaranteed to be a valid non-`none` * reference. */ public final function User FetchByIDHash(BaseText idHash) { local UserID userID; local UserDatabase userDB; local User result; userDB = class'UserDatabase'.static.GetInstance(); if (userDB == none) { return none; } userID = userDB.FetchUserID(idHash); userDB.FreeSelf(); if (userID == none) { return none; } result = userDB.FetchUser(userID); userID.FreeSelf(); return result; } /** * Fetches appropriate `User` object for a player, given his session key. * * @param userKey Key corresponding to a `User` method must to get. * @return Corresponding `User` object. Guaranteed to be a valid non-`none` * reference if `userKey` was actually assigned to any `User` during * current playing session; `none` otherwise. */ public final function User FetchByKey(int userKey) { local User result; local UserDatabase userDB; userDB = class'UserDatabase'.static.GetInstance(); if (userDB != none) { return none; } result = userDB.FetchUserByKey(userKey); userDB.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 passed SteamID is `none` or data wasn't yet loaded - returns empty * array. */ public final /*unreal*/ function array GetGroupsForSteamID( BaseText steamID) { local Users_Feature usersFeature; local array 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 GetGroupsForSteamID_S( string steamID) { local Users_Feature usersFeature; local array 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 GetGroupsForUserID(UserID id) { local Users_Feature usersFeature; local array 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 GetGroupsForUser(User user) { local Users_Feature usersFeature; local array 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 GetGroupMembers(Text groupName) { local Users_Feature usersFeature; local array 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" 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\".") }