Load incomplete content, show errors

This commit is contained in:
Christoph Hagen
2025-02-05 20:56:02 +01:00
parent 2b88584ba1
commit d556a51228
9 changed files with 142 additions and 118 deletions

View File

@@ -23,7 +23,6 @@ import SFSafeSymbols
**Fixes**
- Files: Id change: Check all page contents for links to the renamed file and replace occurences
- Database: Show errors during loading
- Investigate issue with spaces in content file names
*/

View File

@@ -20,6 +20,11 @@ struct StorageErrorView: View {
}
}
.frame(minHeight: 300)
if content.saveState == .savingPausedDueToLoadErrors {
Button("Allow saving", action: { content.resumeSavingAfterLoadingErrors() })
.padding()
Text("Saving has been disabled to prevent data corruption due to loading errors. Enable saving to save the partially loaded data.")
}
Button("Dismiss", action: { isPresented = false })
.padding()
}

View File

@@ -16,9 +16,13 @@ extension Content {
}
func saveIfNeeded() {
guard saveState != .isSaved else {
switch saveState {
case .isSaved, .savingPausedDueToLoadErrors, .storageNotInitialized:
return
default:
break
}
if Date.now.timeIntervalSince(lastModification) < 5 {
// Additional modification made
// Wait for next scheduled invocation of saveIfNeeded()
@@ -43,7 +47,6 @@ extension Content {
}
private func saveToDisk() -> Bool {
guard didLoadContent else { return false }
guard storage.contentScope != nil else {
print("Storage not initialized, not saving content")
return false

View File

@@ -4,9 +4,6 @@ import Combine
final class Content: ObservableObject {
@Published
var didLoadContent = false
@ObservedObject
var storage: Storage
@@ -132,31 +129,37 @@ final class Content: ObservableObject {
func loadFromDisk(callback: @escaping () -> ()) {
DispatchQueue.global().async {
let loader = ModelLoader(content: self, storage: self.storage)
let result = loader.load()
guard result.errors.isEmpty else {
DispatchQueue.main.async {
self.didLoadContent = false
self.storageErrors.append(contentsOf: result.errors)
callback()
}
return
}
DispatchQueue.main.async {
self.files = result.files
self.posts = result.posts
self.pages = result.pages
self.tags = result.tags
self.settings = result.settings
self.tagOverview = result.tagOverview
self.didLoadContent = true
callback()
self.generateMissingVideoThumbnails()
}
self.loadInBackground(callback: callback)
}
}
private func loadInBackground(callback: @escaping () -> ()) {
let loader = ModelLoader(content: self, storage: self.storage)
let result = loader.load()
DispatchQueue.main.async {
self.files = result.files
self.posts = result.posts
self.pages = result.pages
self.tags = result.tags
self.settings = result.settings
self.tagOverview = result.tagOverview
self.storageErrors.append(contentsOf: result.errors)
if !result.errors.isEmpty {
self.saveState = .savingPausedDueToLoadErrors
} else {
self.saveState = .isSaved
}
callback()
self.generateMissingVideoThumbnails()
}
}
func resumeSavingAfterLoadingErrors() {
saveState = .needsSave
saveIfNeeded()
}
func generateMissingVideoThumbnails() {
Task {
for file in self.files {

View File

@@ -3,6 +3,7 @@ import SwiftUICore
enum SaveState {
case storageNotInitialized
case savingPausedDueToLoadErrors
case isSaved
case needsSave
case failedToSave
@@ -11,6 +12,8 @@ enum SaveState {
switch self {
case .storageNotInitialized:
return .folderCircleFill
case .savingPausedDueToLoadErrors:
return .exclamationmarkCircleFill
case .isSaved:
return .checkmarkCircleFill
case .needsSave:
@@ -28,7 +31,7 @@ enum SaveState {
return .green
case .needsSave:
return .yellow
case .failedToSave:
case .failedToSave, .savingPausedDueToLoadErrors:
return .red
}
}

View File

@@ -1,91 +1,6 @@
import SwiftUI
import SFSafeSymbols
enum IssueStatus {
case nominal
case warning
case error
var symbol: SFSymbol {
switch self {
case .nominal: .checkmarkCircleFill
case .warning, .error: .exclamationmarkTriangle
}
}
var color: Color {
switch self {
case .nominal: .green
case .warning: .yellow
case .error: .red
}
}
}
struct GenerationStringIssuesView<T>: View where T: Hashable {
let text: String
let statusWhenNonEmpty: IssueStatus
@Binding
var items: Set<T>
let map: (T) -> String
@State
private var showList = false
var status: IssueStatus {
items.isEmpty ? .nominal : statusWhenNonEmpty
}
init(text: String, statusWhenNonEmpty: IssueStatus = .error, items: Binding<Set<T>>, map: @escaping (T) -> String) {
self.text = text
self.statusWhenNonEmpty = statusWhenNonEmpty
self._items = items
self.map = map
}
var body: some View {
HStack {
Button(action: showListIfNonEmpty) {
Image(systemSymbol: status.symbol)
.foregroundStyle(status.color)
}.buttonStyle(.plain)
Text("\(items.count) \(text)")
}
.sheet(isPresented: $showList) {
VStack {
Text("\(items.count) \(text)")
.font(.title)
List(items.map(map).sorted(), id: \.self) { item in
Text(item)
}
.frame(minHeight: 400)
Button("Close") { showList = false }
}.padding()
}
}
private func showListIfNonEmpty() {
guard !items.isEmpty else {
return
}
showList = true
}
}
extension GenerationStringIssuesView where T == String {
init(text: String, statusWhenNonEmpty: IssueStatus = .error, items: Binding<Set<String>>) {
self.text = text
self.statusWhenNonEmpty = statusWhenNonEmpty
self._items = items
self.map = { $0 }
}
}
struct GenerationResultsIssueView: View {
@State
@@ -117,9 +32,9 @@ struct GenerationResultsIssueView: View {
}
private func showListIfNonEmpty() {
// guard !items.isEmpty else {
// return
// }
guard !items().isEmpty else {
return
}
showList = true
}
}

View File

@@ -0,0 +1,65 @@
import SwiftUI
struct GenerationStringIssuesView<T>: View where T: Hashable {
let text: String
let statusWhenNonEmpty: IssueStatus
@Binding
var items: Set<T>
let map: (T) -> String
@State
private var showList = false
var status: IssueStatus {
items.isEmpty ? .nominal : statusWhenNonEmpty
}
init(text: String, statusWhenNonEmpty: IssueStatus = .error, items: Binding<Set<T>>, map: @escaping (T) -> String) {
self.text = text
self.statusWhenNonEmpty = statusWhenNonEmpty
self._items = items
self.map = map
}
var body: some View {
HStack {
Button(action: showListIfNonEmpty) {
Image(systemSymbol: status.symbol)
.foregroundStyle(status.color)
}.buttonStyle(.plain)
Text("\(items.count) \(text)")
}
.sheet(isPresented: $showList) {
VStack {
Text("\(items.count) \(text)")
.font(.title)
List(items.map(map).sorted(), id: \.self) { item in
Text(item)
}
.frame(minHeight: 400)
Button("Close") { showList = false }
}.padding()
}
}
private func showListIfNonEmpty() {
guard !items.isEmpty else {
return
}
showList = true
}
}
extension GenerationStringIssuesView where T == String {
init(text: String, statusWhenNonEmpty: IssueStatus = .error, items: Binding<Set<String>>) {
self.text = text
self.statusWhenNonEmpty = statusWhenNonEmpty
self._items = items
self.map = { $0 }
}
}

View File

@@ -0,0 +1,23 @@
import SFSafeSymbols
import SwiftUICore
enum IssueStatus {
case nominal
case warning
case error
var symbol: SFSymbol {
switch self {
case .nominal: .checkmarkCircleFill
case .warning, .error: .exclamationmarkTriangle
}
}
var color: Color {
switch self {
case .nominal: .green
case .warning: .yellow
case .error: .red
}
}
}