ChWebsiteApp/CHDataManagement/Generator/LocalizedWebsiteGenerator.swift
2024-12-10 15:21:28 +01:00

193 lines
6.3 KiB
Swift

import Foundation
final class LocalizedWebsiteGenerator {
let language: ContentLanguage
let localizedSettings: LocalizedSettings
private var outputDirectory: URL {
URL(filePath: content.settings.outputDirectoryPath)
}
private var postsPerPage: Int {
content.settings.posts.postsPerPage
}
private var navigationIconPath: String {
content.settings.navigationBar.iconPath
}
private var mainContentMaximumWidth: CGFloat {
CGFloat(content.settings.posts.contentWidth)
}
private let content: Content
private let imageGenerator: ImageGenerator
private var navigationBarData: NavigationBarData {
createNavigationBarData(
settings: content.settings.navigationBar,
iconDescription: localizedSettings.navigationBarIconDescription)
}
init(content: Content, language: ContentLanguage) {
self.language = language
self.content = content
self.localizedSettings = content.settings.localized(in: language)
self.imageGenerator = ImageGenerator(
storage: content.storage,
relativeImageOutputPath: "images") // TODO: Get from settings
}
func generateWebsite(callback: (String) -> Void) -> Bool {
guard imageGenerator.prepareForGeneration() else {
return false
}
guard createMainPostFeedPages() else {
return false
}
#warning("Generate content pages")
guard generateTagPages() else {
return false
}
guard imageGenerator.runJobs(callback: callback) else {
return false
}
return imageGenerator.save()
}
private func createMainPostFeedPages() -> Bool {
let generator = PostListPageGenerator(
language: language,
content: content,
imageGenerator: imageGenerator,
navigationBarData: navigationBarData,
showTitle: false,
pageTitle: localizedSettings.posts.title,
pageDescription: localizedSettings.posts.description,
pageUrlPrefix: localizedSettings.posts.feedUrlPrefix)
return generator.createPages(for: content.posts)
}
private func generateTagPages() -> Bool {
for tag in content.tags {
let posts = content.posts.filter { $0.tags.contains(tag) }
guard posts.count > 0 else { continue }
let localized = tag.localized(in: language)
#warning("Get tag url prefix from settings")
let generator = PostListPageGenerator(
language: language,
content: content,
imageGenerator: imageGenerator,
navigationBarData: navigationBarData,
showTitle: true,
pageTitle: localized.name,
pageDescription: localized.description ?? "",
pageUrlPrefix: "tags/\(localized.urlComponent)")
guard generator.createPages(for: posts) else {
return false
}
}
return true
}
private func createNavigationBarData(settings: NavigationBarSettings, iconDescription: String) -> NavigationBarData {
let navigationItems: [NavigationBarLink] = settings.tags.map {
let localized = $0.localized(in: language)
return .init(text: localized.name, url: localized.urlComponent)
}
return NavigationBarData(
navigationIconPath: navigationIconPath,
iconDescription: iconDescription,
navigationItems: navigationItems)
}
private func generatePagesFolderIfNeeded() -> Bool {
let relativePath = content.settings.pages.pageUrlPrefix
return content.storage.write(in: .outputPath) { folder in
let outputFile = folder.appendingPathComponent(relativePath, isDirectory: true)
do {
try outputFile.ensureFolderExistence()
return true
} catch {
return false
}
}
}
func generate(page: Page) -> Bool {
guard generatePagesFolderIfNeeded() else {
print("Failed to generate output folder")
return false
}
let pageGenerator = PageGenerator(content: content, imageGenerator: imageGenerator, navigationBarData: navigationBarData)
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)")
return false
}
guard !content.trimmed.isEmpty else {
#warning("Generate page with placeholder content")
return true
}
let path = self.content.pageLink(page, language: language) + ".html"
guard save(content, to: path) else {
print("Failed to save page")
return false
}
guard imageGenerator.runJobs(callback: { _ in }) else {
return false
}
guard copy(requiredFiles: results.files) else {
return false
}
return true
}
private func copy(requiredFiles: Set<FileResource>) -> Bool {
//print("Copying \(requiredVideoFiles.count) files...")
for file in requiredFiles {
guard !file.isExternallyStored else {
continue
}
let outputPath: String
switch file.type {
case .video:
outputPath = content.pathToVideo(file)
case .image:
outputPath = content.pathToImage(file)
default:
outputPath = content.pathToFile(file)
}
do {
try content.storage.copy(file: file.id, to: outputPath)
} catch {
print("Failed to copy file \(file.id): \(error)")
return false
}
}
return true
}
private func save(_ content: String, to relativePath: String) -> Bool {
do {
try self.content.storage.write(content: content, to: relativePath)
return true
} catch {
print("Failed to write page \(relativePath)")
return false
}
}
}