Skip to content

Commit 7c29c98

Browse files
committed
v0.12.4 (621)
1 parent bb7810b commit 7c29c98

File tree

40 files changed

+1246
-225
lines changed

40 files changed

+1246
-225
lines changed

CHANGELOG.en.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## [0.12.4 (621)] - 2023-01-19
4+
5+
- You can now change profile by swiping down the profile picture on the top left.
6+
- Improves the reliability of the startup procedure after installing a new Olvid version.
7+
- Fixes a bug where sharing with Olvid could fail from certain Apple's app (such as Music or the Developer app).
8+
- Fixes a bug that could sometimes prevent the profile edition.
9+
- Fixes a crash under iOS 15.7.x
10+
311
## [0.12.3 (611)] - 2023-01-11
412

513
- Introducing a long awaited feature! You can now create as many (independent) profiles as you want! For example, you can create one for family and friends and another for work.

CHANGELOG.fr.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## [0.12.4 (621)] - 2023-01-19
4+
5+
- Vous pouvez maintenant changer de profil en faisant glisser la photo de profil située en haut à gauche.
6+
- Améliore la robustesse de l'app, notamment au démarrage après une mise à jour.
7+
- Corrige un bug qui pouvait empêcher le partage vers Olvid depuis certaines Apps d'Apple (comme Music ou l'app Developer).
8+
- Corrige un bug qui empêchait parfois d'éditer son profil.
9+
- Corrige un crash sous iOS 15.7.x
10+
311
## [0.12.3 (611)] - 2023-01-11
412

513
- Vous l'attendiez tous... Vous pouvez maintenant créer autant de profils (indépendants) que vous voulez ! Par exemple, vous pouvez créer un profil pour la famille et les amis et un autre pour vos activités professionnelles.

CoreDataStack/CoreDataStack/DataMigrationManager.swift

Lines changed: 158 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,6 @@ open class DataMigrationManager<PersistentContainerType: NSPersistentContainer>
114114
try performMigration()
115115
} else {
116116
migrationRunningLog.addEvent(message: "No migration needed")
117-
cleanOldTemporaryMigrationFiles()
118117
}
119118
} catch {
120119
migrationRunningLog.addEvent(message: "The migration failed: \(error.localizedDescription). Domain: \((error as NSError).domain)")
@@ -124,6 +123,7 @@ open class DataMigrationManager<PersistentContainerType: NSPersistentContainer>
124123
migrationRunningLog.addEvent(message: "Creating the core data stack")
125124

126125
self._coreDataStack = CoreDataStack(modelName: modelName, transactionAuthor: transactionAuthor)
126+
cleanOldTemporaryMigrationFiles()
127127
}
128128

129129
private var _coreDataStack: CoreDataStack<PersistentContainerType>!
@@ -162,9 +162,12 @@ open class DataMigrationManager<PersistentContainerType: NSPersistentContainer>
162162
}
163163

164164
private func getSourceStoreMetadata(storeURL: URL) throws -> [String: Any] {
165-
let dict = try NSPersistentStoreCoordinator.metadataForPersistentStore(ofType: NSSQLiteStoreType,
166-
at: storeURL,
167-
options: nil)
165+
let dict: [String: Any]
166+
if #available(iOS 15, *) {
167+
dict = try NSPersistentStoreCoordinator.metadataForPersistentStore(type: .sqlite, at: storeURL)
168+
} else {
169+
dict = try NSPersistentStoreCoordinator.metadataForPersistentStore(ofType: NSSQLiteStoreType, at: storeURL)
170+
}
168171
return dict
169172
}
170173

@@ -209,17 +212,64 @@ open class DataMigrationManager<PersistentContainerType: NSPersistentContainer>
209212

