vibetunnel/ios/VibeTunnel/Services/QuickLookManager.swift
2025-06-21 14:39:44 +02:00

154 lines
4.4 KiB
Swift

import Foundation
import QuickLook
import SwiftUI
@MainActor
class QuickLookManager: NSObject, ObservableObject {
static let shared = QuickLookManager()
@Published var isPresenting = false
@Published var downloadProgress: Double = 0
@Published var isDownloading = false
private var previewItems: [QLPreviewItem] = []
private var currentFile: FileEntry?
private let temporaryDirectory: URL
override init() {
// Create a temporary directory for downloaded files
let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent("QuickLookCache", isDirectory: true)
try? FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
self.temporaryDirectory = tempDir
super.init()
// Clean up old files on init
cleanupTemporaryFiles()
}
func previewFile(_ file: FileEntry, apiClient: APIClient) async throws {
guard !file.isDir else {
throw QuickLookError.isDirectory
}
currentFile = file
isDownloading = true
downloadProgress = 0
do {
let localURL = try await downloadFileForPreview(file: file, apiClient: apiClient)
// Create preview item
let previewItem = PreviewItem(url: localURL, title: file.name)
previewItems = [previewItem]
isDownloading = false
isPresenting = true
} catch {
isDownloading = false
throw error
}
}
private func downloadFileForPreview(file: FileEntry, apiClient: APIClient) async throws -> URL {
// Check if file is already cached
let cachedURL = temporaryDirectory.appendingPathComponent(file.name)
// For now, always download fresh (could implement proper caching later)
if FileManager.default.fileExists(atPath: cachedURL.path) {
try FileManager.default.removeItem(at: cachedURL)
}
// Download the file
let data = try await apiClient.downloadFile(path: file.path) { progress in
Task { @MainActor in
self.downloadProgress = progress
}
}
// Save to temporary location
try data.write(to: cachedURL)
return cachedURL
}
func cleanupTemporaryFiles() {
// Remove files older than 1 hour
let oneHourAgo = Date().addingTimeInterval(-3_600)
guard let files = try? FileManager.default.contentsOfDirectory(
at: temporaryDirectory,
includingPropertiesForKeys: [.creationDateKey]
) else {
return
}
for file in files {
if let creationDate = try? file.resourceValues(forKeys: [.creationDateKey]).creationDate,
creationDate < oneHourAgo {
try? FileManager.default.removeItem(at: file)
}
}
}
func makePreviewController() -> QLPreviewController {
let controller = QLPreviewController()
controller.dataSource = self
controller.delegate = self
return controller
}
}
// MARK: - QLPreviewControllerDataSource
extension QuickLookManager: QLPreviewControllerDataSource {
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
previewItems.count
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
previewItems[index]
}
}
// MARK: - QLPreviewControllerDelegate
extension QuickLookManager: QLPreviewControllerDelegate {
nonisolated func previewControllerDidDismiss(_ controller: QLPreviewController) {
Task { @MainActor in
isPresenting = false
previewItems = []
currentFile = nil
}
}
}
// MARK: - Preview Item
private class PreviewItem: NSObject, QLPreviewItem {
let previewItemURL: URL?
let previewItemTitle: String?
init(url: URL, title: String) {
self.previewItemURL = url
self.previewItemTitle = title
}
}
// MARK: - Errors
enum QuickLookError: LocalizedError {
case isDirectory
case downloadFailed
case unsupportedFileType
var errorDescription: String? {
switch self {
case .isDirectory:
"Cannot preview directories"
case .downloadFailed:
"Failed to download file"
case .unsupportedFileType:
"This file type cannot be previewed"
}
}
}