mirror of
https://github.com/yattee/yattee.git
synced 2026-02-20 17:59:45 +00:00
Yattee v2 rewrite
This commit is contained in:
138
Yattee/Services/PlayerControls/PlayerControlsBackupService.swift
Normal file
138
Yattee/Services/PlayerControls/PlayerControlsBackupService.swift
Normal file
@@ -0,0 +1,138 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
|
||||
/// Checks if a valid backup exists.
|
||||
/// - Returns: True if a valid backup file exists.
|
||||
func hasValidBackup() async -> Bool {
|
||||
guard fileManager.fileExists(atPath: backupFileURL.path) else {
|
||||
return false
|
||||
}
|
||||
|
||||
// Try to decode to verify it's valid
|
||||
do {
|
||||
let data = try Data(contentsOf: backupFileURL)
|
||||
_ = try decoder.decode([LayoutPreset].self, from: data)
|
||||
return true
|
||||
} catch {
|
||||
LoggingService.shared.error("Backup file exists but is invalid: \(error.localizedDescription)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
/// Deletes the backup file.
|
||||
func deleteBackup() async throws {
|
||||
guard fileManager.fileExists(atPath: backupFileURL.path) else {
|
||||
return
|
||||
}
|
||||
|
||||
try fileManager.removeItem(at: backupFileURL)
|
||||
LoggingService.shared.info("Deleted player controls backup")
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func formattedDate() -> String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .short
|
||||
formatter.timeStyle = .short
|
||||
return formatter.string(from: Date())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user