// Copyright (c) 2024 Synty Studios Limited. All rights reserved. // // Use of this software is subject to the terms and conditions of the Synty Studios End User Licence Agreement (EULA) // available at: https://syntystore.com/pages/end-user-licence-agreement // // For additional details, see the LICENSE.MD file bundled with this software. // using SqlCipher4Unity3D; // using SqlCipher4Unity3D; using SQLite; using Synty.SidekickCharacters.Database.DTO; using System; using System.Linq; namespace Synty.SidekickCharacters.Database { /// /// Manages connecting to, initializing and validating a local Sidekick SQLite database /// public class DatabaseManager { private static readonly string _DATABASE_PATH = "Assets/External/Models/SidekickCharacters/Database/Side_Kick_Data.db"; private readonly string _CURRENT_VERSION = "1.0.2"; private static SQLiteConnection _connection; private static int _connectionHash; /// /// Gets the DB connection with the given connection details. /// If the current connection is not for the given details, the connection is replaced with the correct connection. /// /// The path to the DB. /// The connection key for the DB. /// Whether or not to valiate the structure of the DB after connecting to it. /// A connection to a DB with the given connection details. public SQLiteConnection GetDbConnection(bool checkDbOnLoad = false) { if (_connection == null) { _connection = new SQLiteConnection(_DATABASE_PATH, true); } else { return _connection; } if (checkDbOnLoad) { bool createTables = !IsDatabaseConfigured(); InitialiseDatabase(createTables); } return _connection; } /// /// Returns whatever the current DB connection is, regardless of state. /// /// The current DB connection. public SQLiteConnection GetCurrentDbConnection() { return _connection; } /// /// Closes the current DB connection. /// public void CloseConnection() { if (_connection == null) { return; } // NOTE: Previously, if we didn't clear the pool in the connection, it resulted in a file lock on the database, // which in some cases could cause problems when trying to update the DB to a new version. // Our SQLCipher library doesn't use pool clearing unless using SQLiteAsyncConnection. _connection.Dispose(); // Our SQLCipher library doesn't surface checking connection state; disposed connections need their reference removed _connection = null; } /// /// Initialises the Sidekicks database with required data, if they don't already exist. /// /// Whether to update the database and create the needed tables or not. private void InitialiseDatabase(bool createTables = false) { // ensure we have a default color set (a bunch of other code relies on this, not safe to remove yet) try { SidekickColorSet.GetDefault(this); } catch (Exception) { SidekickColorSet newSet = new SidekickColorSet { Species = new SidekickSpecies { ID = -1, Name = "None" }, Name = "Default", SourceColorPath = "Assets/External/Models/SidekickCharacters/Resources/Textures/T_ColorMap.png", SourceMetallicPath = "Assets/External/Models/SidekickCharacters/Resources/Textures/T_MetallicMap.png", SourceSmoothnessPath = "Assets/External/Models/SidekickCharacters/Resources/Textures/T_SmoothnessMap.png", SourceReflectionPath = "Assets/External/Models/SidekickCharacters/Resources/Textures/T_ReflectionMap.png", SourceEmissionPath = "Assets/External/Models/SidekickCharacters/Resources/Textures/T_EmissionMap.png", SourceOpacityPath = "Assets/External/Models/SidekickCharacters/Resources/Textures/T_OpacityMap.png", }; newSet.Save(this); } if (createTables) { GetCurrentDbConnection().CreateTable(); SidekickDBVersion version; if (GetCurrentDbConnection().Table().Any()) { version = GetCurrentDbConnection().Table().FirstOrDefault(); version.SemanticVersion = _CURRENT_VERSION; version.LastUpdated = DateTime.Now; } else { version = new SidekickDBVersion() { ID = -1, SemanticVersion = _CURRENT_VERSION, LastUpdated = DateTime.Now }; } version.Save(this); GetCurrentDbConnection().CreateTable(); GetCurrentDbConnection().CreateTable(); GetCurrentDbConnection().CreateTable(); GetCurrentDbConnection().CreateTable(); GetCurrentDbConnection().CreateTable(); GetCurrentDbConnection().CreateTable(); GetCurrentDbConnection().CreateTable(); GetCurrentDbConnection().CreateTable(); } } /// /// Checks to see if the current database has the required tables. /// TODO: Check DB version, not just if table is present. /// /// True if the tables are present; otherwise false. private bool IsDatabaseConfigured() { bool configured = !(GetCurrentDbConnection().GetTableInfo("sk_vdata").Count < 1); if (GetCurrentDbConnection().GetTableInfo("sk_part").Count < 9) { configured = false; } if (GetCurrentDbConnection().GetTableInfo("sk_part_image").Count < 1) { configured = false; } if (GetCurrentDbConnection().GetTableInfo("sk_part_filter").Count < 1) { configured = false; } if (GetCurrentDbConnection().GetTableInfo("sk_part_filter_row").Count < 1) { configured = false; } if (GetCurrentDbConnection().GetTableInfo("sk_part_preset_row").Count < 5) { configured = false; } if (GetCurrentDbConnection().GetTableInfo("sk_part_species_link").Count < 3) { configured = false; } if (GetCurrentDbConnection().GetTableInfo("sk_preset_filter").Count < 1) { configured = false; } if (GetCurrentDbConnection().GetTableInfo("sk_preset_filter_row").Count < 1) { configured = false; } return configured; } /// /// Retrieves the current database version. /// /// Semantic version (major.minor.patch). public Version GetDatabaseVersion() { return new Version(GetCurrentDbConnection()?.Table().FirstOrDefault().SemanticVersion ?? "0.0.1ea"); } } }