210213
private func getStoreManagedObjectModel(storeURL: URL) throws -> NSManagedObjectModel {
211214
let storeMetadata = try getSourceStoreMetadata(storeURL: storeURL)
215+
212216
let allModels = try getAllManagedObjectModels()
213217
for model in allModels {
214218
if model.isConfiguration(withName: nil, compatibleWithStoreMetadata: storeMetadata) {
215219
return model
216220
}
217221
}
222+
223+
// If we reach this point, we could not find a model compatible with the store metadata
224+
// We log a few things to debug this situation
218225
migrationRunningLog.addEvent(message: "Could not determine the store managed object model on disk")
226+
do {
227+
logStoreMetadataTo(migrationRunningLog: migrationRunningLog, storeMetadata: storeMetadata)
228+
if let storeModelVersionIdentifiers = (storeMetadata[NSStoreModelVersionIdentifiersKey] as? NSArray)?.firstObject as? String {
229+
migrationRunningLog.addEvent(message: "The store model version identifier from the store metadadata found on disk is \(storeModelVersionIdentifiers)")
230+
if let model = allModels.first(where: { $0.versionIdentifier == storeModelVersionIdentifiers }) {
231+
migrationRunningLog.addEvent(message: "We found a model having the version identifier \(storeModelVersionIdentifiers). Logging its details now.")
232+
logNSManagedObjectModelTo(migrationRunningLog: migrationRunningLog, model: model)
233+
} else {
234+
migrationRunningLog.addEvent(message: "Among all the models, we could not find a model having an identifier equal to \(storeModelVersionIdentifiers)")
235+
}
236+
} else {
237+
migrationRunningLog.addEvent(message: "Could not determine the store model version identifier from the store metadadata found on disk")
238+
}
239+
}
240+
219241
throw DataMigrationManager.makeError(message: "Could not determine the store managed object model on disk")
220242
}
221243

222244

245+
private func logStoreMetadataTo(migrationRunningLog: RunningLogError, storeMetadata: [String: Any]) {
246+
migrationRunningLog.addEvent(message: "Content of Store Metadata on disk:")
247+
for (key, value) in storeMetadata {
248+
if key == "NSStoreModelVersionHashes", let modelVersionHashes = value as? [String: Data] {
249+
migrationRunningLog.addEvent(message: " \(key) :")
250+
let sortedModelVersionHashes = modelVersionHashes.sorted { $0.key < $1.key }
251+
for (key, value) in sortedModelVersionHashes {
252+
migrationRunningLog.addEvent(message: " \(key) : \(value.hexString())")
253+
}
254+
} else {
255+
migrationRunningLog.addEvent(message: " \(key) : \(String(describing: value))")
256+
}
257+
}
258+
}
259+
260+
261+
private func logNSManagedObjectModelTo(migrationRunningLog: RunningLogError, model: NSManagedObjectModel) {
262+
let entities = model.entities.sorted { ($0.name ?? "") < ($1.name ?? "") }
263+
for entity in entities {
264+
if let entityName = entity.name {
265+
migrationRunningLog.addEvent(message: " \(entityName) : \(entity.versionHash.hexString())")
266+
} else {
267+
migrationRunningLog.addEvent(message: " Entity without name : \(entity.versionHash.hexString())")
268+
}
269+
}
270+
}
271+
272+
223273
// MARK: - Is migration needed
224274

