Debrid: Add common functions for existing magnets/downloads

This fixes cloud magnet fetching and also doesn't duplicate magnets
inside the cloud service. Unrestricted links don't get duplicated,
so no need to check against those.

Signed-off-by: kingbri <bdashore3@proton.me>
This commit is contained in:
kingbri 2024-06-03 23:56:56 -04:00 committed by Brian Dashore
parent 551083f521
commit 2d220045c6
8 changed files with 88 additions and 94 deletions

View file

@ -181,10 +181,18 @@ public class AllDebrid: PollingDebridSource {
// MARK: - Downloading // MARK: - Downloading
// Wrapper function to fetch a download link from the API // Wrapper function to fetch a download link from the API
public func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> String { public func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?, userTorrents: [DebridCloudTorrent] = []) async throws -> String {
let magnetID = try await addMagnet(magnet: magnet) let selectedMagnetId: String
if let existingMagnet = userTorrents.first(where: { $0.hash == magnet.hash && $0.status == "Ready" }) {
selectedMagnetId = existingMagnet.torrentId
} else {
let magnetId = try await addMagnet(magnet: magnet)
selectedMagnetId = String(magnetId)
}
let lockedLink = try await fetchMagnetStatus( let lockedLink = try await fetchMagnetStatus(
magnetId: magnetID, magnetId: selectedMagnetId,
selectedIndex: iaFile?.fileId ?? 0 selectedIndex: iaFile?.fileId ?? 0
) )
@ -221,9 +229,9 @@ public class AllDebrid: PollingDebridSource {
} }
} }
public func fetchMagnetStatus(magnetId: Int, selectedIndex: Int?) async throws -> String { public func fetchMagnetStatus(magnetId: String, selectedIndex: Int?) async throws -> String {
let queryItems = [ let queryItems = [
URLQueryItem(name: "id", value: String(magnetId)) URLQueryItem(name: "id", value: magnetId)
] ]
var request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/magnet/status", queryItems: queryItems)) var request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/magnet/status", queryItems: queryItems))
@ -315,6 +323,11 @@ public class AllDebrid: PollingDebridSource {
return downloads return downloads
} }
// Not used
public func checkUserDownloads(link: String, userDownloads: [DebridCloudDownload]) async throws -> String? {
nil
}
// The downloadId is actually the download link // The downloadId is actually the download link
public func deleteDownload(downloadId: String) async throws { public func deleteDownload(downloadId: String) async throws {
let queryItems = [ let queryItems = [

View file

@ -251,7 +251,7 @@ public class Premiumize: OAuthDebridSource {
// MARK: - Downloading // MARK: - Downloading
// Wrapper function to fetch a DDL link from the API // Wrapper function to fetch a DDL link from the API
public func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> String { public func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?, userTorrents: [DebridCloudTorrent] = []) async throws -> String {
// Store the item in PM cloud for later use // Store the item in PM cloud for later use
try await createTransfer(magnet: magnet) try await createTransfer(magnet: magnet)
@ -316,6 +316,11 @@ public class Premiumize: OAuthDebridSource {
return rawResponse return rawResponse
} }
public func checkUserDownloads(link: String, userDownloads: [DebridCloudDownload]) async throws -> String? {
// Link is the cloud item ID
try await itemDetails(itemID: link).link
}
public func deleteDownload(downloadId: String) async throws { public func deleteDownload(downloadId: String) async throws {
var request = URLRequest(url: URL(string: "\(baseApiUrl)/item/delete")!) var request = URLRequest(url: URL(string: "\(baseApiUrl)/item/delete")!)
request.httpMethod = "POST" request.httpMethod = "POST"

View file

@ -295,14 +295,22 @@ public class RealDebrid: PollingDebridSource {
// MARK: - Downloading // MARK: - Downloading
// Wrapper function to fetch a download link from the API // Wrapper function to fetch a download link from the API
public func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> String { public func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?, userTorrents: [DebridCloudTorrent] = []) async throws -> String {
let selectedMagnetId = try await addMagnet(magnet: magnet) let selectedMagnetId: String
try await selectFiles(debridID: selectedMagnetId, fileIds: iaFile?.batchIds ?? []) // Don't queue a new job if the torrent already exists
if let existingTorrent = userTorrents.first(where: { $0.hash == magnet.hash && $0.status == "downloaded" }) {
selectedMagnetId = existingTorrent.torrentId
} else {
selectedMagnetId = try await addMagnet(magnet: magnet)
try await selectFiles(debridID: selectedMagnetId, fileIds: iaFile?.batchIds ?? [])
}
// RealDebrid has 1 as the first ID for a file
let torrentLink = try await torrentInfo( let torrentLink = try await torrentInfo(
debridID: selectedMagnetId, debridID: selectedMagnetId,
selectedIndex: iaFile?.fileId ?? 0 selectedFileId: iaFile?.fileId ?? 1
) )
let downloadLink = try await unrestrictLink(debridDownloadLink: torrentLink) let downloadLink = try await unrestrictLink(debridDownloadLink: torrentLink)
@ -351,13 +359,13 @@ public class RealDebrid: PollingDebridSource {
} }
// Gets the info of a torrent from a given ID // Gets the info of a torrent from a given ID
public func torrentInfo(debridID: String, selectedIndex: Int?) async throws -> String { public func torrentInfo(debridID: String, selectedFileId: Int?) async throws -> String {
var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/info/\(debridID)")!) var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/info/\(debridID)")!)
let data = try await performRequest(request: &request, requestName: #function) let data = try await performRequest(request: &request, requestName: #function)
let rawResponse = try jsonDecoder.decode(TorrentInfoResponse.self, from: data) let rawResponse = try jsonDecoder.decode(TorrentInfoResponse.self, from: data)
let filteredFiles = rawResponse.files.filter { $0.selected == 1 } let filteredFiles = rawResponse.files.filter { $0.selected == 1 }
let linkIndex = filteredFiles.firstIndex(where: { $0.id == selectedIndex }) let linkIndex = filteredFiles.firstIndex(where: { $0.id == selectedFileId })
// Let the user know if a torrent is downloading // Let the user know if a torrent is downloading
if let torrentLink = rawResponse.links[safe: linkIndex ?? -1], rawResponse.status == "downloaded" { if let torrentLink = rawResponse.links[safe: linkIndex ?? -1], rawResponse.status == "downloaded" {
@ -429,6 +437,11 @@ public class RealDebrid: PollingDebridSource {
return downloads return downloads
} }
// Not used
public func checkUserDownloads(link: String, userDownloads: [DebridCloudDownload]) -> String? {
nil
}
public func deleteDownload(downloadId: String) async throws { public func deleteDownload(downloadId: String) async throws {
var request = URLRequest(url: URL(string: "\(baseApiUrl)/downloads/delete/\(downloadId)")!) var request = URLRequest(url: URL(string: "\(baseApiUrl)/downloads/delete/\(downloadId)")!)
request.httpMethod = "DELETE" request.httpMethod = "DELETE"

View file

@ -21,14 +21,16 @@ public protocol DebridSource {
// Fetches a download link from a source // Fetches a download link from a source
// Include the instant availability information with the args // Include the instant availability information with the args
func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> String // Torrents also checked here
func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?, userTorrents: [DebridCloudTorrent]) async throws -> String
// Fetches cloud information from the service // User downloads functions
func getUserDownloads() async throws -> [DebridCloudDownload] func getUserDownloads() async throws -> [DebridCloudDownload]
func getUserTorrents() async throws -> [DebridCloudTorrent] func checkUserDownloads(link: String, userDownloads: [DebridCloudDownload]) async throws -> String?
// Deletes information from the service
func deleteDownload(downloadId: String) async throws func deleteDownload(downloadId: String) async throws
// User torrent functions
func getUserTorrents() async throws -> [DebridCloudTorrent]
func deleteTorrent(torrentId: String) async throws func deleteTorrent(torrentId: String) async throws
} }

View file

@ -559,42 +559,21 @@ public class DebridManager: ObservableObject {
switch selectedDebridType { switch selectedDebridType {
case .realDebrid: case .realDebrid:
await fetchRdDownload(magnet: magnet, existingLink: cloudInfo) await fetchRdDownload(magnet: magnet, cloudInfo: cloudInfo)
case .allDebrid: case .allDebrid:
await fetchAdDownload(magnet: magnet, existingLockedLink: cloudInfo) await fetchAdDownload(magnet: magnet, cloudInfo: cloudInfo)
case .premiumize: case .premiumize:
await fetchPmDownload(magnet: magnet, cloudItemId: cloudInfo) await fetchPmDownload(magnet: magnet, cloudInfo: cloudInfo)
case .none: case .none:
break break
} }
} }
func fetchRdDownload(magnet: Magnet?, existingLink: String?) async { func fetchRdDownload(magnet: Magnet?, cloudInfo: String?) async {
// If an existing link is passed in args, set it to that. Otherwise, find one from RD cloud.
/*
let torrentLink: String?
if let existingLink {
torrentLink = existingLink
} else {
// Bypass the TTL for up to date information
await fetchRdCloud(bypassTTL: true)
let existingTorrent = realDebridCloudTorrents.first { $0.hash == selectedRealDebridItem?.magnet.hash && $0.status == "downloaded" }
torrentLink = existingTorrent?.links[safe: selectedRealDebridFile?.batchFileIndex ?? 0]
}
*/
do { do {
// If the links match from a user's downloads, no need to re-run a download
/*
if let torrentLink,
let downloadLink = await checkRdUserDownloads(userTorrentLink: torrentLink)
{
downloadUrl = downloadLink
} else */
if let magnet { if let magnet {
let downloadLink = try await realDebrid.getDownloadLink( let downloadLink = try await realDebrid.getDownloadLink(
magnet: magnet, ia: selectedRealDebridItem, iaFile: selectedRealDebridFile magnet: magnet, ia: selectedRealDebridItem, iaFile: selectedRealDebridFile, userTorrents: realDebridCloudTorrents
) )
// Update the UI // Update the UI
@ -612,7 +591,9 @@ public class DebridManager: ObservableObject {
default: default:
await sendDebridError(error, prefix: "RealDebrid download error", cancelString: "Download cancelled") await sendDebridError(error, prefix: "RealDebrid download error", cancelString: "Download cancelled")
// await deleteRdTorrent(torrentID: selectedRealDebridID, presentError: false) if let torrentId = selectedRealDebridID {
try? await realDebrid.deleteTorrent(torrentId: torrentId)
}
} }
logManager?.hideIndeterminateToast() logManager?.hideIndeterminateToast()
@ -685,32 +666,11 @@ public class DebridManager: ObservableObject {
} }
} }
func fetchAdDownload(magnet: Magnet?, existingLockedLink: String?) async { func fetchAdDownload(magnet: Magnet?, cloudInfo: String?) async {
// If an existing link is passed in args, set it to that. Otherwise, find one from AD cloud.
/*
let lockedLink: String?
if let existingLockedLink {
lockedLink = existingLockedLink
} else {
// Bypass the TTL for up to date information
await fetchAdCloud(bypassTTL: true)
let existingMagnet = allDebridCloudMagnets.first { $0.hash == selectedAllDebridItem?.magnet.hash && $0.status == "Ready" }
lockedLink = existingMagnet?.links[safe: selectedAllDebridFile?.fileId ?? 0]?.link
}
*/
do { do {
/*
if let lockedLink,
let unlockedLink = await checkAdUserLinks(lockedLink: lockedLink)
{
downloadUrl = unlockedLink
} else if let magnet {
*/
if let magnet { if let magnet {
let downloadLink = try await allDebrid.getDownloadLink( let downloadLink = try await allDebrid.getDownloadLink(
magnet: magnet, ia: selectedAllDebridItem, iaFile: selectedAllDebridFile magnet: magnet, ia: selectedAllDebridItem, iaFile: selectedAllDebridFile, userTorrents: allDebridCloudMagnets
) )
// Update UI // Update UI
@ -777,11 +737,14 @@ public class DebridManager: ObservableObject {
} }
} }
func fetchPmDownload(magnet: Magnet?, cloudItemId: String? = nil) async { func fetchPmDownload(magnet: Magnet?, cloudInfo: String? = nil) async {
do { do {
if let cloudItemId { if let cloudInfo {
downloadUrl = try await premiumize.itemDetails(itemID: cloudItemId).link downloadUrl = try await premiumize.checkUserDownloads(link: cloudInfo, userDownloads: premiumizeCloudItems) ?? ""
} else if let magnet { return
}
if let magnet {
let downloadLink = try await premiumize.getDownloadLink( let downloadLink = try await premiumize.getDownloadLink(
magnet: magnet, ia: selectedPremiumizeItem, iaFile: selectedPremiumizeFile magnet: magnet, ia: selectedPremiumizeItem, iaFile: selectedPremiumizeFile
) )

View file

@ -67,9 +67,14 @@ struct AllDebridCloudView: View {
) )
Task { Task {
if cloudTorrent.links.count == 1 { let magnet = Magnet(hash: cloudTorrent.hash, link: nil)
if let torrentLink = cloudTorrent.links[safe: 0] { await debridManager.populateDebridIA([magnet])
await debridManager.fetchDebridDownload(magnet: nil, cloudInfo: torrentLink) if debridManager.selectDebridResult(magnet: magnet) {
// Is this a batch?
if cloudTorrent.links.count == 1 {
await debridManager.fetchDebridDownload(magnet: magnet)
if !debridManager.downloadUrl.isEmpty { if !debridManager.downloadUrl.isEmpty {
historyInfo.url = debridManager.downloadUrl historyInfo.url = debridManager.downloadUrl
PersistenceController.shared.createHistory(historyInfo, performSave: true) PersistenceController.shared.createHistory(historyInfo, performSave: true)
@ -79,14 +84,8 @@ struct AllDebridCloudView: View {
navModel: navModel navModel: navModel
) )
} }
} } else {
} else { navModel.selectedMagnet = magnet
let magnet = Magnet(hash: cloudTorrent.hash, link: nil)
// Do not clear old IA values
await debridManager.populateDebridIA([magnet])
if debridManager.selectDebridResult(magnet: magnet) {
navModel.selectedHistoryInfo = historyInfo navModel.selectedHistoryInfo = historyInfo
navModel.currentChoiceSheet = .batch navModel.currentChoiceSheet = .batch
} }

View file

@ -68,9 +68,14 @@ struct RealDebridCloudView: View {
) )
Task { Task {
if cloudTorrent.links.count == 1 { let magnet = Magnet(hash: cloudTorrent.hash, link: nil)
if let torrentLink = cloudTorrent.links[safe: 0] { await debridManager.populateDebridIA([magnet])
await debridManager.fetchDebridDownload(magnet: nil, cloudInfo: torrentLink) if debridManager.selectDebridResult(magnet: magnet) {
// Is this a batch?
if cloudTorrent.links.count == 1 {
await debridManager.fetchDebridDownload(magnet: magnet)
if !debridManager.downloadUrl.isEmpty { if !debridManager.downloadUrl.isEmpty {
historyInfo.url = debridManager.downloadUrl historyInfo.url = debridManager.downloadUrl
PersistenceController.shared.createHistory(historyInfo, performSave: true) PersistenceController.shared.createHistory(historyInfo, performSave: true)
@ -80,14 +85,8 @@ struct RealDebridCloudView: View {
navModel: navModel navModel: navModel
) )
} }
} } else {
} else { navModel.selectedMagnet = magnet
let magnet = Magnet(hash: cloudTorrent.hash, link: nil)
// Do not clear old IA values
await debridManager.populateDebridIA([magnet])
if debridManager.selectDebridResult(magnet: magnet) {
navModel.selectedHistoryInfo = historyInfo navModel.selectedHistoryInfo = historyInfo
navModel.currentChoiceSheet = .batch navModel.currentChoiceSheet = .batch
} }

View file

@ -85,7 +85,7 @@ struct BatchChoiceView: View {
// Common function to communicate betwen VMs and queue/display a download // Common function to communicate betwen VMs and queue/display a download
func queueCommonDownload(fileName: String) { func queueCommonDownload(fileName: String) {
debridManager.currentDebridTask = Task { debridManager.currentDebridTask = Task {
await debridManager.fetchDebridDownload(magnet: navModel.resultFromCloud ? nil : navModel.selectedMagnet) await debridManager.fetchDebridDownload(magnet: navModel.selectedMagnet)
if !debridManager.downloadUrl.isEmpty { if !debridManager.downloadUrl.isEmpty {
try? await Task.sleep(seconds: 1) try? await Task.sleep(seconds: 1)