Support for zero data retention on
This commit is contained in:
parent
d23d54e208
commit
2970895376
@ -11,4 +11,5 @@ extension Notification.Name {
|
||||
static let navigateToDestination = Notification.Name("navigateToDestination")
|
||||
static let promptSelectionChanged = Notification.Name("promptSelectionChanged")
|
||||
static let powerModeConfigurationApplied = Notification.Name("powerModeConfigurationApplied")
|
||||
static let transcriptionCreated = Notification.Name("transcriptionCreated")
|
||||
}
|
||||
|
||||
@ -137,6 +137,7 @@ class AudioTranscriptionManager: ObservableObject {
|
||||
)
|
||||
modelContext.insert(transcription)
|
||||
try modelContext.save()
|
||||
NotificationCenter.default.post(name: .transcriptionCreated, object: transcription)
|
||||
currentTranscription = transcription
|
||||
} catch {
|
||||
logger.error("Enhancement failed: \(error.localizedDescription)")
|
||||
@ -149,6 +150,7 @@ class AudioTranscriptionManager: ObservableObject {
|
||||
)
|
||||
modelContext.insert(transcription)
|
||||
try modelContext.save()
|
||||
NotificationCenter.default.post(name: .transcriptionCreated, object: transcription)
|
||||
currentTranscription = transcription
|
||||
}
|
||||
} else {
|
||||
@ -161,6 +163,7 @@ class AudioTranscriptionManager: ObservableObject {
|
||||
)
|
||||
modelContext.insert(transcription)
|
||||
try modelContext.save()
|
||||
NotificationCenter.default.post(name: .transcriptionCreated, object: transcription)
|
||||
currentTranscription = transcription
|
||||
}
|
||||
|
||||
|
||||
@ -110,6 +110,7 @@ class AudioTranscriptionService: ObservableObject {
|
||||
modelContext.insert(newTranscription)
|
||||
do {
|
||||
try modelContext.save()
|
||||
NotificationCenter.default.post(name: .transcriptionCreated, object: newTranscription)
|
||||
} catch {
|
||||
logger.error("❌ Failed to save transcription: \(error.localizedDescription)")
|
||||
}
|
||||
@ -130,6 +131,7 @@ class AudioTranscriptionService: ObservableObject {
|
||||
modelContext.insert(newTranscription)
|
||||
do {
|
||||
try modelContext.save()
|
||||
NotificationCenter.default.post(name: .transcriptionCreated, object: newTranscription)
|
||||
} catch {
|
||||
logger.error("❌ Failed to save transcription: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ struct GeneralSettings: Codable {
|
||||
let isMenuBarOnly: Bool?
|
||||
let useAppleScriptPaste: Bool?
|
||||
let recorderType: String?
|
||||
let doNotMaintainTranscriptHistory: Bool?
|
||||
let isAudioCleanupEnabled: Bool?
|
||||
let audioRetentionPeriod: Int?
|
||||
|
||||
@ -43,6 +44,7 @@ class ImportExportService {
|
||||
private let keyIsMenuBarOnly = "IsMenuBarOnly"
|
||||
private let keyUseAppleScriptPaste = "UseAppleScriptPaste"
|
||||
private let keyRecorderType = "RecorderType"
|
||||
private let keyDoNotMaintainTranscriptHistory = "DoNotMaintainTranscriptHistory"
|
||||
private let keyIsAudioCleanupEnabled = "IsAudioCleanupEnabled"
|
||||
private let keyAudioRetentionPeriod = "AudioRetentionPeriod"
|
||||
|
||||
@ -87,6 +89,7 @@ class ImportExportService {
|
||||
isMenuBarOnly: menuBarManager.isMenuBarOnly,
|
||||
useAppleScriptPaste: UserDefaults.standard.bool(forKey: keyUseAppleScriptPaste),
|
||||
recorderType: whisperState.recorderType,
|
||||
doNotMaintainTranscriptHistory: UserDefaults.standard.bool(forKey: keyDoNotMaintainTranscriptHistory),
|
||||
isAudioCleanupEnabled: UserDefaults.standard.bool(forKey: keyIsAudioCleanupEnabled),
|
||||
audioRetentionPeriod: UserDefaults.standard.integer(forKey: keyAudioRetentionPeriod),
|
||||
|
||||
@ -230,6 +233,9 @@ class ImportExportService {
|
||||
if let recType = general.recorderType {
|
||||
whisperState.recorderType = recType
|
||||
}
|
||||
if let doNotMaintainHistory = general.doNotMaintainTranscriptHistory {
|
||||
UserDefaults.standard.set(doNotMaintainHistory, forKey: self.keyDoNotMaintainTranscriptHistory)
|
||||
}
|
||||
if let audioCleanup = general.isAudioCleanupEnabled {
|
||||
UserDefaults.standard.set(audioCleanup, forKey: self.keyIsAudioCleanupEnabled)
|
||||
}
|
||||
|
||||
69
VoiceInk/Services/TranscriptionAutoCleanupService.swift
Normal file
69
VoiceInk/Services/TranscriptionAutoCleanupService.swift
Normal file
@ -0,0 +1,69 @@
|
||||
import Foundation
|
||||
import SwiftData
|
||||
import OSLog
|
||||
|
||||
/// A service that automatically deletes transcriptions when "Do Not Maintain Transcript History" is enabled
|
||||
class TranscriptionAutoCleanupService {
|
||||
static let shared = TranscriptionAutoCleanupService()
|
||||
|
||||
private let logger = Logger(subsystem: "com.prakashjoshipax.voiceink", category: "TranscriptionAutoCleanupService")
|
||||
private var modelContext: ModelContext?
|
||||
|
||||
private init() {}
|
||||
|
||||
/// Start monitoring for new transcriptions and auto-delete if needed
|
||||
func startMonitoring(modelContext: ModelContext) {
|
||||
self.modelContext = modelContext
|
||||
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(handleTranscriptionCreated(_:)),
|
||||
name: .transcriptionCreated,
|
||||
object: nil
|
||||
)
|
||||
|
||||
logger.info("TranscriptionAutoCleanupService started monitoring")
|
||||
}
|
||||
|
||||
/// Stop monitoring for transcriptions
|
||||
func stopMonitoring() {
|
||||
NotificationCenter.default.removeObserver(self, name: .transcriptionCreated, object: nil)
|
||||
logger.info("TranscriptionAutoCleanupService stopped monitoring")
|
||||
}
|
||||
|
||||
@objc private func handleTranscriptionCreated(_ notification: Notification) {
|
||||
// Check if no-retention mode is enabled
|
||||
guard UserDefaults.standard.bool(forKey: "DoNotMaintainTranscriptHistory") else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let transcription = notification.object as? Transcription,
|
||||
let modelContext = self.modelContext else {
|
||||
logger.error("Invalid transcription or missing model context")
|
||||
return
|
||||
}
|
||||
|
||||
logger.info("Auto-deleting transcription for zero data retention")
|
||||
|
||||
// Delete the audio file if it exists
|
||||
if let urlString = transcription.audioFileURL,
|
||||
let url = URL(string: urlString) {
|
||||
do {
|
||||
try FileManager.default.removeItem(at: url)
|
||||
logger.debug("Deleted audio file: \(url.lastPathComponent)")
|
||||
} catch {
|
||||
logger.error("Failed to delete audio file: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the transcription from the database
|
||||
modelContext.delete(transcription)
|
||||
|
||||
do {
|
||||
try modelContext.save()
|
||||
logger.debug("Successfully deleted transcription from database")
|
||||
} catch {
|
||||
logger.error("Failed to save after transcription deletion: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,7 @@ struct AudioCleanupSettingsView: View {
|
||||
@EnvironmentObject private var whisperState: WhisperState
|
||||
|
||||
// Audio cleanup settings
|
||||
@AppStorage("DoNotMaintainTranscriptHistory") private var doNotMaintainTranscriptHistory = false
|
||||
@AppStorage("IsAudioCleanupEnabled") private var isAudioCleanupEnabled = true
|
||||
@AppStorage("AudioRetentionPeriod") private var audioRetentionPeriod = 7
|
||||
@State private var isPerformingCleanup = false
|
||||
@ -16,16 +17,47 @@ struct AudioCleanupSettingsView: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("VoiceInk can automatically delete audio files from transcription history while preserving the text transcripts.")
|
||||
Text("Control how VoiceInk handles your transcription data and audio recordings for privacy and storage management.")
|
||||
.font(.system(size: 13))
|
||||
.foregroundColor(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
Toggle("Enable automatic audio cleanup", isOn: $isAudioCleanupEnabled)
|
||||
Text("Data Retention")
|
||||
.font(.system(size: 14, weight: .semibold))
|
||||
.foregroundColor(.primary)
|
||||
.padding(.top, 8)
|
||||
|
||||
Toggle("Do not maintain transcript history", isOn: $doNotMaintainTranscriptHistory)
|
||||
.toggleStyle(.switch)
|
||||
.padding(.vertical, 4)
|
||||
|
||||
if isAudioCleanupEnabled {
|
||||
if doNotMaintainTranscriptHistory {
|
||||
Text("When enabled, no transcription history will be saved. This provides zero data retention for maximum privacy.")
|
||||
.font(.system(size: 13))
|
||||
.foregroundColor(.orange)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.top, 2)
|
||||
}
|
||||
|
||||
if !doNotMaintainTranscriptHistory {
|
||||
Divider()
|
||||
.padding(.vertical, 8)
|
||||
|
||||
Text("Audio File Management")
|
||||
.font(.system(size: 14, weight: .semibold))
|
||||
.foregroundColor(.primary)
|
||||
|
||||
Text("Automatically delete audio files from transcription history while preserving the text transcripts.")
|
||||
.font(.system(size: 13))
|
||||
.foregroundColor(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
Toggle("Enable automatic audio cleanup", isOn: $isAudioCleanupEnabled)
|
||||
.toggleStyle(.switch)
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
|
||||
if isAudioCleanupEnabled && !doNotMaintainTranscriptHistory {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Retention Period")
|
||||
.font(.system(size: 14, weight: .medium))
|
||||
|
||||
@ -193,11 +193,11 @@ struct SettingsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// Audio Cleanup Section
|
||||
// Data & Privacy Section
|
||||
SettingsSection(
|
||||
icon: "trash.circle",
|
||||
title: "Audio Cleanup",
|
||||
subtitle: "Manage recording storage"
|
||||
icon: "lock.shield",
|
||||
title: "Data & Privacy",
|
||||
subtitle: "Control transcript history and storage"
|
||||
) {
|
||||
AudioCleanupSettingsView()
|
||||
}
|
||||
|
||||
@ -21,6 +21,9 @@ struct VoiceInkApp: App {
|
||||
// Audio cleanup manager for automatic deletion of old audio files
|
||||
private let audioCleanupManager = AudioCleanupManager.shared
|
||||
|
||||
// Transcription auto-cleanup service for zero data retention
|
||||
private let transcriptionAutoCleanupService = TranscriptionAutoCleanupService.shared
|
||||
|
||||
init() {
|
||||
do {
|
||||
let schema = Schema([
|
||||
@ -98,6 +101,9 @@ struct VoiceInkApp: App {
|
||||
|
||||
// Start the automatic audio cleanup process
|
||||
audioCleanupManager.startAutomaticCleanup(modelContext: container.mainContext)
|
||||
|
||||
// Start the transcription auto-cleanup service for zero data retention
|
||||
transcriptionAutoCleanupService.startMonitoring(modelContext: container.mainContext)
|
||||
}
|
||||
.background(WindowAccessor { window in
|
||||
WindowManager.shared.configureWindow(window)
|
||||
@ -107,6 +113,9 @@ struct VoiceInkApp: App {
|
||||
|
||||
// Stop the automatic audio cleanup process
|
||||
audioCleanupManager.stopAutomaticCleanup()
|
||||
|
||||
// Stop the transcription auto-cleanup service
|
||||
transcriptionAutoCleanupService.stopMonitoring()
|
||||
}
|
||||
} else {
|
||||
OnboardingView(hasCompletedOnboarding: $hasCompletedOnboarding)
|
||||
|
||||
@ -311,6 +311,7 @@ class WhisperState: NSObject, ObservableObject {
|
||||
)
|
||||
modelContext.insert(newTranscription)
|
||||
try? modelContext.save()
|
||||
NotificationCenter.default.post(name: .transcriptionCreated, object: newTranscription)
|
||||
text = enhancedText
|
||||
} catch {
|
||||
let newTranscription = Transcription(
|
||||
@ -323,6 +324,7 @@ class WhisperState: NSObject, ObservableObject {
|
||||
)
|
||||
modelContext.insert(newTranscription)
|
||||
try? modelContext.save()
|
||||
NotificationCenter.default.post(name: .transcriptionCreated, object: newTranscription)
|
||||
|
||||
await MainActor.run {
|
||||
NotificationManager.shared.showNotification(
|
||||
@ -341,6 +343,7 @@ class WhisperState: NSObject, ObservableObject {
|
||||
)
|
||||
modelContext.insert(newTranscription)
|
||||
try? modelContext.save()
|
||||
NotificationCenter.default.post(name: .transcriptionCreated, object: newTranscription)
|
||||
}
|
||||
|
||||
if case .trialExpired = licenseViewModel.licenseState {
|
||||
@ -395,6 +398,7 @@ class WhisperState: NSObject, ObservableObject {
|
||||
|
||||
modelContext.insert(failedTranscription)
|
||||
try? modelContext.save()
|
||||
NotificationCenter.default.post(name: .transcriptionCreated, object: failedTranscription)
|
||||
}
|
||||
} catch {
|
||||
logger.error("❌ Could not create a record for the failed transcription: \(error.localizedDescription)")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user