Adds ability to search games database to change game artwork
Searching database is done via GamesDatabaseBrowserViewController
This commit is contained in:
parent
54da484423
commit
45c18cc8e2
@ -76,6 +76,8 @@
|
||||
BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.swift */; };
|
||||
BF7AE8081C2E858400B1B5BC /* PauseMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7AE8041C2E858400B1B5BC /* PauseMenuViewController.swift */; };
|
||||
BF7AE80A1C2E8C7600B1B5BC /* UIColor+Delta.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7AE8091C2E8C7600B1B5BC /* UIColor+Delta.swift */; };
|
||||
BF95E2771E4977BF0030E7AD /* GameMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF95E2761E4977BF0030E7AD /* GameMetadata.swift */; };
|
||||
BF95E2791E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF95E2781E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift */; };
|
||||
BF99A5971DC2F9C400468E9E /* ControllerSkinTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF99A5961DC2F9C400468E9E /* ControllerSkinTableViewCell.swift */; };
|
||||
BF99C6941D0A9AA600BA92BC /* SNESDeltaCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFC134E01AAD82460087AD7B /* SNESDeltaCore.framework */; };
|
||||
BF99C6951D0A9AA600BA92BC /* SNESDeltaCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFC134E01AAD82460087AD7B /* SNESDeltaCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
@ -156,7 +158,7 @@
|
||||
BF5942681E09BBD00051894B /* GridCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GridCollectionViewCell.swift; path = "Components/Collection View/GridCollectionViewCell.swift"; sourceTree = "<group>"; };
|
||||
BF5942691E09BBD00051894B /* GridCollectionViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GridCollectionViewLayout.swift; path = "Components/Collection View/GridCollectionViewLayout.swift"; sourceTree = "<group>"; };
|
||||
BF59426D1E09BC5D0051894B /* DatabaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DatabaseManager.swift; path = Database/DatabaseManager.swift; sourceTree = "<group>"; };
|
||||
BF59426E1E09BC5D0051894B /* GamesDatabase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GamesDatabase.swift; path = Database/GamesDatabase.swift; sourceTree = "<group>"; };
|
||||
BF59426E1E09BC5D0051894B /* GamesDatabase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GamesDatabase.swift; path = Database/OpenVGDB/GamesDatabase.swift; sourceTree = "<group>"; };
|
||||
BF5942721E09BC700051894B /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; name = Model.xcdatamodel; path = Database/Model/Model.xcdatamodel; sourceTree = "<group>"; };
|
||||
BF5942771E09BC830051894B /* Cheat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Cheat.swift; path = Database/Model/Human/Cheat.swift; sourceTree = "<group>"; };
|
||||
BF5942781E09BC830051894B /* ControllerSkin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ControllerSkin.swift; path = Database/Model/Human/ControllerSkin.swift; sourceTree = "<group>"; };
|
||||
@ -184,6 +186,8 @@
|
||||
BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UILabel+FontSize.swift"; sourceTree = "<group>"; };
|
||||
BF7AE8041C2E858400B1B5BC /* PauseMenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PauseMenuViewController.swift; path = "Pause Menu/PauseMenuViewController.swift"; sourceTree = "<group>"; };
|
||||
BF7AE8091C2E8C7600B1B5BC /* UIColor+Delta.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Delta.swift"; sourceTree = "<group>"; };
|
||||
BF95E2761E4977BF0030E7AD /* GameMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameMetadata.swift; path = Database/OpenVGDB/GameMetadata.swift; sourceTree = "<group>"; };
|
||||
BF95E2781E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GamesDatabaseBrowserViewController.swift; path = Database/OpenVGDB/GamesDatabaseBrowserViewController.swift; sourceTree = "<group>"; };
|
||||
BF99A5961DC2F9C400468E9E /* ControllerSkinTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ControllerSkinTableViewCell.swift; path = "Controller Skins/ControllerSkinTableViewCell.swift"; sourceTree = "<group>"; };
|
||||
BF9F4FCE1AAD7B87004C9500 /* DeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BFAA1FEC1B8AA4FA00495943 /* Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
|
||||
@ -323,8 +327,8 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF59426D1E09BC5D0051894B /* DatabaseManager.swift */,
|
||||
BF59426E1E09BC5D0051894B /* GamesDatabase.swift */,
|
||||
BF5942711E09BC690051894B /* Model */,
|
||||
BF95E2751E49763D0030E7AD /* OpenVGDB */,
|
||||
);
|
||||
name = Database;
|
||||
sourceTree = "<group>";
|
||||
@ -413,6 +417,16 @@
|
||||
name = Segues;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF95E2751E49763D0030E7AD /* OpenVGDB */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF59426E1E09BC5D0051894B /* GamesDatabase.swift */,
|
||||
BF95E2761E4977BF0030E7AD /* GameMetadata.swift */,
|
||||
BF95E2781E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift */,
|
||||
);
|
||||
name = OpenVGDB;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF9F4FCD1AAD7B25004C9500 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -730,6 +744,7 @@
|
||||
BF5942701E09BC5D0051894B /* GamesDatabase.swift in Sources */,
|
||||
BF34FA071CF0F510006624C7 /* EditCheatViewController.swift in Sources */,
|
||||
BF5942881E09BC8B0051894B /* _Game.swift in Sources */,
|
||||
BF95E2771E4977BF0030E7AD /* GameMetadata.swift in Sources */,
|
||||
BF11734D1DA32A5200047DF8 /* GameType+Localization.swift in Sources */,
|
||||
BFFC461F1D59823500AF2CC6 /* GamesStoryboardSegue.swift in Sources */,
|
||||
BF2B98E61C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift in Sources */,
|
||||
@ -741,6 +756,7 @@
|
||||
BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */,
|
||||
BF5942641E09BBB10051894B /* LoadControllerSkinImageOperation.swift in Sources */,
|
||||
BF5942871E09BC8B0051894B /* _ControllerSkin.swift in Sources */,
|
||||
BF95E2791E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift in Sources */,
|
||||
BFD097211D3A01B8005A44C2 /* SaveStatesViewController.swift in Sources */,
|
||||
BF3540021C5DA3D500C1184C /* PauseStoryboardSegue.swift in Sources */,
|
||||
BFC314771E0C8CFC0056E3A8 /* GameType+Delta.swift in Sources */,
|
||||
@ -855,6 +871,7 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.3;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_SWIFT_FLAGS = "-Xfrontend -debug-time-function-bodies";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.rileytestut.Delta;
|
||||
PRODUCT_NAME = Delta;
|
||||
SDKROOT = iphoneos;
|
||||
@ -897,6 +914,7 @@
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.3;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
OTHER_SWIFT_FLAGS = "-Xfrontend -debug-time-function-bodies";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.rileytestut.Delta;
|
||||
PRODUCT_NAME = Delta;
|
||||
SDKROOT = iphoneos;
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11760" systemVersion="16B2657" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="SPq-Bk-fQl">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11762" systemVersion="16C68" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="SPq-Bk-fQl">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11755"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@ -48,11 +48,59 @@
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
<connections>
|
||||
<segue destination="6bq-zy-UZU" kind="presentation" identifier="gamesDatabaseBrowser" id="7TT-mP-bjt"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="JYx-xE-nis" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1036" y="1002"/>
|
||||
</scene>
|
||||
<!--Games Database-->
|
||||
<scene sceneID="S7I-gw-igt">
|
||||
<objects>
|
||||
<tableViewController id="SB6-jW-dhZ" customClass="GamesDatabaseBrowserViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" id="bJf-Sa-ZOX">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" textLabel="aZq-iY-vhF" style="IBUITableViewCellStyleDefault" id="4cJ-4B-Kgt">
|
||||
<rect key="frame" x="0.0" y="28" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="4cJ-4B-Kgt" id="7ze-s0-mpI">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="aZq-iY-vhF">
|
||||
<rect key="frame" x="15" y="0.0" width="345" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="SB6-jW-dhZ" id="2aq-ZA-84E"/>
|
||||
<outlet property="delegate" destination="SB6-jW-dhZ" id="WgY-cp-m7K"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Games Database" id="rwF-kd-avR">
|
||||
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="BnB-5n-Rff">
|
||||
<connections>
|
||||
<segue destination="mUU-ug-yNs" kind="unwind" unwindAction="unwindFromGamesDatabaseBrowserWith:" id="zdg-Az-WwQ"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="f3a-hX-Qnu" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
<exit id="mUU-ug-yNs" userLabel="Exit" sceneMemberID="exit"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2652" y="1002"/>
|
||||
</scene>
|
||||
<!--Game Collection View Controller-->
|
||||
<scene sceneID="qNA-NP-TiF">
|
||||
<objects>
|
||||
@ -84,13 +132,14 @@
|
||||
</collectionView>
|
||||
<connections>
|
||||
<segue destination="X2o-q6-XD5" kind="unwind" identifier="unwindFromGames" unwindAction="unwindFromGamesViewControllerWith:" id="k8C-Xn-maU"/>
|
||||
<segue destination="MPk-bF-nkj" kind="presentation" identifier="showSaveStates" customClass="SaveStatesStoryboardSegue" customModule="Delta" customModuleProvider="target" id="1Xp-2J-0cq"/>
|
||||
<segue destination="MPk-bF-nkj" kind="presentation" identifier="saveStates" customClass="SaveStatesStoryboardSegue" customModule="Delta" customModuleProvider="target" id="1Xp-2J-0cq"/>
|
||||
<segue destination="6bq-zy-UZU" kind="presentation" identifier="gamesDatabaseBrowser" id="mzX-Bb-MaX"/>
|
||||
</connections>
|
||||
</collectionViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="bW1-t8-idm" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
<exit id="X2o-q6-XD5" userLabel="Exit" sceneMemberID="exit"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1764" y="1719"/>
|
||||
<point key="canvasLocation" x="1855" y="1719"/>
|
||||
</scene>
|
||||
<!--Launch View Controller-->
|
||||
<scene sceneID="p7y-IT-nlb">
|
||||
@ -219,13 +268,13 @@
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
<segue destination="Eae-Qk-9MI" kind="relationship" relationship="rootViewController" id="1Jh-Zf-ntp"/>
|
||||
<segue destination="WQV-Du-4IA" kind="unwind" identifier="unwindFromSaveStates" customClass="SaveStatesStoryboardUnwindSegue" customModule="Delta" customModuleProvider="target" unwindAction="unwindFromSaveStatesViewController:" id="dwO-iv-XDr"/>
|
||||
<segue destination="WQV-Du-4IA" kind="unwind" identifier="unwindFromSaveStates" customClass="SaveStatesStoryboardUnwindSegue" customModule="Delta" customModuleProvider="target" unwindAction="unwindFromSaveStatesViewControllerWith:" id="dwO-iv-XDr"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="htj-tq-2KP" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
<exit id="WQV-Du-4IA" userLabel="Exit" sceneMemberID="exit"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2562" y="1718"/>
|
||||
<point key="canvasLocation" x="2652" y="1718"/>
|
||||
</scene>
|
||||
<!--saveStatesViewController-->
|
||||
<scene sceneID="f1R-Kb-FOU">
|
||||
@ -235,13 +284,32 @@
|
||||
</viewControllerPlaceholder>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="eln-PZ-00u" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="3319" y="1717"/>
|
||||
<point key="canvasLocation" x="3409" y="1716"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="REv-V5-eEz">
|
||||
<objects>
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="6bq-zy-UZU" sceneMemberID="viewController">
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" id="uzY-vR-coL">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
<segue destination="SB6-jW-dhZ" kind="relationship" relationship="rootViewController" id="b0w-Fq-hrk"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Hr9-N6-XXA" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1854" y="1002"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="Settings_Button" width="22" height="22"/>
|
||||
</resources>
|
||||
<inferredMetricsTieBreakers>
|
||||
<segue reference="mzX-Bb-MaX"/>
|
||||
<segue reference="Tey-6Z-UHp"/>
|
||||
</inferredMetricsTieBreakers>
|
||||
</document>
|
||||
|
||||
@ -57,7 +57,7 @@ final class DatabaseManager: NSPersistentContainer
|
||||
{
|
||||
static let shared = DatabaseManager()
|
||||
|
||||
fileprivate let gamesDatabase: GamesDatabase?
|
||||
fileprivate var gamesDatabase: GamesDatabase? = nil
|
||||
|
||||
private init()
|
||||
{
|
||||
@ -66,22 +66,6 @@ final class DatabaseManager: NSPersistentContainer
|
||||
let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL)
|
||||
else { fatalError("Core Data model cannot be found. Aborting.") }
|
||||
|
||||
do
|
||||
{
|
||||
if let gamesDatabaseURL = Bundle.main.url(forResource: "openvgdb", withExtension: "sqlite")
|
||||
{
|
||||
self.gamesDatabase = try GamesDatabase(fileURL: gamesDatabaseURL)
|
||||
}
|
||||
else
|
||||
{
|
||||
self.gamesDatabase = nil
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
self.gamesDatabase = nil
|
||||
print(error)
|
||||
}
|
||||
|
||||
super.init(name: "Delta", managedObjectModel: managedObjectModel)
|
||||
|
||||
@ -135,6 +119,21 @@ private extension DatabaseManager
|
||||
print("Failed to import standard controller skins:", error)
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
if !FileManager.default.fileExists(atPath: DatabaseManager.gamesDatabaseURL.path)
|
||||
{
|
||||
guard let bundleURL = Bundle.main.url(forResource: "openvgdb", withExtension: "sqlite") else { throw GamesDatabase.Error.doesNotExist }
|
||||
try FileManager.default.copyItem(at: bundleURL, to: DatabaseManager.gamesDatabaseURL)
|
||||
}
|
||||
|
||||
self.gamesDatabase = try GamesDatabase()
|
||||
}
|
||||
catch
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
|
||||
completion()
|
||||
|
||||
}
|
||||
@ -179,11 +178,13 @@ extension DatabaseManager
|
||||
let filename = identifier + "." + url.pathExtension
|
||||
|
||||
let game = Game.insertIntoManagedObjectContext(context)
|
||||
game.name = url.deletingPathExtension().lastPathComponent
|
||||
game.identifier = identifier
|
||||
game.filename = filename
|
||||
game.artworkURL = self.gamesDatabase?.artworkURL(for: game)
|
||||
|
||||
let databaseMetadata = self.gamesDatabase?.metadata(for: game)
|
||||
game.name = databaseMetadata?.name ?? url.deletingPathExtension().lastPathComponent
|
||||
game.artworkURL = databaseMetadata?.artworkURL
|
||||
|
||||
let gameCollection = GameCollection.gameSystemCollectionForPathExtension(url.pathExtension, inManagedObjectContext: context)
|
||||
game.type = GameType(rawValue: gameCollection.identifier)
|
||||
game.gameCollections.insert(gameCollection)
|
||||
@ -439,6 +440,12 @@ extension DatabaseManager
|
||||
|
||||
return databaseDirectoryURL
|
||||
}
|
||||
|
||||
class var gamesDatabaseURL: URL
|
||||
{
|
||||
let gamesDatabaseURL = self.defaultDirectoryURL().appendingPathComponent("openvgdb.sqlite")
|
||||
return gamesDatabaseURL
|
||||
}
|
||||
|
||||
class var gamesDirectoryURL: URL
|
||||
{
|
||||
|
||||
@ -1,48 +0,0 @@
|
||||
//
|
||||
// GamesDatabase.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 11/16/16.
|
||||
// Copyright © 2016 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SQLite
|
||||
|
||||
class GamesDatabase
|
||||
{
|
||||
private let connection: Connection
|
||||
|
||||
init(fileURL: URL) throws
|
||||
{
|
||||
self.connection = try Connection(fileURL.path)
|
||||
}
|
||||
|
||||
func artworkURL(for game: Game) -> URL?
|
||||
{
|
||||
let roms = Table("ROMs")
|
||||
let releases = Table("RELEASES")
|
||||
|
||||
let hash = Expression<String>("romHashSHA1")
|
||||
let romID = Expression<Int>("romID")
|
||||
let artworkAddress = Expression<String?>("releaseCoverFront")
|
||||
|
||||
let gameHash = game.identifier.uppercased()
|
||||
let query = roms.select(artworkAddress).filter(hash == gameHash).join(releases, on: roms[romID] == releases[romID])
|
||||
|
||||
do
|
||||
{
|
||||
if let row = try self.connection.pluck(query), let address = row[artworkAddress]
|
||||
{
|
||||
let url = URL(string: address)
|
||||
return url
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
16
Delta/Database/OpenVGDB/GameMetadata.swift
Normal file
16
Delta/Database/OpenVGDB/GameMetadata.swift
Normal file
@ -0,0 +1,16 @@
|
||||
//
|
||||
// GameMetadata.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 2/6/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// Must be a class (not struct) so it can be used with Objective-C generics
|
||||
class GameMetadata
|
||||
{
|
||||
var name: String?
|
||||
var artworkURL: URL?
|
||||
}
|
||||
175
Delta/Database/OpenVGDB/GamesDatabase.swift
Normal file
175
Delta/Database/OpenVGDB/GamesDatabase.swift
Normal file
@ -0,0 +1,175 @@
|
||||
//
|
||||
// GamesDatabase.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 11/16/16.
|
||||
// Copyright © 2016 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SQLite
|
||||
|
||||
extension ExpressionType
|
||||
{
|
||||
static var name: SQLite.Expression<String?> {
|
||||
return SQLite.Expression<String?>("releaseTitleName")
|
||||
}
|
||||
|
||||
static var artworkAddress: SQLite.Expression<String?> {
|
||||
return SQLite.Expression<String?>("releaseCoverFront")
|
||||
}
|
||||
|
||||
static var hash: SQLite.Expression<String> {
|
||||
return SQLite.Expression<String>("romHashSHA1")
|
||||
}
|
||||
|
||||
static var romID: SQLite.Expression<Int> {
|
||||
return SQLite.Expression<Int>("romID")
|
||||
}
|
||||
}
|
||||
|
||||
extension Table
|
||||
{
|
||||
static var roms: Table {
|
||||
return Table("ROMs")
|
||||
}
|
||||
|
||||
static var releases: Table {
|
||||
return Table("RELEASES")
|
||||
}
|
||||
}
|
||||
|
||||
extension VirtualTable
|
||||
{
|
||||
static var search: VirtualTable {
|
||||
return VirtualTable("Search")
|
||||
}
|
||||
}
|
||||
|
||||
extension GamesDatabase
|
||||
{
|
||||
enum Error: Swift.Error
|
||||
{
|
||||
case doesNotExist
|
||||
case connection(Swift.Error)
|
||||
}
|
||||
}
|
||||
|
||||
class GamesDatabase
|
||||
{
|
||||
fileprivate let connection: Connection
|
||||
|
||||
init() throws
|
||||
{
|
||||
let fileURL = DatabaseManager.gamesDatabaseURL
|
||||
|
||||
do
|
||||
{
|
||||
self.connection = try Connection(fileURL.path)
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw Error.connection(error)
|
||||
}
|
||||
}
|
||||
|
||||
func metadataResults(forGameName gameName: String) -> [GameMetadata]
|
||||
{
|
||||
let name = Expression<Any>.name
|
||||
let artworkAddress = Expression<Any>.artworkAddress
|
||||
|
||||
let query = VirtualTable.search.select(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]
|
||||
|
||||
if let address = row[artworkAddress]
|
||||
{
|
||||
metadata.artworkURL = URL(string: address)
|
||||
}
|
||||
|
||||
return metadata
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
catch SQLite.Result.error(_, let code, _) where code == 1
|
||||
{
|
||||
// Table does not exist
|
||||
|
||||
if self.prepareFTS()
|
||||
{
|
||||
return self.metadataResults(forGameName: gameName)
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
func metadata(for game: Game) -> GameMetadata?
|
||||
{
|
||||
let name = Expression<Any>.name
|
||||
let artworkAddress = Expression<Any>.artworkAddress
|
||||
let hash = Expression<Any>.hash
|
||||
let romID = Expression<Any>.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])
|
||||
|
||||
do
|
||||
{
|
||||
if let row = try self.connection.pluck(query)
|
||||
{
|
||||
let metadata = GameMetadata()
|
||||
metadata.name = row[name]
|
||||
|
||||
if let address = row[artworkAddress]
|
||||
{
|
||||
metadata.artworkURL = URL(string: address)
|
||||
}
|
||||
|
||||
return metadata
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private extension GamesDatabase
|
||||
{
|
||||
func prepareFTS() -> Bool
|
||||
{
|
||||
let name = Expression<Any>.name
|
||||
let artworkAddress = Expression<Any>.artworkAddress
|
||||
|
||||
do
|
||||
{
|
||||
try self.connection.run(VirtualTable.search.create(.FTS4([name, artworkAddress], tokenize: .Unicode61())))
|
||||
|
||||
let update = VirtualTable.search.insert(Table.releases.select(name, artworkAddress))
|
||||
_ = try self.connection.run(update)
|
||||
}
|
||||
catch
|
||||
{
|
||||
print(error)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
143
Delta/Database/OpenVGDB/GamesDatabaseBrowserViewController.swift
Normal file
143
Delta/Database/OpenVGDB/GamesDatabaseBrowserViewController.swift
Normal file
@ -0,0 +1,143 @@
|
||||
//
|
||||
// GamesDatabaseBrowserViewController.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 2/6/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Roxas
|
||||
|
||||
class GamesDatabaseBrowserViewController: UITableViewController
|
||||
{
|
||||
var selectionHandler: ((GameMetadata) -> Void)?
|
||||
|
||||
fileprivate let database: GamesDatabase?
|
||||
fileprivate let dataSource: RSTArrayTableViewDataSource<GameMetadata>
|
||||
|
||||
fileprivate let operationQueue = RSTOperationQueue()
|
||||
fileprivate let imageCache = NSCache<NSURL, UIImage>()
|
||||
|
||||
override init(style: UITableViewStyle) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder)
|
||||
{
|
||||
do
|
||||
{
|
||||
self.database = try GamesDatabase()
|
||||
}
|
||||
catch
|
||||
{
|
||||
self.database = nil
|
||||
print(error)
|
||||
}
|
||||
|
||||
self.dataSource = RSTArrayTableViewDataSource<GameMetadata>(items: [])
|
||||
|
||||
let titleText = NSLocalizedString("Games Database", comment: "")
|
||||
let detailText = NSLocalizedString("To search the database, type the name of a game in the search bar.", comment: "")
|
||||
|
||||
let placeholderView = RSTBackgroundView()
|
||||
placeholderView.textLabel.text = titleText
|
||||
placeholderView.detailTextLabel.text = detailText
|
||||
self.dataSource.placeholderView = placeholderView
|
||||
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
self.dataSource.cellConfigurationHandler = self.configure(cell:with:for:)
|
||||
|
||||
if let database = self.database
|
||||
{
|
||||
self.dataSource.searchController.searchHandler = { [unowned database, unowned dataSource] (searchValue, previousSearchValue) in
|
||||
|
||||
return RSTBlockOperation(executionBlock: { [unowned database, unowned dataSource] (operation) in
|
||||
let results = database.metadataResults(forGameName: searchValue.text)
|
||||
|
||||
guard !operation.isCancelled else { return }
|
||||
|
||||
dataSource.items = results
|
||||
|
||||
if searchValue.text == ""
|
||||
{
|
||||
rst_dispatch_sync_on_main_thread {
|
||||
placeholderView.textLabel.text = titleText
|
||||
placeholderView.detailTextLabel.text = detailText
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rst_dispatch_sync_on_main_thread {
|
||||
placeholderView.textLabel.text = NSLocalizedString("No Games Found", comment: "")
|
||||
placeholderView.detailTextLabel.text = NSLocalizedString("Please make sure the name is correct, or try searching for another game.", comment: "")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
self.definesPresentationContext = true
|
||||
}
|
||||
|
||||
override func viewDidLoad()
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.tableView.dataSource = self.dataSource
|
||||
self.tableView.tableHeaderView = self.dataSource.searchController.searchBar
|
||||
self.tableView.rowHeight = 64
|
||||
}
|
||||
|
||||
override func didReceiveMemoryWarning()
|
||||
{
|
||||
super.didReceiveMemoryWarning()
|
||||
}
|
||||
|
||||
func configure(cell: UITableViewCell, with metadata: GameMetadata, for indexPath: IndexPath)
|
||||
{
|
||||
cell.textLabel?.text = metadata.name ?? NSLocalizedString("Unknown", comment: "")
|
||||
cell.textLabel?.numberOfLines = 2
|
||||
|
||||
cell.imageView?.image = #imageLiteral(resourceName: "BoxArt")
|
||||
cell.imageView?.contentMode = .scaleAspectFit
|
||||
|
||||
if let artworkURL = metadata.artworkURL
|
||||
{
|
||||
let operation = LoadImageURLOperation(url: artworkURL)
|
||||
operation.resultsCache = self.imageCache
|
||||
operation.resultHandler = { (image, error) in
|
||||
if let image = image
|
||||
{
|
||||
DispatchQueue.main.async {
|
||||
cell.imageView?.image = image
|
||||
cell.imageView?.superview?.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.operationQueue.addOperation(operation, forKey: indexPath as NSIndexPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension GamesDatabaseBrowserViewController
|
||||
{
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
|
||||
{
|
||||
if self.dataSource.searchController.presentingViewController != nil
|
||||
{
|
||||
self.dataSource.searchController.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
let metadata = self.dataSource.item(at: indexPath)
|
||||
self.selectionHandler?(metadata)
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath)
|
||||
{
|
||||
let operation = self.operationQueue[indexPath as NSIndexPath]
|
||||
operation?.cancel()
|
||||
}
|
||||
}
|
||||
@ -117,7 +117,7 @@ extension GameCollectionViewController
|
||||
|
||||
switch identifier
|
||||
{
|
||||
case "showSaveStates":
|
||||
case "saveStates":
|
||||
let game = sender as! Game
|
||||
|
||||
let saveStatesViewController = (segue.destination as! UINavigationController).topViewController as! SaveStatesViewController
|
||||
@ -126,6 +126,24 @@ extension GameCollectionViewController
|
||||
saveStatesViewController.mode = .loading
|
||||
saveStatesViewController.theme = self.theme
|
||||
|
||||
case "gamesDatabaseBrowser":
|
||||
let game = sender as! Game
|
||||
|
||||
let gamesDatabaseBrowserViewController = (segue.destination as! UINavigationController).topViewController as! GamesDatabaseBrowserViewController
|
||||
gamesDatabaseBrowserViewController.selectionHandler = { (metadata) in
|
||||
|
||||
DatabaseManager.shared.performBackgroundTask({ (context) in
|
||||
let temporaryGame = context.object(with: game.objectID) as! Game
|
||||
temporaryGame.artworkURL = metadata.artworkURL
|
||||
context.saveWithErrorLogging()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
gamesDatabaseBrowserViewController.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
case "unwindFromGames":
|
||||
let destinationViewController = segue.destination as! GameViewController
|
||||
let cell = sender as! UICollectionViewCell
|
||||
@ -166,7 +184,14 @@ extension GameCollectionViewController
|
||||
|
||||
default: break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@IBAction private func unwindFromSaveStatesViewController(with segue: UIStoryboardSegue)
|
||||
{
|
||||
}
|
||||
|
||||
@IBAction private func unwindFromGamesDatabaseBrowser(with segue: UIStoryboardSegue)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,6 +204,7 @@ private extension GameCollectionViewController
|
||||
let fetchRequest = Game.rst_fetchRequest() as! NSFetchRequest<Game>
|
||||
fetchRequest.predicate = NSPredicate(format: "ANY %K == %@", #keyPath(Game.gameCollections), self.gameCollection)
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(Game.name), ascending: true)]
|
||||
fetchRequest.returnsObjectsAsFaults = false
|
||||
|
||||
self.dataSource = RSTFetchedResultsCollectionViewDataSource(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
|
||||
self.dataSource.cellConfigurationHandler = { [unowned self] (cell, item, indexPath) in
|
||||
@ -205,7 +231,7 @@ private extension GameCollectionViewController
|
||||
cell.maximumImageSize = CGSize(width: 90, height: 90)
|
||||
cell.textLabel.text = game.name
|
||||
cell.textLabel.textColor = UIColor.gray
|
||||
|
||||
|
||||
if let artworkURL = game.artworkURL, !ignoreImageOperations
|
||||
{
|
||||
cell.imageView.sd_setImage(with: artworkURL, placeholderImage: #imageLiteral(resourceName: "BoxArt"), options: .continueInBackground) { (image, error, type, url) in
|
||||
@ -250,6 +276,10 @@ private extension GameCollectionViewController
|
||||
self.rename(game)
|
||||
})
|
||||
|
||||
let changeArtworkAction = Action(title: NSLocalizedString("Change Artwork", comment: ""), style: .default) { [unowned self] action in
|
||||
self.changeArtwork(for: game)
|
||||
}
|
||||
|
||||
let shareAction = Action(title: NSLocalizedString("Share", comment: ""), style: .default, action: { [unowned self] action in
|
||||
self.share(game)
|
||||
})
|
||||
@ -264,8 +294,8 @@ private extension GameCollectionViewController
|
||||
|
||||
switch game.type
|
||||
{
|
||||
case GameType.unknown: return [cancelAction, renameAction, shareAction, deleteAction]
|
||||
default: return [cancelAction, renameAction, shareAction, saveStatesAction, deleteAction]
|
||||
case GameType.unknown: return [cancelAction, renameAction, changeArtworkAction, shareAction, deleteAction]
|
||||
default: return [cancelAction, renameAction, changeArtworkAction, shareAction, saveStatesAction, deleteAction]
|
||||
}
|
||||
}
|
||||
|
||||
@ -289,7 +319,7 @@ private extension GameCollectionViewController
|
||||
|
||||
func viewSaveStates(for game: Game)
|
||||
{
|
||||
self.performSegue(withIdentifier: "showSaveStates", sender: game)
|
||||
self.performSegue(withIdentifier: "saveStates", sender: game)
|
||||
}
|
||||
|
||||
func rename(_ game: Game)
|
||||
@ -330,6 +360,11 @@ private extension GameCollectionViewController
|
||||
self._renameAction = nil
|
||||
}
|
||||
|
||||
func changeArtwork(for game: Game)
|
||||
{
|
||||
self.performSegue(withIdentifier: "gamesDatabaseBrowser", sender: game)
|
||||
}
|
||||
|
||||
func share(_ game: Game)
|
||||
{
|
||||
let temporaryDirectory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
||||
|
||||
@ -142,10 +142,6 @@ extension GamesViewController
|
||||
@IBAction private func unwindFromSettingsViewController(_ segue: UIStoryboardSegue)
|
||||
{
|
||||
}
|
||||
|
||||
@IBAction private func unwindFromSaveStatesViewController(_ segue: UIStoryboardSegue)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UI -
|
||||
|
||||
Loading…
Reference in New Issue
Block a user