Add lead workflows, PDF signing, AR projects and photo gallery integration
This commit is contained in:
91
GasTankAR/Data/Leads/DefaultLeadDetailRepository.swift
Normal file
91
GasTankAR/Data/Leads/DefaultLeadDetailRepository.swift
Normal file
@ -0,0 +1,91 @@
|
||||
import Foundation
|
||||
import PDFKit
|
||||
import UIKit
|
||||
|
||||
@MainActor
|
||||
final class DefaultLeadDetailRepository: LeadDetailRepository {
|
||||
private struct FileKey: Hashable {
|
||||
let reference: LeadFileReference
|
||||
let leadID: String
|
||||
let salesCode: String
|
||||
}
|
||||
|
||||
private let remoteService: any LeadDetailRemoteService
|
||||
private let cacheDirectory: URL
|
||||
private var cachedFiles: [FileKey: URL] = [:]
|
||||
private var downloads: [FileKey: Task<URL, Error>] = [:]
|
||||
|
||||
init(remoteService: any LeadDetailRemoteService,
|
||||
cacheDirectory: URL = FileManager.default.temporaryDirectory.appendingPathComponent("LeadFiles")) {
|
||||
self.remoteService = remoteService
|
||||
self.cacheDirectory = cacheDirectory
|
||||
}
|
||||
|
||||
func detail(id: String, salesCode: String) async throws -> LeadDetail {
|
||||
do {
|
||||
return try await remoteService.fetchDetail(id: id, salesCode: salesCode).toDomain(expectedID: id)
|
||||
} catch {
|
||||
throw DefaultLeadRepository.map(error, leadID: id)
|
||||
}
|
||||
}
|
||||
|
||||
func file(_ reference: LeadFileReference, leadID: String, salesCode: String) async throws -> URL {
|
||||
try Task.checkCancellation()
|
||||
let key = FileKey(reference: reference, leadID: leadID, salesCode: salesCode)
|
||||
if let url = cachedFiles[key], FileManager.default.fileExists(atPath: url.path) {
|
||||
return url
|
||||
}
|
||||
if let download = downloads[key] {
|
||||
let url = try await download.value
|
||||
try Task.checkCancellation()
|
||||
return url
|
||||
}
|
||||
let directory = cacheDirectory
|
||||
let service = remoteService
|
||||
// A shared download survives one viewer closing; that viewer checks cancellation below.
|
||||
let download = Task {
|
||||
let data: Data
|
||||
do {
|
||||
data = try await service.download(reference, leadID: leadID, salesCode: salesCode)
|
||||
} catch LeadRemoteError.http(404) {
|
||||
throw LeadFileError.notFound
|
||||
} catch {
|
||||
throw DefaultLeadRepository.map(error)
|
||||
}
|
||||
return try await Task.detached {
|
||||
try Self.persist(data, kind: reference.kind, directory: directory)
|
||||
}.value
|
||||
}
|
||||
downloads[key] = download
|
||||
do {
|
||||
let url = try await download.value
|
||||
downloads[key] = nil
|
||||
cachedFiles[key] = url
|
||||
try Task.checkCancellation()
|
||||
return url
|
||||
} catch {
|
||||
downloads[key] = nil
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
nonisolated private static func persist(_ data: Data, kind: LeadFileKind, directory: URL) throws -> URL {
|
||||
switch kind {
|
||||
case .pdf:
|
||||
guard let document = PDFDocument(data: data), document.pageCount > 0, !document.isLocked else {
|
||||
throw LeadFileError.invalidFile
|
||||
}
|
||||
case .image:
|
||||
guard UIImage(data: data) != nil else { throw LeadFileError.invalidFile }
|
||||
}
|
||||
do {
|
||||
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
|
||||
let url = directory.appendingPathComponent(UUID().uuidString)
|
||||
.appendingPathExtension(kind == .pdf ? "pdf" : "png")
|
||||
try data.write(to: url, options: .atomic)
|
||||
return url
|
||||
} catch {
|
||||
throw LeadFileError.storageUnavailable
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user