Adds support for syncing with Dropbox

This commit is contained in:
Riley Testut 2019-03-20 00:14:12 -07:00
parent 8f6b8d763a
commit 483ad69678
17 changed files with 366 additions and 105 deletions

View File

@ -33,6 +33,8 @@
BF1173501DA32CF600047DF8 /* ControllersSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF11734F1DA32CF600047DF8 /* ControllersSettingsViewController.swift */; };
BF13A7561D5D29B0000BB055 /* PreviewGameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF13A7551D5D29B0000BB055 /* PreviewGameViewController.swift */; };
BF13A7581D5D2FD9000BB055 /* EmulatorCore+Cheats.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF13A7571D5D2FD9000BB055 /* EmulatorCore+Cheats.swift */; };
BF144C642238511400C387E1 /* Harmony_Dropbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFDE2CC6222DF345008038E0 /* Harmony_Dropbox.framework */; };
BF144C652238511400C387E1 /* Harmony_Dropbox.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFDE2CC6222DF345008038E0 /* Harmony_Dropbox.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
BF15AF841F54B43B009B6AAB /* ActionInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF15AF831F54B43B009B6AAB /* ActionInput.swift */; };
BF18B61F1E2985F900F70067 /* UIAlertController+Importing.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18B61E1E2985F900F70067 /* UIAlertController+Importing.swift */; };
BF1DAD5D1D9F576000E752A7 /* SystemControllerSkinsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1DAD5C1D9F576000E752A7 /* SystemControllerSkinsViewController.swift */; };
@ -132,6 +134,10 @@
BFD097211D3A01B8005A44C2 /* SaveStatesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3540041C5DA70400C1184C /* SaveStatesViewController.swift */; };
BFDB3418219E4B1700595A62 /* SyncStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB3417219E4B1700595A62 /* SyncStatusViewController.swift */; };
BFDD04F11D5E2C27002D450E /* GameCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDD04F01D5E2C27002D450E /* GameCollectionViewController.swift */; };
BFDE2CD1222DF36A008038E0 /* SwiftyDropbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFDE2CC8222DF345008038E0 /* SwiftyDropbox.framework */; };
BFDE2CD2222DF36A008038E0 /* SwiftyDropbox.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFDE2CC8222DF345008038E0 /* SwiftyDropbox.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
BFDE2CD3222DF36A008038E0 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFDE2CC7222DF345008038E0 /* Alamofire.framework */; };
BFDE2CD4222DF36A008038E0 /* Alamofire.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFDE2CC7222DF345008038E0 /* Alamofire.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
BFE022A01F5B57FF0052D888 /* PopoverMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE0229F1F5B577D0052D888 /* PopoverMenuButton.swift */; };
BFE4269E1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE4269D1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift */; };
BFE593CA21F3F8B7003412A6 /* GameSave.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE593C921F3F8B7003412A6 /* GameSave.swift */; };
@ -161,11 +167,14 @@
dstSubfolderSpec = 10;
files = (
BF9F4FD01AAD7B87004C9500 /* DeltaCore.framework in Embed Frameworks */,
BFDE2CD2222DF36A008038E0 /* SwiftyDropbox.framework in Embed Frameworks */,
BF48F75C219A1F8A00BC2FC1 /* Harmony_Drive.framework in Embed Frameworks */,
BF48F756219A1EF000BC2FC1 /* Harmony.framework in Embed Frameworks */,
BFF0742D1E9DC17500ACDF4A /* GBCDeltaCore.framework in Embed Frameworks */,
BFEC732E1AAECC4A00650035 /* Roxas.framework in Embed Frameworks */,
BF0418151D01E93400E85BCF /* GBADeltaCore.framework in Embed Frameworks */,
BFDE2CD4222DF36A008038E0 /* Alamofire.framework in Embed Frameworks */,
BF144C652238511400C387E1 /* Harmony_Dropbox.framework in Embed Frameworks */,
BF98C9832204D9AB006B95AC /* NESDeltaCore.framework in Embed Frameworks */,
BF99C6951D0A9AA600BA92BC /* SNESDeltaCore.framework in Embed Frameworks */,
BF072011219A3A9D00F05DA4 /* ZIPFoundation.framework in Embed Frameworks */,
@ -293,6 +302,9 @@
BFCEA67D1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewControllerContextTransitioning+Conveniences.swift"; sourceTree = "<group>"; };
BFDB3417219E4B1700595A62 /* SyncStatusViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncStatusViewController.swift; sourceTree = "<group>"; };
BFDD04F01D5E2C27002D450E /* GameCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameCollectionViewController.swift; sourceTree = "<group>"; };
BFDE2CC6222DF345008038E0 /* Harmony_Dropbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Harmony_Dropbox.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BFDE2CC7222DF345008038E0 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Alamofire.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BFDE2CC8222DF345008038E0 /* SwiftyDropbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SwiftyDropbox.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BFE0229F1F5B577D0052D888 /* PopoverMenuButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopoverMenuButton.swift; sourceTree = "<group>"; };
BFE4269D1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaveStatesStoryboardSegue.swift; sourceTree = "<group>"; };
BFE593C921F3F8B7003412A6 /* GameSave.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameSave.swift; sourceTree = "<group>"; };
@ -321,11 +333,14 @@
files = (
BF9F4FCF1AAD7B87004C9500 /* DeltaCore.framework in Frameworks */,
BFEC732D1AAECC4A00650035 /* Roxas.framework in Frameworks */,
BFDE2CD3222DF36A008038E0 /* Alamofire.framework in Frameworks */,
BF48F75B219A1F8A00BC2FC1 /* Harmony_Drive.framework in Frameworks */,
BF99C6941D0A9AA600BA92BC /* SNESDeltaCore.framework in Frameworks */,
BF98C9822204D9AB006B95AC /* NESDeltaCore.framework in Frameworks */,
BF0418141D01E93400E85BCF /* GBADeltaCore.framework in Frameworks */,
BF072010219A3A9D00F05DA4 /* ZIPFoundation.framework in Frameworks */,
BFDE2CD1222DF36A008038E0 /* SwiftyDropbox.framework in Frameworks */,
BF144C642238511400C387E1 /* Harmony_Dropbox.framework in Frameworks */,
BFF0742C1E9DC17500ACDF4A /* GBCDeltaCore.framework in Frameworks */,
BF48F755219A1EF000BC2FC1 /* Harmony.framework in Frameworks */,
4FE8465FD28810191C3E5212 /* Pods_Delta.framework in Frameworks */,
@ -617,6 +632,9 @@
BF9F4FCD1AAD7B25004C9500 /* Frameworks */ = {
isa = PBXGroup;
children = (
BFDE2CC6222DF345008038E0 /* Harmony_Dropbox.framework */,
BFDE2CC8222DF345008038E0 /* SwiftyDropbox.framework */,
BFDE2CC7222DF345008038E0 /* Alamofire.framework */,
BF98C9812204D9A1006B95AC /* NESDeltaCore.framework */,
BF07200E219A3A9500F05DA4 /* ZIPFoundation.framework */,
BF48F754219A1EEB00BC2FC1 /* Harmony.framework */,

View File

@ -192,6 +192,10 @@
argument = "-com.apple.CoreData.MigrationDebug 1"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "-com.rileytestut.Harmony.Debug 1"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "-com.apple.CoreData.ConcurrencyDebug 1"
isEnabled = "YES">

View File

@ -9,6 +9,7 @@
import UIKit
import DeltaCore
import Harmony_Dropbox
import Fabric
import Crashlytics
@ -112,6 +113,10 @@ extension AppDelegate
return self.importControllerSkin(at: url)
}
}
else if DropboxService.shared.handleDropboxURL(url)
{
return true
}
else
{
return self.deepLinkController.handle(.url(url))

View File

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="ssH-mM-uG6">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.64" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="ssH-mM-uG6">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.47"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -18,7 +18,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.93725490199999995" green="0.93725490199999995" blue="0.95686274510000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<label key="tableFooterView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Delta 0.6.0" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Str-BY-agW">
<rect key="frame" x="0.0" y="817" width="375" height="44"/>
<rect key="frame" x="0.0" y="861" width="375" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
@ -241,14 +241,14 @@
<tableViewSection headerTitle="Syncing" id="y6U-7a-bnX" userLabel="Syncing">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="DetailCell" textLabel="4U1-fe-PIb" detailTextLabel="kLY-5g-v8n" style="IBUITableViewCellStyleValue1" id="bwW-PG-BcV">
<rect key="frame" x="0.0" y="611" width="375" height="44"/>
<rect key="frame" x="0.0" y="655" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="bwW-PG-BcV" id="RNA-99-evH">
<rect key="frame" x="0.0" y="0.0" width="341" height="43.5"/>
<rect key="frame" x="0.0" y="0.0" width="349" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Service" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="4U1-fe-PIb">
<rect key="frame" x="16" y="12" width="54" height="19.5"/>
<rect key="frame" x="15" y="12" width="54" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -268,14 +268,14 @@
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="BadgeCell" textLabel="1u0-gh-zP7" style="IBUITableViewCellStyleDefault" id="JPg-6O-DRe" customClass="BadgedTableViewCell" customModule="Delta" customModuleProvider="target">
<rect key="frame" x="0.0" y="655" width="375" height="44"/>
<rect key="frame" x="0.0" y="699" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="JPg-6O-DRe" id="zcZ-QR-Nno">
<rect key="frame" x="0.0" y="0.0" width="341" height="43.5"/>
<rect key="frame" x="0.0" y="0.0" width="349" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Status" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="1u0-gh-zP7">
<rect key="frame" x="16" y="0.0" width="324" height="43.5"/>
<rect key="frame" x="15" y="0.0" width="325" height="43.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -292,14 +292,14 @@
<tableViewSection headerTitle="3D Touch" id="fdp-8c-oOc">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="Cell" textLabel="c5i-qG-ir9" style="IBUITableViewCellStyleDefault" id="SSL-t4-QZj">
<rect key="frame" x="0.0" y="755" width="375" height="44"/>
<rect key="frame" x="0.0" y="799" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="SSL-t4-QZj" id="hQB-Iy-bzy">
<rect key="frame" x="0.0" y="0.0" width="341" height="43.5"/>
<rect key="frame" x="0.0" y="0.0" width="349" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="App Icon Shortcuts" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="c5i-qG-ir9">
<rect key="frame" x="16" y="0.0" width="324" height="43.5"/>
<rect key="frame" x="15" y="0.0" width="325" height="43.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -715,36 +715,47 @@
</objects>
<point key="canvasLocation" x="3270" y="-244"/>
</scene>
<!--Syncing Service-->
<!--Syncing-->
<scene sceneID="8nM-uV-t0b">
<objects>
<tableViewController title="Syncing Service" id="R9m-rV-VgV" customClass="SyncingServicesViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
<tableViewController title="Syncing" id="R9m-rV-VgV" customClass="SyncingServicesViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="18" id="Zsb-6q-tLe">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
<sections>
<tableViewSection headerTitle="Service" id="mIB-Au-dYz">
<tableViewSection id="m5I-He-R1D">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" textLabel="QAp-WA-1g3" style="IBUITableViewCellStyleDefault" id="vkb-8K-t7E">
<rect key="frame" x="0.0" y="55.5" width="375" height="44"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="none" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="SwitchCell" rowHeight="44" id="fIu-zg-60Y" customClass="SwitchTableViewCell">
<rect key="frame" x="0.0" y="35" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="vkb-8K-t7E" id="YcK-vq-ABN">
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="fIu-zg-60Y" id="e2H-i1-YQc">
<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="None" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QAp-WA-1g3">
<rect key="frame" x="16" y="0.0" width="343" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vXS-JG-YrF">
<rect key="frame" x="310" y="6.5" width="51" height="31"/>
<connections>
<action selector="toggleSyncing:" destination="R9m-rV-VgV" eventType="primaryActionTriggered" id="KNw-Wb-hIW"/>
</connections>
</switch>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="vXS-JG-YrF" firstAttribute="trailing" secondItem="e2H-i1-YQc" secondAttribute="trailingMargin" id="G2g-ka-Gds"/>
<constraint firstItem="vXS-JG-YrF" firstAttribute="centerY" secondItem="e2H-i1-YQc" secondAttribute="centerY" id="RDW-mS-nrf"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="switchView" destination="vXS-JG-YrF" id="RaC-P2-WCJ"/>
</connections>
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection headerTitle="Service" id="mIB-Au-dYz">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" textLabel="4fb-TC-FrG" style="IBUITableViewCellStyleDefault" id="hBZ-Fp-9Kh">
<rect key="frame" x="0.0" y="99.5" width="375" height="44"/>
<rect key="frame" x="0.0" y="135" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="hBZ-Fp-9Kh" id="rfN-2N-L43">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
@ -760,12 +771,29 @@
</subviews>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" textLabel="qR3-zd-gmV" style="IBUITableViewCellStyleDefault" id="Kfm-x4-Gub">
<rect key="frame" x="0.0" y="179" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Kfm-x4-Gub" id="IAV-o1-LfP">
<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="Dropbox" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="qR3-zd-gmV">
<rect key="frame" x="16" y="0.0" width="343" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection headerTitle="Account" id="1Wk-cG-HDE">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" textLabel="AYq-XK-j5L" style="IBUITableViewCellStyleDefault" id="nrN-mu-0HX">
<rect key="frame" x="0.0" y="199.5" width="375" height="44"/>
<rect key="frame" x="0.0" y="279" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="nrN-mu-0HX" id="lHU-qJ-uhj">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
@ -786,7 +814,7 @@
<tableViewSection headerTitle="" id="Jnq-12-IOu">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" textLabel="4TQ-cm-2sN" style="IBUITableViewCellStyleDefault" id="wRv-En-k1Y">
<rect key="frame" x="0.0" y="279.5" width="375" height="44"/>
<rect key="frame" x="0.0" y="359" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="wRv-En-k1Y" id="7QF-ID-Gu2">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
@ -810,6 +838,9 @@
<outlet property="delegate" destination="R9m-rV-VgV" id="qqu-iI-H9F"/>
</connections>
</tableView>
<connections>
<outlet property="syncingEnabledSwitch" destination="vXS-JG-YrF" id="xf0-Jg-iRi"/>
</connections>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="uqz-XU-aTr" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>

View File

@ -346,13 +346,13 @@ private extension GameCollectionViewController
guard game.fileURL != self.activeEmulatorCore?.game.fileURL else { throw LaunchError.alreadyRunning }
}
if SyncManager.shared.syncCoordinator.isSyncing
if let coordinator = SyncManager.shared.coordinator, coordinator.isSyncing
{
if let gameSave = game.gameSave
{
do
{
if let record = try SyncManager.shared.recordController.fetchRecords(for: [gameSave]).first
if let record = try coordinator.recordController.fetchRecords(for: [gameSave]).first
{
if record.isSyncingEnabled && !record.isConflicted && (record.localStatus == nil || record.remoteStatus == .updated)
{
@ -535,7 +535,7 @@ private extension GameCollectionViewController
context.saveWithErrorLogging()
// Local image URLs may not change despite being a different image, so manually mark record as updated.
SyncManager.shared.recordController.updateRecord(for: temporaryGame)
SyncManager.shared.recordController?.updateRecord(for: temporaryGame)
DispatchQueue.main.async {
self.presentedViewController?.dismiss(animated: true, completion: nil)
@ -625,7 +625,7 @@ private extension GameCollectionViewController
if let gameSave = game.gameSave
{
SyncManager.shared.recordController.updateRecord(for: gameSave)
SyncManager.shared.recordController?.updateRecord(for: gameSave)
}
}
}

View File

@ -382,7 +382,7 @@ private extension GamesViewController
func showSyncingToastViewIfNeeded()
{
guard SyncManager.shared.syncCoordinator.isSyncing && self.syncingToastView == nil else { return }
guard let coordinator = SyncManager.shared.coordinator, coordinator.isSyncing && self.syncingToastView == nil else { return }
let toastView = RSTToastView(text: NSLocalizedString("Syncing...", comment: ""), detailText: nil)
toastView.activityIndicatorView.startAnimating()

View File

@ -59,14 +59,9 @@ extension LaunchViewController
}
let isSyncingManagerStarted = RSTLaunchCondition(condition: { self.didAttemptStartingSyncManager }) { (completionHandler) in
SyncManager.shared.syncCoordinator.start { (error) in
self.didAttemptStartingSyncManager = true
completionHandler(nil)
}
}
let isRecordControllerSeeded = RSTLaunchCondition(condition: { SyncManager.shared.syncCoordinator.recordController.isSeeded }) { (completionHandler) in
SyncManager.shared.syncCoordinator.recordController.seedFromPersistentContainer() { (result) in
self.didAttemptStartingSyncManager = true
SyncManager.shared.start(service: Settings.syncingService) { (result) in
switch result
{
case .success: completionHandler(nil)
@ -75,7 +70,7 @@ extension LaunchViewController
}
}
return [isDatabaseManagerStarted, isSyncingManagerStarted, isRecordControllerSeeded]
return [isDatabaseManagerStarted, isSyncingManagerStarted]
}
override func handleLaunchError(_ error: Error)

View File

@ -130,10 +130,13 @@ extension Settings
}
}
static var syncingService: SyncingService {
get { return SyncingService(rawValue: UserDefaults.standard.syncingService) ?? .none }
static var syncingService: SyncManager.Service? {
get {
guard let syncingService = UserDefaults.standard.syncingService else { return nil }
return SyncManager.Service(rawValue: syncingService)
}
set {
UserDefaults.standard.syncingService = newValue.rawValue
UserDefaults.standard.syncingService = newValue?.rawValue
NotificationCenter.default.post(name: .settingsDidChange, object: nil, userInfo: [NotificationUserInfoKey.name: Name.syncingService])
}
}
@ -234,5 +237,5 @@ private extension UserDefaults
@NSManaged var gameShortcutsMode: String
@NSManaged var gameShortcutIdentifiers: [String]
@NSManaged var syncingService: String
@NSManaged var syncingService: String?
}

View File

@ -131,11 +131,11 @@ private extension SettingsViewController
self.controllerOpacitySlider.value = Float(Settings.translucentControllerSkinOpacity)
self.updateControllerOpacityLabel()
self.syncingServiceLabel.text = Settings.syncingService.localizedName
self.syncingServiceLabel.text = Settings.syncingService?.localizedName
do
{
let records = try SyncManager.shared.recordController.fetchConflictedRecords()
let records = try SyncManager.shared.recordController?.fetchConflictedRecords() ?? []
self.syncingConflictsCount = records.count
}
catch
@ -233,7 +233,7 @@ extension SettingsViewController
{
case .controllers: return 1 // Temporarily hide other controller indexes until controller logic is finalized
case .controllerSkins: return System.allCases.count
case .syncing: return Settings.syncingService == .none ? 1 : super.tableView(tableView, numberOfRowsInSection: sectionIndex)
case .syncing: return SyncManager.shared.coordinator?.account == nil ? 1 : super.tableView(tableView, numberOfRowsInSection: sectionIndex)
default:
if isSectionHidden(section)
{

View File

@ -57,7 +57,7 @@ class GameSyncStatusViewController: UITableViewController
do
{
let records = try SyncManager.shared.recordController.fetchRecords(for: [recordedObject])
let records = try SyncManager.shared.recordController?.fetchRecords(for: [recordedObject]) ?? []
let recordSyncStatusViewController = segue.destination as! RecordSyncStatusViewController
recordSyncStatusViewController.record = records.first
@ -132,12 +132,14 @@ private extension GameSyncStatusViewController
func fetchRecords()
{
guard let recordController = SyncManager.shared.recordController else { return }
var recordsByObjectURI = [URL: Record<NSManagedObject>]()
do
{
let recordedObjects = ([self.game, self.game.gameSave].compactMap { $0 } + Array(self.game.saveStates) + Array(self.game.cheats)) as! [Syncable]
let records = try SyncManager.shared.recordController.fetchRecords(for: recordedObjects)
let records = try recordController.fetchRecords(for: recordedObjects)
for record in records
{

View File

@ -196,7 +196,7 @@ private extension RecordVersionsViewController
func fetchVersions()
{
SyncManager.shared.syncCoordinator.fetchVersions(for: self.record) { (result) in
SyncManager.shared.coordinator?.fetchVersions(for: self.record) { (result) in
do
{
let versions = try result.get().map(Version.init)
@ -242,6 +242,8 @@ private extension RecordVersionsViewController
guard let indexPath = self._selectedVersionIndexPath else { return }
guard let coordinator = SyncManager.shared.coordinator else { return }
func finish<T: Error>(_ result: Result<AnyRecord, T>)
{
DispatchQueue.main.async {
@ -298,19 +300,19 @@ private extension RecordVersionsViewController
case (.restoreVersion, _):
let version = self.dataSource.item(at: indexPath)
progress = SyncManager.shared.syncCoordinator.restore(self.record, to: version.version) { (result) in
progress = coordinator.restore(self.record, to: version.version) { (result) in
finish(result)
}
case (.resolveConflict, .local):
progress = SyncManager.shared.syncCoordinator.resolveConflictedRecord(self.record, resolution: .local) { (result) in
progress = coordinator.resolveConflictedRecord(self.record, resolution: .local) { (result) in
finish(result)
}
case (.resolveConflict, .remote):
let version = self.dataSource.item(at: indexPath)
progress = SyncManager.shared.syncCoordinator.resolveConflictedRecord(self.record, resolution: .remote(version.version)) { (result) in
progress = coordinator.resolveConflictedRecord(self.record, resolution: .remote(version.version)) { (result) in
finish(result)
}

View File

@ -116,12 +116,14 @@ private extension SyncStatusViewController
func fetchConflictedRecords()
{
guard let recordController = SyncManager.shared.recordController else { return }
DispatchQueue.global().async {
do
{
var gameConflictsCount = [URL: Int]()
let records = try SyncManager.shared.recordController.fetchConflictedRecords()
let records = try recordController.fetchConflictedRecords()
for record in records
{

View File

@ -13,24 +13,11 @@ import Harmony_Drive
import Roxas
enum SyncingService: String, CaseIterable
{
case none
case googleDrive
var localizedName: String {
switch self
{
case .none: return NSLocalizedString("None", comment: "")
case .googleDrive: return NSLocalizedString("Google Drive", comment: "")
}
}
}
extension SyncingServicesViewController
{
enum Section: Int, CaseIterable
{
case syncing
case service
case account
case authenticate
@ -38,12 +25,88 @@ extension SyncingServicesViewController
}
class SyncingServicesViewController: UITableViewController
{
@IBOutlet private var syncingEnabledSwitch: UISwitch!
private var selectedSyncingService = Settings.syncingService
override func viewDidLoad()
{
super.viewDidLoad()
self.syncingEnabledSwitch.onTintColor = .deltaPurple
self.syncingEnabledSwitch.isOn = (self.selectedSyncingService != nil)
}
}
private extension SyncingServicesViewController
{
@IBAction func toggleSyncing(_ sender: UISwitch)
{
if sender.isOn
{
self.changeService(to: SyncManager.Service.allCases.first)
}
else
{
if SyncManager.shared.coordinator?.account != nil
{
let alertController = UIAlertController(title: NSLocalizedString("Disable Syncing?", comment: ""), message: NSLocalizedString("Enabling syncing again later may result in conflicts that must be resolved manually.", comment: ""), preferredStyle: .alert)
alertController.addAction(.cancel)
alertController.addAction(UIAlertAction(title: NSLocalizedString("Disable", comment: ""), style: .default) { (action) in
self.changeService(to: nil)
})
self.present(alertController, animated: true, completion: nil)
}
else
{
self.changeService(to: nil)
}
}
}
func changeService(to service: SyncManager.Service?)
{
SyncManager.shared.reset(for: service) { (result) in
DispatchQueue.main.async {
do
{
try result.get()
let previousService = self.selectedSyncingService
self.selectedSyncingService = service
// Set to non-nil if we later authenticate.
Settings.syncingService = nil
if (previousService == nil && service != nil) || (previousService != nil && service == nil)
{
self.tableView.reloadSections(IndexSet(integersIn: Section.service.rawValue ... Section.authenticate.rawValue), with: .fade)
}
else
{
self.tableView.reloadData()
}
}
catch
{
let alertController = UIAlertController(title: NSLocalizedString("Unable to Change Syncing Service", comment: ""), error: error)
self.present(alertController, animated: true, completion: nil)
}
}
}
}
}
private extension SyncingServicesViewController
{
func isSectionHidden(_ section: Section) -> Bool
{
switch section
{
case .account: return SyncManager.shared.syncCoordinator.account == nil
case .service: return !self.syncingEnabledSwitch.isOn
case .account: return !self.syncingEnabledSwitch.isOn || SyncManager.shared.coordinator?.account == nil
case .authenticate: return !self.syncingEnabledSwitch.isOn
default: return false
}
}
@ -51,28 +114,24 @@ class SyncingServicesViewController: UITableViewController
extension SyncingServicesViewController
{
override func numberOfSections(in tableView: UITableView) -> Int
{
guard Settings.syncingService != .none else { return 1 }
return super.numberOfSections(in: tableView)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = super.tableView(tableView, cellForRowAt: indexPath)
switch Section.allCases[indexPath.section]
{
case .syncing:
cell.textLabel?.text = NSLocalizedString("Syncing", comment: "")
case .service:
let service = SyncingService.allCases[indexPath.row]
cell.accessoryType = (service == Settings.syncingService) ? .checkmark : .none
let service = SyncManager.Service.allCases[indexPath.row]
cell.accessoryType = (service == self.selectedSyncingService) ? .checkmark : .none
case .account:
cell.textLabel?.text = SyncManager.shared.syncCoordinator.account?.name ?? NSLocalizedString("Unknown Account", comment: "")
cell.textLabel?.text = SyncManager.shared.coordinator?.account?.name ?? NSLocalizedString("Unknown Account", comment: "")
case .authenticate:
if SyncManager.shared.syncCoordinator.isAuthenticated
if SyncManager.shared.coordinator?.account != nil
{
cell.textLabel?.textColor = .red
cell.textLabel?.text = NSLocalizedString("Sign Out", comment: "")
@ -91,31 +150,40 @@ extension SyncingServicesViewController
{
switch Section.allCases[indexPath.section]
{
case .syncing: break
case .service:
Settings.syncingService = SyncingService.allCases[indexPath.row]
let syncingService = SyncManager.Service.allCases[indexPath.row]
guard syncingService != self.selectedSyncingService else { return }
if Settings.syncingService == .none && self.tableView.numberOfSections > 1
if SyncManager.shared.coordinator?.account != nil
{
self.tableView.deleteSections(IndexSet(integersIn: Section.account.rawValue ... Section.authenticate.rawValue), with: .fade)
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to change sync services?", comment: ""), message: NSLocalizedString("Switching back later may result in conflicts that must be resolved manually.", comment: ""), preferredStyle: .actionSheet)
alertController.addAction(.cancel)
alertController.addAction(UIAlertAction(title: NSLocalizedString("Change Sync Service", comment: ""), style: .destructive, handler: { (action) in
self.changeService(to: syncingService)
}))
self.present(alertController, animated: true, completion: nil)
}
else if Settings.syncingService != .none && self.tableView.numberOfSections == 1
else
{
self.tableView.insertSections(IndexSet(integersIn: Section.account.rawValue ... Section.authenticate.rawValue), with: .fade)
self.changeService(to: syncingService)
}
self.tableView.reloadSections(IndexSet(integer: Section.service.rawValue), with: .none)
case .account: break
case .authenticate:
if SyncManager.shared.syncCoordinator.isAuthenticated
case .authenticate:
if SyncManager.shared.coordinator?.account != nil
{
SyncManager.shared.syncCoordinator.deauthenticate { (result) in
SyncManager.shared.deauthenticate { (result) in
DispatchQueue.main.async {
do
{
try result.get()
self.tableView.reloadData()
Settings.syncingService = nil
}
catch
{
@ -127,16 +195,21 @@ extension SyncingServicesViewController
}
else
{
SyncManager.shared.syncCoordinator.authenticate(presentingViewController: self) { (result) in
SyncManager.shared.authenticate(presentingViewController: self) { (result) in
DispatchQueue.main.async {
do
{
_ = try result.get()
self.tableView.reloadData()
Settings.syncingService = self.selectedSyncingService
}
catch GeneralError.cancelled.self
{
// Ignore
}
catch
{
let alertController = UIAlertController(title: NSLocalizedString("Failed to Sign In", comment: ""), error: error)
self.present(alertController, animated: true, completion: nil)
}

View File

@ -113,6 +113,14 @@
<string>com.googleusercontent.apps.457607414709-7oc45nq59frd7rre6okq22fafftd55g1</string>
</array>
</dict>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>db-f5btgysf9ma9bb6</string>
</array>
<key>CFBundleURLName</key>
<string></string>
</dict>
</array>
<key>CFBundleVersion</key>
<string>7</string>
@ -150,7 +158,7 @@
</dict>
</dict>
<key>NSPhotoLibraryUsageDescription</key>
<string>Press "OK" to allow Delta to use images from your Photo Library as game artwork.</string>
<string>Press &quot;OK&quot; to allow Delta to use images from your Photo Library as game artwork.</string>
<key>UIFileSharingEnabled</key>
<true/>
<key>UILaunchStoryboardName</key>
@ -272,5 +280,10 @@
</dict>
</dict>
</array>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>dbapi-8-emm</string>
<string>dbapi-2</string>
</array>
</dict>
</plist>

View File

@ -8,6 +8,7 @@
import Harmony
import Harmony_Drive
import Harmony_Dropbox
extension SyncManager
{
@ -34,6 +35,40 @@ extension SyncManager
}
}
}
enum Service: String, CaseIterable
{
case googleDrive = "com.rileytestut.Harmony.Drive"
case dropbox = "com.rileytestut.Harmony.Dropbox"
var localizedName: String {
switch self
{
case .googleDrive: return NSLocalizedString("Google Drive", comment: "")
case .dropbox: return NSLocalizedString("Dropbox", comment: "")
}
}
var service: Harmony.Service {
switch self
{
case .googleDrive: return DriveService.shared
case .dropbox: return DropboxService.shared
}
}
}
enum Error: LocalizedError
{
case nilService
var errorDescription: String? {
switch self
{
case .nilService: return NSLocalizedString("There is no chosen service for syncing.", comment: "")
}
}
}
}
extension Syncable where Self: NSManagedObject
@ -48,22 +83,26 @@ final class SyncManager
{
static let shared = SyncManager()
var service: Service {
return self.syncCoordinator.service
var service: Service? {
guard let service = self.coordinator?.service else { return nil }
return Service(rawValue: service.identifier)
}
var recordController: RecordController {
return self.syncCoordinator.recordController
var recordController: RecordController? {
return self.coordinator?.recordController
}
private(set) var previousSyncResult: SyncResult?
let syncCoordinator = SyncCoordinator(service: DriveService.shared, persistentContainer: DatabaseManager.shared)
private(set) var coordinator: SyncCoordinator?
private init()
{
DriveService.shared.clientID = "457607414709-7oc45nq59frd7rre6okq22fafftd55g1.apps.googleusercontent.com"
DropboxService.shared.clientID = "f5btgysf9ma9bb6"
DropboxService.shared.preferredDirectoryName = "Delta Emulator"
NotificationCenter.default.addObserver(self, selector: #selector(SyncManager.syncingDidFinish(_:)), name: SyncCoordinator.didFinishSyncingNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(SyncManager.didEnterBackground(_:)), name: UIApplication.didEnterBackgroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(SyncManager.willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
@ -72,11 +111,83 @@ final class SyncManager
extension SyncManager
{
func start(service: Service?, completionHandler: @escaping (Result<Void, Swift.Error>) -> Void)
{
guard let service = service else { return completionHandler(.success) }
let coordinator = SyncCoordinator(service: service.service, persistentContainer: DatabaseManager.shared)
coordinator.start { (result) in
do
{
_ = try result.get()
self.coordinator = coordinator
completionHandler(.success)
}
catch
{
completionHandler(.failure(error))
}
}
}
func reset(for service: Service?, completionHandler: @escaping (Result<Void, Swift.Error>) -> Void)
{
if let coordinator = self.coordinator
{
coordinator.deauthenticate { (result) in
self.coordinator = nil
self.start(service: service, completionHandler: completionHandler)
}
}
else
{
self.start(service: service, completionHandler: completionHandler)
}
}
func authenticate(presentingViewController: UIViewController? = nil, completionHandler: @escaping (Result<Account, AuthenticationError>) -> Void)
{
guard let coordinator = self.coordinator else { return completionHandler(.failure(AuthenticationError(Error.nilService))) }
coordinator.authenticate(presentingViewController: presentingViewController) { (result) in
do
{
let account = try result.get()
if !coordinator.recordController.isSeeded
{
coordinator.recordController.seedFromPersistentContainer { (result) in
switch result
{
case .success: completionHandler(.success(account))
case .failure(let error): completionHandler(.failure(AuthenticationError(error)))
}
}
}
else
{
completionHandler(.success(account))
}
}
catch
{
completionHandler(.failure(AuthenticationError(error)))
}
}
}
func deauthenticate(completionHandler: @escaping (Result<Void, DeauthenticationError>) -> Void)
{
guard let coordinator = self.coordinator else { return completionHandler(.success) }
coordinator.deauthenticate(completionHandler: completionHandler)
}
func sync()
{
guard Settings.syncingService != .none else { return }
self.syncCoordinator.sync()
self.coordinator?.sync()
}
}
@ -86,6 +197,8 @@ private extension SyncManager
{
guard let result = notification.userInfo?[SyncCoordinator.syncResultKey] as? SyncResult else { return }
self.previousSyncResult = result
print("Finished syncing!")
}
@objc func didEnterBackground(_ notification: Notification)

2
External/Harmony vendored

@ -1 +1 @@
Subproject commit 2cfca813e9e4d0ecbc824050ab0336cb9b7c6b37
Subproject commit 39311d36660c98bc6f240d7134c39f8361fc7b3e

2
External/Roxas vendored

@ -1 +1 @@
Subproject commit 1945d97204d113c635ec959f7777e7200d40cdee
Subproject commit ffd65b85417db90895361446ea848d68a8ef9e4c