diff --git a/Delta/Database/OpenVGDB/GameMetadata.swift b/Delta/Database/OpenVGDB/GameMetadata.swift index 584dac4..666daa5 100644 --- a/Delta/Database/OpenVGDB/GameMetadata.swift +++ b/Delta/Database/OpenVGDB/GameMetadata.swift @@ -8,9 +8,32 @@ import Foundation -// Must be a class (not struct) so it can be used with Objective-C generics -class GameMetadata +// Must be an NSObject subclass so it can be used with RSTCellContentDataSource. +class GameMetadata: NSObject { - var name: String? - var artworkURL: URL? + let identifier: Int + + let name: String? + let artworkURL: URL? + + init(identifier: Int, name: String?, artworkURL: URL?) + { + self.name = name + self.identifier = identifier + self.artworkURL = artworkURL + } +} + +extension GameMetadata +{ + override var hash: Int { + return self.identifier.hashValue + } + + override func isEqual(_ object: Any?) -> Bool + { + guard let metadata = object as? GameMetadata else { return false } + + return self.identifier == metadata.identifier + } } diff --git a/Delta/Database/OpenVGDB/GamesDatabase.swift b/Delta/Database/OpenVGDB/GamesDatabase.swift index 97d00bc..43952e0 100644 --- a/Delta/Database/OpenVGDB/GamesDatabase.swift +++ b/Delta/Database/OpenVGDB/GamesDatabase.swift @@ -9,6 +9,11 @@ import Foundation import SQLite +private extension UserDefaults +{ + @NSManaged var previousGamesDatabaseVersion: Int +} + extension ExpressionType { static var name: SQLite.Expression { @@ -19,13 +24,17 @@ extension ExpressionType return SQLite.Expression("releaseCoverFront") } - static var hash: SQLite.Expression { + static var sha1Hash: SQLite.Expression { return SQLite.Expression("romHashSHA1") } static var romID: SQLite.Expression { return SQLite.Expression("romID") } + + static var releaseID: SQLite.Expression { + return SQLite.Expression("releaseID") + } } extension Table @@ -57,6 +66,8 @@ extension GamesDatabase class GamesDatabase { + static let version = -1 + fileprivate let connection: Connection init() throws @@ -71,28 +82,36 @@ class GamesDatabase { throw Error.connection(error) } + + self.invalidateVirtualTableIfNeeded() } func metadataResults(forGameName gameName: String) -> [GameMetadata] { + let releaseID = Expression.releaseID let name = Expression.name let artworkAddress = Expression.artworkAddress - let query = VirtualTable.search.select(name, artworkAddress).filter(name.match(gameName + "*")) + let query = VirtualTable.search.select(releaseID, name, artworkAddress).filter(name.match(gameName + "*")) do { let rows = try self.connection.prepare(query) - let results = rows.map { row -> GameMetadata in - let metadata = GameMetadata() - metadata.name = row[name] - + let results = rows.map { (row) -> GameMetadata in + + let artworkURL: URL? if let address = row[artworkAddress] { - metadata.artworkURL = URL(string: address) + artworkURL = URL(string: address) + } + else + { + artworkURL = nil } + + let metadata = GameMetadata(identifier: row[releaseID], name: row[name], artworkURL: artworkURL) return metadata } @@ -118,26 +137,31 @@ class GamesDatabase func metadata(for game: Game) -> GameMetadata? { + let releaseID = Expression.releaseID let name = Expression.name let artworkAddress = Expression.artworkAddress - let hash = Expression.hash + + let sha1Hash = Expression.sha1Hash let romID = Expression.romID let gameHash = game.identifier.uppercased() - let query = Table.roms.select(name, artworkAddress).filter(hash == gameHash).join(Table.releases, on: Table.roms[romID] == Table.releases[romID]) + let query = Table.roms.select(releaseID, name, artworkAddress).filter(sha1Hash == gameHash).join(Table.releases, on: Table.roms[romID] == Table.releases[romID]) do { if let row = try self.connection.pluck(query) { - let metadata = GameMetadata() - metadata.name = row[name] - + let artworkURL: URL? if let address = row[artworkAddress] { - metadata.artworkURL = URL(string: address) + artworkURL = URL(string: address) + } + else + { + artworkURL = nil } + let metadata = GameMetadata(identifier: row[releaseID], name: row[name], artworkURL: artworkURL) return metadata } } @@ -152,16 +176,33 @@ class GamesDatabase private extension GamesDatabase { + func invalidateVirtualTableIfNeeded() + { + guard UserDefaults.standard.previousGamesDatabaseVersion != GamesDatabase.version else { return } + + do + { + try self.connection.run(VirtualTable.search.drop(ifExists: true)) + + UserDefaults.standard.previousGamesDatabaseVersion = GamesDatabase.version + } + catch + { + print(error) + } + } + func prepareFTS() -> Bool { let name = Expression.name let artworkAddress = Expression.artworkAddress + let releaseID = Expression.releaseID do { - try self.connection.run(VirtualTable.search.create(.FTS4([name, artworkAddress], tokenize: .Unicode61()))) + try self.connection.run(VirtualTable.search.create(.FTS4([releaseID, name, artworkAddress], tokenize: .Unicode61()))) - let update = VirtualTable.search.insert(Table.releases.select(name, artworkAddress)) + let update = VirtualTable.search.insert(Table.releases.select(releaseID, name, artworkAddress)) _ = try self.connection.run(update) } catch