// // CheatBase.swift // Delta // // Created by Riley Testut on 1/17/23. // Copyright © 2023 Riley Testut. All rights reserved. // import Foundation import SQLite import Roxas private extension UserDefaults { @NSManaged var previousCheatBaseVersion: Int } extension ExpressionType { static var cheatID: SQLite.Expression { return SQLite.Expression("cheatID") } static var cheatName: SQLite.Expression { return SQLite.Expression("cheatName") } static var cheatDescription: SQLite.Expression { return SQLite.Expression("cheatDescription") } static var cheatCode: SQLite.Expression { return SQLite.Expression("cheatCode") } static var cheatDeviceID: SQLite.Expression { return SQLite.Expression("cheatDeviceID") } static var cheatActivation: SQLite.Expression { return SQLite.Expression("cheatActivation") } static var cheatCategoryID: SQLite.Expression { return SQLite.Expression("cheatCategoryID") } static var cheatCategoryName: SQLite.Expression { return SQLite.Expression("cheatCategory") } static var cheatCategoryDescription: SQLite.Expression { return SQLite.Expression("cheatCategoryDescription") } } extension Table { static var cheats: Table { return Table("CHEATS") } static var cheatCategories: Table { return Table("CHEAT_CATEGORIES") } } @available(iOS 14, *) class CheatBase: GamesDatabase { static let cheatsVersion = 1 static var previousCheatsVersion: Int? { return UserDefaults.standard.previousCheatBaseVersion } private let connection: Connection override init() throws { let fileURL = DatabaseManager.cheatBaseURL guard FileManager.default.fileExists(atPath: fileURL.path) else { throw GamesDatabase.Error.doesNotExist } self.connection = try Connection(fileURL.path) try super.init() UserDefaults.standard.previousCheatBaseVersion = CheatBase.cheatsVersion } func cheats(for game: Game) async throws -> [CheatMetadata]? { let metadata = await withCheckedContinuation { continuation in if let context = game.managedObjectContext { context.perform { let metadata = self.metadata(for: game) continuation.resume(returning: metadata) } } else { let metadata = self.metadata(for: game) continuation.resume(returning: metadata) } } guard let romIDValue = metadata?.romID else { return nil } let cheatID = Expression.cheatID let cheatName = Expression.cheatName let cheatCode = Expression.cheatCode let cheatDescription = Expression.cheatDescription let cheatActivation = Expression.cheatActivation let cheatDeviceID = Expression.cheatDeviceID let categoryID = Expression.cheatCategoryID let categoryName = Expression.cheatCategoryName let categoryDescription = Expression.cheatCategoryDescription let romID = Expression.romID let query = Table.cheats.select(cheatID, cheatName, cheatCode, cheatDescription, cheatActivation, cheatDeviceID, Table.cheats[categoryID], categoryName, categoryDescription) .filter(romID == romIDValue) .join(Table.cheatCategories, on: Table.cheats[categoryID] == Table.cheatCategories[categoryID]) .order(cheatName) let rows = try self.connection.prepare(query) let results = rows.compactMap { (row) -> CheatMetadata? in guard case let deviceID = Int16(row[cheatDeviceID]), let device = CheatDevice(rawValue: deviceID) else { return nil } let id = row[Table.cheats[categoryID]] let category = CheatCategory(id: id, name: row[categoryName], categoryDescription: row[categoryDescription]) let metadata = CheatMetadata(id: row[cheatID], name: row[cheatName], code: row[cheatCode], description: row[cheatDescription], activationHint: row[cheatActivation], device: device, category: category) return metadata } return results } }