1*5f54863aSWill Schurman // Copyright 2015-present 650 Industries. All rights reserved. 2*5f54863aSWill Schurman 3*5f54863aSWill Schurman import Foundation 4*5f54863aSWill Schurman import EXUpdates 5*5f54863aSWill Schurman 6*5f54863aSWill Schurman @objc(EXHomeAppLoaderTaskDelegate) 7*5f54863aSWill Schurman public protocol HomeAppLoaderTaskDelegate: AnyObject { homeAppLoaderTasknull8*5f54863aSWill Schurman func homeAppLoaderTask(_: HomeAppLoaderTask, didFinishWithLauncher launcher: AppLauncher) 9*5f54863aSWill Schurman func homeAppLoaderTask(_: HomeAppLoaderTask, didFinishWithError error: Error) 10*5f54863aSWill Schurman } 11*5f54863aSWill Schurman 12*5f54863aSWill Schurman /** 13*5f54863aSWill Schurman Derivation of AppLoaderTask from expo-updates that just does a simple one-loader using HomeAppLoader. 14*5f54863aSWill Schurman */ 15*5f54863aSWill Schurman @objc(EXHomeAppLoaderTask) 16*5f54863aSWill Schurman @objcMembers 17*5f54863aSWill Schurman public final class HomeAppLoaderTask: NSObject { 18*5f54863aSWill Schurman private static let ErrorDomain = "HomeAppLoaderTask" 19*5f54863aSWill Schurman 20*5f54863aSWill Schurman public weak var delegate: HomeAppLoaderTaskDelegate? 21*5f54863aSWill Schurman 22*5f54863aSWill Schurman private let manifestAndAssetRequestHeaders: ManifestAndAssetRequestHeaders 23*5f54863aSWill Schurman private let config: UpdatesConfig 24*5f54863aSWill Schurman private let database: UpdatesDatabase 25*5f54863aSWill Schurman private let directory: URL 26*5f54863aSWill Schurman private let selectionPolicy: SelectionPolicy 27*5f54863aSWill Schurman private let delegateQueue: DispatchQueue 28*5f54863aSWill Schurman 29*5f54863aSWill Schurman private let loaderTaskQueue: DispatchQueue 30*5f54863aSWill Schurman 31*5f54863aSWill Schurman public required init( 32*5f54863aSWill Schurman manifestAndAssetRequestHeaders: ManifestAndAssetRequestHeaders, 33*5f54863aSWill Schurman config: UpdatesConfig, 34*5f54863aSWill Schurman database: UpdatesDatabase, 35*5f54863aSWill Schurman directory: URL, 36*5f54863aSWill Schurman selectionPolicy: SelectionPolicy, 37*5f54863aSWill Schurman delegateQueue: DispatchQueue 38*5f54863aSWill Schurman ) { 39*5f54863aSWill Schurman self.manifestAndAssetRequestHeaders = manifestAndAssetRequestHeaders 40*5f54863aSWill Schurman self.config = config 41*5f54863aSWill Schurman self.database = database 42*5f54863aSWill Schurman self.directory = directory 43*5f54863aSWill Schurman self.selectionPolicy = selectionPolicy 44*5f54863aSWill Schurman self.delegateQueue = delegateQueue 45*5f54863aSWill Schurman self.loaderTaskQueue = DispatchQueue(label: "expo.loader.LoaderTaskQueue") 46*5f54863aSWill Schurman } 47*5f54863aSWill Schurman 48*5f54863aSWill Schurman public func start() { 49*5f54863aSWill Schurman self.loadHomeUpdate { remoteError, remoteUpdate in 50*5f54863aSWill Schurman self.loaderTaskQueue.async { 51*5f54863aSWill Schurman self.launchUpdate(remoteUpdate?.manifestUpdateResponsePart?.updateManifest, error: remoteError) 52*5f54863aSWill Schurman } 53*5f54863aSWill Schurman } 54*5f54863aSWill Schurman } 55*5f54863aSWill Schurman 56*5f54863aSWill Schurman private func finish(withLauncher launcher: AppLauncher?, error: Error?) { 57*5f54863aSWill Schurman dispatchPrecondition(condition: .onQueue(loaderTaskQueue)) 58*5f54863aSWill Schurman 59*5f54863aSWill Schurman if let delegate = delegate { 60*5f54863aSWill Schurman delegateQueue.async { 61*5f54863aSWill Schurman // swiftlint:disable force_unwrapping 62*5f54863aSWill Schurman if let launcher = launcher, 63*5f54863aSWill Schurman launcher.launchAssetUrl != nil || launcher.launchedUpdate!.status == .StatusDevelopment { 64*5f54863aSWill Schurman delegate.homeAppLoaderTask(self, didFinishWithLauncher: launcher) 65*5f54863aSWill Schurman } else { 66*5f54863aSWill Schurman delegate.homeAppLoaderTask( 67*5f54863aSWill Schurman self, 68*5f54863aSWill Schurman didFinishWithError: error ?? NSError( 69*5f54863aSWill Schurman domain: HomeAppLoaderTask.ErrorDomain, 70*5f54863aSWill Schurman code: 1031, 71*5f54863aSWill Schurman userInfo: [ 72*5f54863aSWill Schurman NSLocalizedDescriptionKey: "HomeAppLoaderTask encountered an unexpected error and could not launch an update." 73*5f54863aSWill Schurman ] 74*5f54863aSWill Schurman ) 75*5f54863aSWill Schurman ) 76*5f54863aSWill Schurman } 77*5f54863aSWill Schurman // swiftlint:enable force_unwrapping 78*5f54863aSWill Schurman } 79*5f54863aSWill Schurman } 80*5f54863aSWill Schurman } 81*5f54863aSWill Schurman 82*5f54863aSWill Schurman private func runReaper(withLauncher launcher: AppLauncher) { 83*5f54863aSWill Schurman if let launchedUpdate = launcher.launchedUpdate { 84*5f54863aSWill Schurman UpdatesReaper.reapUnusedUpdates( 85*5f54863aSWill Schurman withConfig: config, 86*5f54863aSWill Schurman database: database, 87*5f54863aSWill Schurman directory: directory, 88*5f54863aSWill Schurman selectionPolicy: selectionPolicy, 89*5f54863aSWill Schurman launchedUpdate: launchedUpdate 90*5f54863aSWill Schurman ) 91*5f54863aSWill Schurman } 92*5f54863aSWill Schurman } 93*5f54863aSWill Schurman 94*5f54863aSWill Schurman private func loadHomeUpdate(withCompletion completion: @escaping (_ remoteError: Error?, _ updateResponse: UpdateResponse?) -> Void) { 95*5f54863aSWill Schurman HomeAppLoader( 96*5f54863aSWill Schurman manifestAndAssetRequestHeaders: self.manifestAndAssetRequestHeaders, 97*5f54863aSWill Schurman config: config, 98*5f54863aSWill Schurman database: database, 99*5f54863aSWill Schurman directory: directory, 100*5f54863aSWill Schurman launchedUpdate: nil, 101*5f54863aSWill Schurman completionQueue: loaderTaskQueue 102*5f54863aSWill Schurman ).loadHome { _ in 103*5f54863aSWill Schurman return true 104*5f54863aSWill Schurman } asset: { _, _, _, _ in 105*5f54863aSWill Schurman // no-op 106*5f54863aSWill Schurman } success: { updateResponse in 107*5f54863aSWill Schurman completion(nil, updateResponse) 108*5f54863aSWill Schurman } error: { error in 109*5f54863aSWill Schurman completion(error, nil) 110*5f54863aSWill Schurman } 111*5f54863aSWill Schurman } 112*5f54863aSWill Schurman 113*5f54863aSWill Schurman private func launchUpdate(_ updateBeingLaunched: Update?, error: Error?) { 114*5f54863aSWill Schurman if let updateBeingLaunched = updateBeingLaunched { 115*5f54863aSWill Schurman let launcher = AppLauncherWithDatabase( 116*5f54863aSWill Schurman config: self.config, 117*5f54863aSWill Schurman database: self.database, 118*5f54863aSWill Schurman directory: self.directory, 119*5f54863aSWill Schurman completionQueue: self.loaderTaskQueue 120*5f54863aSWill Schurman ) 121*5f54863aSWill Schurman launcher.launchUpdate(withSelectionPolicy: self.selectionPolicy) { error, success in 122*5f54863aSWill Schurman if success { 123*5f54863aSWill Schurman self.finish(withLauncher: launcher, error: nil) 124*5f54863aSWill Schurman } else { 125*5f54863aSWill Schurman self.finish(withLauncher: nil, error: error) 126*5f54863aSWill Schurman NSLog("Downloaded update but failed to launch: %@", error?.localizedDescription ?? "") 127*5f54863aSWill Schurman } 128*5f54863aSWill Schurman self.runReaper(withLauncher: launcher) 129*5f54863aSWill Schurman } 130*5f54863aSWill Schurman } else { 131*5f54863aSWill Schurman // there's no update, there should be an error 132*5f54863aSWill Schurman self.finish(withLauncher: nil, error: error) 133*5f54863aSWill Schurman } 134*5f54863aSWill Schurman } 135*5f54863aSWill Schurman } 136