225275
private func isMigrationNeeded() throws -> Bool {
@@ -235,6 +285,64 @@ open class DataMigrationManager<PersistentContainerType: NSPersistentContainer>
235285
migrationRunningLog.addEvent(message: "Failed to get source store metadata: \(error.localizedDescription)")
236286
os_log("Failed to get source store metadata: %{public}@", log: log, type: .fault, error.localizedDescription)
237287
logDebugInformation()
288+
assert(SQLITE_CORRUPT == 11)
289+
let nsError = error as NSError
290+
if (nsError.domain == NSSQLiteErrorDomain && nsError.code == SQLITE_CORRUPT) ||
291+
(nsError.domain == NSCocoaErrorDomain && nsError.code == NSPersistentStoreInvalidTypeError) {
292+
// If the database is corrupted, we know we won't be able to do anything with the file.
293+
// Before giving up, we look for another (not corrupted) .sqlite file in the same directory.
294+
// If non is found, we have no choice but to throw an error.
295+
// If one or more are found, we keep the one with the latest version, use it to replace the corrupted .sqlite file, and try again.
296+
migrationRunningLog.addEvent(message: "[RECOVERY] Since the database is corrupted, we try to recover")
297+
if let urlOfLatestUsableSQLiteFile = getURLOfLatestUsableSQLiteFile(distinctFrom: storeURL) {
298+
299+
migrationRunningLog.addEvent(message: "[RECOVERY] We found a candidate for the database replacement: \(urlOfLatestUsableSQLiteFile.lastPathComponent)")
300+
301+
// Step 1: remove all files relating to the corrupted database (in that order shm file -> wal file -> sqlite file)
302+
let shmFile = self.storeURL.deletingPathExtension().appendingPathExtension("sqlite-shm")
303+
let walFile = self.storeURL.deletingPathExtension().appendingPathExtension("sqlite-wal")
304+
do {
305+
if FileManager.default.fileExists(atPath: shmFile.path) {
306+
migrationRunningLog.addEvent(message: "[RECOVERY] Deleting \(shmFile.lastPathComponent)")
307+
try FileManager.default.removeItem(at: shmFile)
308+
} else {
309+
migrationRunningLog.addEvent(message: "[RECOVERY] No \(shmFile.lastPathComponent) to delete")
310+
}
311+
if FileManager.default.fileExists(atPath: walFile.path) {
312+
migrationRunningLog.addEvent(message: "[RECOVERY] Deleting \(walFile.lastPathComponent)")
313+
try FileManager.default.removeItem(at: walFile)
314+
} else {
315+
migrationRunningLog.addEvent(message: "[RECOVERY] No \(walFile.lastPathComponent) to delete")
316+
}
317+
if FileManager.default.fileExists(atPath: storeURL.path) {
318+
migrationRunningLog.addEvent(message: "[RECOVERY] Deleting \(storeURL.lastPathComponent)")
319+
try FileManager.default.removeItem(at: storeURL)
320+
} else {
321+
migrationRunningLog.addEvent(message: "[RECOVERY] No \(storeURL.lastPathComponent) to delete")
322+
}
323+
}
324+
325+
// Step 2: move the latest usable SQLite file (and its associated files, in that order: sqlite file -> wal file -> shm file)
326+
let shmFileSource = urlOfLatestUsableSQLiteFile.deletingPathExtension().appendingPathExtension("sqlite-shm")
327+
let walFileSource = urlOfLatestUsableSQLiteFile.deletingPathExtension().appendingPathExtension("sqlite-wal")
328+
try FileManager.default.moveItem(at: urlOfLatestUsableSQLiteFile, to: storeURL)
329+
migrationRunningLog.addEvent(message: "[RECOVERY] Did move \(urlOfLatestUsableSQLiteFile.lastPathComponent) to \(storeURL.lastPathComponent)")
330+
if FileManager.default.fileExists(atPath: walFileSource.path) {
331+
try FileManager.default.moveItem(at: walFileSource, to: walFile)
332+
migrationRunningLog.addEvent(message: "[RECOVERY] Did move \(walFileSource.lastPathComponent) to \(walFile.lastPathComponent)")
333+
}
334+
if FileManager.default.fileExists(atPath: shmFileSource.path) {
335+
try FileManager.default.moveItem(at: shmFileSource, to: shmFile)
336+
migrationRunningLog.addEvent(message: "[RECOVERY] Did move \(shmFileSource.lastPathComponent) to \(shmFile.lastPathComponent)")
337+
}
338+
339+
// We have replaced the corrupted database found by the best possible candidate. Now we try again.
340+
341+
migrationRunningLog.addEvent(message: "[RECOVERY] Done with recovery operations. We test again whether a migration is needed.")
342+
return try isMigrationNeeded()
343+
}
344+
migrationRunningLog.addEvent(message: "[RECOVERY] We could not recover as no temporary SQLite file could be found")
345+
}
238346
throw error
239347
}
240348

