GBA002/Delta/Syncing/SyncManager.swift
2019-03-25 15:46:39 -07:00

247 lines
8.3 KiB
Swift

//
// SyncManager.swift
// Delta
//
// Created by Riley Testut on 11/12/18.
// Copyright © 2018 Riley Testut. All rights reserved.
//
import Harmony
import Harmony_Drive
import Harmony_Dropbox
extension SyncManager
{
enum RecordType: String, Hashable
{
case game = "Game"
case gameCollection = "GameCollection"
case cheat = "Cheat"
case saveState = "SaveState"
case controllerSkin = "ControllerSkin"
case gameControllerInputMapping = "GameControllerInputMapping"
case gameSave = "GameSave"
var localizedName: String {
switch self
{
case .game: return NSLocalizedString("Game", comment: "")
case .gameCollection: return NSLocalizedString("Game Collection", comment: "")
case .cheat: return NSLocalizedString("Cheat", comment: "")
case .saveState: return NSLocalizedString("Save State", comment: "")
case .controllerSkin: return NSLocalizedString("Controller Skin", comment: "")
case .gameControllerInputMapping: return NSLocalizedString("Game Controller Input Mapping", comment: "")
case .gameSave: return NSLocalizedString("Game Save", comment: "")
}
}
}
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
{
var recordType: SyncManager.RecordType {
let recordType = SyncManager.RecordType(rawValue: self.syncableType)!
return recordType
}
}
final class SyncManager
{
static let shared = SyncManager()
var service: Service? {
guard let service = self.coordinator?.service else { return nil }
return Service(rawValue: service.identifier)
}
var recordController: RecordController? {
return self.coordinator?.recordController
}
private(set) var syncProgress: Progress?
private(set) var previousSyncResult: SyncResult?
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)
}
}
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 let authError as AuthenticationError
{
// Authentication failed, but otherwise started successfully so still assign self.coordinator.
self.coordinator = coordinator
switch authError
{
case .other(ServiceError.connectionFailed):
// Authentication failed due to network connection, but otherwise started successfully so we ignore this error.
completionHandler(.success)
default:
// Another authentication error occured, so we'll deauthenticate ourselves.
print("SyncManager.start auth error:", authError)
self.deauthenticate() { (result) in
switch result
{
case .success:
completionHandler(.success)
case .failure:
// authError is more useful than result's error.
completionHandler(.failure(authError))
}
}
}
}
catch
{
print("SyncManager.start error:", error)
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()
{
let progress = self.coordinator?.sync()
self.syncProgress = progress
}
}
private extension SyncManager
{
@objc func syncingDidFinish(_ notification: Notification)
{
guard let result = notification.userInfo?[SyncCoordinator.syncResultKey] as? SyncResult else { return }
self.previousSyncResult = result
self.syncProgress = nil
print("Finished syncing!")
}
@objc func didEnterBackground(_ notification: Notification)
{
self.sync()
}
@objc func willEnterForeground(_ notification: Notification)
{
self.sync()
}
}