Ferrite-backup/Ferrite/ViewModels/DebridManager.swift
kingbri 1761f8dfb4 Debrid: Add loading indicator and fix iOS <14.5 issues
When a search result is selected, there is usually a delay due to
the debrid dance of API routes for grabbing a download link to stream.
Add a loading indicator and prevent any other tasks from loading
unless the user cancels it.

iOS 14.5 was a huge update which added many QoL SwiftUI changes that
are consistent to modern iOS versions.

However, Ferrite supports iOS versions less than 14.5, mainly 14.3.
More fixes had to be added to make sure UI is consistent across
all OS versions.

Signed-off-by: kingbri <bdashore3@gmail.com>
2022-08-31 18:47:02 -04:00

188 lines
5.9 KiB
Swift

//
// DebridManager.swift
// Ferrite
//
// Created by Brian Dashore on 7/20/22.
//
import Foundation
import SwiftUI
@MainActor
public class DebridManager: ObservableObject {
// UI Variables
var toastModel: ToastViewModel?
@Published var showWebView: Bool = false
@Published var showLoadingProgress: Bool = false
// Service agnostic variables
@Published var currentDebridTask: Task<Void, Never>?
// RealDebrid auth variables
let realDebrid: RealDebrid = .init()
@Published var realDebridEnabled: Bool = false {
didSet {
UserDefaults.standard.set(realDebridEnabled, forKey: "RealDebrid.Enabled")
}
}
@Published var realDebridAuthProcessing: Bool = false
@Published var realDebridAuthUrl: String = ""
// RealDebrid fetch variables
@Published var realDebridHashes: [RealDebridIA] = []
@Published var realDebridDownloadUrl: String = ""
@Published var selectedRealDebridItem: RealDebridIA?
@Published var selectedRealDebridFile: RealDebridIAFile?
init() {
realDebridEnabled = UserDefaults.standard.bool(forKey: "RealDebrid.Enabled")
}
public func populateDebridHashes(_ searchResults: [SearchResult]) async {
var hashes: [String] = []
for result in searchResults {
if let hash = result.magnetHash {
hashes.append(hash)
}
}
do {
let debridHashes = try await realDebrid.instantAvailability(magnetHashes: hashes)
realDebridHashes = debridHashes
} catch {
let error = error as NSError
if error.code != -999 {
toastModel?.updateToastDescription("RealDebrid hash error: \(error)")
}
print("RealDebrid hash error: \(error)")
}
}
public func matchSearchResult(result: SearchResult?) -> RealDebridIAStatus {
guard let result = result else {
return .none
}
guard let debridMatch = realDebridHashes.first(where: { result.magnetHash == $0.hash }) else {
return .none
}
if debridMatch.batches.isEmpty {
return .full
} else {
return .partial
}
}
public func setSelectedRdResult(result: SearchResult) -> Bool {
guard let magnetHash = result.magnetHash else {
toastModel?.updateToastDescription("Could not find the torrent magnet hash")
return false
}
if let realDebridItem = realDebridHashes.first(where: { magnetHash == $0.hash }) {
selectedRealDebridItem = realDebridItem
return true
} else {
toastModel?.updateToastDescription("Could not find the associated RealDebrid entry for magnet hash \(magnetHash)")
return false
}
}
public func authenticateRd() async {
do {
realDebridAuthProcessing = true
let verificationResponse = try await realDebrid.getVerificationInfo()
realDebridAuthUrl = verificationResponse.directVerificationURL
showWebView.toggle()
try await realDebrid.getDeviceCredentials(deviceCode: verificationResponse.deviceCode)
realDebridEnabled = true
} catch {
toastModel?.updateToastDescription("RealDebrid authentication error: \(error)")
realDebrid.authTask?.cancel()
print("RealDebrid authentication error: \(error)")
}
}
public func logoutRd() async {
do {
try await realDebrid.deleteTokens()
realDebridEnabled = false
realDebridAuthProcessing = false
} catch {
toastModel?.updateToastDescription("RealDebrid logout error: \(error)")
print("RealDebrid logout error: \(error)")
}
}
public func fetchRdDownload(searchResult: SearchResult, iaFile: RealDebridIAFile? = nil) async {
defer {
currentDebridTask = nil
showLoadingProgress = false
}
showLoadingProgress = true
guard let magnetLink = searchResult.magnetLink else {
toastModel?.updateToastDescription("Could not run your action because the magnet link is invalid.")
print("RealDebrid error: Invalid magnet link")
return
}
var realDebridId: String?
do {
realDebridId = try await realDebrid.addMagnet(magnetLink: magnetLink)
var fileIds: [Int] = []
if let iaFile = iaFile {
guard let iaBatchFromFile = selectedRealDebridItem?.batches[safe: iaFile.batchIndex] else {
return
}
fileIds = iaBatchFromFile.files.map(\.id)
}
if let realDebridId = realDebridId {
try await realDebrid.selectFiles(debridID: realDebridId, fileIds: fileIds)
let torrentLink = try await realDebrid.torrentInfo(debridID: realDebridId, selectedIndex: iaFile?.batchFileIndex ?? 0)
let downloadLink = try await realDebrid.unrestrictLink(debridDownloadLink: torrentLink)
realDebridDownloadUrl = downloadLink
} else {
toastModel?.updateToastDescription("Could not cache this torrent. Aborting.")
}
} catch {
let error = error as NSError
switch error.code {
case -999:
toastModel?.updateToastDescription("Download cancelled", newToastType: .info)
default:
toastModel?.updateToastDescription("RealDebrid download error: \(error)")
}
// Delete the torrent download if it exists
if let realDebridId = realDebridId {
try? await realDebrid.deleteTorrent(debridID: realDebridId)
}
showLoadingProgress = false
print("RealDebrid download error: \(error)")
}
}
}