ConfigDBManager.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. // Copyright 2024 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. import FirebaseCore
  15. import FirebaseCoreExtension
  16. import Foundation
  17. import SQLite3
  18. @objc public enum UpdateOption: Int {
  19. case applyTime
  20. case defaultTime
  21. case fetchStatus
  22. }
  23. /// Column names in metadata table
  24. let RCNKeyBundleIdentifier = "bundle_identifier"
  25. let RCNKeyNamespace = "namespace"
  26. let RCNKeyFetchTime = "fetch_time"
  27. let RCNKeyDigestPerNamespace = "digest_per_ns"
  28. let RCNKeyDeviceContext = "device_context"
  29. let RCNKeyAppContext = "app_context"
  30. let RCNKeySuccessFetchTime = "success_fetch_time"
  31. let RCNKeyFailureFetchTime = "failure_fetch_time"
  32. let RCNKeyLastFetchStatus = "last_fetch_status"
  33. let RCNKeyLastFetchError = "last_fetch_error"
  34. let RCNKeyLastApplyTime = "last_apply_time"
  35. let RCNKeyLastSetDefaultsTime = "last_set_defaults_time"
  36. /// SQLite file name in versions 0, 1 and 2.
  37. private let RCNDatabaseName = "RemoteConfig.sqlite3"
  38. /// The storage sub-directory that the Remote Config database resides in.
  39. private let RCNRemoteConfigStorageSubDirectory = "Google/RemoteConfig"
  40. // TODO: Delete all publics, opens, and objc's
  41. // TODO: Convert all callback functions to `async` functions
  42. // TODO: Consider deleting this file completely in favor of direct async calls to DatabaseActor.
  43. /// Persist config data in sqlite database on device. Managing data read/write from/to database.
  44. @objc(RCNConfigDBManager)
  45. open class ConfigDBManager: NSObject {
  46. /// Shared Singleton Instance
  47. @objc public static let sharedInstance = ConfigDBManager()
  48. let databaseActor: DatabaseActor
  49. @objc public var isNewDatabase: Bool
  50. @objc public init(dbPath: String = remoteConfigPathForDatabase()) {
  51. databaseActor = DatabaseActor(dbPath: dbPath)
  52. isNewDatabase = !FileManager.default.fileExists(atPath: dbPath)
  53. super.init()
  54. }
  55. /// Returns the current version of the Remote Config database.
  56. public static func remoteConfigPathForDatabase() -> String {
  57. #if os(tvOS)
  58. let dirPaths = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)
  59. #else
  60. let dirPaths = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory,
  61. .userDomainMask, true)
  62. #endif
  63. let storageDirPath = dirPaths[0]
  64. let dbPath = URL(fileURLWithPath: storageDirPath)
  65. .appendingPathComponent(RCNRemoteConfigStorageSubDirectory)
  66. .appendingPathComponent(RCNDatabaseName).path
  67. return dbPath
  68. }
  69. // MARK: - Insert
  70. @objc public
  71. func insertMetadataTable(withValues columnNameToValue: [String: Any],
  72. completionHandler handler: ((Bool, [String: AnyHashable]?) -> Void)? =
  73. nil) {
  74. Task { // Use Task to call the actor method asynchronously
  75. let success = await self.databaseActor.insertMetadataTable(withValues: columnNameToValue)
  76. if let handler {
  77. handler(success, nil) // Call the completion handler
  78. }
  79. }
  80. }
  81. @objc public
  82. func insertMainTable(withValues values: [Any],
  83. fromSource source: DBSource,
  84. completionHandler handler: ((Bool, [String: AnyHashable]?) -> Void)? = nil) {
  85. Task { // Use Task to call the actor method asynchronously
  86. let success = await self.databaseActor.insertMainTable(withValues: values, fromSource: source)
  87. if let handler {
  88. handler(success, nil) // Call the completion handler
  89. }
  90. }
  91. }
  92. @objc public
  93. func insertInternalMetadataTable(withValues values: [Any],
  94. completionHandler handler: ((Bool, [String: AnyHashable]?)
  95. -> Void)? = nil) {
  96. Task { // Use Task to call the actor method asynchronously
  97. let success = await self.databaseActor.insertInternalMetadataTable(withValues: values)
  98. if let handler {
  99. handler(success, nil) // Call the completion handler
  100. }
  101. }
  102. }
  103. @objc public
  104. func insertExperimentTable(withKey key: String,
  105. value serializedValue: Data,
  106. completionHandler handler: ((Bool, [String: AnyHashable]?) -> Void)? =
  107. nil) {
  108. Task { // Use Task to call the actor method asynchronously
  109. let success = key == ConfigConstants.experimentTableKeyMetadata ?
  110. await self.databaseActor.update(experimentMetadata: serializedValue) :
  111. await self.databaseActor.insertExperimentTable(withKey: key, value: serializedValue)
  112. if let handler {
  113. handler(success, nil) // Call the completion handler
  114. }
  115. }
  116. }
  117. @objc public
  118. func insertOrUpdatePersonalizationConfig(_ dataValue: [String: Any],
  119. fromSource source: DBSource) {
  120. do {
  121. let payload = try JSONSerialization.data(withJSONObject: dataValue,
  122. options: .prettyPrinted)
  123. Task {
  124. await databaseActor.insertOrUpdatePersonalizationConfig(payload, fromSource: source)
  125. }
  126. } catch {
  127. RCLog.error("I-RCN000075",
  128. "Invalid Personalization payload to be serialized.")
  129. }
  130. }
  131. @objc public
  132. func insertOrUpdateRolloutTable(withKey key: String,
  133. value metadataList: [[String: Any]],
  134. completionHandler handler: ((Bool, [String: AnyHashable]?)
  135. -> Void)? = nil) {
  136. Task { // Use Task to call the actor method asynchronously
  137. let success = await self.databaseActor.insertOrUpdateRolloutTable(
  138. withKey: key,
  139. value: metadataList
  140. )
  141. if let handler {
  142. handler(success, nil) // Call the completion handler
  143. }
  144. }
  145. }
  146. // MARK: - Update
  147. @objc public
  148. func updateMetadata(withOption option: UpdateOption,
  149. namespace: String,
  150. values: [Any],
  151. completionHandler handler: ((Bool, [String: AnyHashable]?) -> Void)? = nil) {
  152. Task { // Use Task to call the actor method asynchronously
  153. let success = await self.databaseActor.updateMetadataTable(withOption: option,
  154. namespace: namespace,
  155. values: values)
  156. if let handler {
  157. handler(success, nil) // Call the completion handler
  158. }
  159. }
  160. }
  161. // MARK: - Load from DB
  162. @objc public
  163. func loadMetadata(withBundleIdentifier bundleIdentifier: String,
  164. namespace: String,
  165. completionHandler handler: @escaping (([String: Sendable]) -> Void)) {
  166. Task { // Use Task to call the actor method asynchronously
  167. let table = await self.databaseActor.loadMetadataTable(withBundleIdentifier: bundleIdentifier,
  168. namespace: namespace)
  169. handler(table) // Call the completion handler
  170. }
  171. }
  172. // MARK: - Load
  173. @objc public
  174. func loadMain(withBundleIdentifier bundleIdentifier: String,
  175. completionHandler handler: ((Bool, [String: [String: RemoteConfigValue]],
  176. [String: [String: RemoteConfigValue]],
  177. [String: [String: RemoteConfigValue]],
  178. [String: [[String: Sendable]]]) -> Void)? = nil) {
  179. Task {
  180. let fetchedConfig = await self.databaseActor.loadMainTable(
  181. withBundleIdentifier: bundleIdentifier,
  182. fromSource: .fetched
  183. )
  184. let activeConfig = await self.databaseActor.loadMainTable(
  185. withBundleIdentifier: bundleIdentifier,
  186. fromSource: .active
  187. )
  188. let defaultConfig = await self.databaseActor.loadMainTable(
  189. withBundleIdentifier: bundleIdentifier,
  190. fromSource: .default
  191. )
  192. let fetchedRolloutMetadata = await self.databaseActor.loadRolloutTable(
  193. fromKey: ConfigConstants.rolloutTableKeyFetchedMetadata
  194. )
  195. let activeRolloutMetadata = await self.databaseActor.loadRolloutTable(
  196. fromKey: ConfigConstants.rolloutTableKeyActiveMetadata
  197. )
  198. if let handler {
  199. handler(true, fetchedConfig, activeConfig, defaultConfig, [
  200. ConfigConstants.rolloutTableKeyFetchedMetadata: fetchedRolloutMetadata,
  201. ConfigConstants.rolloutTableKeyActiveMetadata: activeRolloutMetadata,
  202. ])
  203. }
  204. }
  205. }
  206. @objc public
  207. func loadExperiment(completionHandler handler: ((Bool, [String: Sendable]?) -> Void)? = nil) {
  208. Task {
  209. let experimentPayloads = await self.databaseActor.loadExperimentTable(
  210. fromKey: ConfigConstants.experimentTableKeyPayload
  211. ) ?? []
  212. let metadata = await self.databaseActor.loadExperimentTable(
  213. fromKey: ConfigConstants.experimentTableKeyMetadata
  214. ) ?? []
  215. let experimentMetadata =
  216. if let experiments = metadata.first,
  217. // There should be only one entry for experiment metadata.
  218. let object =
  219. try? JSONSerialization.jsonObject(with: experiments,
  220. options: .mutableContainers) as? [String: Sendable] {
  221. object
  222. } else {
  223. [String: String]()
  224. }
  225. let activeExperimentPayloads = (await self.databaseActor.loadExperimentTable(
  226. fromKey: ConfigConstants.experimentTableKeyActivePayload
  227. ) ?? [])
  228. if let handler {
  229. handler(true, [
  230. ConfigConstants.experimentTableKeyPayload: experimentPayloads,
  231. ConfigConstants.experimentTableKeyMetadata: experimentMetadata,
  232. ConfigConstants.experimentTableKeyActivePayload: activeExperimentPayloads,
  233. ])
  234. }
  235. }
  236. }
  237. @objc public
  238. func loadPersonalization(completionHandler handler: ((Bool, [String: AnyHashable],
  239. [String: AnyHashable]) -> Void)? = nil) {
  240. Task {
  241. let activePersonalizationData =
  242. await self.databaseActor.loadPersonalizationTable(fromKey: DBSource.active.rawValue)
  243. let activePersonalization =
  244. if let activePersonalizationData,
  245. let object =
  246. try? JSONSerialization
  247. .jsonObject(with: activePersonalizationData, options: []) as? [String: String] {
  248. object
  249. } else {
  250. [String: String]()
  251. }
  252. let fetchedPersonalizationData =
  253. await self.databaseActor.loadPersonalizationTable(fromKey: DBSource.fetched.rawValue)
  254. let fetchedPersonalization =
  255. if let fetchedPersonalizationData,
  256. let object =
  257. try? JSONSerialization
  258. .jsonObject(with: fetchedPersonalizationData, options: []) as? [String: String] {
  259. object
  260. } else {
  261. [String: String]()
  262. }
  263. if let handler {
  264. handler(true, fetchedPersonalization, activePersonalization)
  265. }
  266. }
  267. }
  268. @objc public
  269. func loadInternalMetadataTable(completionHandler handler: @escaping (([String: Data]) -> Void)) {
  270. Task {
  271. let metadata = await self.databaseActor.loadInternalMetadataTableInternal()
  272. handler(metadata)
  273. }
  274. }
  275. // MARK: - Delete
  276. @objc public
  277. func deleteRecord(fromMainTableWithNamespace namespace: String,
  278. bundleIdentifier: String,
  279. fromSource source: DBSource) {
  280. Task {
  281. await self.databaseActor
  282. .deleteRecord(
  283. fromMainTableWithNamespace: namespace,
  284. bundleIdentifier: bundleIdentifier,
  285. fromSource: source
  286. )
  287. }
  288. }
  289. @objc public
  290. func deleteRecord(withBundleIdentifier bundleIdentifier: String,
  291. namespace: String) {
  292. Task {
  293. await self.databaseActor.deleteRecord(
  294. withBundleIdentifier: bundleIdentifier,
  295. namespace: namespace
  296. )
  297. }
  298. }
  299. @objc public
  300. func deleteExperimentTable(forKey key: String) {
  301. Task {
  302. await self.databaseActor.deleteExperimentTable(forKey: key)
  303. }
  304. }
  305. // MARK: - for unit tests
  306. @objc public func removeDatabase(path: String) {
  307. Task {
  308. await databaseActor.removeDatabase(atPath: path)
  309. }
  310. }
  311. @objc func createOrOpenDatabase() {
  312. Task {
  313. await databaseActor.createOrOpenDatabase()
  314. }
  315. }
  316. }