Complete rename across all bundle IDs, App Groups, CloudKit containers, StoreKit product IDs, data store filenames, URL schemes, logger subsystems, Swift identifiers, user-facing strings (7 languages), file names, directory names, Xcode project, schemes, assets, and documentation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
135 lines
4.6 KiB
Swift
135 lines
4.6 KiB
Swift
//
|
|
// DataControllerDELETE.swift
|
|
// Reflect
|
|
//
|
|
// SwiftData DELETE operations.
|
|
//
|
|
|
|
import SwiftData
|
|
import Foundation
|
|
import os.log
|
|
|
|
extension DataController {
|
|
|
|
// MARK: - Photo Cleanup
|
|
|
|
func cleanupPhotoIfNeeded(for entry: MoodEntryModel) {
|
|
if let photoID = entry.photoID {
|
|
if !PhotoManager.shared.deletePhoto(id: photoID) {
|
|
AppLogger.general.warning("Failed to delete orphaned photo \(photoID.uuidString) for entry on \(entry.forDate)")
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Delete Operations
|
|
|
|
func clearDB() {
|
|
do {
|
|
// Clean up photo files before batch delete
|
|
let descriptor = FetchDescriptor<MoodEntryModel>()
|
|
if let allEntries = try? modelContext.fetch(descriptor) {
|
|
for entry in allEntries {
|
|
cleanupPhotoIfNeeded(for: entry)
|
|
}
|
|
}
|
|
|
|
try modelContext.delete(model: MoodEntryModel.self)
|
|
saveAndRunDataListeners()
|
|
AnalyticsManager.shared.track(.allDataCleared)
|
|
} catch {
|
|
AppLogger.general.error("Failed to clear database: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
|
|
func deleteLast(numberOfEntries: Int) {
|
|
guard let startDate = Calendar.current.date(byAdding: .day, value: -numberOfEntries, to: Date()) else { return }
|
|
let entries = getData(startDate: startDate, endDate: Date(), includedDays: [])
|
|
|
|
for entry in entries {
|
|
cleanupPhotoIfNeeded(for: entry)
|
|
modelContext.delete(entry)
|
|
}
|
|
saveAndRunDataListeners()
|
|
}
|
|
|
|
func deleteRandomFromLast(numberOfEntries: Int) {
|
|
guard let startDate = Calendar.current.date(byAdding: .day, value: -numberOfEntries, to: Date()) else { return }
|
|
let entries = getData(startDate: startDate, endDate: Date(), includedDays: [])
|
|
|
|
for entry in entries where Bool.random() {
|
|
cleanupPhotoIfNeeded(for: entry)
|
|
modelContext.delete(entry)
|
|
}
|
|
saveAndRunDataListeners()
|
|
}
|
|
|
|
/// Get ALL entries for a specific date (not just the first one)
|
|
func getAllEntries(byDate date: Date) -> [MoodEntryModel] {
|
|
let startDate = Calendar.current.startOfDay(for: date)
|
|
let endDate = Calendar.current.date(byAdding: .day, value: 1, to: startDate)!
|
|
|
|
let descriptor = FetchDescriptor<MoodEntryModel>(
|
|
predicate: #Predicate { entry in
|
|
entry.forDate >= startDate && entry.forDate < endDate
|
|
},
|
|
sortBy: [SortDescriptor(\.forDate, order: .forward)]
|
|
)
|
|
|
|
return (try? modelContext.fetch(descriptor)) ?? []
|
|
}
|
|
|
|
/// Delete ALL entries for a specific date
|
|
func deleteAllEntries(forDate date: Date) {
|
|
let entries = getAllEntries(byDate: date)
|
|
for entry in entries {
|
|
cleanupPhotoIfNeeded(for: entry)
|
|
modelContext.delete(entry)
|
|
}
|
|
saveAndRunDataListeners()
|
|
}
|
|
|
|
/// Find and remove duplicate entries, keeping only the most recent for each date
|
|
/// Returns the number of duplicates removed
|
|
@discardableResult
|
|
func removeDuplicates() -> Int {
|
|
let allEntries = getData(startDate: Date(timeIntervalSince1970: 0), endDate: Date(), includedDays: [])
|
|
|
|
// Group by day
|
|
var entriesByDay = [Date: [MoodEntryModel]]()
|
|
let calendar = Calendar.current
|
|
|
|
for entry in allEntries {
|
|
let dayStart = calendar.startOfDay(for: entry.forDate)
|
|
entriesByDay[dayStart, default: []].append(entry)
|
|
}
|
|
|
|
var duplicatesRemoved = 0
|
|
|
|
for (_, dayEntries) in entriesByDay where dayEntries.count > 1 {
|
|
// Sort by timestamp (most recent first), preferring non-missing moods
|
|
let sorted = dayEntries.sorted { a, b in
|
|
// Prefer non-missing moods
|
|
if a.mood != .missing && b.mood == .missing { return true }
|
|
if a.mood == .missing && b.mood != .missing { return false }
|
|
// Then by timestamp (most recent first)
|
|
return a.timestamp > b.timestamp
|
|
}
|
|
|
|
// Keep the first (best) entry, delete the rest
|
|
for entry in sorted.dropFirst() {
|
|
cleanupPhotoIfNeeded(for: entry)
|
|
modelContext.delete(entry)
|
|
duplicatesRemoved += 1
|
|
}
|
|
}
|
|
|
|
if duplicatesRemoved > 0 {
|
|
saveAndRunDataListeners()
|
|
AppLogger.general.info("Removed \(duplicatesRemoved) duplicate entries")
|
|
AnalyticsManager.shared.track(.duplicatesRemoved(count: duplicatesRemoved))
|
|
}
|
|
|
|
return duplicatesRemoved
|
|
}
|
|
}
|