Improve storage, paths
This commit is contained in:
@ -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",
|
||||
|
7
CHDataManagement/Storage/SecurityScopeBookmark.swift
Normal file
7
CHDataManagement/Storage/SecurityScopeBookmark.swift
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
enum SecurityScopeBookmark: String {
|
||||
|
||||
case outputPath = "outputPathBookmark"
|
||||
|
||||
case contentPath = "contentPathBookmark"
|
||||
}
|
15
CHDataManagement/Storage/SecurityScopeStatus.swift
Normal file
15
CHDataManagement/Storage/SecurityScopeStatus.swift
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
enum SecurityScopeStatus {
|
||||
|
||||
case noPath
|
||||
|
||||
case urlMismatch
|
||||
|
||||
case noBookmark
|
||||
|
||||
case bookmarkCorrupted
|
||||
|
||||
case stale
|
||||
|
||||
case nominal
|
||||
}
|
@ -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
|
||||
|
||||
/**
|
||||
|
33
CHDataManagement/Storage/StorageAccessError.swift
Normal file
33
CHDataManagement/Storage/StorageAccessError.swift
Normal 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)"
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user