jellyflood/Shared/Services/DownloadManager.swift

110 lines
3.1 KiB
Swift

//
// Swiftfin is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2025 Jellyfin & Jellyfin Contributors
//
import Factory
import Files
import Foundation
import JellyfinAPI
extension Container {
var downloadManager: Factory<DownloadManager> { self { DownloadManager() }.shared }
}
class DownloadManager: ObservableObject {
@Injected(\.logService)
private var logger
@Published
private(set) var downloads: [DownloadTask] = []
fileprivate init() {
createDownloadDirectory()
}
private func createDownloadDirectory() {
try? FileManager.default.createDirectory(
at: URL.downloads,
withIntermediateDirectories: true
)
}
func clearTmp() {
do {
try Folder(path: URL.tmp.path).files.delete()
logger.trace("Cleared tmp directory")
} catch {
logger.error("Unable to clear tmp directory: \(error.localizedDescription)")
}
}
func download(task: DownloadTask) {
guard !downloads.contains(where: { $0.item == task.item }) else { return }
downloads.append(task)
task.download()
}
func task(for item: BaseItemDto) -> DownloadTask? {
if let currentlyDownloading = downloads.first(where: { $0.item == item }) {
return currentlyDownloading
} else {
var isDir: ObjCBool = true
guard let downloadFolder = item.downloadFolder else { return nil }
guard FileManager.default.fileExists(atPath: downloadFolder.path, isDirectory: &isDir) else { return nil }
return parseDownloadItem(with: item.id!)
}
}
func cancel(task: DownloadTask) {
guard downloads.contains(where: { $0.item == task.item }) else { return }
task.cancel()
remove(task: task)
}
func remove(task: DownloadTask) {
downloads.removeAll(where: { $0.item == task.item })
}
func downloadedItems() -> [DownloadTask] {
do {
let downloadContents = try FileManager.default.contentsOfDirectory(atPath: URL.downloads.path)
return downloadContents.compactMap(parseDownloadItem(with:))
} catch {
logger.error("Error retrieving all downloads: \(error.localizedDescription)")
return []
}
}
private func parseDownloadItem(with id: String) -> DownloadTask? {
let itemMetadataFile = URL.downloads
.appendingPathComponent(id)
.appendingPathComponent("Metadata")
.appendingPathComponent("Item.json")
guard let itemMetadataData = FileManager.default.contents(atPath: itemMetadataFile.path) else { return nil }
let jsonDecoder = JSONDecoder()
guard let offlineItem = try? jsonDecoder.decode(BaseItemDto.self, from: itemMetadataData) else { return nil }
let task = DownloadTask(item: offlineItem)
task.state = .complete
return task
}
}