From 3c950d47a2bf4d617af133b5f04b2d21119a4c7e Mon Sep 17 00:00:00 2001 From: Christoph Hagen Date: Mon, 2 Dec 2024 22:00:35 +0100 Subject: [PATCH] Add videos, delete unused files --- CHDataManagement/Model/Content.swift | 40 ++++++++--- CHDataManagement/Model/ContentLanguage.swift | 4 ++ .../Preview Content/Content+Mock.swift | 1 + CHDataManagement/Storage/Storage.swift | 72 +++++++++---------- 4 files changed, 70 insertions(+), 47 deletions(-) diff --git a/CHDataManagement/Model/Content.swift b/CHDataManagement/Model/Content.swift index f1ed7aa..1d36cbd 100644 --- a/CHDataManagement/Model/Content.swift +++ b/CHDataManagement/Model/Content.swift @@ -19,6 +19,9 @@ final class Content: ObservableObject { @Published var images: [ImageResource] + @Published + var videos: [String] + @Published var files: [FileResource] @@ -42,6 +45,7 @@ final class Content: ObservableObject { tags: [Tag], images: [ImageResource], files: [FileResource], + videos: [String], storedContentPath: String) { self.websiteData = websiteData self.posts = posts @@ -49,6 +53,7 @@ final class Content: ObservableObject { self.tags = tags self.images = images self.files = files + self.videos = videos self.storedContentPath = storedContentPath self.contentPath = storedContentPath self.storage = Storage(baseFolder: URL(filePath: storedContentPath)) @@ -70,6 +75,7 @@ final class Content: ObservableObject { self.tags = [] self.images = [] self.files = [] + self.videos = [] contentPath = storedContentPath do { @@ -142,21 +148,23 @@ final class Content: ObservableObject { let postsData = try storage.loadAllPosts() let filesData = try storage.loadAllFiles() - let images: [String : ImageResource] = filesData.reduce(into: [:]) { dict, item in - let (file, url) = item - let ext = file.components(separatedBy: ".").last!.lowercased() - let type = FileType(fileExtension: ext) - guard type == .image else { return } - dict[file] = ImageResource(uniqueId: file, altText: .init(en: "", de: ""), fileUrl: url) - } + var images: [String : ImageResource] = [:] + var files: [FileResource] = [] + var videos: [String] = [] - let files: [FileResource] = filesData.compactMap { file, url in + for (file, url) in filesData { let ext = file.components(separatedBy: ".").last!.lowercased() let type = FileType(fileExtension: ext) - guard type == .file else { - return nil + switch type { + case .image: + images[file] = ImageResource(uniqueId: file, altText: .init(en: "", de: ""), fileUrl: url) + case .file: + files.append(FileResource(uniqueId: file, description: "")) + case .video: + videos.append(file) + case .resource: + break } - return FileResource(uniqueId: file, description: "") } let tags = tagData.reduce(into: [:]) { (tags, data) in @@ -189,6 +197,7 @@ final class Content: ObservableObject { self.pages = pages.values.sorted(ascending: false) { $0.startDate } self.files = files.sorted { $0.uniqueId } self.images = images.values.sorted { $0.id } + self.videos = videos self.posts = posts.sorted(ascending: false) { $0.startDate } self.websiteData = WebsiteData( navigationTags: websiteData.navigationTags.map { tags[$0]! }, @@ -228,6 +237,15 @@ final class Content: ObservableObject { } storage.save(websiteData: websiteData.dataFile) + 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 }) + let allFiles = files.map { $0.uniqueId } + images.map { $0.id } + videos + try storage.deleteFiles(notIn: allFiles) + } catch { + print("Failed to remove unused files: \(error)") + } // TODO: Remove all files that are no longer in use (they belong to deleted items) //print("Finished save") } diff --git a/CHDataManagement/Model/ContentLanguage.swift b/CHDataManagement/Model/ContentLanguage.swift index 2fbfb93..004b7d7 100644 --- a/CHDataManagement/Model/ContentLanguage.swift +++ b/CHDataManagement/Model/ContentLanguage.swift @@ -10,3 +10,7 @@ enum ContentLanguage: String { extension ContentLanguage: Codable { } + +extension ContentLanguage: CaseIterable { + +} diff --git a/CHDataManagement/Preview Content/Content+Mock.swift b/CHDataManagement/Preview Content/Content+Mock.swift index 00fd2cf..1458e69 100644 --- a/CHDataManagement/Preview Content/Content+Mock.swift +++ b/CHDataManagement/Preview Content/Content+Mock.swift @@ -21,5 +21,6 @@ extension Content { tags: [.hiking, .mountains, .nature, .sports], images: [], files: [], + videos: [], storedContentPath: dbPath) } diff --git a/CHDataManagement/Storage/Storage.swift b/CHDataManagement/Storage/Storage.swift index d897047..faeda47 100644 --- a/CHDataManagement/Storage/Storage.swift +++ b/CHDataManagement/Storage/Storage.swift @@ -76,9 +76,7 @@ final class Storage { func createFolderStructure() throws { try create(folder: pagesFolder) - try create(folder: imagesFolder) try create(folder: filesFolder) - try create(folder: videosFolder) try create(folder: postsFolder) try create(folder: tagsFolder) } @@ -88,16 +86,20 @@ final class Storage { /// The folder path where the markdown and metadata files of the pages are stored (by their id/url component) private var pagesFolder: URL { subFolder("pages") } - private func pageFileUrl(pageId: String) -> URL { - pagesFolder.appending(path: pageId, directoryHint: .notDirectory) + private func pageContentFileName(_ id: String, _ language: ContentLanguage) -> String { + "\(id)-\(language.rawValue).md" + } + + private func pageFileName(_ id: String) -> String { + id + ".json" } private func pageContentUrl(pageId: String, language: ContentLanguage) -> URL { - pagesFolder.appending(path: "\(pageId)-\(language.rawValue).md", directoryHint: .notDirectory) + pagesFolder.appending(path: pageContentFileName(pageId, language), directoryHint: .notDirectory) } private func pageMetadataUrl(pageId: String) -> URL { - pagesFolder.appending(path: pageId + ".json", directoryHint: .notDirectory) + pagesFolder.appending(path: pageFileName(pageId), directoryHint: .notDirectory) } @discardableResult @@ -136,6 +138,14 @@ final class Storage { } } + func deletePageFiles(notIn pages: [String]) throws { + var files = Set(pages.map(pageFileName)) + for language in ContentLanguage.allCases { + files.formUnion(pages.map { pageContentFileName($0, language) }) + } + try deleteFiles(in: pagesFolder, notIn: files) + } + // MARK: Posts /// The folder path where the markdown files of the posts are stored (by their unique id/url component) @@ -164,6 +174,11 @@ final class Storage { return try post(at: url) } + func deletePostFiles(notIn posts: [String]) throws { + let files = Set(posts.map { $0 + ".json" }) + try deleteFiles(in: postsFolder, notIn: files) + } + // MARK: Tags /// The folder path where the source images are stored (by their unique name) @@ -187,19 +202,9 @@ final class Storage { try loadAll(in: tagsFolder) } - // MARK: Images - - /// The folder path where the source images are stored (by their unique name) - private var imagesFolder: URL { subFolder("images") } - - private func imageUrl(image: String) -> URL { - imagesFolder.appending(path: image, directoryHint: .notDirectory) - } - - @discardableResult - func copyImage(at url: URL, imageId: String) -> Bool { - let contentUrl = imageUrl(image: imageId) - return copy(file: url, to: contentUrl, type: "image", id: imageId) + func deleteTagFiles(notIn tags: [String]) throws { + let files = Set(tags.map { $0 + ".json" }) + try deleteFiles(in: tagsFolder, notIn: files) } // MARK: Files @@ -223,23 +228,8 @@ final class Storage { } } - // MARK: Videos - - /// The folder path where source videos are stored (by their unique name) - private var videosFolder: URL { subFolder("videos") } - - private func videoUrl(video: String) -> URL { - videosFolder.appending(path: video, directoryHint: .notDirectory) - } - - @discardableResult - func copyVideo(at url: URL, videoId: String) -> Bool { - let contentUrl = videoUrl(video: videoId) - return copy(file: url, to: contentUrl, type: "video", id: videoId) - } - - func loadAllVideos() throws -> [String] { - try fileNames(in: videosFolder) + func deleteFiles(notIn fileSet: [String]) throws { + try deleteFiles(in: filesFolder, notIn: Set(fileSet)) } // MARK: Website data @@ -259,6 +249,16 @@ final class Storage { // MARK: Writing files + private func deleteFiles(in folder: URL, notIn fileSet: Set) throws { + let filesToDelete = try files(in: folder) + .filter { !fileSet.contains($0.lastPathComponent) } + + for file in filesToDelete { + try fm.removeItem(at: file) + print("Deleted \(file.path())") + } + } + /** Encode a value and write it to a file, if the content changed */