2024-12-01 15:01:36 +01:00

291 lines
11 KiB
Swift

import Foundation
final class Importer {
var posts: [String : PostFile] = [:]
var pages: [String : PageOnDisk] = [:]
var tags: [String : TagFile] = [:]
var files: [String : FileOnDisk] = [:]
var ignoredFiles: [URL] = []
var foldersToSearch: [(path: String, tag: String)] = [
("/Users/ch/Downloads/Website/projects/electronics", "electronics"),
("/Users/ch/Downloads/Website/projects/endeavor", "endeavor"),
("/Users/ch/Downloads/Website/projects/furniture", "furniture"),
("/Users/ch/Downloads/Website/projects/lighting", "lighting"),
("/Users/ch/Downloads/Website/projects/other", "other"),
("/Users/ch/Downloads/Website/projects/sewing", "sewing"),
("/Users/ch/Downloads/Website/projects/software", "software"),
("/Users/ch/Downloads/Website/articles", "articles"),
("/Users/ch/Downloads/Website/photography", "photography"),
("/Users/ch/Downloads/Website/travel", "travel")
]
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)
}
}
}
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)
let thumbnailUrl = folder.appending(path: "thumbnail.jpg", directoryHint: .notDirectory)
var thumbnail: FileOnDisk? = nil
if FileManager.default.fileExists(atPath: thumbnailUrl.path()) {
thumbnail = FileOnDisk(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,
isVisible: true,
german: deTag,
english: enTag)
tags[tagId] = tag
return tagId
}
private func findPageFolders(in folder: URL) throws -> [URL] {
try FileManager.default
.contentsOfDirectory(at: folder, includingPropertiesForKeys: [.isDirectoryKey])
.filter { $0.hasDirectoryPath }
}
private func findResources(in folder: URL, pageId: String) throws -> [FileOnDisk] {
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 FileOnDisk(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())")
return
}
let data = try Data(contentsOf: metadataUrl)
let meta = try JSONDecoder().decode(GenericMetadata.self, from: data)
let pageId = meta.customId ?? url.lastPathComponent
let resources = try findResources(in: url, pageId: pageId)
guard let languages = meta.languages else {
print("No languages for \(url.path())")
return
}
let externalFiles = meta.externalFiles ?? []
let requiredFiles = meta.requiredFiles ?? []
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.sorted(),
externalFiles: externalFiles.sorted(),
requiredFiles: requiredFiles.sorted(),
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],
createdDate: date,
startDate: date,
endDate: endDate,
german: dePage,
english: enPage)
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 add(resource: FileOnDisk) {
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: [FileOnDisk], folder: URL, customPath: String?, pageId: String, language: String) throws -> FileOnDisk? {
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 -> FileOnDisk? {
guard let thumbnailUrl = findThumbnailUrl(in: folder, customPath: customPath, language: language) else {
return nil
}
let id = pageId + "-" + thumbnailUrl.lastPathComponent
return FileOnDisk(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: images.sorted(),
title: page.linkPreviewTitle ?? page.title,
content: content,
lastModifiedDate: nil,
linkPreviewImage: nil,
linkPreviewTitle: nil,
linkPreviewDescription: nil)
}
}
private extension String {
private static let metadataDate: DateFormatter = {
let df = DateFormatter()
df.dateFormat = "dd.MM.yy"
return df
}()
func toDate() -> Date {
String.metadataDate.date(from: self)!
}
}