Improve storage

This commit is contained in:
Christoph Hagen
2024-12-19 16:25:05 +01:00
parent 9c828ff80a
commit 41887a1401
30 changed files with 926 additions and 831 deletions

View File

@ -2,6 +2,40 @@ import Foundation
extension Content {
func generateFeed() -> Bool {
#warning("Implement feed generation")
return false
}
func generateAllPages() -> Bool {
guard startGenerating() else { return false }
defer { endGenerating() }
for page in pages {
for language in ContentLanguage.allCases {
guard generateInternal(page, in: language) else {
return false
}
}
}
let failedAssetCopies = results.values
.reduce(Set()) { $0.union($1.assets) }
.filter { !$0.isExternallyStored }
.filter { !storage.copy(file: $0.id, to: $0.assetUrl) }
let failedFileCopies = results.values
.reduce(Set()) { $0.union($1.files) }
.filter { !$0.isExternallyStored }
.filter { !storage.copy(file: $0.id, to: $0.absoluteUrl) }
guard imageGenerator.runJobs(callback: { _ in }) else {
return false
}
return true
}
func generatePage(_ page: Page) -> Bool {
guard startGenerating() else { return false }
defer { endGenerating() }
@ -11,6 +45,20 @@ extension Content {
return false
}
}
guard imageGenerator.runJobs(callback: { _ in }) else {
return false
}
let failedAssetCopies = results.values
.reduce(Set()) { $0.union($1.assets) }
.filter { !$0.isExternallyStored }
.filter { !storage.copy(file: $0.id, to: $0.assetUrl) }
let failedFileCopies = results.values
.reduce(Set()) { $0.union($1.files) }
.filter { !$0.isExternallyStored }
.filter { !storage.copy(file: $0.id, to: $0.absoluteUrl) }
return true
}
@ -73,6 +121,13 @@ extension Content {
return result
}
// MARK: Images
func recalculateGeneratedImages() {
let images = Set(self.images.map { $0.id })
imageGenerator.recalculateGeneratedImages(by: images)
}
// MARK: Generation
private func startGenerating() -> Bool {
@ -90,64 +145,36 @@ extension Content {
}
private func generateInternal(_ page: Page, in language: ContentLanguage) -> Bool {
let pagesFolder = settings.paths.pagesOutputFolderPath
guard storage.create(folder: pagesFolder, in: .outputPath) else {
print("Failed to generate output folder")
return false
}
let imageGenerator = ImageGenerator(
storage: storage,
settings: settings)
let pageGenerator = PageGenerator(
content: self,
imageGenerator: imageGenerator)
let content: String
let results: PageGenerationResults
do {
(content, results) = try pageGenerator.generate(page: page, language: language)
} catch {
print("Failed to generate page \(page.id) in language \(language): \(error)")
guard let (content, results) = pageGenerator.generate(page: page, language: language) else {
print("Failed to generate page \(page.id) in language \(language)")
return false
}
guard !content.trimmed.isEmpty else {
#warning("Generate page with placeholder content")
return true
DispatchQueue.main.async {
let id = ItemId(itemId: page.id, language: language, itemType: .page)
self.results[id] = results
}
let path = page.absoluteUrl(in: language) + ".html"
do {
try storage.write(content: content, to: path)
} catch {
print("Failed to save page \(page.id): \(error)")
guard storage.write(content, to: path) else {
print("Failed to save page \(page.id)")
return false
}
guard imageGenerator.runJobs(callback: { _ in }) else {
return false
}
guard copy(requiredFiles: results.files) else {
return false
}
return true
return true
}
private func copy(requiredFiles: Set<FileResource>) -> Bool {
//print("Copying \(requiredVideoFiles.count) files...")
for file in requiredFiles {
guard !file.isExternallyStored else {
continue
}
do {
try storage.copy(file: file.id, to: file.absoluteUrl)
} catch {
print("Failed to copy file \(file.id): \(error)")
return false
}
}
return true
}
}
prefix operator ~>
prefix func ~> (operation: () throws -> Void) -> Bool {
do {
try operation()
return true
} catch {
return false
}
}

View File

@ -41,28 +41,44 @@ extension Content {
}
func loadFromDisk() throws {
guard storage.hasContentFolders else {
guard storage.contentScope != nil else {
print("Storage not initialized, not loading content")
throw StorageAccessError.noBookmarkData
}
let settings = try storage.loadSettings() // Uses defaults if missing
let imageDescriptions = try storage.loadFileDescriptions().reduce(into: [:]) { descriptions, description in
descriptions[description.fileId] = description
let settings = storage.loadSettings() ?? .default
let imageDescriptions = storage.loadFileDescriptions()
.default([])
.reduce(into: [:]) { $0[$1.fileId] = $1 }
guard let tagData = storage.loadAllTags() else {
print("Failed to load file tags")
return
}
let tagData = try storage.loadAllTags()
let pagesData = try storage.loadAllPages()
let postsData = try storage.loadAllPosts()
let fileList = try storage.loadAllFiles()
let externalFiles = try storage.loadExternalFileList()
let tagOverviewData = try storage.loadTagOverview()
if tagData.isEmpty { print("No tags loaded") }
guard let pagesData = storage.loadAllPages() else {
print("Failed to load file pages")
return
}
if pagesData.isEmpty { print("No pages loaded") }
guard let postsData = storage.loadAllPosts() else {
print("Failed to load file posts")
return
}
if postsData.isEmpty { print("No posts loaded") }
guard let fileList = storage.loadAllFiles() else {
print("Failed to load file list")
return
}
if fileList.isEmpty { print("No files loaded") }
let externalFiles = storage.loadExternalFileList() ?? []
if externalFiles.isEmpty { print("No external files loaded") }
let tagOverviewData = storage.loadTagOverview()
if tagOverviewData == nil { print("No tag overview loaded") }
print("Loaded data from disk, processing...")

View File

@ -3,23 +3,16 @@ import Foundation
extension Content {
func saveToDisk() throws {
guard storage.hasContentFolders else {
guard storage.contentScope != nil else {
print("Storage not initialized, not saving content")
return
}
//print("Starting save")
for page in pages {
try storage.save(pageMetadata: page.pageFile, for: page.id)
}
for post in posts {
try storage.save(post: post.postFile, for: post.id)
}
for tag in tags {
try storage.save(tagMetadata: tag.tagFile, for: tag.id)
}
try storage.save(settings: settings.file)
var failedSaves = 0
failedSaves += pages.count { !storage.save(pageMetadata: $0.pageFile, for: $0.id) }
failedSaves += posts.count { !storage.save(post: $0.postFile, for: $0.id) }
failedSaves += tags.count { !storage.save(tagMetadata: $0.tagFile, for: $0.id) }
failedSaves.increment(!storage.save(settings: settings.file))
let fileDescriptions: [FileDescriptions] = files.sorted().compactMap { file in
guard !file.english.isEmpty || !file.german.isEmpty else {
@ -31,21 +24,33 @@ extension Content {
english: file.english.nonEmpty)
}
try storage.save(fileDescriptions: fileDescriptions)
try storage.save(tagOverview: tagOverview?.file)
failedSaves.increment(!storage.save(fileDescriptions: fileDescriptions))
failedSaves.increment(!storage.save(tagOverview: tagOverview?.file))
let externalFileList = files.filter { $0.isExternallyStored }.map { $0.id }
try storage.save(externalFileList: externalFileList)
failedSaves.increment(!storage.save(externalFileList: externalFileList))
do {
try storage.deletePostFiles(notIn: posts.map { $0.id })
try storage.deletePageFiles(notIn: pages.map { $0.id })
try storage.deleteTagFiles(notIn: tags.map { $0.id })
try storage.deleteFileResources(notIn: files.map { $0.id })
} catch {
print("Failed to remove unused files: \(error)")
if failedSaves > 0 {
print("Save partially failed with \(failedSaves) errors")
}
}
func removeUnlinkedFiles() -> Bool {
var success = true
if !storage.deletePostFiles(notIn: posts.map { $0.id }) {
success = false
}
if !storage.deletePageFiles(notIn: pages.map { $0.id }) {
success = false
}
if !storage.deleteTagFiles(notIn: tags.map { $0.id }) {
success = false
}
if !storage.deleteFileResources(notIn: files.map { $0.id }) {
success = false
}
return success
}
}
private extension Page {

View File

@ -5,7 +5,7 @@ import Combine
final class Content: ObservableObject {
@ObservedObject
var storage = Storage()
var storage: Storage
@Published
var settings: Settings
@ -26,11 +26,13 @@ final class Content: ObservableObject {
var tagOverview: TagOverviewPage?
@Published
private(set) var results: [ItemId : PageGenerationResults]
var results: [ItemId : PageGenerationResults]
@Published
private(set) var isGeneratingWebsite = false
let imageGenerator: ImageGenerator
init(settings: Settings,
posts: [Post],
pages: [Page],
@ -44,16 +46,29 @@ final class Content: ObservableObject {
self.files = files
self.tagOverview = tagOverview
self.results = [:]
let storage = Storage()
self.storage = storage
self.imageGenerator = ImageGenerator(
storage: storage,
settings: settings)
}
init() {
self.settings = .default
let settings = Settings.default
self.settings = settings
self.posts = []
self.pages = []
self.tags = []
self.files = []
self.tagOverview = nil
self.results = [:]
let storage = Storage()
self.storage = storage
self.imageGenerator = ImageGenerator(
storage: storage,
settings: settings)
}
private func clear() {

View File

@ -39,16 +39,11 @@ final class FileResource: Item {
// MARK: Text
func textContent() -> String {
do {
return try content.storage.fileContent(for: id)
} catch {
print("Failed to load text of file \(id): \(error)")
return ""
}
content.storage.fileContent(for: id) ?? ""
}
func dataContent() throws -> Data {
try content.storage.fileData(for: id)
func dataContent() -> Data? {
content.storage.fileData(for: id)
}
// MARK: Images
@ -61,11 +56,8 @@ final class FileResource: Item {
}
var imageToDisplay: Image {
let imageData: Data
do {
imageData = try content.storage.fileData(for: id)
} catch {
print("Failed to load data for image \(id): \(error)")
guard let imageData = content.storage.fileData(for: id) else {
print("Failed to load data for image \(id)")
return failureImage
}
guard let loadedImage = NSImage(data: imageData) else {
@ -123,14 +115,12 @@ final class FileResource: Item {
id = newId
return true
}
do {
try content.storage.move(file: id, to: newId)
id = newId
return true
} catch {
guard content.storage.move(file: id, to: newId) else {
print("Failed to move file \(id) to \(newId)")
return false
}
id = newId
return true
}
}

View File

@ -73,9 +73,7 @@ final class Post: ObservableObject {
@discardableResult
func update(id newId: String) -> Bool {
do {
try content.storage.move(post: id, to: newId)
} catch {
guard content.storage.move(post: id, to: newId) else {
print("Failed to move file of post \(id)")
return false
}