| | |
| | | // MARK: Export / Import |
| | | |
| | | static func exportTo(_ url: URL) throws { |
| | | try ProEntitlementPolicy.requireUnlocked(.layoutExport) |
| | | let store = loadWithEnsuredCategoryScheme() |
| | | let data = try JSONEncoder().encode(store) |
| | | try data.write(to: url, options: .atomic) |
| | | } |
| | | |
| | | static func importFrom(_ url: URL) throws -> Store { |
| | | try ProEntitlementPolicy.requireUnlocked(.layoutImport) |
| | | let previousStore = loadWithEnsuredCategoryScheme() |
| | | let data = try Data(contentsOf: url) |
| | | var store = try JSONDecoder().decode(Store.self, from: data) |
| | |
| | | |
| | | /// Persist the visible app order for one stable container. |
| | | static func reorderApps(inContainer containerID: String, orderedPaths: [String]) { |
| | | guard ProEntitlementPolicy.isUnlocked(.persistentAppSorting) else { return } |
| | | |
| | | var store = TagDatabase.load() |
| | | _ = TagDatabase.normalizeContainerAppOrder(in: &store) |
| | | let previousStore = store |
| | |
| | | TagDatabase.save(store) |
| | | } |
| | | |
| | | static func setAppNote(_ note: String, for path: String) { |
| | | @discardableResult |
| | | static func setAppNote(_ note: String, for path: String) -> ProNoteSaveDecision { |
| | | var store = TagDatabase.load() |
| | | let previousStore = store |
| | | let trimmed = note.trimmingCharacters(in: .whitespacesAndNewlines) |
| | | let limited = String(trimmed.prefix(TagDatabase.maxAppNoteLength)) |
| | | let decision = ProEntitlementPolicy.noteSaveDecision( |
| | | note: limited, |
| | | for: path, |
| | | in: store |
| | | ) |
| | | |
| | | guard case .allow = decision else { |
| | | return decision |
| | | } |
| | | |
| | | let previousStore = store |
| | | if limited.isEmpty { |
| | | store.appNotes.removeValue(forKey: path) |
| | | store.appNoteMetadata[path] = TagDatabase.AppNoteMetadata( |
| | |
| | | previous: previousStore, |
| | | reason: "edit-app-note" |
| | | ) |
| | | return decision |
| | | } |
| | | |
| | | static func moveApp(path: String, from sourceTag: String, to targetTag: String, color: Int, copy: Bool) { |