native macOS codings agent orchestrator prowl.onev.cat
Something went wrong. Try again.
4.0 kB · 98 lines
Swift
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899import Dependenciesimport Foundation
/// File-system gateway for user-imported repository icon images. Wraps/// the actual disk operations behind closures so both the live build/// (real `FileManager`) and tests (in-memory) can drive the same code/// paths without forking implementations.////// All filenames returned by the store are bare names (e.g./// `"3F2D…ABC.svg"`) — never absolute paths — so the persisted/// `RepositoryAppearance.icon` stays portable: moving a repository/// directory takes its icons with it without rewriting JSON.nonisolated struct RepositoryIconAssetStore: Sendable { /// Imports a user-picked image into the per-repo icons directory and /// returns the bare filename to persist. The implementation chooses /// the filename (UUID + extension), creating intermediate directories /// as needed. var importImage: @Sendable ( _ sourceURL: URL, _ repositoryRootURL: URL ) throws -> String
/// Removes a previously-imported image. No-op when the file is /// already gone (idempotent so reset/replace can call without /// guarding against stale state). var remove: @Sendable ( _ filename: String, _ repositoryRootURL: URL ) throws -> Void
/// Returns whether a previously-stored filename still resolves to an /// existing file. Renderers use this to decide whether to fall back. var exists: @Sendable ( _ filename: String, _ repositoryRootURL: URL ) -> Bool}
nonisolated extension RepositoryIconAssetStore { static var liveValue: RepositoryIconAssetStore { fileSystemValue(repositoryIconsDirectory: ProwlPaths.repositoryIconsDirectory(for:)) }
static func fileSystemValue( repositoryIconsDirectory: @escaping @Sendable (_ repositoryRootURL: URL) -> URL ) -> RepositoryIconAssetStore { RepositoryIconAssetStore( importImage: { sourceURL, rootURL in // No extension whitelist — the file picker filters down to // image UTTypes already, and anything that NSImage can't // render later falls back to the dashed-questionmark // placeholder in `RepositoryIconImage`. The `.svg` suffix // remains the lone meaningful signal because it gates the // template-tinting branch downstream; everything else is // treated as an opaque bitmap. let normalizedExt = sourceURL.pathExtension.lowercased().isEmpty ? "img" : sourceURL.pathExtension.lowercased() let directory = repositoryIconsDirectory(rootURL) try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) let filename = "\(UUID().uuidString.lowercased()).\(normalizedExt)" let destination = directory.appending(path: filename, directoryHint: .notDirectory) let data = try Data(contentsOf: sourceURL) try data.write(to: destination, options: [.atomic]) return filename }, remove: { filename, rootURL in let url = repositoryIconsDirectory(rootURL) .appending(path: filename, directoryHint: .notDirectory) if FileManager.default.fileExists(atPath: url.path(percentEncoded: false)) { try FileManager.default.removeItem(at: url) } }, exists: { filename, rootURL in let url = repositoryIconsDirectory(rootURL) .appending(path: filename, directoryHint: .notDirectory) return FileManager.default.fileExists(atPath: url.path(percentEncoded: false)) } ) }}
nonisolated enum RepositoryIconAssetStoreKey: DependencyKey { static var liveValue: RepositoryIconAssetStore { .liveValue } static var previewValue: RepositoryIconAssetStore { .liveValue } static var testValue: RepositoryIconAssetStore { .liveValue }}
extension DependencyValues { nonisolated var repositoryIconAssetStore: RepositoryIconAssetStore { get { self[RepositoryIconAssetStoreKey.self] } set { self[RepositoryIconAssetStoreKey.self] = newValue } }}