233 lines
7.9 KiB
Swift
233 lines
7.9 KiB
Swift
//
|
|
// DebridManager.swift
|
|
// Ferrite
|
|
//
|
|
// Created by Brian Dashore on 7/20/22.
|
|
//
|
|
|
|
import Foundation
|
|
import SwiftUI
|
|
|
|
@MainActor
|
|
public class DebridManager: ObservableObject {
|
|
// Linked classes
|
|
var toastModel: ToastViewModel?
|
|
let realDebrid: RealDebrid = .init()
|
|
|
|
// UI Variables
|
|
@Published var showWebView: Bool = false
|
|
@Published var showLoadingProgress: Bool = false
|
|
|
|
// Service agnostic variables
|
|
var currentDebridTask: Task<Void, Never>?
|
|
|
|
// RealDebrid auth variables
|
|
@Published var realDebridEnabled: Bool = false {
|
|
didSet {
|
|
UserDefaults.standard.set(realDebridEnabled, forKey: "RealDebrid.Enabled")
|
|
}
|
|
}
|
|
|
|
@Published var realDebridAuthProcessing: Bool = false
|
|
var realDebridAuthUrl: String = ""
|
|
|
|
// RealDebrid fetch variables
|
|
@Published var realDebridIAValues: [RealDebrid.IA] = []
|
|
var realDebridDownloadUrl: String = ""
|
|
|
|
@Published var showDeleteAlert: Bool = false
|
|
|
|
// TODO: Switch to an individual item based sheet system to remove these variables
|
|
var selectedRealDebridItem: RealDebrid.IA?
|
|
var selectedRealDebridFile: RealDebrid.IAFile?
|
|
var selectedRealDebridID: String?
|
|
|
|
init() {
|
|
realDebridEnabled = UserDefaults.standard.bool(forKey: "RealDebrid.Enabled")
|
|
}
|
|
|
|
public func populateDebridHashes(_ resultHashes: [String]) async {
|
|
do {
|
|
let now = Date()
|
|
|
|
// If a hash isn't found in the IA, update it
|
|
// If the hash is expired, remove it and update it
|
|
let sendHashes = resultHashes.filter { hash in
|
|
if let IAIndex = realDebridIAValues.firstIndex(where: { $0.hash == hash }) {
|
|
if now.timeIntervalSince1970 > realDebridIAValues[IAIndex].expiryTimeStamp {
|
|
realDebridIAValues.remove(at: IAIndex)
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
} else {
|
|
return true
|
|
}
|
|
}
|
|
|
|
if !sendHashes.isEmpty {
|
|
let fetchedIAValues = try await realDebrid.instantAvailability(magnetHashes: sendHashes)
|
|
|
|
realDebridIAValues += fetchedIAValues
|
|
}
|
|
} 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?) -> RealDebrid.IAStatus {
|
|
guard let result else {
|
|
return .none
|
|
}
|
|
|
|
guard let debridMatch = realDebridIAValues.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 = realDebridIAValues.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) 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
|
|
}
|
|
|
|
do {
|
|
var fileIds: [Int] = []
|
|
|
|
if let iaFile = selectedRealDebridFile {
|
|
guard let iaBatchFromFile = selectedRealDebridItem?.batches[safe: iaFile.batchIndex] else {
|
|
return
|
|
}
|
|
|
|
fileIds = iaBatchFromFile.files.map(\.id)
|
|
}
|
|
|
|
// If there's an existing torrent, check for a download link. Otherwise check for an unrestrict link
|
|
let existingTorrents = try await realDebrid.userTorrents().filter { $0.hash == selectedRealDebridItem?.hash }
|
|
|
|
// If the links match from a user's downloads, no need to re-run a download
|
|
if let existingTorrent = existingTorrents[safe: 0],
|
|
let torrentLink = existingTorrent.links[safe: selectedRealDebridFile?.batchFileIndex ?? 0]
|
|
{
|
|
let existingLinks = try await realDebrid.userDownloads().filter { $0.link == torrentLink }
|
|
if let existingLink = existingLinks[safe: 0]?.download {
|
|
realDebridDownloadUrl = existingLink
|
|
} else {
|
|
let downloadLink = try await realDebrid.unrestrictLink(debridDownloadLink: torrentLink)
|
|
|
|
realDebridDownloadUrl = downloadLink
|
|
}
|
|
|
|
} else {
|
|
// Add a magnet after all the cache checks fail
|
|
selectedRealDebridID = try await realDebrid.addMagnet(magnetLink: magnetLink)
|
|
|
|
if let realDebridId = selectedRealDebridID {
|
|
try await realDebrid.selectFiles(debridID: realDebridId, fileIds: fileIds)
|
|
|
|
let torrentLink = try await realDebrid.torrentInfo(debridID: realDebridId, selectedIndex: selectedRealDebridFile?.batchFileIndex ?? 0)
|
|
let downloadLink = try await realDebrid.unrestrictLink(debridDownloadLink: torrentLink)
|
|
|
|
realDebridDownloadUrl = downloadLink
|
|
} else {
|
|
toastModel?.updateToastDescription("Could not cache this torrent. Aborting.")
|
|
}
|
|
}
|
|
} catch {
|
|
switch error {
|
|
case RealDebrid.RDError.EmptyTorrents:
|
|
showDeleteAlert.toggle()
|
|
default:
|
|
let error = error as NSError
|
|
|
|
switch error.code {
|
|
case -999:
|
|
toastModel?.updateToastDescription("Download cancelled", newToastType: .info)
|
|
default:
|
|
toastModel?.updateToastDescription("RealDebrid download error: \(error)")
|
|
}
|
|
|
|
await deleteRdTorrent()
|
|
}
|
|
|
|
showLoadingProgress = false
|
|
|
|
print("RealDebrid download error: \(error)")
|
|
}
|
|
}
|
|
|
|
public func deleteRdTorrent() async {
|
|
if let realDebridId = selectedRealDebridID {
|
|
try? await realDebrid.deleteTorrent(debridID: realDebridId)
|
|
}
|
|
|
|
selectedRealDebridID = nil
|
|
}
|
|
}
|