Import old content, load from disk

This commit is contained in:
Christoph Hagen
2024-11-18 20:19:20 +01:00
parent 0989f06d87
commit 943d8d962b
24 changed files with 1326 additions and 210 deletions

View File

@ -1,21 +1,71 @@
import Foundation
struct ImportedContent {
enum FileType {
case image
case file
case video
case resource
let posts: [Post]
let categories: [Tag]
init(fileExtension: String) {
switch fileExtension.lowercased() {
case "jpg", "jpeg", "png", "gif":
self = .image
case "html", "stl", "f3d", "step", "f3z", "zip", "json", "conf", "css", "js", "cpp", "cddx", "svg", "glb", "mp3", "pdf", "swift":
self = .file
case "mp4":
self = .video
case "key", "psd":
self = .resource
default:
print("Unhandled file type: \(fileExtension)")
self = .resource
}
}
}
struct ImportedPage {
let page: PageFile
let deContentUrl: URL
let enContentUrl: URL
}
struct FileResource {
let type: FileType
let url: URL
let name: String
init(image: String, url: URL) {
self.type = .image
self.url = url
self.name = image
}
init(type: FileType, url: URL, name: String) {
self.type = type
self.url = url
self.name = name
}
}
final class Importer {
var posts: [Post] = []
var posts: [String : PostFile] = [:]
var pages: [Page] = []
var pages: [String : ImportedPage] = [:]
var tags: [Tag] = []
var tags: [String : TagFile] = [:]
var images: [ImageResource] = []
var files: [String : FileResource] = [:]
var ignoredFiles: [URL] = []
var foldersToSearch: [(path: String, tag: String)] = [
("/Users/ch/Downloads/Website/projects/electronics", "electronics"),
@ -30,93 +80,250 @@ final class Importer {
("/Users/ch/Downloads/Website/travel", "travel")
]
func importOldContent() throws {
for (folder, tagName) in foldersToSearch {
let url = URL(filePath: folder)
let tag = try importTag(name: tagName, folder: url)
try importEntries(in: url, tag: tag)
tags.append(tag)
func importContent() throws {
for (path, name) in foldersToSearch {
let folder = URL(filePath: path)
let pageFolders = try findPageFolders(in: folder)
let tag = try importTag(name: name, folder: folder)
for pageFolder in pageFolders {
try importEntry(at: pageFolder, tag: tag)
}
}
posts.sort { $0.startDate > $1.startDate }
//pages.sort { $0.startDate > $1.startDate }
tags.sort()
}
private func importTag(name: String, folder: URL) throws -> Tag {
private func importTag(name: String, folder: URL) throws -> String {
let metadataUrl = folder.appending(path: "metadata.json", directoryHint: .notDirectory)
let data = try Data(contentsOf: metadataUrl)
let meta = try JSONDecoder().decode(ImportableTag.self, from: data)
return .init(
en: meta.info(for: .english)!.title,
de: meta.info(for: .german)!.title)
let thumbnailUrl = folder.appending(path: "thumbnail.jpg", directoryHint: .notDirectory)
var thumbnail: FileResource? = nil
if FileManager.default.fileExists(atPath: thumbnailUrl.path()) {
thumbnail = FileResource(type: .image, url: thumbnailUrl, name: "\(name)-thumbnail.jpg")
add(resource: thumbnail!)
}
func makeTag(metadata: TagLanguage) throws -> LocalizedTagFile {
let language = ContentLanguage(rawValue: metadata.language)!
let originalUrl = folder
.appendingPathComponent("\(language.rawValue).html", isDirectory: false)
.path()
.replacingOccurrences(of: "/Users/ch/Downloads/Website", with: "")
return LocalizedTagFile(
urlComponent: metadata.title.lowercased().replacingOccurrences(of: " ", with: "-"),
name: metadata.title,
subtitle: metadata.subtitle,
description: metadata.description,
thumbnail: thumbnail?.name,
originalURL: originalUrl)
}
let en = meta.info(for: .english)!
let de = meta.info(for: .german)!
let tagId = en.title.lowercased().replacingOccurrences(of: " ", with: "-")
let enTag = try makeTag(metadata: en)
let deTag = try makeTag(metadata: de)
let tag = TagFile(
id: enTag.urlComponent,
german: deTag,
english: enTag)
tags[tagId] = tag
return tagId
}
private func importEntries(in folder: URL, tag: Tag) throws {
private func findPageFolders(in folder: URL) throws -> [URL] {
try FileManager.default
.contentsOfDirectory(at: folder, includingPropertiesForKeys: [.isDirectoryKey])
.filter { $0.hasDirectoryPath }
.forEach { try importEntry(at: $0, tag: tag) }
}
private func importEntry(at url: URL, tag: Tag) throws {
private func findResources(in folder: URL, pageId: String) throws -> [FileResource] {
try FileManager.default
.contentsOfDirectory(at: folder, includingPropertiesForKeys: [.isDirectoryKey])
.filter { !$0.hasDirectoryPath }
.compactMap { url in
let fileName = url.lastPathComponent
let fileExtension = url.pathExtension
guard fileName != "metadata.json",
fileName != "de.md",
fileName != "en.md" else {
return nil
}
let type = FileType(fileExtension: fileExtension)
guard type != .resource else {
self.ignoredFiles.append(url)
return nil
}
let name = pageId + "-" + fileName
return FileResource(type: type, url: url, name: name)
}
}
private func importEntry(at url: URL, tag: String) throws {
let metadataUrl = url.appending(path: "metadata.json", directoryHint: .notDirectory)
guard FileManager.default.fileExists(atPath: metadataUrl.path()) else {
//print("No entry at \(url.path())")
print("No entry at \(url.path())")
return
}
let data = try Data(contentsOf: metadataUrl)
let meta = try JSONDecoder().decode(GenericMetadata.self, from: data)
let page = Page(
id: meta.customId ?? url.lastPathComponent,
isDraft: meta.state == "draft",
metadata: meta.languages!.map(convertPageContent),
externalFiles: meta.externalFiles ?? [],
requiredFiles: meta.requiredFiles ?? [],
images: meta.images ?? [])
pages.append(page)
let pageId = meta.customId ?? url.lastPathComponent
let de = meta.languages!.first { $0.language == "de" }!
let en = meta.languages!.first { $0.language == "en" }!
let resources = try findResources(in: url, pageId: pageId)
let thumbnailImageName = meta.thumbnailPath ?? "thumbnail.jpg"
let thumbnailImageUrl = url.appending(path: thumbnailImageName, directoryHint: .notDirectory)
var images: [ImageResource] = []
if tag.id != "articles" {
if FileManager.default.fileExists(atPath: thumbnailImageUrl.path()) {
let thumbnail = ImageResource(
uniqueId: meta.customId ?? url.lastPathComponent,
altText: .init(en: "An image about \(en.title!)", de: "Ein Bild zu \(de.title!)"),
fileUrl: thumbnailImageUrl)
images.append(thumbnail)
self.images.append(thumbnail)
} else {
print("Thumbnail \(thumbnailImageUrl.path()) not found")
}
guard let languages = meta.languages else {
print("No languages for \(url.path())")
return
}
let lastPostId = posts.last?.id ?? 0
let externalFiles = meta.externalFiles ?? []
let requiredFiles = meta.requiredFiles ?? []
let post = Post(
id: lastPostId + 1,
isDraft: meta.state == "draft" || meta.state == "hidden",
startDate: meta.date!.toDate(),
endDate: meta.endDate?.toDate(),
title: .init(en: en.linkPreviewTitle ?? en.title!,
de: de.linkPreviewTitle ?? de.title!),
text: .init(en: en.linkPreviewDescription ?? en.description ?? "No description",
de: de.linkPreviewDescription ?? de.description ?? "Keine Beschreibung"),
let date = meta.date!.toDate()
let endDate = meta.endDate?.toDate()
let de = languages.first { $0.language == "de" }!
let en = languages.first { $0.language == "en" }!
@discardableResult
func makePage(_ content: GenericMetadata.LocalizedMetadata) throws -> (LocalizedPageFile, URL, LocalizedPostFile) {
let language = ContentLanguage(rawValue: content.language!)!
let id: String
if language == .english {
id = pageId
} else {
id = pageId + "-" + language.rawValue
}
let originalUrl = url
.appendingPathComponent("\(language.rawValue).html", isDirectory: false)
.path()
.replacingOccurrences(of: "/Users/ch/Downloads/Website", with: "")
var pageFiles = Set(resources.map { $0.name })
let thumbnail = try determineThumbnail(in: resources, folder: url, customPath: meta.thumbnailPath, pageId: id, language: language.rawValue)
if let thumbnail {
pageFiles.insert(thumbnail.name)
}
let page = LocalizedPageFile(
url: id,
files: pageFiles,
externalFiles: externalFiles,
requiredFiles: requiredFiles,
title: content.title!,
linkPreviewImage: thumbnail?.name,
linkPreviewTitle: content.linkPreviewTitle,
linkPreviewDescription: content.linkPreviewDescription,
lastModifiedDate: nil,
originalURL: originalUrl)
let contentUrl = url.appendingPathComponent("\(content.language!).md", isDirectory: false)
let postContent = content.linkPreviewDescription ?? content.description ?? ""
let post = createPost(page: page, content: postContent)
return (page, contentUrl, post)
}
let (dePage, deUrl, dePost) = try makePage(de)
let (enPage, enUrl, enPost) = try makePage(en)
let page = PageFile(
isDraft: meta.state == "draft",
tags: [tag],
images: images)
createdDate: date,
startDate: date,
endDate: endDate,
german: dePage,
english: enPage)
posts.append(post)
if pages[pageId] != nil {
print("Conflicting page id \(pageId)")
}
pages[pageId] = .init(page: page, deContentUrl: deUrl, enContentUrl: enUrl)
for resource in resources {
add(resource: resource)
}
let post = PostFile(
isDraft: page.isDraft || meta.state == "hidden",
createdDate: page.createdDate,
startDate: page.startDate,
endDate: page.endDate,
tags: page.tags,
german: dePost,
english: enPost,
linkedPageId: pageId)
posts[pageId] = post
}
private func convertPageContent(_ meta: GenericMetadata.LocalizedMetadata) -> LocalizedPage {
.init(language: ContentLanguage(rawValue: meta.language!)!,
urlString: nil,
headline: meta.title!)
private func add(resource: FileResource) {
guard let existingFile = files[resource.name] else {
files[resource.name] = resource
return
}
guard existingFile.url != resource.url else {
return
}
print("Conflicting name for file \(resource.name)")
}
private func determineThumbnail(in resources: [FileResource], folder: URL, customPath: String?, pageId: String, language: String) throws -> FileResource? {
guard let thumbnailUrl = findThumbnailUrl(in: folder, customPath: customPath, language: language) else {
return nil
}
return resources.first { $0.url == thumbnailUrl }
}
private func determineThumbnail(in folder: URL, customPath: String?, pageId: String, language: String) throws -> FileResource? {
guard let thumbnailUrl = findThumbnailUrl(in: folder, customPath: customPath, language: language) else {
return nil
}
let id = pageId + "-" + thumbnailUrl.lastPathComponent
return FileResource(image: id, url: thumbnailUrl)
}
private func findThumbnailUrl(in folder: URL, customPath: String?, language: String) -> URL? {
if let customPath {
return folder.appending(path: customPath, directoryHint: .notDirectory)
}
let thumbnailImageUrl = folder.appending(path: "thumbnail.jpg", directoryHint: .notDirectory)
if FileManager.default.fileExists(atPath: thumbnailImageUrl.path()) {
return thumbnailImageUrl
}
let localizedThumbnail = folder.appending(path: "thumbnail-\(language).jpg", directoryHint: .notDirectory)
if FileManager.default.fileExists(atPath: localizedThumbnail.path()) {
return localizedThumbnail
}
print("No thumbnail found in \(folder.path())")
return nil
}
private func createPost(page: LocalizedPageFile, content: String) -> LocalizedPostFile {
let images = page.linkPreviewImage.map { [$0] } ?? []
return LocalizedPostFile(
images: Set(images),
title: page.linkPreviewTitle ?? page.title,
content: content,
lastModifiedDate: nil)
}
}