2025-02-05 20:56:02 +01:00

195 lines
4.6 KiB
Swift

import Foundation
import SwiftUI
import Combine
final class Content: ObservableObject {
@ObservedObject
var storage: Storage
@Published
var settings: Settings!
@Published
var posts: [Post]
@Published
var pages: [Page]
@Published
var tags: [Tag]
@Published
var files: [FileResource]
@Published
var tagOverview: Tag?
@Published
var results: GenerationResults
@Published
var storageErrors: [StorageError] = []
@Published
var generationStatus: String = "Ready to generate"
@Published
private(set) var isGeneratingWebsite = false
@Published
private(set) var shouldGenerateWebsite = false
@Published
private(set) var saveState: SaveState = .isSaved
let imageGenerator: ImageGenerator
var errorCallback: ((StorageError) -> Void)?
init() {
let settings = Settings.default
self.settings = settings
self.posts = []
self.pages = []
self.tags = []
self.files = []
self.tagOverview = nil
self.results = .init()
let storage = Storage()
self.storage = storage
self.imageGenerator = ImageGenerator(
storage: storage,
settings: settings)
storage.errorNotification = { [weak self] error in
self?.storageErrors.append(error)
}
settings.content = self
}
private func clear() {
self.settings = .default
self.posts = []
self.pages = []
self.tags = []
self.files = []
self.tagOverview = nil
self.results = .init()
}
var images: [FileResource] {
files.filter { $0.type.isImage }
}
func set(isGenerating: Bool) {
self.isGeneratingWebsite = isGenerating
}
func set(shouldGenerate: Bool) {
self.shouldGenerateWebsite = shouldGenerate
}
func add(_ file: FileResource) {
// TODO: Insert at correct index?
files.insert(file, at: 0)
}
func add(_ page: Page) {
// TODO: Insert at correct index?
pages.insert(page, at: 0)
}
func update(contentPath: URL, callback: @escaping () -> ()) {
guard storage.save(contentPath: contentPath) else {
return
}
loadFromDisk(callback: callback)
}
func remove(_ file: FileResource) {
files.remove(file)
for post in posts {
post.remove(file)
}
for page in pages {
page.remove(file)
}
for tag in tags {
tag.remove(file)
}
settings.remove(file)
}
func file(withOutputPath: String) -> FileResource? {
files.first { $0.absoluteUrl == withOutputPath }
}
func loadFromDisk(callback: @escaping () -> ()) {
DispatchQueue.global().async {
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 {
guard file.type.isVideo else { continue }
guard !file.isExternallyStored else { continue }
guard !storage.hasVideoThumbnail(for: file.id) else { continue }
if await imageGenerator.createVideoThumbnail(for: file.id) {
print("Generated thumbnail for \(file.id)")
file.didChange()
}
}
}
}
// MARK: Saving
private(set) var lastSave: Date = .now
private(set) var lastModification: Date = .now
func update(saveState: SaveState) {
self.saveState = saveState
}
func setModificationTimestamp() {
self.lastModification = .now
}
func setLastSaveTimestamp() {
self.lastSave = .now
}
}