From 0caf8a81202fee21531e11692b1fd6e4d17a3df7 Mon Sep 17 00:00:00 2001 From: kingbri Date: Mon, 3 Jun 2024 23:56:56 -0400 Subject: [PATCH] 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 --- Ferrite/API/AllDebridWrapper.swift | 23 ++++-- Ferrite/API/PremiumizeWrapper.swift | 7 +- Ferrite/API/RealDebridWrapper.swift | 25 +++++-- Ferrite/Protocols/Debrid.swift | 12 ++-- Ferrite/ViewModels/DebridManager.swift | 71 +++++-------------- .../Library/Cloud/AllDebridCloudView.swift | 21 +++--- .../Library/Cloud/RealDebridCloudView.swift | 21 +++--- .../Views/SheetViews/BatchChoiceView.swift | 2 +- 8 files changed, 88 insertions(+), 94 deletions(-) diff --git a/Ferrite/API/AllDebridWrapper.swift b/Ferrite/API/AllDebridWrapper.swift index eb0cbb7..7111cd1 100644 --- a/Ferrite/API/AllDebridWrapper.swift +++ b/Ferrite/API/AllDebridWrapper.swift @@ -181,10 +181,18 @@ public class AllDebrid: PollingDebridSource { // MARK: - Downloading // Wrapper function to fetch a download link from the API - public func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> String { - let magnetID = try await addMagnet(magnet: magnet) + public func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?, userTorrents: [DebridCloudTorrent] = []) async throws -> String { + 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( - magnetId: magnetID, + magnetId: selectedMagnetId, 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 = [ - URLQueryItem(name: "id", value: String(magnetId)) + URLQueryItem(name: "id", value: magnetId) ] var request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/magnet/status", queryItems: queryItems)) @@ -315,6 +323,11 @@ public class AllDebrid: PollingDebridSource { return downloads } + // Not used + public func checkUserDownloads(link: String, userDownloads: [DebridCloudDownload]) async throws -> String? { + nil + } + // The downloadId is actually the download link public func deleteDownload(downloadId: String) async throws { let queryItems = [ diff --git a/Ferrite/API/PremiumizeWrapper.swift b/Ferrite/API/PremiumizeWrapper.swift index 7d2b6fb..8ec7356 100644 --- a/Ferrite/API/PremiumizeWrapper.swift +++ b/Ferrite/API/PremiumizeWrapper.swift @@ -251,7 +251,7 @@ public class Premiumize: OAuthDebridSource { // MARK: - Downloading // 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 try await createTransfer(magnet: magnet) @@ -316,6 +316,11 @@ public class Premiumize: OAuthDebridSource { 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 { var request = URLRequest(url: URL(string: "\(baseApiUrl)/item/delete")!) request.httpMethod = "POST" diff --git a/Ferrite/API/RealDebridWrapper.swift b/Ferrite/API/RealDebridWrapper.swift index 379df7b..2ec74ee 100644 --- a/Ferrite/API/RealDebridWrapper.swift +++ b/Ferrite/API/RealDebridWrapper.swift @@ -295,14 +295,22 @@ public class RealDebrid: PollingDebridSource { // MARK: - Downloading // Wrapper function to fetch a download link from the API - public func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?) async throws -> String { - let selectedMagnetId = try await addMagnet(magnet: magnet) + public func getDownloadLink(magnet: Magnet, ia: DebridIA?, iaFile: DebridIAFile?, userTorrents: [DebridCloudTorrent] = []) async throws -> String { + 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( debridID: selectedMagnetId, - selectedIndex: iaFile?.fileId ?? 0 + selectedFileId: iaFile?.fileId ?? 1 ) 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 - 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)")!) let data = try await performRequest(request: &request, requestName: #function) let rawResponse = try jsonDecoder.decode(TorrentInfoResponse.self, from: data) 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 if let torrentLink = rawResponse.links[safe: linkIndex ?? -1], rawResponse.status == "downloaded" { @@ -429,6 +437,11 @@ public class RealDebrid: PollingDebridSource { return downloads } + // Not used + public func checkUserDownloads(link: String, userDownloads: [DebridCloudDownload]) -> String? { + nil + } + public func deleteDownload(downloadId: String) async throws { var request = URLRequest(url: URL(string: "\(baseApiUrl)/downloads/delete/\(downloadId)")!) request.httpMethod = "DELETE" diff --git a/Ferrite/Protocols/Debrid.swift b/Ferrite/Protocols/Debrid.swift index 8853145..c2d6e20 100644 --- a/Ferrite/Protocols/Debrid.swift +++ b/Ferrite/Protocols/Debrid.swift @@ -21,14 +21,16 @@ public protocol DebridSource { // Fetches a download link from a source // 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 getUserTorrents() async throws -> [DebridCloudTorrent] - - // Deletes information from the service + func checkUserDownloads(link: String, userDownloads: [DebridCloudDownload]) async throws -> String? func deleteDownload(downloadId: String) async throws + + // User torrent functions + func getUserTorrents() async throws -> [DebridCloudTorrent] func deleteTorrent(torrentId: String) async throws } diff --git a/Ferrite/ViewModels/DebridManager.swift b/Ferrite/ViewModels/DebridManager.swift index 4cb85bd..632ae3c 100644 --- a/Ferrite/ViewModels/DebridManager.swift +++ b/Ferrite/ViewModels/DebridManager.swift @@ -559,42 +559,21 @@ public class DebridManager: ObservableObject { switch selectedDebridType { case .realDebrid: - await fetchRdDownload(magnet: magnet, existingLink: cloudInfo) + await fetchRdDownload(magnet: magnet, cloudInfo: cloudInfo) case .allDebrid: - await fetchAdDownload(magnet: magnet, existingLockedLink: cloudInfo) + await fetchAdDownload(magnet: magnet, cloudInfo: cloudInfo) case .premiumize: - await fetchPmDownload(magnet: magnet, cloudItemId: cloudInfo) + await fetchPmDownload(magnet: magnet, cloudInfo: cloudInfo) case .none: break } } - func fetchRdDownload(magnet: Magnet?, existingLink: 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] - } - */ - + func fetchRdDownload(magnet: Magnet?, cloudInfo: String?) async { 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 { let downloadLink = try await realDebrid.getDownloadLink( - magnet: magnet, ia: selectedRealDebridItem, iaFile: selectedRealDebridFile + magnet: magnet, ia: selectedRealDebridItem, iaFile: selectedRealDebridFile, userTorrents: realDebridCloudTorrents ) // Update the UI @@ -612,7 +591,9 @@ public class DebridManager: ObservableObject { default: 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() @@ -685,32 +666,11 @@ public class DebridManager: ObservableObject { } } - func fetchAdDownload(magnet: Magnet?, existingLockedLink: 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 - } - */ - + func fetchAdDownload(magnet: Magnet?, cloudInfo: String?) async { do { - /* - if let lockedLink, - let unlockedLink = await checkAdUserLinks(lockedLink: lockedLink) - { - downloadUrl = unlockedLink - } else if let magnet { - */ if let magnet { let downloadLink = try await allDebrid.getDownloadLink( - magnet: magnet, ia: selectedAllDebridItem, iaFile: selectedAllDebridFile + magnet: magnet, ia: selectedAllDebridItem, iaFile: selectedAllDebridFile, userTorrents: allDebridCloudMagnets ) // 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 { - if let cloudItemId { - downloadUrl = try await premiumize.itemDetails(itemID: cloudItemId).link - } else if let magnet { + if let cloudInfo { + downloadUrl = try await premiumize.checkUserDownloads(link: cloudInfo, userDownloads: premiumizeCloudItems) ?? "" + return + } + + if let magnet { let downloadLink = try await premiumize.getDownloadLink( magnet: magnet, ia: selectedPremiumizeItem, iaFile: selectedPremiumizeFile ) diff --git a/Ferrite/Views/ComponentViews/Library/Cloud/AllDebridCloudView.swift b/Ferrite/Views/ComponentViews/Library/Cloud/AllDebridCloudView.swift index ed09447..0f342b5 100644 --- a/Ferrite/Views/ComponentViews/Library/Cloud/AllDebridCloudView.swift +++ b/Ferrite/Views/ComponentViews/Library/Cloud/AllDebridCloudView.swift @@ -67,9 +67,14 @@ struct AllDebridCloudView: View { ) Task { - if cloudTorrent.links.count == 1 { - if let torrentLink = cloudTorrent.links[safe: 0] { - await debridManager.fetchDebridDownload(magnet: nil, cloudInfo: torrentLink) + let magnet = Magnet(hash: cloudTorrent.hash, link: nil) + await debridManager.populateDebridIA([magnet]) + if debridManager.selectDebridResult(magnet: magnet) { + // Is this a batch? + + if cloudTorrent.links.count == 1 { + await debridManager.fetchDebridDownload(magnet: magnet) + if !debridManager.downloadUrl.isEmpty { historyInfo.url = debridManager.downloadUrl PersistenceController.shared.createHistory(historyInfo, performSave: true) @@ -79,14 +84,8 @@ struct AllDebridCloudView: View { navModel: navModel ) } - } - } else { - let magnet = Magnet(hash: cloudTorrent.hash, link: nil) - - // Do not clear old IA values - await debridManager.populateDebridIA([magnet]) - - if debridManager.selectDebridResult(magnet: magnet) { + } else { + navModel.selectedMagnet = magnet navModel.selectedHistoryInfo = historyInfo navModel.currentChoiceSheet = .batch } diff --git a/Ferrite/Views/ComponentViews/Library/Cloud/RealDebridCloudView.swift b/Ferrite/Views/ComponentViews/Library/Cloud/RealDebridCloudView.swift index f1a2224..383c1ea 100644 --- a/Ferrite/Views/ComponentViews/Library/Cloud/RealDebridCloudView.swift +++ b/Ferrite/Views/ComponentViews/Library/Cloud/RealDebridCloudView.swift @@ -68,9 +68,14 @@ struct RealDebridCloudView: View { ) Task { - if cloudTorrent.links.count == 1 { - if let torrentLink = cloudTorrent.links[safe: 0] { - await debridManager.fetchDebridDownload(magnet: nil, cloudInfo: torrentLink) + let magnet = Magnet(hash: cloudTorrent.hash, link: nil) + await debridManager.populateDebridIA([magnet]) + if debridManager.selectDebridResult(magnet: magnet) { + // Is this a batch? + + if cloudTorrent.links.count == 1 { + await debridManager.fetchDebridDownload(magnet: magnet) + if !debridManager.downloadUrl.isEmpty { historyInfo.url = debridManager.downloadUrl PersistenceController.shared.createHistory(historyInfo, performSave: true) @@ -80,14 +85,8 @@ struct RealDebridCloudView: View { navModel: navModel ) } - } - } else { - let magnet = Magnet(hash: cloudTorrent.hash, link: nil) - - // Do not clear old IA values - await debridManager.populateDebridIA([magnet]) - - if debridManager.selectDebridResult(magnet: magnet) { + } else { + navModel.selectedMagnet = magnet navModel.selectedHistoryInfo = historyInfo navModel.currentChoiceSheet = .batch } diff --git a/Ferrite/Views/SheetViews/BatchChoiceView.swift b/Ferrite/Views/SheetViews/BatchChoiceView.swift index 5e7e9da..7d2d3f4 100644 --- a/Ferrite/Views/SheetViews/BatchChoiceView.swift +++ b/Ferrite/Views/SheetViews/BatchChoiceView.swift @@ -85,7 +85,7 @@ struct BatchChoiceView: View { // Common function to communicate betwen VMs and queue/display a download func queueCommonDownload(fileName: String) { debridManager.currentDebridTask = Task { - await debridManager.fetchDebridDownload(magnet: navModel.resultFromCloud ? nil : navModel.selectedMagnet) + await debridManager.fetchDebridDownload(magnet: navModel.selectedMagnet) if !debridManager.downloadUrl.isEmpty { try? await Task.sleep(seconds: 1)