Implement local song project store
This commit is contained in:
@@ -1,6 +1,81 @@
|
||||
public protocol ProjectStore: Sendable {
|
||||
associatedtype Project
|
||||
import Foundation
|
||||
|
||||
func save(_ project: Project) async throws
|
||||
public protocol ProjectStore: Sendable {
|
||||
func create(_ project: SongProject) async throws
|
||||
func save(_ project: SongProject) async throws
|
||||
func open(id: String) async throws -> SongProject
|
||||
func loadProjectIDs() async throws -> [String]
|
||||
}
|
||||
|
||||
public actor LocalSongProjectStore: ProjectStore {
|
||||
private let directoryURL: URL
|
||||
private let encoder: JSONEncoder
|
||||
private let decoder: JSONDecoder
|
||||
|
||||
public init(directoryURL: URL) {
|
||||
self.directoryURL = directoryURL
|
||||
self.encoder = JSONEncoder()
|
||||
self.decoder = JSONDecoder()
|
||||
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
|
||||
}
|
||||
|
||||
public func create(_ project: SongProject) async throws {
|
||||
try ensureDirectoryExists()
|
||||
let url = projectURL(for: project.id)
|
||||
guard !FileManager.default.fileExists(atPath: url.path) else {
|
||||
throw LocalSongProjectStoreError.projectAlreadyExists(project.id)
|
||||
}
|
||||
try saveEncoded(project, to: url)
|
||||
}
|
||||
|
||||
public func save(_ project: SongProject) async throws {
|
||||
try ensureDirectoryExists()
|
||||
try saveEncoded(project, to: projectURL(for: project.id))
|
||||
}
|
||||
|
||||
public func open(id: String) async throws -> SongProject {
|
||||
let url = projectURL(for: id)
|
||||
guard FileManager.default.fileExists(atPath: url.path) else {
|
||||
throw LocalSongProjectStoreError.projectNotFound(id)
|
||||
}
|
||||
let data = try Data(contentsOf: url)
|
||||
return try decoder.decode(SongProject.self, from: data)
|
||||
}
|
||||
|
||||
public func loadProjectIDs() async throws -> [String] {
|
||||
try ensureDirectoryExists()
|
||||
let fileURLs = try FileManager.default.contentsOfDirectory(
|
||||
at: directoryURL,
|
||||
includingPropertiesForKeys: nil
|
||||
)
|
||||
return fileURLs
|
||||
.filter { $0.pathExtension == Self.fileExtension }
|
||||
.map { $0.deletingPathExtension().lastPathComponent }
|
||||
.sorted()
|
||||
}
|
||||
|
||||
private func ensureDirectoryExists() throws {
|
||||
try FileManager.default.createDirectory(
|
||||
at: directoryURL,
|
||||
withIntermediateDirectories: true
|
||||
)
|
||||
}
|
||||
|
||||
private func saveEncoded(_ project: SongProject, to url: URL) throws {
|
||||
let data = try encoder.encode(project)
|
||||
try data.write(to: url, options: [.atomic])
|
||||
}
|
||||
|
||||
private func projectURL(for id: String) -> URL {
|
||||
directoryURL
|
||||
.appendingPathComponent(id)
|
||||
.appendingPathExtension(Self.fileExtension)
|
||||
}
|
||||
|
||||
private static let fileExtension = "json"
|
||||
}
|
||||
|
||||
public enum LocalSongProjectStoreError: Error, Equatable, Sendable {
|
||||
case projectAlreadyExists(String)
|
||||
case projectNotFound(String)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user