/** * API that provides methods for creating/destroying and managing available * databases. * Copyright 2021 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 DBAPI extends AcediaObject; var private const class localDBClass; // Store all already loaded databases to make sure we do not create two // different `LocalDatabaseInstance` that are trying to make changes // separately. var private AssociativeArray loadedLocalDatabases; private final function CreateLocalDBMapIfMissing() { if (loadedLocalDatabases == none) { loadedLocalDatabases = __().collections.EmptyAssociativeArray(); } } /** * Creates new local database with name `databaseName`. * * This method will fail if: * 1. `databaseName` is `none` or empty; * 2. Local database with name `databaseName` already exists. * * @param databaseName Name for the new database. * @return Reference to created database. Returns `none` iff method failed. */ public final function LocalDatabaseInstance NewLocal(Text databaseName) { local DBRecord rootRecord; local Text rootRecordName; local LocalDatabase newConfig; local LocalDatabaseInstance newLocalDBInstance; CreateLocalDBMapIfMissing(); // No need to check `databaseName` for being valid, // since `Load()` will just return `none` if it is not. newConfig = class'LocalDatabase'.static.Load(databaseName); if (newConfig == none) return none; if (newConfig.HasDefinedRoot()) return none; if (loadedLocalDatabases.HasKey(databaseName)) return none; newLocalDBInstance = LocalDatabaseInstance(_.memory.Allocate(localDBClass)); loadedLocalDatabases.SetItem(databaseName.Copy(), newLocalDBInstance); rootRecord = class'DBRecord'.static.NewRecord(databaseName); rootRecordName = _.text.FromString(string(rootRecord.name)); newConfig.SetRootName(rootRecordName); newConfig.Save(); newLocalDBInstance.Initialize(newConfig, rootRecord); _.memory.Free(rootRecordName); return newLocalDBInstance; } /** * Loads and returns local database with the name `databaseName`. * * If specified database is already loaded - simply returns it's reference * (consequent calls to `LoadLocal()` will keep returning the same reference, * unless database is deleted). * * @param databaseName Name of the database to load. * @return Loaded local database. `none` if it does not exist. */ public final function LocalDatabaseInstance LoadLocal(Text databaseName) { local DBRecord rootRecord; local Text rootRecordName; local LocalDatabase newConfig; local LocalDatabaseInstance newLocalDBInstance; CreateLocalDBMapIfMissing(); if (loadedLocalDatabases.HasKey(databaseName)) { return LocalDatabaseInstance(loadedLocalDatabases .GetItem(databaseName)); } // No need to check `databaseName` for being valid, // since `Load()` will just return `none` if it is not. newConfig = class'LocalDatabase'.static.Load(databaseName); if (newConfig == none) return none; if (!newConfig.HasDefinedRoot()) return none; newLocalDBInstance = LocalDatabaseInstance(_.memory.Allocate(localDBClass)); loadedLocalDatabases.SetItem(databaseName.Copy(), newLocalDBInstance); rootRecordName = newConfig.GetRootName(); rootRecord = class'DBRecord'.static .LoadRecord(rootRecordName, databaseName); newLocalDBInstance.Initialize(newConfig, rootRecord); _.memory.Free(rootRecordName); return newLocalDBInstance; } /** * Checks if local database with the name `databaseName` already exists. * * @param databaseName Name of the database to check. * @return `true` if database with specified name exists and `false` otherwise. */ public final function bool ExistsLocal(Text databaseName) { return LoadLocal(databaseName) != none; } /** * Deletes local database with name `databaseName`. * * @param databaseName Name of the database to delete. * @return `true` if database with specified name existed and was deleted and * `false` otherwise. */ public final function bool DeleteLocal(Text databaseName) { local LocalDatabase localDatabaseConfig; local LocalDatabaseInstance localDatabase; local AssociativeArray.Entry dbEntry; CreateLocalDBMapIfMissing(); // To delete database we first need to load it localDatabase = LoadLocal(databaseName); if (localDatabase != none) { localDatabaseConfig = localDatabase.GetConfig(); } dbEntry = loadedLocalDatabases.TakeEntry(databaseName); // Delete `LocalDatabaseInstance` before erasing the package, // to allow it to clean up safely _.memory.Free(dbEntry.key); _.memory.Free(dbEntry.value); if (localDatabaseConfig != none) { EraseAllPackageData(localDatabaseConfig.GetPackageName()); localDatabaseConfig.DeleteSelf(); return true; } return false; } private function EraseAllPackageData(Text packageToErase) { local int i; local string packageName; local GameInfo game; local DBRecord nextRecord; local array allRecords; packageName = _.text.ToString(packageToErase); if (packageName == "") { return; } game = _.unreal.GetGameType(); game.DeletePackage(packageName); // Delete any leftover objects. This has to be done *after* // `DeletePackage()` call, otherwise removed garbage can reappear. // No clear idea why it works this way. foreach game.AllDataObjects(class'DBRecord', nextRecord, packageName) { allRecords[allRecords.length] = nextRecord; } for (i = 0; i < allRecords.length; i += 1) { game.DeleteDataObject( class'DBRecord', string(allRecords[i].name), packageName); } } /** * Returns array of names of all available local databases. * * @return List of names of all local databases. */ public final function array ListLocal() { local int i; local array dbNames; local array dbNamesAsStrings; dbNamesAsStrings = GetPerObjectNames( "AcediaDB", string(class'LocalDatabase'.name), MaxInt); for (i = 0; i < dbNamesAsStrings.length; i += 1) { dbNames[dbNames.length] = _.text.FromString(dbNamesAsStrings[i]); } return dbNames; } defaultproperties { localDBClass = class'LocalDatabaseInstance' }