127 lines
4.4 KiB
Swift
127 lines
4.4 KiB
Swift
import Foundation
|
|
import SwiftData
|
|
import OSLog
|
|
|
|
class TranscriptionAutoCleanupService {
|
|
static let shared = TranscriptionAutoCleanupService()
|
|
|
|
private let logger = Logger(subsystem: "com.prakashjoshipax.voiceink", category: "TranscriptionAutoCleanupService")
|
|
private var modelContext: ModelContext?
|
|
|
|
private let keyIsEnabled = "IsTranscriptionCleanupEnabled"
|
|
private let keyRetentionMinutes = "TranscriptionRetentionMinutes"
|
|
|
|
private let defaultRetentionMinutes: Int = 24 * 60
|
|
|
|
private init() {}
|
|
|
|
func startMonitoring(modelContext: ModelContext) {
|
|
self.modelContext = modelContext
|
|
|
|
NotificationCenter.default.addObserver(
|
|
self,
|
|
selector: #selector(handleTranscriptionCreated(_:)),
|
|
name: .transcriptionCreated,
|
|
object: nil
|
|
)
|
|
|
|
if UserDefaults.standard.bool(forKey: keyIsEnabled) {
|
|
|
|
Task { [weak self] in
|
|
guard let self = self, let modelContext = self.modelContext else { return }
|
|
await self.sweepOldTranscriptions(modelContext: modelContext)
|
|
}
|
|
} else {}
|
|
}
|
|
|
|
func stopMonitoring() {
|
|
NotificationCenter.default.removeObserver(self, name: .transcriptionCreated, object: nil)
|
|
|
|
}
|
|
|
|
func runManualCleanup(modelContext: ModelContext) async {
|
|
await sweepOldTranscriptions(modelContext: modelContext)
|
|
}
|
|
|
|
@objc private func handleTranscriptionCreated(_ notification: Notification) {
|
|
let isEnabled = UserDefaults.standard.bool(forKey: keyIsEnabled)
|
|
guard isEnabled else { return }
|
|
|
|
let minutes = UserDefaults.standard.integer(forKey: keyRetentionMinutes)
|
|
if minutes > 0 {
|
|
// Trigger a sweep based on the retention window whenever a new item is added
|
|
if let modelContext = self.modelContext {
|
|
Task { [weak self] in
|
|
guard let self = self else { return }
|
|
await self.sweepOldTranscriptions(modelContext: modelContext)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
guard let transcription = notification.object as? Transcription,
|
|
let modelContext = self.modelContext else {
|
|
logger.error("Invalid transcription or missing model context")
|
|
return
|
|
}
|
|
|
|
|
|
|
|
// Delete the audio file if it exists
|
|
if let urlString = transcription.audioFileURL,
|
|
let url = URL(string: urlString) {
|
|
do {
|
|
try FileManager.default.removeItem(at: url)
|
|
|
|
} catch {
|
|
logger.error("Failed to delete audio file: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
|
|
// Delete the transcription from the database
|
|
modelContext.delete(transcription)
|
|
|
|
do {
|
|
try modelContext.save()
|
|
|
|
} catch {
|
|
logger.error("Failed to save after transcription deletion: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
|
|
private func sweepOldTranscriptions(modelContext: ModelContext) async {
|
|
guard UserDefaults.standard.bool(forKey: keyIsEnabled) else {
|
|
return
|
|
}
|
|
|
|
let retentionMinutes = UserDefaults.standard.integer(forKey: keyRetentionMinutes)
|
|
let effectiveMinutes = max(retentionMinutes, 0)
|
|
|
|
let cutoffDate = Date().addingTimeInterval(TimeInterval(-effectiveMinutes * 60))
|
|
|
|
do {
|
|
try await MainActor.run {
|
|
let descriptor = FetchDescriptor<Transcription>(
|
|
predicate: #Predicate<Transcription> { transcription in
|
|
transcription.timestamp < cutoffDate
|
|
}
|
|
)
|
|
let items = try modelContext.fetch(descriptor)
|
|
var deletedCount = 0
|
|
for transcription in items {
|
|
// Remove audio file if present
|
|
if let urlString = transcription.audioFileURL,
|
|
let url = URL(string: urlString),
|
|
FileManager.default.fileExists(atPath: url.path) {
|
|
try? FileManager.default.removeItem(at: url)
|
|
}
|
|
modelContext.delete(transcription)
|
|
deletedCount += 1
|
|
}
|
|
if deletedCount > 0 { try modelContext.save() }
|
|
}
|
|
} catch {
|
|
logger.error("Failed during transcription cleanup: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
} |