Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Bitkit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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" */ = {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 31 additions & 2 deletions Bitkit/Services/BackupService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
])
Comment on lines +214 to +217

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add the required changelog fragment

This commit is a user-facing fix: because it restores legacy backups that otherwise fail to decode, but it does not add a fragment under changelog.d/next/; root AGENTS.md says “ALWAYS add exactly ONE changelog fragment for user-facing feat: and fix: PRs”. Without the fragment, release automation will omit this restore fix from the release notes.

Useful? React with 👍 / 👎.

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)
Expand All @@ -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)

Expand Down Expand Up @@ -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)
Expand Down
Loading