1 //  Copyright © 2019 650 Industries. All rights reserved.
2 
3 import Foundation
4 import ABI49_0_0EXManifests
5 
assertType<T>null6 func assertType<T>(value: Any, description: String) -> T {
7   if !(value is T) {
8     let exception = NSException(
9       name: NSExceptionName.internalInconsistencyException,
10       reason: description,
11       userInfo: [:]
12     )
13     exception.raise()
14   }
15 
16   // exception above will preempt force_cast
17   // swiftlint:disable:next force_cast
18   return value as! T
19 }
20 
21 public extension Optional {
requirenull22   func require(_ desc: String) -> Wrapped {
23     if self == nil {
24       let exception = NSException(
25         name: NSExceptionName.internalInconsistencyException,
26         reason: desc,
27         userInfo: [:]
28       )
29       exception.raise()
30     }
31 
32     // exception above will preempt force_unwrapping
33     // swiftlint:disable:next force_unwrapping
34     return self!
35   }
36 }
37 
38 /**
39  * Download status that indicates whether or under what conditions an
40  * update is able to be launched.
41  *
42  * It's important that the integer value of each status stays constant across
43  * all versions of this library since they are stored in SQLite on user devices.
44  */
45 @objc(ABI49_0_0EXUpdatesUpdateStatus)
46 public enum UpdateStatus: Int {
47   case Status0_Unused = 0
48   /**
49    * The update has been fully downloaded and is ready to launch.
50    */
51   case StatusReady = 1
52   case Status2_Unused = 2
53   /**
54    * The update manifest has been download from the server but not all
55    * assets have finished downloading successfully.
56    */
57   case StatusPending = 3
58   case Status4_Unused = 4
59   /**
60    * The update has been partially loaded (copied) from its location
61    * embedded in the app bundle, but not all assets have been copied
62    * successfully. The update may be able to be launched directly from
63    * its embedded location unless a new binary version with a new
64    * embedded update has been installed.
65    */
66   case StatusEmbedded = 5
67   /**
68    * The update manifest has been downloaded and indicates that the
69    * update is being served from a developer tool. It can be launched by a
70    * host application that can run a development bundle.
71    */
72   case StatusDevelopment = 6
73 }
74 
75 @objc(ABI49_0_0EXUpdatesUpdateError)
76 public enum UpdateError: Int, Error {
77   case invalidExpoProtocolVersion
78 }
79 
80 @objc(ABI49_0_0EXUpdatesUpdate)
81 @objcMembers
82 public class Update: NSObject {
83   public let updateId: UUID
84   public let scopeKey: String?
85   public var commitTime: Date
86   public let runtimeVersion: String
87   public let keep: Bool
88   public let isDevelopmentMode: Bool
89   private let assetsFromManifest: [UpdateAsset]?
90 
91   public let manifest: Manifest
92 
93   public var status: UpdateStatus
94   public var lastAccessed: Date
95   public var successfulLaunchCount: Int
96   public var failedLaunchCount: Int
97 
98   private let config: UpdatesConfig
99   private let database: UpdatesDatabase?
100 
101   public init(
102     manifest: Manifest,
103     config: UpdatesConfig,
104     database: UpdatesDatabase?,
105     updateId: UUID,
106     scopeKey: String?,
107     commitTime: Date,
108     runtimeVersion: String,
109     keep: Bool,
110     status: UpdateStatus,
111     isDevelopmentMode: Bool,
112     assetsFromManifest: [UpdateAsset]?
113   ) {
114     self.updateId = updateId
115     self.commitTime = commitTime
116     self.runtimeVersion = runtimeVersion
117     self.keep = keep
118     self.manifest = manifest
119     self.config = config
120     self.database = database
121     self.scopeKey = scopeKey
122     self.status = status
123     self.assetsFromManifest = assetsFromManifest
124 
125     self.lastAccessed = Date()
126     self.successfulLaunchCount = 0
127     self.failedLaunchCount = 0
128     self.isDevelopmentMode = isDevelopmentMode
129   }
130 
131   internal static func update(
132     withManifest: [String: Any],
133     responseHeaderData: ResponseHeaderData,
134     extensions: [String: Any],
135     config: UpdatesConfig,
136     database: UpdatesDatabase
137   ) throws -> Update {
138     let protocolVersion = responseHeaderData.protocolVersion
139     switch protocolVersion {
140     case nil:
141       return LegacyUpdate.update(
142         withLegacyManifest: LegacyManifest(rawManifestJSON: withManifest),
143         config: config,
144         database: database
145       )
146     case 0, 1:
147       return NewUpdate.update(
148         withNewManifest: NewManifest(rawManifestJSON: withManifest),
149         extensions: extensions,
150         config: config,
151         database: database
152       )
153     default:
154       throw UpdateError.invalidExpoProtocolVersion
155     }
156   }
157 
158   public static func update(
159     withEmbeddedManifest: [String: Any],
160     config: UpdatesConfig,
161     database: UpdatesDatabase?
162   ) -> Update {
163     if withEmbeddedManifest["releaseId"] != nil {
164       return LegacyUpdate.update(
165         withLegacyManifest: LegacyManifest(rawManifestJSON: withEmbeddedManifest),
166         config: config,
167         database: database
168       )
169     } else {
170       return BareUpdate.update(
171         withBareManifest: BareManifest(rawManifestJSON: withEmbeddedManifest),
172         config: config,
173         database: database
174       )
175     }
176   }
177 
178   /**
179    * Accessing this property may lazily load the assets from the database, if this update object
180    * originated from the database.
181    */
assetsnull182   public func assets() -> [UpdateAsset]? {
183     guard let assetsFromManifest = self.assetsFromManifest else {
184       return self.assetsFromDatabase()
185     }
186     return assetsFromManifest
187   }
188 
assetsFromDatabasenull189   private func assetsFromDatabase() -> [UpdateAsset]? {
190     guard let database = self.database else {
191       return nil
192     }
193 
194     var assetsLocal: [UpdateAsset] = []
195     database.databaseQueue.sync {
196       // The pattern is valid, so it'll never throw
197       // swiftlint:disable:next force_try
198       assetsLocal = try! database.assets(withUpdateId: self.updateId)
199     }
200     return assetsLocal
201   }
202 
loggingIdnull203   public func loggingId() -> String {
204     self.updateId.uuidString.lowercased()
205   }
206 }
207