RCNConfigDBManager.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. import Foundation
  2. import sqlite3
  3. class RCNConfigDBManager {
  4. private var _database: OpaquePointer? = nil
  5. private var _databaseOperationQueue: DispatchQueue
  6. static let sharedInstance = RCNConfigDBManager()
  7. private var gIsNewDatabase: Bool = false
  8. init() {
  9. _databaseOperationQueue = DispatchQueue(label: "com.google.GoogleConfigService.database", qos: .default)
  10. createOrOpenDatabase()
  11. }
  12. func migrateV1NamespaceToV2Namespace() {
  13. for table in 0...2 {
  14. var tableName = ""
  15. switch table {
  16. case 0:
  17. tableName = RCNTableNameMain
  18. break
  19. case 1:
  20. tableName = RCNTableNameMainActive
  21. break
  22. case 2:
  23. tableName = RCNTableNameMainDefault
  24. break
  25. default:
  26. break
  27. }
  28. let SQLString = String(format: "SELECT namespace FROM %@ WHERE namespace NOT LIKE '%%:%%'",
  29. tableName)
  30. let SQL = SQLString.utf8String
  31. let statement = prepareSQL(sql: SQL!)
  32. if statement == nil {
  33. return
  34. }
  35. var namespaceArray: [String] = []
  36. while sqlite3_step(statement) == SQLITE_ROW {
  37. if let configNamespace = String(utf8String: String(cString: sqlite3_column_text(statement, 0))) {
  38. namespaceArray.append(configNamespace)
  39. }
  40. }
  41. sqlite3_finalize(statement)
  42. // Update.
  43. for namespaceToUpdate in namespaceArray {
  44. let newNamespace = String(format: "%@:%@", namespaceToUpdate, kFIRDefaultAppName)
  45. let updateSQLString = String(format: "UPDATE %@ SET namespace = ? WHERE namespace = ?", tableName)
  46. let updateSQL = updateSQLString.utf8String
  47. let updateStatement = prepareSQL(sql: updateSQL!)
  48. if updateStatement == nil {
  49. return
  50. }
  51. let updateParams = [newNamespace, namespaceToUpdate]
  52. bindStringsToStatement(statement: updateStatement!, stringArray: updateParams)
  53. let result = sqlite3_step(updateStatement)
  54. if result != SQLITE_DONE {
  55. logError(sql: updateSQL, finalizeStatement: updateStatement, returnValue: false)
  56. return;
  57. }
  58. sqlite3_finalize(updateStatement)
  59. }
  60. }
  61. }
  62. func createOrOpenDatabase() {
  63. __weak RCNConfigDBManager *weakSelf = self;
  64. dispatch_async(_databaseOperationQueue, {
  65. let strongSelf = weakSelf;
  66. if strongSelf == nil {
  67. return;
  68. }
  69. let oldV0DBPath = RemoteConfigPathForOldDatabaseV0()
  70. // Backward Compatibility
  71. if FileManager.default.fileExists(atPath: oldV0DBPath) {
  72. FIRLogInfo(RCNRemoteConfigQueueLabel, "I-RCN000009",
  73. "Old database V0 exists, removed it and replace with the new one.")
  74. strongSelf.removeDatabase(path: oldV0DBPath)
  75. }
  76. let dbPath = RCNConfigDBManager.remoteConfigPathForDatabase()
  77. FIRLogInfo(RCNRemoteConfigQueueLabel, "I-RCN000062", "Loading database at path \(dbPath)")
  78. let databasePath = dbPath.utf8String
  79. // Create or open database path.
  80. if !RemoteConfigCreateFilePathIfNotExist(filePath: dbPath) {
  81. return
  82. }
  83. var flags :Int32 = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX
  84. #if SQLITE_OPEN_FILEPROTECTION_COMPLETEUNTILFIRSTUSERAUTHENTICATION
  85. flags |= SQLITE_OPEN_FILEPROTECTION_COMPLETEUNTILFIRSTUSERAUTHENTICATION
  86. #endif
  87. if sqlite3_open_v2(databasePath, &strongSelf->_database, Int32(flags), nil) == SQLITE_OK {
  88. // Always try to create table if not exists for backward compatibility.
  89. if !(strongSelf.createTableSchema()) {
  90. // Remove database before fail.
  91. strongSelf.removeDatabase(path: dbPath)
  92. FIRLogError(RCNRemoteConfigQueueLabel, "I-RCN000010", "Failed to create table.");
  93. // Create a new database if existing database file is corrupted.
  94. if (!RemoteConfigCreateFilePathIfNotExist(filePath: dbPath)) {
  95. return
  96. }
  97. } else {
  98. // DB file already exists. Migrate any V1 namespace column entries to V2 fully qualified
  99. // 'namespace:FIRApp' entries.
  100. [self migrateV1NamespaceToV2Namespace];
  101. // Exclude the app data used from iCloud backup.
  102. RemoteConfigAddSkipBackupAttributeToItemAtPath(filePath: dbPath)
  103. }
  104. } else {
  105. strongSelf.logDatabaseError()
  106. }
  107. });
  108. }
  109. func logError(sql: String?, finalizeStatement: OpaquePointer?, returnValue: Bool) -> Bool {
  110. guard let statement = statement else {
  111. return false
  112. }
  113. var message: String = ""
  114. if let errorMessage = String(utf8String: sqlite3_errmsg(self._database)) {
  115. message = String(format: "%s", errorMessage)
  116. }
  117. FIRLogError(RCNRemoteConfigQueueLabel, "I-RCN000012", "Failed to execute query with error %s.",
  118. [self errorMessage])
  119. return false
  120. }
  121. func logDatabaseError() {
  122. FIRLogError(RCNRemoteConfigQueueLabel, "I-RCN000015", "Error message: %@. Error code: %d.",
  123. String(format: "%s", [self errorMessage]), self.errorCode())
  124. }
  125. func removeDatabase(path: String) {
  126. if sqlite3_close(_database) != SQLITE_OK {
  127. logDatabaseError()
  128. }
  129. _database = nil
  130. let fileManager = FileManager.default
  131. do {
  132. try fileManager.removeItem(atPath: path, error: nil)
  133. } catch {
  134. FIRLogError(RCNRemoteConfigQueueLabel, "I-RCN000011",
  135. "Failed to remove database at path \(path) for error %@.", error)
  136. }
  137. }
  138. func logError(sql: String?, finalizeStatement: sqlite3_stmt?, returnValue: Bool) -> Bool {
  139. guard let statement = statement else {
  140. return false
  141. }
  142. let errorMessage: String = String(format:"%@", sqlite3_errmsg(self._database))
  143. FIRLogError(RCNRemoteConfigQueueLabel, "I-RCN000012", "Failed to execute query with error %s.",
  144. String(format: "%@", errorMessage))
  145. if (statement != nil) {
  146. sqlite3_finalize(statement)
  147. }
  148. return returnValue
  149. }
  150. func logErrorWithSQL(sql: String?, finalizeStatement: OpaquePointer?, returnValue: Bool) -> Bool {
  151. guard let statement = statement else {
  152. return false
  153. }
  154. let errorMessage: String = String(format:"%@", sqlite3_errmsg(_database))
  155. FIRLogError(RCNRemoteConfigQueueLabel, "I-RCN000016", "Failed with SQL: %@", sql ?? "");
  156. FIRLogError(RCNRemoteConfigQueueLabel, "I-RCN000015", "Error message: %@. Error code: %d.", errorMessage, self.errorCode())
  157. if (statement != nil) {
  158. sqlite3_finalize(statement)
  159. }
  160. return returnValue
  161. }
  162. func bindStringsToStatement(statement: OpaquePointer?, stringArray: [String]) -> Bool {
  163. var index = 1
  164. for value in array {
  165. if !bindStringToStatement(statement: statement, index: index, string: value) {
  166. return false
  167. }
  168. index+=1
  169. }
  170. return true
  171. }
  172. func bindStringToStatement(statement: OpaquePointer?, index: Int, string: String) -> Bool {
  173. if sqlite3_bind_text(statement, Int32(index), value.utf8String!, -1, SQLITE_TRANSIENT) != SQLITE_OK {
  174. return false
  175. }
  176. return true
  177. }
  178. func executeQuery(SQL: String?) -> Bool{
  179. return false
  180. }
  181. func createTableSchema() -> Bool {
  182. let createTableMain =
  183. "create TABLE IF NOT EXISTS \(RCNTableNameMain) (_id INTEGER PRIMARY KEY, bundle_identifier TEXT, namespace TEXT, key TEXT, value BLOB)"
  184. let createTableMainActive =
  185. "create TABLE IF NOT EXISTS \(RCNTableNameMainActive) (_id INTEGER PRIMARY KEY, bundle_identifier TEXT, namespace TEXT, key TEXT, value BLOB)"
  186. let createTableMainDefault =
  187. "create TABLE IF NOT EXISTS \(RCNTableNameMainDefault) (_id INTEGER PRIMARY KEY, bundle_identifier TEXT, namespace TEXT, key TEXT, value BLOB)"
  188. let createTableMetadata =
  189. "create TABLE IF NOT EXISTS \(RCNTableNameMetadata) (_id INTEGER PRIMARY KEY, bundle_identifier TEXT, namespace TEXT, fetch_time INTEGER, digest_per_ns BLOB, device_context BLOB, app_context BLOB, success_fetch_time BLOB, failure_fetch_time BLOB, last_fetch_status INTEGER, last_fetch_error INTEGER, last_apply_time INTEGER, last_set_defaults_time INTEGER)"
  190. let createTableExperiment = "create TABLE IF NOT EXISTS \(RCNTableNameExperiment) (_id INTEGER PRIMARY KEY, key TEXT, value BLOB)"
  191. let createTablePersonalization =
  192. "create TABLE IF NOT EXISTS \(RCNTableNamePersonalization) (_id INTEGER PRIMARY KEY, key INTEGER, value BLOB)"
  193. let createTableRollout = "create TABLE IF NOT EXISTS \(RCNTableNameRollout) (_id INTEGER PRIMARY KEY, key TEXT, value BLOB)"
  194. return executeQuery(sql: createTableMain) &&
  195. executeQuery(sql: createTableMainActive) &&
  196. executeQuery(sql: createTableMainDefault) &&
  197. executeQuery(sql: createTableMetadata) &&
  198. executeQuery(sql: createTableExperiment) &&
  199. executeQuery(sql: createTablePersonalization) &&
  200. executeQuery(sql: createTableRollout)
  201. }
  202. func prepareSQL(sql: String?) -> OpaquePointer? {
  203. var statement: OpaquePointer? = nil
  204. if (sqlite3_prepare_v2(_database, sql?.utf8String!, -1, &statement, nil) != SQLITE_OK) {
  205. logError(sql: String(cString: sql!), finalizeStatement: statement, returnValue: false)
  206. return nil
  207. }
  208. return statement
  209. }
  210. func executeQuery(sql: String?) -> Bool {
  211. var error: UnsafeMutablePointer<Int8>? = nil
  212. if (sqlite3_exec(_database, sql?.utf8String!, nil, nil, &error) != SQLITE_OK) {
  213. FIRLogError(RCNRemoteConfigQueueLabel, "I-RCN000012", "Failed to execute query with error %@",String(cString: error!));
  214. return false;
  215. }
  216. return true
  217. }
  218. func bindStringsToStatement(statement: OpaquePointer?, stringArray: [String]) -> Bool {
  219. var index : Int = 1
  220. for param in array {
  221. if (!bindStringToStatement(statement: statement, index: index, string: param)) {
  222. return logError(sql: nil, finalizeStatement: statement, returnValue: false)
  223. }
  224. index+=1
  225. }
  226. return true;
  227. }
  228. func bindStringToStatement(statement: OpaquePointer?, index: Int, string: String) -> Bool {
  229. if sqlite3_bind_text(statement, Int32(index), value.utf8String!, -1, SQLITE_TRANSIENT) != SQLITE_OK {
  230. return false
  231. }
  232. return true
  233. }
  234. func errorMessage() -> String {
  235. return String(format: "%s", sqlite3_errmsg(_database))
  236. }
  237. func errorCode() -> Int {
  238. return sqlite3_errcode(_database)
  239. }
  240. func logError(sql: String?, finalizeStatement: OpaquePointer?, returnValue: Bool) -> Bool {
  241. if (statement != nil) {
  242. sqlite3_finalize(statement);
  243. }
  244. FIRLogError(RCNRemoteConfigQueueLabel, "I-RCN000016", "Failed with SQL: %@", sql ?? "");
  245. return false
  246. }
  247. func prepareSQL(sql: String?) -> OpaquePointer? {
  248. var statement: OpaquePointer? = nil
  249. if (sqlite3_prepare_v2(_database, sql?.utf8String, -1, &statement, nil) != SQLITE_OK) {
  250. logError(sql: sql, finalizeStatement: statement, returnValue: false)
  251. return nil
  252. }
  253. return statement
  254. }
  255. func removeDatabase(path: String) {
  256. if sqlite3_close(_database) != SQLITE_OK {
  257. logDatabaseError()
  258. }
  259. _database = nil;
  260. let fileManager = FileManager.default;
  261. var error: Error? = nil;
  262. do {
  263. try fileManager.removeItem(atPath: path, error: &error)
  264. } catch {
  265. FIRLogError(RCNRemoteConfigQueueLabel, "I-RCN000011",
  266. "Failed to remove database at path \(path) for error %@.",
  267. error?.localizedDescription ?? "");
  268. }
  269. }
  270. func bindStringToStatement(statement: OpaquePointer?, index: Int, string: String) -> Bool {
  271. if sqlite3_bind_text(statement, Int32(index), value.utf8String, -1, SQLITE_TRANSIENT) != SQLITE_OK {
  272. return false
  273. }
  274. return true
  275. }
  276. func errorMessage() -> String {
  277. return String(utf8String: sqlite3_errmsg(_database))
  278. }
  279. func errorCode() -> Int {
  280. return sqlite3_errcode(_database)
  281. }
  282. func logDatabaseError() {
  283. FIRLogError(RCNRemoteConfigQueueLabel, "I-RCN000015", "Error message: %@. Error code: %d.",
  284. String(format: "%s", errorMessage()), errorCode())
  285. }
  286. }