// // Attachments.swift // kordophone2 // // Created by Assistant on 8/31/25. // import Foundation import UniformTypeIdentifiers enum AttachmentState { case staged case uploading(progress: Double) case uploaded case failed } struct OutgoingAttachment: Identifiable, Hashable { let id: String let originalURL: URL let stagedURL: URL let fileName: String let contentType: UTType? var state: AttachmentState init(id: String = UUID().uuidString, originalURL: URL, stagedURL: URL, fileName: String? = nil, contentType: UTType? = nil, state: AttachmentState = .staged) { self.id = id self.originalURL = originalURL self.stagedURL = stagedURL self.fileName = fileName ?? originalURL.lastPathComponent self.contentType = contentType self.state = state } static func ==(lhs: OutgoingAttachment, rhs: OutgoingAttachment) -> Bool { return lhs.id == rhs.id } func hash(into hasher: inout Hasher) { hasher.combine(id) hasher.combine(originalURL) hasher.combine(stagedURL) hasher.combine(fileName) } } final class AttachmentManager { static let shared = AttachmentManager() private let fileManager = FileManager.default private let stagingRoot: URL private init() { let base = fileManager.temporaryDirectory.appendingPathComponent("kordophone-staging", isDirectory: true) try? fileManager.createDirectory(at: base, withIntermediateDirectories: true) self.stagingRoot = base } func stageIfImage(url: URL) -> OutgoingAttachment? { guard isImage(url: url) else { return nil } let staged = stage(url: url) let type = (try? url.resourceValues(forKeys: [.contentTypeKey]))?.contentType ?? UTType(filenameExtension: url.pathExtension) return OutgoingAttachment(originalURL: url, stagedURL: staged, fileName: url.lastPathComponent, contentType: type) } // MARK: - Helpers private func isImage(url: URL) -> Bool { if let type = (try? url.resourceValues(forKeys: [.contentTypeKey]))?.contentType { return type.conforms(to: .image) } return UTType(filenameExtension: url.pathExtension)?.conforms(to: .image) ?? false } private func stage(url: URL) -> URL { let ext = url.pathExtension let dest = stagingRoot.appendingPathComponent(UUID().uuidString).appendingPathExtension(ext) do { if fileManager.fileExists(atPath: dest.path) { try? fileManager.removeItem(at: dest) } try fileManager.copyItem(at: url, to: dest) return dest } catch { do { try fileManager.moveItem(at: url, to: dest) return dest } catch { return url } } } }