Sapling/Sources/SaplingWorkspace/WorkspaceManager.swift

102 lines
3.4 KiB
Swift

import Foundation
import SaplingCore
import SaplingGit
import SaplingStorage
public protocol WorkspaceManaging: Sendable {
func openWorkspace(at url: URL) async throws -> Workspace
func sampleWorkspace() -> Workspace
}
public final class LocalWorkspaceManager: WorkspaceManaging, @unchecked Sendable {
private let gitProvider: any GitProvider
private let metadataStore: any WorkspaceMetadataStore
private let fileManager: FileManager
public init(
gitProvider: any GitProvider,
metadataStore: any WorkspaceMetadataStore,
fileManager: FileManager = .default
) {
self.gitProvider = gitProvider
self.metadataStore = metadataStore
self.fileManager = fileManager
}
public func openWorkspace(at url: URL) async throws -> Workspace {
let items = try scanItems(at: url, relativeTo: url)
let workspace = Workspace(name: url.lastPathComponent, rootURL: url, items: items)
try SaplingRules.validateWorkspace(workspace)
try metadataStore.saveMetadata(WorkspaceMetadata(workspaceID: workspace.id))
return workspace
}
public func sampleWorkspace() -> Workspace {
SaplingSampleData.workspace
}
private func scanItems(at url: URL, relativeTo rootURL: URL) throws -> [WorkspaceItem] {
guard let children = try? fileManager.contentsOfDirectory(
at: url,
includingPropertiesForKeys: [.isDirectoryKey],
options: [.skipsHiddenFiles]
) else {
return []
}
return try children
.sorted { $0.lastPathComponent.localizedStandardCompare($1.lastPathComponent) == .orderedAscending }
.map { childURL in
if isGitRepository(at: childURL) {
return .project(project(at: childURL))
}
let values = try childURL.resourceValues(forKeys: [.isDirectoryKey])
if values.isDirectory == true {
return .folder(
WorkspaceFolder(
name: childURL.lastPathComponent,
url: childURL,
children: try scanItems(at: childURL, relativeTo: rootURL)
)
)
}
return .file(
WorkspaceFile(
name: childURL.lastPathComponent,
url: childURL,
kind: fileKind(for: childURL)
)
)
}
}
private func isGitRepository(at url: URL) -> Bool {
fileManager.fileExists(atPath: url.appendingPathComponent(".git").path)
}
private func project(at url: URL) -> Project {
let repository = GitRepository(
name: url.lastPathComponent,
rootURL: url,
statusSummary: .unknown
)
return Project(
name: url.lastPathComponent,
repositoryURL: url,
gitRepository: repository
)
}
private func fileKind(for url: URL) -> WorkspaceFileKind {
switch url.pathExtension.lowercased() {
case "md", "markdown":
return .markdown
case "png", "jpg", "jpeg", "gif", "webp", "pdf", "mp3", "mp4":
return .attachment
default:
return .other
}
}
}