diff --git a/Bitkit.xcodeproj/project.pbxproj b/Bitkit.xcodeproj/project.pbxproj index a1a39b9a..5764cba3 100644 --- a/Bitkit.xcodeproj/project.pbxproj +++ b/Bitkit.xcodeproj/project.pbxproj @@ -1201,7 +1201,7 @@ repositoryURL = "https://github.com/synonymdev/bitkit-core"; requirement = { kind = exactVersion; - version = 0.3.9; + version = 0.4.0; }; }; 96E20CD22CB6D91A00C24149 /* XCRemoteSwiftPackageReference "CodeScanner" */ = { diff --git a/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a418ff0d..3d786848 100644 --- a/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/synonymdev/bitkit-core", "state" : { - "revision" : "5e08294bc5701d8b48337745990a417841d6e026", - "version" : "0.3.9" + "revision" : "4979f4d23aaaeec11a09b77c8117a0afdaad4317", + "version" : "0.4.0" } }, { diff --git a/Bitkit/Services/BackupService.swift b/Bitkit/Services/BackupService.swift index 5a8c0c38..a82138fd 100644 --- a/Bitkit/Services/BackupService.swift +++ b/Bitkit/Services/BackupService.swift @@ -211,7 +211,11 @@ class BackupService { } try await performRestore(category: .activity) { dataBytes in - let payload = try JSONDecoder().decode(ActivityBackupV1.self, from: dataBytes) + let migrated = try self.migrateCoreOwnedBackupFields(dataBytes, fieldMigrations: [ + "activities": migrateBackupActivitiesJson, + "activityTags": migrateBackupActivityTagsJson, + ]) + let payload = try JSONDecoder().decode(ActivityBackupV1.self, from: migrated) try await CoreService.shared.activity.upsertList(payload.activities) try await CoreService.shared.activity.upsertTags(payload.activityTags) @@ -224,7 +228,10 @@ class BackupService { } try await performRestore(category: .metadata) { dataBytes in - let payload = try JSONDecoder().decode(MetadataBackupV1.self, from: dataBytes) + let migrated = try self.migrateCoreOwnedBackupFields(dataBytes, fieldMigrations: [ + "tagMetadata": migrateBackupPreActivityMetadataJson, + ]) + let payload = try JSONDecoder().decode(MetadataBackupV1.self, from: migrated) try await CoreService.shared.activity.upsertPreActivityMetadata(payload.tagMetadata) @@ -739,6 +746,28 @@ class BackupService { } } + /// Fills in wallet ids that predate wallet-scoped activity data before a + /// backup envelope is decoded. Each Core-owned array field is handed to the + /// matching Core migration helper as raw JSON, so the app never edits Core + /// model JSON itself. Records that already carry a wallet id are left + /// unchanged, so this is safe to run on current backups too. + private func migrateCoreOwnedBackupFields( + _ dataBytes: Data, + fieldMigrations: [String: (String) throws -> String] + ) throws -> Data { + guard var root = try JSONSerialization.jsonObject(with: dataBytes) as? [String: Any] else { + return dataBytes + } + for (field, migrate) in fieldMigrations { + guard let array = root[field] as? [Any] else { continue } + let arrayData = try JSONSerialization.data(withJSONObject: array) + let migratedJson = try migrate(String(decoding: arrayData, as: UTF8.self)) + guard let migratedData = migratedJson.data(using: .utf8) else { continue } + root[field] = try JSONSerialization.jsonObject(with: migratedData) + } + return try JSONSerialization.data(withJSONObject: root) + } + private func performRestore(category: BackupCategory, restoreAction: (Data) async throws -> Void) async throws { do { let item = try await vssBackupClient.getObject(key: category.rawValue)