Improve storage, paths

This commit is contained in:
Christoph Hagen
2024-12-16 21:01:38 +01:00
parent b22b76fd32
commit 849585acc7
19 changed files with 393 additions and 268 deletions

View File

@ -1,6 +1,8 @@
struct PathSettingsFile {
let contentDirectoryPath: String
let outputDirectoryPath: String
let assetsOutputFolderPath: String
@ -15,13 +17,15 @@ struct PathSettingsFile {
let tagsOutputFolderPath: String
init(outputDirectoryPath: String,
init(contentDirectoryPath: String,
outputDirectoryPath: String,
assetsOutputFolderPath: String,
pagesOutputFolderPath: String,
imagesOutputFolderPath: String,
filesOutputFolderPath: String,
videosOutputFolderPath: String,
tagsOutputFolderPath: String) {
self.contentDirectoryPath = contentDirectoryPath
self.outputDirectoryPath = outputDirectoryPath
self.assetsOutputFolderPath = assetsOutputFolderPath
self.pagesOutputFolderPath = pagesOutputFolderPath
@ -40,6 +44,7 @@ extension PathSettingsFile {
static var `default`: PathSettingsFile {
PathSettingsFile(
contentDirectoryPath: "",
outputDirectoryPath: "build",
assetsOutputFolderPath: "asset",
pagesOutputFolderPath: "page",

View File

@ -0,0 +1,7 @@
enum SecurityScopeBookmark: String {
case outputPath = "outputPathBookmark"
case contentPath = "contentPathBookmark"
}

View File

@ -0,0 +1,15 @@
enum SecurityScopeStatus {
case noPath
case urlMismatch
case noBookmark
case bookmarkCorrupted
case stale
case nominal
}

View File

@ -1,44 +1,5 @@
import Foundation
enum SecurityScopeBookmark: String {
case outputPath = "outputPathBookmark"
case contentPath = "contentPathBookmark"
}
enum StorageAccessError: Error {
case noBookmarkData
case bookmarkDataCorrupted(Error)
case folderAccessFailed(URL)
case stringConversionFailed
case fileNotFound(String)
}
extension StorageAccessError: CustomStringConvertible {
var description: String {
switch self {
case .noBookmarkData:
return "No bookmark data to access resources in folder"
case .bookmarkDataCorrupted(let error):
return "Failed to resolve bookmark: \(error)"
case .folderAccessFailed(let url):
return "Failed to access folder: \(url.path())"
case .stringConversionFailed:
return "Failed to convert string to data"
case .fileNotFound(let path):
return "File not found: \(path)"
}
}
}
/**
A class that handles the storage of the website data.
@ -48,11 +9,10 @@ extension StorageAccessError: CustomStringConvertible {
- files: Contains additional files
- videos: Contains raw video files
- posts: Contains the markdown files for localized posts, file name is the post id
-
*/
final class Storage {
private(set) var baseFolder: URL
- Note: The base folder and output folder are stored as security-scoped bookmarks in user defaults.
*/
final class Storage: ObservableObject {
private let encoder = JSONEncoder()
@ -60,20 +20,21 @@ final class Storage {
private let fm = FileManager.default
@Published
var contentFolderStatus: SecurityScopeStatus = .noBookmark
@Published
var outputFolderStatus: SecurityScopeStatus = .noBookmark
/**
Create the storage.
*/
init(baseFolder: URL) {
self.baseFolder = baseFolder
init() {
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
}
// MARK: Helper
private func subFolder(_ name: String) -> URL {
baseFolder.appending(path: name, directoryHint: .isDirectory)
}
private func files(in folder: URL) throws -> [URL] {
do {
return try fm.contentsOfDirectory(at: folder, includingPropertiesForKeys: [.isDirectoryKey])
@ -96,8 +57,7 @@ final class Storage {
// MARK: Folders
func update(baseFolder: URL) throws {
self.baseFolder = baseFolder
func updateBaseFolder() throws {
try createFolderStructure()
}
@ -447,6 +407,19 @@ final class Storage {
}
}
func create(folder relativePath: String, in scopr: SecurityScopeBookmark) -> Bool {
return write(in: .outputPath) { folder in
let url = folder.appendingPathComponent(relativePath, isDirectory: true)
do {
try url.ensureFolderExistence()
return true
} catch {
print("Failed to create folder \(url.path()): \(error)")
return false
}
}
}
func write(in scope: SecurityScopeBookmark, operation: (URL) -> Bool) -> Bool {
do {
return try operate(in: scope, operation: operation)
@ -486,6 +459,7 @@ final class Storage {
if isStale {
print("Bookmark is stale, consider saving a new bookmark.")
#warning("Show warning about stale bookmark")
}
// Start accessing the security-scoped resource
@ -496,6 +470,45 @@ final class Storage {
return try operation(folderUrl)
}
@discardableResult
func check(contentPath: String) -> SecurityScopeStatus {
contentFolderStatus = Storage.ensure(securityScope: .contentPath, matches: contentPath)
return contentFolderStatus
}
@discardableResult
func check(outputPath: String) -> SecurityScopeStatus {
outputFolderStatus = Storage.ensure(securityScope: .outputPath, matches: outputPath)
return outputFolderStatus
}
private static func ensure(securityScope: SecurityScopeBookmark, matches path: String) -> SecurityScopeStatus {
guard path != "" else {
return .noPath
}
guard let bookmarkData = UserDefaults.standard.data(forKey: securityScope.rawValue) else {
return .noBookmark
}
do {
var isStale = false
let url = try URL(
resolvingBookmarkData: bookmarkData,
options: .withSecurityScope,
relativeTo: nil,
bookmarkDataIsStale: &isStale)
guard !isStale else {
return .stale
}
guard url.path() == path else {
return .urlMismatch
}
return .nominal
} catch {
return .bookmarkCorrupted
}
}
// MARK: Writing files
/**

View File

@ -0,0 +1,33 @@
import Foundation
enum StorageAccessError: Error {
case noBookmarkData
case bookmarkDataCorrupted(Error)
case folderAccessFailed(URL)
case stringConversionFailed
case fileNotFound(String)
}
extension StorageAccessError: CustomStringConvertible {
var description: String {
switch self {
case .noBookmarkData:
return "No bookmark data to access resources in folder"
case .bookmarkDataCorrupted(let error):
return "Failed to resolve bookmark: \(error)"
case .folderAccessFailed(let url):
return "Failed to access folder: \(url.path())"
case .stringConversionFailed:
return "Failed to convert string to data"
case .fileNotFound(let path):
return "File not found: \(path)"
}
}
}