185 lines
7.1 KiB
Swift
185 lines
7.1 KiB
Swift
import Foundation
|
|
import SwiftData
|
|
|
|
/// A utility class that manages automatic cleanup of audio files while preserving transcript data
|
|
class AudioCleanupManager {
|
|
static let shared = AudioCleanupManager()
|
|
|
|
private var cleanupTimer: Timer?
|
|
|
|
// Default cleanup settings
|
|
private let defaultRetentionDays = 7
|
|
private let cleanupCheckInterval: TimeInterval = 86400 // Check once per day (in seconds)
|
|
|
|
private init() {}
|
|
|
|
/// Start the automatic cleanup process
|
|
func startAutomaticCleanup(modelContext: ModelContext) {
|
|
// Cancel any existing timer
|
|
cleanupTimer?.invalidate()
|
|
|
|
// Perform initial cleanup
|
|
Task {
|
|
await performCleanup(modelContext: modelContext)
|
|
}
|
|
|
|
// Schedule regular cleanup
|
|
cleanupTimer = Timer.scheduledTimer(withTimeInterval: cleanupCheckInterval, repeats: true) { [weak self] _ in
|
|
Task { [weak self] in
|
|
await self?.performCleanup(modelContext: modelContext)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Stop the automatic cleanup process
|
|
func stopAutomaticCleanup() {
|
|
cleanupTimer?.invalidate()
|
|
cleanupTimer = nil
|
|
}
|
|
|
|
/// Get information about the files that would be cleaned up
|
|
func getCleanupInfo(modelContext: ModelContext) async -> (fileCount: Int, totalSize: Int64, transcriptions: [Transcription]) {
|
|
// Get retention period from UserDefaults
|
|
let retentionDays = UserDefaults.standard.integer(forKey: "AudioRetentionPeriod")
|
|
let effectiveRetentionDays = retentionDays > 0 ? retentionDays : defaultRetentionDays
|
|
|
|
// Calculate the cutoff date
|
|
let calendar = Calendar.current
|
|
guard let cutoffDate = calendar.date(byAdding: .day, value: -effectiveRetentionDays, to: Date()) else {
|
|
return (0, 0, [])
|
|
}
|
|
|
|
do {
|
|
// Execute SwiftData operations on the main thread
|
|
return try await MainActor.run {
|
|
// Create a predicate to find transcriptions with audio files older than the cutoff date
|
|
let descriptor = FetchDescriptor<Transcription>(
|
|
predicate: #Predicate<Transcription> { transcription in
|
|
transcription.timestamp < cutoffDate &&
|
|
transcription.audioFileURL != nil
|
|
}
|
|
)
|
|
|
|
let transcriptions = try modelContext.fetch(descriptor)
|
|
|
|
// Calculate stats (can be done on any thread)
|
|
var fileCount = 0
|
|
var totalSize: Int64 = 0
|
|
var eligibleTranscriptions: [Transcription] = []
|
|
|
|
for transcription in transcriptions {
|
|
if let urlString = transcription.audioFileURL,
|
|
let url = URL(string: urlString),
|
|
FileManager.default.fileExists(atPath: url.path) {
|
|
if let attributes = try? FileManager.default.attributesOfItem(atPath: url.path),
|
|
let fileSize = attributes[.size] as? Int64 {
|
|
totalSize += fileSize
|
|
fileCount += 1
|
|
eligibleTranscriptions.append(transcription)
|
|
}
|
|
}
|
|
}
|
|
|
|
return (fileCount, totalSize, eligibleTranscriptions)
|
|
}
|
|
} catch {
|
|
return (0, 0, [])
|
|
}
|
|
}
|
|
|
|
/// Perform the cleanup operation
|
|
private func performCleanup(modelContext: ModelContext) async {
|
|
// Get retention period from UserDefaults
|
|
let retentionDays = UserDefaults.standard.integer(forKey: "AudioRetentionPeriod")
|
|
let effectiveRetentionDays = retentionDays > 0 ? retentionDays : defaultRetentionDays
|
|
|
|
// Check if automatic cleanup is enabled
|
|
let isCleanupEnabled = UserDefaults.standard.bool(forKey: "IsAudioCleanupEnabled")
|
|
guard isCleanupEnabled else { return }
|
|
|
|
// Calculate the cutoff date
|
|
let calendar = Calendar.current
|
|
guard let cutoffDate = calendar.date(byAdding: .day, value: -effectiveRetentionDays, to: Date()) else {
|
|
return
|
|
}
|
|
|
|
do {
|
|
// Execute SwiftData operations on the main thread
|
|
try await MainActor.run {
|
|
// Create a predicate to find transcriptions with audio files older than the cutoff date
|
|
let descriptor = FetchDescriptor<Transcription>(
|
|
predicate: #Predicate<Transcription> { transcription in
|
|
transcription.timestamp < cutoffDate &&
|
|
transcription.audioFileURL != nil
|
|
}
|
|
)
|
|
|
|
let transcriptions = try modelContext.fetch(descriptor)
|
|
var deletedCount = 0
|
|
|
|
for transcription in transcriptions {
|
|
if let urlString = transcription.audioFileURL,
|
|
let url = URL(string: urlString),
|
|
FileManager.default.fileExists(atPath: url.path) {
|
|
try? FileManager.default.removeItem(at: url)
|
|
transcription.audioFileURL = nil
|
|
deletedCount += 1
|
|
}
|
|
}
|
|
|
|
if deletedCount > 0 {
|
|
try modelContext.save()
|
|
}
|
|
}
|
|
} catch {
|
|
// Silently fail - cleanup is non-critical
|
|
}
|
|
}
|
|
|
|
/// Run cleanup manually - can be called from settings
|
|
func runManualCleanup(modelContext: ModelContext) async {
|
|
await performCleanup(modelContext: modelContext)
|
|
}
|
|
|
|
/// Run cleanup on the specified transcriptions
|
|
func runCleanupForTranscriptions(modelContext: ModelContext, transcriptions: [Transcription]) async -> (deletedCount: Int, errorCount: Int) {
|
|
do {
|
|
// Execute SwiftData operations on the main thread
|
|
return try await MainActor.run {
|
|
var deletedCount = 0
|
|
var errorCount = 0
|
|
|
|
for transcription in transcriptions {
|
|
if let urlString = transcription.audioFileURL,
|
|
let url = URL(string: urlString),
|
|
FileManager.default.fileExists(atPath: url.path) {
|
|
do {
|
|
try FileManager.default.removeItem(at: url)
|
|
transcription.audioFileURL = nil
|
|
deletedCount += 1
|
|
} catch {
|
|
errorCount += 1
|
|
}
|
|
}
|
|
}
|
|
|
|
if deletedCount > 0 || errorCount > 0 {
|
|
try? modelContext.save()
|
|
}
|
|
|
|
return (deletedCount, errorCount)
|
|
}
|
|
} catch {
|
|
return (0, 0)
|
|
}
|
|
}
|
|
|
|
/// Format file size in human-readable form
|
|
func formatFileSize(_ size: Int64) -> String {
|
|
let byteCountFormatter = ByteCountFormatter()
|
|
byteCountFormatter.allowedUnits = [.useKB, .useMB, .useGB]
|
|
byteCountFormatter.countStyle = .file
|
|
return byteCountFormatter.string(fromByteCount: size)
|
|
}
|
|
}
|