Files
yattee/Yattee/Services/PlayerControls/PlayerControlsBackupService.swift
Arkadiusz Fal 4770bbe3b9 Remove dead code found by Periphery on all platforms
Delete declarations that Periphery reports unused on every platform that
compiles them (iOS, macOS, tvOS), including nine views used only by their
own previews. Platform-gated code still used on another platform, device-
only MPV render paths, migration models, CloudKit record names, App
Intents metadata and unused parameters are kept.
2026-10-08 23:57:56 +02:00

111 lines
4.0 KiB
Swift

//
// PlayerControlsBackupService.swift
// Yattee
//
// Manages backup and recovery of player controls layout presets.
//
import Foundation
/// Manages backup and recovery of player controls layout presets.
actor PlayerControlsBackupService {
// MARK: - Storage
/// URL for the backup file in Application Support.
private var backupFileURL: URL {
let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
let yatteeDir = appSupport.appendingPathComponent("Yattee", isDirectory: true)
return yatteeDir.appendingPathComponent("PlayerControlsBackup.json")
}
private let fileManager = FileManager.default
private let encoder = JSONEncoder()
private let decoder = JSONDecoder()
// MARK: - Initialization
init() {
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
encoder.dateEncodingStrategy = .iso8601
decoder.dateDecodingStrategy = .iso8601
}
// MARK: - Backup Operations
/// Creates a backup of the given presets.
/// - Parameter presets: The presets to back up.
func createBackup(presets: [LayoutPreset]) async throws {
// Ensure directory exists
let directory = backupFileURL.deletingLastPathComponent()
if !fileManager.fileExists(atPath: directory.path) {
try fileManager.createDirectory(at: directory, withIntermediateDirectories: true)
}
// Encode and write
let data = try encoder.encode(presets)
try data.write(to: backupFileURL, options: .atomic)
LoggingService.shared.info("Created player controls backup with \(presets.count) presets")
}
/// Restores presets from backup.
/// - Returns: The restored presets, or nil if no valid backup exists.
func restoreFromBackup() async throws -> [LayoutPreset]? {
guard fileManager.fileExists(atPath: backupFileURL.path) else {
LoggingService.shared.info("No player controls backup file found")
return nil
}
let data = try Data(contentsOf: backupFileURL)
let presets = try decoder.decode([LayoutPreset].self, from: data)
LoggingService.shared.info("Restored \(presets.count) presets from backup")
return presets
}
/// Attempts to create a recovered preset from corrupted data.
/// - Parameter data: The corrupted data.
/// - Returns: A recovered preset if partial recovery is possible.
func createRecoveredPreset(from data: Data) async -> LayoutPreset? {
// Try to extract any valid layout from the data
// This is a best-effort recovery attempt
// First, try decoding as a single preset
if let preset = try? decoder.decode(LayoutPreset.self, from: data) {
let recovered = preset.duplicate(name: "Recovered \(formattedDate())")
LoggingService.shared.info("Recovered single preset from corrupted data")
return recovered
}
// Try decoding as an array and take the first valid one
if let presets = try? decoder.decode([LayoutPreset].self, from: data),
let first = presets.first {
let recovered = first.duplicate(name: "Recovered \(formattedDate())")
LoggingService.shared.info("Recovered preset from corrupted array data")
return recovered
}
// Try decoding just the layout
if let layout = try? decoder.decode(PlayerControlsLayout.self, from: data) {
let recovered = LayoutPreset(
name: "Recovered \(formattedDate())",
layout: layout
)
LoggingService.shared.info("Recovered layout from corrupted data")
return recovered
}
LoggingService.shared.error("Failed to recover any preset from corrupted data")
return nil
}
// MARK: - Helpers
private func formattedDate() -> String {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .short
return formatter.string(from: Date())
}
}