Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions Type4Me/Database/HistoryStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,41 @@ actor HistoryStore {
}
}

/// Deletes multiple rows in one transaction; posts a single change notification on success.
func delete(ids: [String]) {
guard !ids.isEmpty else { return }
let chunkSize = 500
guard sqlite3_exec(db, "BEGIN IMMEDIATE", nil, nil, nil) == SQLITE_OK else { return }
var ok = true
for chunkStart in stride(from: 0, to: ids.count, by: chunkSize) {
let chunk = Array(ids[chunkStart ..< min(chunkStart + chunkSize, ids.count)])
let placeholders = chunk.map { _ in "?" }.joined(separator: ",")
let sql = "DELETE FROM recognition_history WHERE id IN (\(placeholders));"
var stmt: OpaquePointer?
guard sqlite3_prepare_v2(db, sql, -1, &stmt, nil) == SQLITE_OK else {
ok = false
break
}
defer { sqlite3_finalize(stmt) }
for (idx, id) in chunk.enumerated() {
bind(stmt, Int32(idx + 1), id)
}
if sqlite3_step(stmt) != SQLITE_DONE {
ok = false
break
}
}
if ok {
if sqlite3_exec(db, "COMMIT", nil, nil, nil) == SQLITE_OK {
postDidChangeNotification()
} else {
sqlite3_exec(db, "ROLLBACK", nil, nil, nil)
}
} else {
sqlite3_exec(db, "ROLLBACK", nil, nil, nil)
}
}

func deleteAll() {
if sqlite3_exec(db, "DELETE FROM recognition_history;", nil, nil, nil) == SQLITE_OK {
postDidChangeNotification()
Expand Down
26 changes: 26 additions & 0 deletions Type4Me/UI/Settings/HistorySelectionHelpers.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Foundation

/// Pure helpers for history batch selection (unit-tested).
enum HistorySelectionHelpers {

/// True when `filteredIds` is non-empty and every id is in `selectedIds`.
static func isAllFilteredSelected(filteredIds: Set<String>, selectedIds: Set<String>) -> Bool {
guard !filteredIds.isEmpty else { return false }
return filteredIds.isSubset(of: selectedIds)
}

/// Toggles “select all in current list” vs “deselect all in current list”.
/// - If every `filteredIds` is selected, removes those ids from the selection (others unchanged).
/// - Otherwise unions `filteredIds` into the selection.
/// - If `filteredIds` is empty, returns `selectedIds` unchanged.
static func togglingSelectAllInFiltered(
filteredIds: Set<String>,
selectedIds: Set<String>
) -> Set<String> {
guard !filteredIds.isEmpty else { return selectedIds }
if filteredIds.isSubset(of: selectedIds) {
return selectedIds.subtracting(filteredIds)
}
return selectedIds.union(filteredIds)
}
}
Loading