101 lines
4.1 KiB
Swift
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))
|
|
}
|
|
}
|
|
}
|