| | |
| | | private static let cacheLock = NSLock() |
| | | private static var cachedBaseSnapshot: BaseSnapshot? |
| | | private static var cachedLocalizationSnapshots: [String: LocalizationSnapshot] = [:] |
| | | private static let legacyDefaultNoteFingerprintsByBundleIdentifier: [String: Set<String>] = [ |
| | | "com.apple.chess": [ |
| | | "9f72f1448c7e9b47", |
| | | "a3b754a4f06eee33", |
| | | "abc8528f6a2ca356" |
| | | ], |
| | | "com.apple.freeform": [ |
| | | "32fee0886cf2c6bb", |
| | | "503c25567e9640c2", |
| | | "aee5e9a7730252f7" |
| | | ], |
| | | "com.apple.home": [ |
| | | "003e996159a187cc", |
| | | "2707ce5206adfb2a", |
| | | "5b0207586af532a9" |
| | | ], |
| | | "com.apple.music": [ |
| | | "545fa7c8a0d216fa", |
| | | "8867798c868c8c30", |
| | | "bce11863b08fac8b" |
| | | ], |
| | | "com.apple.passwords": [ |
| | | "58b38191593ec9b9", |
| | | "a72f91e414c90254", |
| | | "fa1ce5e1bb5998cd" |
| | | ], |
| | | "com.apple.mobilephone": [ |
| | | "703bd99a660e80e4", |
| | | "80d0df54c7ddf2d1", |
| | | "9d57741d3114a1a2" |
| | | ], |
| | | "com.apple.tv": [ |
| | | "6a29d8ece97cd432", |
| | | "e1efb690bf1c6b94", |
| | | "e28785e11f914401" |
| | | ], |
| | | "com.apple.backup.launcher": [ |
| | | "78edffa39f28cee2", |
| | | "cbf81a2b5e77725b", |
| | | "dc568bb30164e897" |
| | | ], |
| | | "com.apple.diskutility": [ |
| | | "77324bc5de72e176", |
| | | "c43f8e117573f5ed", |
| | | "fd581938fd34e888" |
| | | ], |
| | | "com.apple.screencontinuity": [ |
| | | "171a629bd17970fa", |
| | | "2965e73ff5566d19", |
| | | "bb4662c4f15ab83c" |
| | | ] |
| | | ] |
| | | |
| | | static func isKnownAppleApp(_ app: AppInfo) -> Bool { |
| | | entry(for: app) != nil |
| | |
| | | let categoryIDs = entry.defaultTag |
| | | .compactMap { SmartCategoryID(rawValue: $0) } |
| | | .filter { $0 != .other } |
| | | .map(\.smartStartDefaultCategoryID) |
| | | guard !categoryIDs.isEmpty else { return nil } |
| | | return AppleDefaultCategorization( |
| | | canonicalName: entry.canonicalName, |
| | |
| | | |
| | | @discardableResult |
| | | static func relocalizeDefaultNotesForCurrentLanguage(apps: [AppInfo]) -> Bool { |
| | | guard let base = loadBaseSnapshot() else { return false } |
| | | |
| | | var store = TagDatabase.load() |
| | | let originalStore = store |
| | | var changed = false |
| | | var adoptedLegacyDefaultNote = false |
| | | |
| | | for app in apps where app.isAppleApp { |
| | | let path = app.path.path |
| | | guard let currentNote = normalizedNote(store.appNotes[path]) else { continue } |
| | | guard let metadata = store.appNoteMetadata[path], |
| | | metadata.origin == .appleDefault |
| | | guard let bundleIdentifier = normalizedBundleIdentifier(app.bundleIdentifier), |
| | | base.byBundleIdentifier[bundleIdentifier] != nil, |
| | | let currentNote = normalizedNote(store.appNotes[path], limit: base.noteLimit), |
| | | let localizedNote = localizedNote(forBundleIdentifier: bundleIdentifier) |
| | | else { continue } |
| | | |
| | | let metadata = store.appNoteMetadata[path] |
| | | if metadata?.origin == .manual { |
| | | continue |
| | | } |
| | | |
| | | let currentFingerprint = TagDatabase.noteFingerprint(currentNote) |
| | | let expectedFingerprint = metadata.apple?.noteFingerprint ?? metadata.noteFingerprint |
| | | guard expectedFingerprint == currentFingerprint else { continue } |
| | | guard let defaultNote = Self.defaultNote(for: app), |
| | | defaultNote.note != currentNote |
| | | else { continue } |
| | | let matchesKnownAppleDefault = noteMatchesKnownDefault( |
| | | currentNote, |
| | | forBundleIdentifier: bundleIdentifier, |
| | | base: base |
| | | ) |
| | | |
| | | store.appNotes[path] = defaultNote.note |
| | | let canRelocalize: Bool |
| | | switch metadata?.origin { |
| | | case .some(.manual): |
| | | canRelocalize = false |
| | | case .some(.appleDefault): |
| | | let expectedFingerprint = metadata?.apple?.noteFingerprint ?? metadata?.noteFingerprint |
| | | canRelocalize = expectedFingerprint == currentFingerprint || matchesKnownAppleDefault |
| | | if expectedFingerprint != currentFingerprint && matchesKnownAppleDefault { |
| | | adoptedLegacyDefaultNote = true |
| | | } |
| | | case .some(.catalogDefault): |
| | | canRelocalize = matchesKnownAppleDefault |
| | | if canRelocalize { |
| | | adoptedLegacyDefaultNote = true |
| | | } |
| | | case .none: |
| | | canRelocalize = matchesKnownAppleDefault |
| | | if canRelocalize { |
| | | adoptedLegacyDefaultNote = true |
| | | } |
| | | } |
| | | |
| | | guard canRelocalize else { continue } |
| | | |
| | | let metadataMatchesLocalizedNote = metadata?.origin == .appleDefault |
| | | && metadata?.apple?.entryID == localizedNote.provenance.entryID |
| | | && metadata?.apple?.languageCode == localizedNote.provenance.languageCode |
| | | && metadata?.apple?.notesVersion == localizedNote.provenance.notesVersion |
| | | && metadata?.apple?.noteFingerprint == localizedNote.provenance.noteFingerprint |
| | | && metadata?.noteFingerprint == localizedNote.provenance.noteFingerprint |
| | | guard localizedNote.note != currentNote || !metadataMatchesLocalizedNote else { continue } |
| | | |
| | | store.appNotes[path] = localizedNote.note |
| | | store.appNoteMetadata[path] = TagDatabase.AppNoteMetadata( |
| | | origin: .appleDefault, |
| | | apple: defaultNote.provenance, |
| | | noteFingerprint: defaultNote.provenance.noteFingerprint |
| | | apple: localizedNote.provenance, |
| | | noteFingerprint: localizedNote.provenance.noteFingerprint |
| | | ) |
| | | changed = true |
| | | } |
| | | |
| | | if changed { |
| | | if adoptedLegacyDefaultNote { |
| | | _ = TagDatabase.backup(originalStore, reason: "apple-default-note-migration") |
| | | } |
| | | TagDatabase.save(store) |
| | | } |
| | | return changed |
| | |
| | | return nil |
| | | } |
| | | |
| | | private static func noteMatchesKnownDefault( |
| | | _ note: String, |
| | | forBundleIdentifier bundleIdentifier: String, |
| | | base: BaseSnapshot |
| | | ) -> Bool { |
| | | let fingerprint = TagDatabase.noteFingerprint(note) |
| | | return knownDefaultNoteFingerprints( |
| | | forBundleIdentifier: bundleIdentifier, |
| | | base: base |
| | | ).contains(fingerprint) |
| | | } |
| | | |
| | | private static func knownDefaultNoteFingerprints( |
| | | forBundleIdentifier bundleIdentifier: String, |
| | | base: BaseSnapshot |
| | | ) -> Set<String> { |
| | | var result = Set<String>() |
| | | for languageCode in base.supportedLanguages { |
| | | guard let snapshot = loadLocalizationSnapshot(languageCode: languageCode), |
| | | let note = normalizedNote(snapshot.notes[bundleIdentifier], limit: base.noteLimit) |
| | | else { continue } |
| | | for variant in legacyDefaultNoteVariants(note, limit: base.noteLimit) { |
| | | result.insert(TagDatabase.noteFingerprint(variant)) |
| | | } |
| | | } |
| | | result.formUnion(legacyDefaultNoteFingerprintsByBundleIdentifier[bundleIdentifier] ?? []) |
| | | return result |
| | | } |
| | | |
| | | private static func legacyDefaultNoteVariants(_ note: String, limit: Int) -> [String] { |
| | | let trimmed = String(note.trimmingCharacters(in: .whitespacesAndNewlines).prefix(limit)) |
| | | guard !trimmed.isEmpty else { return [] } |
| | | |
| | | var variants = [trimmed] |
| | | let stripped = noteByRemovingTrailingSentencePunctuation(trimmed) |
| | | if stripped != trimmed { |
| | | variants.append(stripped) |
| | | } |
| | | |
| | | for suffix in [".", "。"] { |
| | | let punctuated = String((stripped + suffix).prefix(limit)) |
| | | if !punctuated.isEmpty { |
| | | variants.append(punctuated) |
| | | } |
| | | } |
| | | |
| | | var seen = Set<String>() |
| | | return variants.filter { seen.insert($0).inserted } |
| | | } |
| | | |
| | | private static func noteByRemovingTrailingSentencePunctuation(_ value: String) -> String { |
| | | var result = value.trimmingCharacters(in: .whitespacesAndNewlines) |
| | | let punctuation = CharacterSet(charactersIn: ".。.!!??") |
| | | while let scalar = result.unicodeScalars.last, |
| | | punctuation.contains(scalar) { |
| | | result.removeLast() |
| | | } |
| | | return result |
| | | } |
| | | |
| | | private static func loadBaseSnapshot() -> BaseSnapshot? { |
| | | cacheLock.lock() |
| | | if let snapshot = cachedBaseSnapshot { |