xref: /expo/ios/Client/HomeAppLoaderTask.swift (revision 5f54863a)
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