102 lines
3.4 KiB
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
|
|
}
|
|
}
|
|
}
|