SeanutSwift/Sources/Commands/Download.swift
2024-02-16 14:58:42 -05:00

101 lines
4.1 KiB
Swift

// Download.swift
import Foundation
import ArgumentParser
import JellyfinAPI
extension Seanut {
struct DownloadCommand: AsyncParsableCommand {
static var configuration = CommandConfiguration(
commandName: "download",
abstract: "Downloads media into specified directory (default to current directory)"
)
@OptionGroup var options: Seanut.CommonArguments
@Argument(help: "media id returned from search")
var mediaId: String
@Option(name: .shortAndLong, help: "specifies that we're only looking for a specific season of a show")
var season: Int?
@Option(name: .shortAndLong, help: "location to save downloaded media")
var output: String = FileManager.default.currentDirectoryPath
static var userId: String!
static var client: JellyfinClient!
static let TypesWithChildren: [JellyfinAPI.BaseItemKind] = [
.musicAlbum, .musicGenre, .musicArtist, .playlist, .series, .boxSet
]
func createDirectory(_ path: String) {
do {
try FileManager.default.createDirectory(
atPath: path.expandingTildeInPath.removingPercentEncoding!,
withIntermediateDirectories: true
)
} catch {
fatalError("unable to create output directory at \(path).")
}
}
func downloadRoot(id: String, outputPath: URL) async {
let downloadInfo = Paths.getItem(userID: DownloadCommand.userId, itemID: id)
guard let rootInfo = try? await DownloadCommand.client.send(downloadInfo).value else {
return
}
if DownloadCommand.TypesWithChildren.contains(rootInfo.type!) || rootInfo.childCount ?? 0 > 0 {
let newPath = outputPath.appendingPathComponent(rootInfo.name!)
createDirectory(String(newPath.absoluteString.suffix(newPath.absoluteString.count - 7)))
let childrenParams = Paths.GetItemsParameters(userID: DownloadCommand.userId, parentID: id)
guard let childInfo = try? await DownloadCommand.client.send(Paths.getItems(parameters: childrenParams)).value else {
return
}
for child in childInfo.items! {
await downloadRoot(id: child.id!, outputPath: newPath)
}
} else {
let pathUrl = URL(string: rootInfo.path!)!
let itemName: String = rootInfo.name! + "." + pathUrl.pathExtension
let filePath = outputPath.appendingPathComponent(itemName.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)
await downloadItem(id: id, outputPath: filePath)
}
}
func downloadItem(id: String, outputPath: URL) async {
do {
let response = try await DownloadCommand.client.download(for: Paths.getDownload(itemID: id))
try FileManager.default.moveItem(at: response.location, to: outputPath)
} catch {
fatalError("Encountered \(error) downloading media. Please try again later.")
}
}
mutating func run() async {
// checks if our specified output directory exists,
// if it doesn't then we create it before continuing
if !FileManager.default.fileExists(atPath: output.expandingTildeInPath) {
createDirectory(output)
}
DownloadCommand.client = JellyfinClient(
configuration: Seanut.generateJellyfinConfiguration(url: options.domain.toURL()!),
accessToken: Seanut.retrieveAccessToken(for: options.domain.toURL()!)
)
do {
DownloadCommand.userId = try await DownloadCommand.client.send(Paths.getCurrentUser).value.id!
} catch {
fatalError("failed to fetch user id. quitting...")
}
await downloadRoot(id: mediaId, outputPath: URL(fileURLWithPath: output))
}
}
}