Consolidate images and files
This commit is contained in:
13
CHDataManagement/Storage/Model/FileDescriptions.swift
Normal file
13
CHDataManagement/Storage/Model/FileDescriptions.swift
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
struct FileDescriptions {
|
||||
|
||||
let fileId: String
|
||||
|
||||
let german: String?
|
||||
|
||||
let english: String?
|
||||
}
|
||||
|
||||
extension FileDescriptions: Codable {
|
||||
|
||||
}
|
@ -10,7 +10,7 @@ struct FileOnDisk {
|
||||
|
||||
init(image: String, url: URL) {
|
||||
let ext = image.fileExtension!
|
||||
let type = ImageType(fileExtension: ext)!
|
||||
let type = ImageFileType(fileExtension: ext)!
|
||||
self.type = .image(type)
|
||||
self.url = url
|
||||
self.name = image
|
||||
|
@ -1,13 +0,0 @@
|
||||
|
||||
struct ImageDescriptions {
|
||||
|
||||
let imageId: String
|
||||
|
||||
let german: String?
|
||||
|
||||
let english: String?
|
||||
}
|
||||
|
||||
extension ImageDescriptions: Codable {
|
||||
|
||||
}
|
@ -101,16 +101,20 @@ final class Storage {
|
||||
}
|
||||
|
||||
func createFolderStructure() throws {
|
||||
try create(folder: pagesFolder)
|
||||
try create(folder: filesFolder)
|
||||
try create(folder: postsFolder)
|
||||
try create(folder: tagsFolder)
|
||||
try operate(in: .contentPath) { contentPath in
|
||||
try create(folder: pagesFolder)
|
||||
try create(folder: filesFolder(in: contentPath))
|
||||
try create(folder: postsFolder)
|
||||
try create(folder: tagsFolder)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Pages
|
||||
|
||||
private let pagesFolderName = "pages"
|
||||
|
||||
/// 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 var pagesFolder: URL { subFolder(pagesFolderName) }
|
||||
|
||||
private func pageContentFileName(_ id: String, _ language: ContentLanguage) -> String {
|
||||
"\(id)-\(language.rawValue).md"
|
||||
@ -169,13 +173,15 @@ final class Storage {
|
||||
for language in ContentLanguage.allCases {
|
||||
files.formUnion(pages.map { pageContentFileName($0, language) })
|
||||
}
|
||||
try deleteFiles(in: pagesFolder, notIn: files)
|
||||
try deleteFiles(in: pagesFolderName, notIn: files)
|
||||
}
|
||||
|
||||
// MARK: Posts
|
||||
|
||||
private let postsFolderName = "posts"
|
||||
|
||||
/// The folder path where the markdown files of the posts are stored (by their unique id/url component)
|
||||
private var postsFolder: URL { subFolder("posts") }
|
||||
private var postsFolder: URL { subFolder(postsFolderName) }
|
||||
|
||||
private func postFileUrl(postId: String) -> URL {
|
||||
postsFolder.appending(path: postId, directoryHint: .notDirectory).appendingPathExtension("json")
|
||||
@ -202,13 +208,15 @@ final class Storage {
|
||||
|
||||
func deletePostFiles(notIn posts: [String]) throws {
|
||||
let files = Set(posts.map { $0 + ".json" })
|
||||
try deleteFiles(in: postsFolder, notIn: files)
|
||||
try deleteFiles(in: postsFolderName, notIn: files)
|
||||
}
|
||||
|
||||
// MARK: Tags
|
||||
|
||||
private let tagsFolderName = "tags"
|
||||
|
||||
/// The folder path where the source images are stored (by their unique name)
|
||||
private var tagsFolder: URL { subFolder("tags") }
|
||||
private var tagsFolder: URL { subFolder(tagsFolderName) }
|
||||
|
||||
private func tagFileUrl(tagId: String) -> URL {
|
||||
tagsFolder.appending(path: tagId, directoryHint: .notDirectory)
|
||||
@ -230,44 +238,44 @@ final class Storage {
|
||||
|
||||
func deleteTagFiles(notIn tags: [String]) throws {
|
||||
let files = Set(tags.map { $0 + ".json" })
|
||||
try deleteFiles(in: tagsFolder, notIn: files)
|
||||
try deleteFiles(in: tagsFolderName, notIn: files)
|
||||
}
|
||||
|
||||
// MARK: Files
|
||||
// MARK: File descriptions
|
||||
|
||||
private var imageDescriptionFilename: String {
|
||||
"image-descriptions.json"
|
||||
}
|
||||
private let fileDescriptionFilename = "file-descriptions.json"
|
||||
|
||||
private var imageDescriptionUrl: URL {
|
||||
baseFolder.appending(path: "image-descriptions.json")
|
||||
}
|
||||
|
||||
func loadImageDescriptions() -> [ImageDescriptions] {
|
||||
func loadFileDescriptions() -> [FileDescriptions] {
|
||||
do {
|
||||
return try read(relativePath: imageDescriptionFilename)
|
||||
return try read(relativePath: fileDescriptionFilename)
|
||||
} catch {
|
||||
print("Failed to read image descriptions: \(error)")
|
||||
print("Failed to read file descriptions: \(error)")
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func save(imageDescriptions: [ImageDescriptions]) -> Bool {
|
||||
func save(fileDescriptions: [FileDescriptions]) -> Bool {
|
||||
do {
|
||||
try writeIfChanged(imageDescriptions, to: imageDescriptionFilename)
|
||||
try writeIfChanged(fileDescriptions, to: fileDescriptionFilename)
|
||||
return true
|
||||
} catch {
|
||||
print("Failed to write image descriptions: \(error)")
|
||||
print("Failed to write file descriptions: \(error)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/// The folder path where other files are stored (by their unique name)
|
||||
var filesFolder: URL { subFolder("files") }
|
||||
// MARK: Files
|
||||
|
||||
private func fileUrl(file: String) -> URL {
|
||||
filesFolder.appending(path: file, directoryHint: .notDirectory)
|
||||
private let filesFolderName = "files"
|
||||
|
||||
/// The folder path where other files are stored (by their unique name)
|
||||
func filesFolder(in folder: URL) -> URL {
|
||||
folder.appending(path: filesFolderName, directoryHint: .isDirectory)
|
||||
}
|
||||
|
||||
private func fileUrl(file: String, in folder: URL) -> URL {
|
||||
filesFolder(in: folder).appending(path: file, directoryHint: .notDirectory)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -275,8 +283,30 @@ final class Storage {
|
||||
*/
|
||||
@discardableResult
|
||||
func copyFile(at url: URL, fileId: String) -> Bool {
|
||||
let contentUrl = fileUrl(file: fileId)
|
||||
return copy(file: url, to: contentUrl, type: "file", id: fileId)
|
||||
do {
|
||||
try operate(in: .contentPath) { contentPath in
|
||||
let destination = fileUrl(file: fileId, in: contentPath)
|
||||
try fm.copyItem(at: url, to: destination)
|
||||
}
|
||||
return true
|
||||
} catch {
|
||||
print("Failed to copy external file \(url.path()) to \(fileId): \(error)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func move(file fileId: String, to newFile: String) -> Bool {
|
||||
do {
|
||||
try operate(in: .contentPath) { contentPath in
|
||||
let source = fileUrl(file: fileId, in: contentPath)
|
||||
let destination = fileUrl(file: newFile, in: contentPath)
|
||||
try fm.moveItem(at: source, to: destination)
|
||||
}
|
||||
return true
|
||||
} catch {
|
||||
print("Failed to move file \(fileId) to \(newFile): \(error)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func copy(file fileId: String, to relativeOutputPath: String) -> Bool {
|
||||
@ -287,8 +317,9 @@ final class Storage {
|
||||
if output.exists {
|
||||
return
|
||||
}
|
||||
let input = contentPath.appending(path: "files/\(fileId)", directoryHint: .notDirectory)
|
||||
try output.ensureParentFolderExistence()
|
||||
|
||||
let input = fileUrl(file: fileId, in: contentPath)
|
||||
try FileManager.default.copyItem(at: input, to: output)
|
||||
}
|
||||
}
|
||||
@ -299,14 +330,15 @@ final class Storage {
|
||||
}
|
||||
}
|
||||
|
||||
func loadAllFiles() throws -> [String : URL] {
|
||||
try files(in: filesFolder).reduce(into: [:]) { files, url in
|
||||
files[url.lastPathComponent] = url
|
||||
func loadAllFiles() throws -> [String] {
|
||||
try operate(in: .contentPath) { contentPath in
|
||||
let folder = filesFolder(in: contentPath)
|
||||
return try files(in: folder).map { $0.lastPathComponent }
|
||||
}
|
||||
}
|
||||
|
||||
func deleteFiles(notIn fileSet: [String]) throws {
|
||||
try deleteFiles(in: filesFolder, notIn: Set(fileSet))
|
||||
try deleteFiles(in: filesFolderName, notIn: Set(fileSet))
|
||||
}
|
||||
|
||||
func fileContent(for file: String) throws -> String {
|
||||
@ -318,6 +350,15 @@ final class Storage {
|
||||
}
|
||||
}
|
||||
|
||||
func fileData(for file: String) throws -> Data {
|
||||
try operate(in: .contentPath) { folder in
|
||||
let fileUrl = folder
|
||||
.appending(path: "files", directoryHint: .isDirectory)
|
||||
.appending(path: file, directoryHint: .notDirectory)
|
||||
return try Data(contentsOf: fileUrl)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Website data
|
||||
|
||||
private var settingsDataUrl: URL {
|
||||
@ -403,13 +444,16 @@ final class Storage {
|
||||
|
||||
// MARK: Writing files
|
||||
|
||||
private func deleteFiles(in folder: URL, notIn fileSet: Set<String>) throws {
|
||||
let filesToDelete = try files(in: folder)
|
||||
.filter { !fileSet.contains($0.lastPathComponent) }
|
||||
private func deleteFiles(in folder: String, notIn fileSet: Set<String>) throws {
|
||||
try operate(in: .contentPath) { contentPath in
|
||||
let subFolder = contentPath.appending(path: folder, directoryHint: .isDirectory)
|
||||
let filesToDelete = try files(in: subFolder)
|
||||
.filter { !fileSet.contains($0.lastPathComponent) }
|
||||
|
||||
for file in filesToDelete {
|
||||
try fm.removeItem(at: file)
|
||||
print("Deleted \(file.path())")
|
||||
for file in filesToDelete {
|
||||
try fm.removeItem(at: file)
|
||||
print("Deleted \(file.path())")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user