Improve storage
This commit is contained in:
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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...")
|
||||
|
@ -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 {
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user