ChWebsiteApp/CHDataManagement/Model/FileResource.swift
2025-01-05 09:21:21 +01:00

221 lines
5.8 KiB
Swift

import Foundation
import SwiftUI
final class FileResource: Item {
let type: FileType
@Published
var isExternallyStored: Bool
@Published
var german: String
@Published
var english: String
/// The dimensions of the image
@Published
var imageDimensions: CGSize? = nil
/// The size of the file in bytes
@Published
var fileSize: Int? = nil
init(content: Content, id: String, isExternallyStored: Bool, en: String, de: String) {
self.type = FileType(fileExtension: id.fileExtension)
self.english = en
self.german = de
self.isExternallyStored = isExternallyStored
super.init(content: content, id: id)
}
/**
Only for bundle images
*/
init(resourceImage: String, type: FileType) {
self.type = type
self.english = "A test image included in the bundle"
self.german = "Ein Testbild aus dem Bundle"
self.isExternallyStored = true
super.init(content: .mock, id: resourceImage) // TODO: Add images to mock
}
// MARK: Text
func textContent() -> String {
content.storage.fileContent(for: id) ?? ""
}
func dataContent() -> Data? {
content.storage.fileData(for: id)
}
// MARK: Images
var aspectRatio: CGFloat {
guard let imageDimensions else {
return 0
}
guard imageDimensions.height > 0 else {
return 0
}
return imageDimensions.width / imageDimensions.height
}
var imageToDisplay: Image {
guard let imageData = content.storage.fileData(for: id) else {
print("Failed to load data for image \(id)")
return failureImage
}
if fileSize == nil {
DispatchQueue.main.async {
self.fileSize = imageData.count
}
}
guard let loadedImage = NSImage(data: imageData) else {
print("Failed to create image \(id)")
return failureImage
}
if loadedImage.size != imageDimensions {
DispatchQueue.main.async {
self.imageDimensions = loadedImage.size
}
}
return .init(nsImage: loadedImage)
}
func determineImageDimensions() {
let size = getImageDimensions()
DispatchQueue.main.async {
self.imageDimensions = size
}
}
private func getImageDimensions() -> CGSize? {
guard type.isImage else {
return nil
}
guard let imageData = content.storage.fileData(for: id) else {
return nil
}
guard let loadedImage = NSImage(data: imageData) else {
return nil
}
return loadedImage.size
}
func determineFileSize() {
DispatchQueue.global(qos: .userInitiated).async {
let size = self.content.storage.size(of: self.id)
DispatchQueue.main.async {
self.fileSize = size
}
}
}
func removeGeneratedImages() {
content.imageGenerator.removeVersions(of: id)
content.storage.deleteInOutputFolder(path: outputImageFolder)
}
private var failureImage: Image {
Image(systemSymbol: .exclamationmarkTriangle)
}
/// The path to the output folder where image versions are stored (no leading slash)
var outputImageFolder: String {
"\(content.settings.paths.imagesOutputFolderPath)/\(id.fileNameWithoutExtension)"
}
func outputPath(width: Int, height: Int, type: FileType?) -> String {
let prefix = "/\(outputImageFolder)/\(width)x\(height)"
guard let ext = type?.fileExtension else {
return prefix
}
return prefix + "." + ext
}
func imageSet(width: Int, height: Int, language: ContentLanguage, quality: CGFloat = 0.7) -> ImageSet {
let description = self.localized(in: language)
return .init(
image: self,
maxWidth: width,
maxHeight: height,
description: description,
quality: quality)
}
func imageVersion(width: Int, height: Int, type: FileType) -> ImageVersion {
.init(image: self, type: type, maximumWidth: width, maximumHeight: height)
}
// MARK: Paths
func removeFileFromOutputFolder() {
content.storage.deleteInOutputFolder(path: absoluteUrl)
if type.isImage {
removeGeneratedImages()
}
}
/**
Get the url path to a file in the output folder.
The result is an absolute path from the output folder for use in HTML.
*/
var absoluteUrl: String {
let path = pathPrefix + "/" + id
return makeCleanAbsolutePath(path)
}
private var pathPrefix: String {
if type.isImage {
return content.settings.paths.imagesOutputFolderPath
}
if type.isVideo {
return content.settings.paths.videosOutputFolderPath
}
if type.isAudio {
return content.settings.paths.audioOutputFolderPath
}
if type.isAsset {
return content.settings.paths.assetsOutputFolderPath
}
return content.settings.paths.filesOutputFolderPath
}
// MARK: File
func isValid(id: String) -> Bool {
!id.isEmpty &&
content.isValidIdForFile(id) &&
content.isNewIdForFile(id)
}
@discardableResult
func update(id newId: String) -> Bool {
guard !isExternallyStored else {
id = newId
return true
}
guard content.storage.move(file: id, to: newId) else {
print("Failed to move file \(id) to \(newId)")
return false
}
id = newId
return true
}
}
extension FileResource: LocalizedItem {
}
extension FileResource: CustomStringConvertible {
var description: String {
id
}
}