@@ -266,6 +374,48 @@ open class DataMigrationManager<PersistentContainerType: NSPersistentContainer>
266374
df.timeStyle = .short
267375
return df
268376
}()
377+
378+
379+
private func getURLOfLatestUsableSQLiteFile(distinctFrom urlToSkip: URL) -> URL? {
380+
migrationRunningLog.addEvent(message: "[RECOVERY] Looking for the latest temporary SQLite file")
381+
var urlOfLatestSQLiteFile: URL?
382+
var modelVersionOfLatestSQLiteFile: String?
383+
var isDirectory: ObjCBool = false
384+
if FileManager.default.fileExists(atPath: storeURL.path, isDirectory: &isDirectory), !isDirectory.boolValue {
385+
let storeDirectory = storeURL.deletingLastPathComponent()
386+
migrationRunningLog.addEvent(message: "[RECOVERY] Looking for the latest temporary SQLite file in \(storeDirectory.path)")
387+
if let directoryContents = try? FileManager.default.contentsOfDirectory(at: storeDirectory, includingPropertiesForKeys: nil) {
388+
for file in directoryContents {
389+
guard file.pathExtension == "sqlite" && file != urlToSkip else { continue }
390+
migrationRunningLog.addEvent(message: "[RECOVERY] Found an sqlite file: \(file.lastPathComponent)")
391+
guard let sourceStoreMetadata = try? getSourceStoreMetadata(storeURL: file) else {
392+
migrationRunningLog.addEvent(message: "[RECOVERY] Could not get metadata of file: \(file.lastPathComponent)")
393+
continue
394+
}
395+
guard let sourceVersionIdentifier = (sourceStoreMetadata[NSStoreModelVersionIdentifiersKey] as? [Any])?.first as? String else {
396+
assertionFailure()
397+
migrationRunningLog.addEvent(message: "[RECOVERY] Could not get version identifier from source store metadata of file: \(file.lastPathComponent)")
398+
continue
399+
}
400+
migrationRunningLog.addEvent(message: "[RECOVERY] The source version identifier of file \(file.lastPathComponent) is \(sourceVersionIdentifier)")
401+
guard (try? modelVersion(sourceVersionIdentifier, isMoreRecentThan: modelVersionOfLatestSQLiteFile)) == true else {
402+
migrationRunningLog.addEvent(message: "[RECOVERY] The file \(file.lastPathComponent) is not the latest")
403+
continue
404+
}
405+
// We found a new latest candidate
406+
migrationRunningLog.addEvent(message: "[RECOVERY] We found a new candidate for the latest temporary SQLite file: \(file.lastPathComponent)")
407+
urlOfLatestSQLiteFile = file
408+
modelVersionOfLatestSQLiteFile = sourceVersionIdentifier
409+
}
410+
}
411+
}
412+
if let urlOfLatestSQLiteFile {
413+
migrationRunningLog.addEvent(message: "[RECOVERY] Returning \(urlOfLatestSQLiteFile.lastPathComponent) as the latest temporary sqlite file")
414+
} else {
415+
migrationRunningLog.addEvent(message: "[RECOVERY] We could not find any temporary sqlite file")
416+
}
417+
return urlOfLatestSQLiteFile
418+
}
269419

270420

271421
/// As for now, this method is called when we fail to obtain (metada) information about the current version of the database and thus, fail to migrate to a new version.
@@ -633,6 +783,10 @@ open class DataMigrationManager<PersistentContainerType: NSPersistentContainer>
633783
}
634784

635785

786+
open func modelVersion(_ rawModelVersion: String, isMoreRecentThan otherRawModelVersion: String?) throws -> Bool {
787+
fatalError("Must be overwritten by subclass")
788+
}
789+
636790
}
637791

638792

Engine/ObvDatabaseManager/ObvDatabaseManager/DataMigrationManagerForObvEngine.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ final class DataMigrationManagerForObvEngine: DataMigrationManager<ObvEnginePers
8989
return self.rawValue
9090
}
9191

92+
var intValue: Int? {
93+
let digits = self.rawValue.replacingOccurrences(of: "[^0-9]", with: "", options: .regularExpression)
94+
let intValue = Int(digits)
95+
assert(intValue != nil)
96+
return intValue
97+
}
98+
9299
init(model: NSManagedObjectModel) throws {
93100
guard model.versionIdentifiers.count == 1 else {
94101
assertionFailure()
@@ -214,6 +221,27 @@ final class DataMigrationManagerForObvEngine: DataMigrationManager<ObvEnginePers
214221
}
215222

216223

224+
override func modelVersion(_ rawModelVersion: String, isMoreRecentThan otherRawModelVersion: String?) throws -> Bool {
225+
guard let otherRawModelVersion else { return true }
226+
guard let otherModelVersion = ObvEngineModelVersion(rawValue: otherRawModelVersion) else {
227+
assertionFailure()
228+
throw Self.makeError(message: "Could not parse other raw model version")
229+
}
230+
guard let modelVersion = ObvEngineModelVersion(rawValue: rawModelVersion) else {
231+
assertionFailure()
232+
throw Self.makeError(message: "Could not parse raw model version")
233+
}
234+
guard let otherModelVersionAsInt = otherModelVersion.intValue else {
235+
assertionFailure()
236+
throw Self.makeError(message: "Could not determine int value from other model version")
237+
}
238+
guard let modelVersionAsInt = modelVersion.intValue else {
239+
assertionFailure()
240+
throw Self.makeError(message: "Could not determine int value from model version")
241+
}
242+
return modelVersionAsInt > otherModelVersionAsInt
243+
}
244+
217245
}
218246

219247

0 commit comments

Comments
 (0)