Debrid: Refactor IA and download functions
Use the common protocol to handle these. Signed-off-by: kingbri <bdashore3@proton.me>
This commit is contained in:
parent
7d5cdc5d06
commit
aa739133be
6 changed files with 126 additions and 189 deletions
|
|
@ -19,9 +19,10 @@ public class AllDebrid: PollingDebridSource, ObservableObject {
|
|||
getToken() != nil
|
||||
}
|
||||
|
||||
public var IAValues: [DebridIA] = []
|
||||
public var cloudDownloads: [DebridCloudDownload] = []
|
||||
public var cloudTorrents: [DebridCloudTorrent] = []
|
||||
@Published public var IAValues: [DebridIA] = []
|
||||
@Published public var cloudDownloads: [DebridCloudDownload] = []
|
||||
@Published public var cloudTorrents: [DebridCloudTorrent] = []
|
||||
public var cloudTTL: Double = 0.0
|
||||
|
||||
let baseApiUrl = "https://api.alldebrid.com/v4"
|
||||
let appName = "Ferrite"
|
||||
|
|
@ -163,7 +164,26 @@ public class AllDebrid: PollingDebridSource, ObservableObject {
|
|||
// MARK: - Instant availability
|
||||
|
||||
public func instantAvailability(magnets: [Magnet]) async throws {
|
||||
let queryItems = magnets.map { URLQueryItem(name: "magnets[]", value: $0.hash) }
|
||||
let now = Date().timeIntervalSince1970
|
||||
|
||||
let sendMagnets = magnets.filter { magnet in
|
||||
if let IAIndex = IAValues.firstIndex(where: { $0.magnet.hash == magnet.hash }) {
|
||||
if now > IAValues[IAIndex].expiryTimeStamp {
|
||||
IAValues.remove(at: IAIndex)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if sendMagnets.isEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
let queryItems = sendMagnets.map { URLQueryItem(name: "magnets[]", value: $0.hash) }
|
||||
var request = URLRequest(url: try buildRequestURL(urlString: "\(baseApiUrl)/magnet/instant", queryItems: queryItems))
|
||||
|
||||
let data = try await performRequest(request: &request, requestName: #function)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ public class Premiumize: OAuthDebridSource, ObservableObject {
|
|||
@Published public var IAValues: [DebridIA] = []
|
||||
@Published public var cloudDownloads: [DebridCloudDownload] = []
|
||||
@Published public var cloudTorrents: [DebridCloudTorrent] = []
|
||||
public var cloudTTL: Double = 0.0
|
||||
|
||||
let baseAuthUrl = "https://www.premiumize.me/authorize"
|
||||
let baseApiUrl = "https://www.premiumize.me/api"
|
||||
|
|
@ -127,16 +128,31 @@ public class Premiumize: OAuthDebridSource, ObservableObject {
|
|||
// MARK: - Instant availability
|
||||
|
||||
public func instantAvailability(magnets: [Magnet]) async throws {
|
||||
// Only strip magnets that don't have an associated link for PM
|
||||
let strippedMagnets: [Magnet] = magnets.compactMap {
|
||||
if let magnetLink = $0.link {
|
||||
return Magnet(hash: $0.hash, link: magnetLink)
|
||||
let now = Date().timeIntervalSince1970
|
||||
|
||||
// Remove magnets that don't have an associated link for PM along with existing TTL logic
|
||||
let sendMagnets = magnets.filter { magnet in
|
||||
if magnet.link == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if let IAIndex = IAValues.firstIndex(where: { $0.magnet.hash == magnet.hash }) {
|
||||
if now > IAValues[IAIndex].expiryTimeStamp {
|
||||
IAValues.remove(at: IAIndex)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
let availableMagnets = try await divideCacheRequests(magnets: strippedMagnets)
|
||||
if sendMagnets.isEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
let availableMagnets = try await divideCacheRequests(magnets: sendMagnets)
|
||||
|
||||
// Split DDL requests into chunks of 10
|
||||
for chunk in availableMagnets.chunked(into: 10) {
|
||||
|
|
|
|||
|
|
@ -20,14 +20,10 @@ public class RealDebrid: PollingDebridSource, ObservableObject {
|
|||
FerriteKeychain.shared.get("RealDebrid.AccessToken") != nil
|
||||
}
|
||||
|
||||
@Published public var IAValues: [DebridIA] = [] {
|
||||
willSet {
|
||||
self.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
@Published public var IAValues: [DebridIA] = []
|
||||
@Published public var cloudDownloads: [DebridCloudDownload] = []
|
||||
@Published public var cloudTorrents: [DebridCloudTorrent] = []
|
||||
var cloudTTL: Double = 0.0
|
||||
public var cloudTTL: Double = 0.0
|
||||
|
||||
let baseAuthUrl = "https://api.real-debrid.com/oauth/v2"
|
||||
let baseApiUrl = "https://api.real-debrid.com/rest/1.0"
|
||||
|
|
@ -237,7 +233,26 @@ public class RealDebrid: PollingDebridSource, ObservableObject {
|
|||
|
||||
// Checks if the magnet is streamable on RD
|
||||
public func instantAvailability(magnets: [Magnet]) async throws {
|
||||
var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/instantAvailability/\(magnets.compactMap(\.hash).joined(separator: "/"))")!)
|
||||
let now = Date().timeIntervalSince1970
|
||||
|
||||
let sendMagnets = magnets.filter { magnet in
|
||||
if let IAIndex = IAValues.firstIndex(where: { $0.magnet.hash == magnet.hash }) {
|
||||
if now > IAValues[IAIndex].expiryTimeStamp {
|
||||
IAValues.remove(at: IAIndex)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if sendMagnets.isEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
var request = URLRequest(url: URL(string: "\(baseApiUrl)/torrents/instantAvailability/\(sendMagnets.compactMap(\.hash).joined(separator: "/"))")!)
|
||||
|
||||
let data = try await performRequest(request: &request, requestName: #function)
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ public protocol DebridSource: AnyObservableObject {
|
|||
// Cloud variables
|
||||
var cloudDownloads: [DebridCloudDownload] { get set }
|
||||
var cloudTorrents: [DebridCloudTorrent] { get set }
|
||||
var cloudTTL: Double { get set }
|
||||
|
||||
// User downloads functions
|
||||
func getUserDownloads() async throws -> [DebridCloudDownload]
|
||||
|
|
|
|||
|
|
@ -27,12 +27,8 @@ public class DebridManager: ObservableObject {
|
|||
}
|
||||
|
||||
@Published var selectedDebridSource: DebridSource?
|
||||
|
||||
/*
|
||||
func debridSourceFromName(_ name: String? = nil) -> DebridSource? {
|
||||
debridSources.first { $0.id.name == name ?? selectedDebridId?.name }
|
||||
}
|
||||
*/
|
||||
var selectedDebridItem: DebridIA?
|
||||
var selectedDebridFile: DebridIAFile?
|
||||
|
||||
// Service agnostic variables
|
||||
@Published var enabledDebrids: Set<DebridType> = [] {
|
||||
|
|
@ -184,78 +180,22 @@ public class DebridManager: ObservableObject {
|
|||
|
||||
// Clears all selected files and items
|
||||
public func clearSelectedDebridItems() {
|
||||
switch selectedDebridType {
|
||||
case .realDebrid:
|
||||
selectedRealDebridFile = nil
|
||||
selectedRealDebridItem = nil
|
||||
case .allDebrid:
|
||||
selectedAllDebridFile = nil
|
||||
selectedAllDebridItem = nil
|
||||
case .premiumize:
|
||||
selectedPremiumizeFile = nil
|
||||
selectedPremiumizeItem = nil
|
||||
case .none:
|
||||
break
|
||||
}
|
||||
selectedDebridItem = nil
|
||||
selectedDebridFile = nil
|
||||
}
|
||||
|
||||
// Common function to populate hashes for debrid services
|
||||
public func populateDebridIA(_ resultMagnets: [Magnet]) async {
|
||||
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 sendMagnets = resultMagnets.filter { magnet in
|
||||
if let IAIndex = realDebrid.IAValues.firstIndex(where: { $0.magnet.hash == magnet.hash }), enabledDebrids.contains(.realDebrid) {
|
||||
if now.timeIntervalSince1970 > realDebrid.IAValues[IAIndex].expiryTimeStamp {
|
||||
realDebrid.IAValues.remove(at: IAIndex)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} else if let IAIndex = allDebrid.IAValues.firstIndex(where: { $0.magnet.hash == magnet.hash }), enabledDebrids.contains(.allDebrid) {
|
||||
if now.timeIntervalSince1970 > allDebrid.IAValues[IAIndex].expiryTimeStamp {
|
||||
allDebrid.IAValues.remove(at: IAIndex)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} else if let IAIndex = premiumize.IAValues.firstIndex(where: { $0.magnet.hash == magnet.hash }), enabledDebrids.contains(.premiumize) {
|
||||
if now.timeIntervalSince1970 > premiumize.IAValues[IAIndex].expiryTimeStamp {
|
||||
premiumize.IAValues.remove(at: IAIndex)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Don't exit the function if the API fetch errors
|
||||
if !sendMagnets.isEmpty {
|
||||
if enabledDebrids.contains(.realDebrid) {
|
||||
do {
|
||||
try await realDebrid.instantAvailability(magnets: sendMagnets)
|
||||
} catch {
|
||||
await sendDebridError(error, prefix: "RealDebrid IA fetch error")
|
||||
}
|
||||
for debridSource in debridSources {
|
||||
if !debridSource.isLoggedIn {
|
||||
continue
|
||||
}
|
||||
|
||||
if enabledDebrids.contains(.allDebrid) {
|
||||
do {
|
||||
try await allDebrid.instantAvailability(magnets: sendMagnets)
|
||||
} catch {
|
||||
await sendDebridError(error, prefix: "AllDebrid IA fetch error")
|
||||
}
|
||||
}
|
||||
|
||||
if enabledDebrids.contains(.premiumize) {
|
||||
do {
|
||||
try await premiumize.instantAvailability(magnets: sendMagnets)
|
||||
} catch {
|
||||
await sendDebridError(error, prefix: "Premiumize IA fetch error")
|
||||
}
|
||||
// Don't exit the function if the API fetch errors
|
||||
do {
|
||||
try await debridSource.instantAvailability(magnets: resultMagnets)
|
||||
} catch {
|
||||
await sendDebridError(error, prefix: "\(debridSource.id) IA fetch error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -281,32 +221,15 @@ public class DebridManager: ObservableObject {
|
|||
return false
|
||||
}
|
||||
|
||||
switch selectedDebridSource?.id {
|
||||
case .some("RealDebrid"):
|
||||
if let realDebridItem = realDebrid.IAValues.first(where: { magnetHash == $0.magnet.hash }) {
|
||||
selectedRealDebridItem = realDebridItem
|
||||
return true
|
||||
} else {
|
||||
logManager?.error("DebridManager: Could not find the associated RealDebrid entry for magnet hash \(magnetHash)")
|
||||
return false
|
||||
}
|
||||
case .some("AllDebrid"):
|
||||
if let allDebridItem = allDebrid.IAValues.first(where: { magnetHash == $0.magnet.hash }) {
|
||||
selectedAllDebridItem = allDebridItem
|
||||
return true
|
||||
} else {
|
||||
logManager?.error("DebridManager: Could not find the associated AllDebrid entry for magnet hash \(magnetHash)")
|
||||
return false
|
||||
}
|
||||
case .some("Premiumize"):
|
||||
if let premiumizeItem = premiumize.IAValues.first(where: { magnetHash == $0.magnet.hash }) {
|
||||
selectedPremiumizeItem = premiumizeItem
|
||||
return true
|
||||
} else {
|
||||
logManager?.error("DebridManager: Could not find the associated Premiumize entry for magnet hash \(magnetHash)")
|
||||
return false
|
||||
}
|
||||
default:
|
||||
guard let selectedSource = selectedDebridSource else {
|
||||
return false
|
||||
}
|
||||
|
||||
if let IAItem = selectedSource.IAValues.first(where: { magnetHash == $0.magnet.hash }) {
|
||||
selectedDebridItem = IAItem
|
||||
return true
|
||||
} else {
|
||||
logManager?.error("DebridManager: Could not find the associated \(selectedSource.id) entry for magnet hash \(magnetHash)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -535,23 +458,19 @@ public class DebridManager: ObservableObject {
|
|||
self.currentDebridTask = nil
|
||||
})
|
||||
|
||||
switch selectedDebridType {
|
||||
case .realDebrid:
|
||||
await fetchRdDownload(magnet: magnet, cloudInfo: cloudInfo)
|
||||
case .allDebrid:
|
||||
await fetchAdDownload(magnet: magnet, cloudInfo: cloudInfo)
|
||||
case .premiumize:
|
||||
await fetchPmDownload(magnet: magnet, cloudInfo: cloudInfo)
|
||||
case .none:
|
||||
break
|
||||
guard let debridSource = selectedDebridSource else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func fetchRdDownload(magnet: Magnet?, cloudInfo: String?) async {
|
||||
do {
|
||||
if let cloudInfo {
|
||||
downloadUrl = try await debridSource.checkUserDownloads(link: cloudInfo) ?? ""
|
||||
return
|
||||
}
|
||||
|
||||
if let magnet {
|
||||
let downloadLink = try await realDebrid.getDownloadLink(
|
||||
magnet: magnet, ia: selectedRealDebridItem, iaFile: selectedRealDebridFile
|
||||
let downloadLink = try await debridSource.getDownloadLink(
|
||||
magnet: magnet, ia: selectedDebridItem, iaFile: selectedDebridFile
|
||||
)
|
||||
|
||||
// Update the UI
|
||||
|
|
@ -560,6 +479,28 @@ public class DebridManager: ObservableObject {
|
|||
throw RealDebrid.RDError.FailedRequest(description: "Could not fetch your file from RealDebrid's cache or API")
|
||||
}
|
||||
|
||||
// Fetch one more time to add updated data into the RD cloud cache
|
||||
// TODO: Add common fetch cloud method
|
||||
//await fetchRdCloud(bypassTTL: true)
|
||||
} catch {
|
||||
// TODO: Fix error types and unify errors
|
||||
print("Error \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func fetchRdDownload(magnet: Magnet?, cloudInfo: String?) async {
|
||||
do {
|
||||
guard let magnet else {
|
||||
throw RealDebrid.RDError.FailedRequest(description: "Could not fetch your file from RealDebrid's cache or API")
|
||||
}
|
||||
|
||||
let downloadLink = try await realDebrid.getDownloadLink(
|
||||
magnet: magnet, ia: selectedRealDebridItem, iaFile: selectedRealDebridFile
|
||||
)
|
||||
|
||||
// Update the UI
|
||||
downloadUrl = downloadLink
|
||||
|
||||
// Fetch one more time to add updated data into the RD cloud cache
|
||||
await fetchRdCloud(bypassTTL: true)
|
||||
} catch {
|
||||
|
|
@ -631,21 +572,6 @@ public class DebridManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func checkRdUserDownloads(userTorrentLink: String) async -> String? {
|
||||
do {
|
||||
let existingLinks = realDebridCloudDownloads.first { $0.link == userTorrentLink }
|
||||
if let existingLink = existingLinks?.fileName {
|
||||
return existingLink
|
||||
} else {
|
||||
return try await realDebrid.unrestrictLink(debridDownloadLink: userTorrentLink)
|
||||
}
|
||||
} catch {
|
||||
await sendDebridError(error, prefix: "RealDebrid download check error")
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func fetchAdDownload(magnet: Magnet?, cloudInfo: String?) async {
|
||||
do {
|
||||
if let magnet {
|
||||
|
|
@ -666,22 +592,6 @@ public class DebridManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func checkAdUserLinks(lockedLink: String) async -> String? {
|
||||
do {
|
||||
let existingLinks = allDebridCloudLinks.first { $0.link == lockedLink }
|
||||
if let existingLink = existingLinks?.link {
|
||||
return existingLink
|
||||
} else {
|
||||
try await allDebrid.saveLink(link: lockedLink)
|
||||
return try await allDebrid.unlockLink(lockedLink: lockedLink)
|
||||
}
|
||||
} catch {
|
||||
await sendDebridError(error, prefix: "AllDebrid download check error")
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Refreshes torrents and downloads from a RD user's account
|
||||
public func fetchAdCloud(bypassTTL: Bool = false) async {
|
||||
if bypassTTL || Date().timeIntervalSince1970 > allDebridCloudTTL {
|
||||
|
|
|
|||
|
|
@ -23,39 +23,14 @@ struct BatchChoiceView: View {
|
|||
var body: some View {
|
||||
NavView {
|
||||
List {
|
||||
switch debridManager.selectedDebridSource?.id {
|
||||
case .some("RealDebrid"):
|
||||
ForEach(debridManager.selectedRealDebridItem?.files ?? [], id: \.self) { file in
|
||||
if file.name.lowercased().contains(searchText.lowercased()) || searchText.isEmpty {
|
||||
Button(file.name) {
|
||||
debridManager.selectedRealDebridFile = file
|
||||
ForEach(debridManager.selectedDebridItem?.files ?? [], id: \.self) { file in
|
||||
if file.name.lowercased().contains(searchText.lowercased()) || searchText.isEmpty {
|
||||
Button(file.name) {
|
||||
debridManager.selectedDebridFile = file
|
||||
|
||||
queueCommonDownload(fileName: file.name)
|
||||
}
|
||||
queueCommonDownload(fileName: file.name)
|
||||
}
|
||||
}
|
||||
case .some("AllDebrid"):
|
||||
ForEach(debridManager.selectedAllDebridItem?.files ?? [], id: \.self) { file in
|
||||
if file.name.lowercased().contains(searchText.lowercased()) || searchText.isEmpty {
|
||||
Button(file.name) {
|
||||
debridManager.selectedAllDebridFile = file
|
||||
|
||||
queueCommonDownload(fileName: file.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
case .some("Premiumize"):
|
||||
ForEach(debridManager.selectedPremiumizeItem?.files ?? [], id: \.self) { file in
|
||||
if file.name.lowercased().contains(searchText.lowercased()) || searchText.isEmpty {
|
||||
Button(file.name) {
|
||||
debridManager.selectedPremiumizeFile = file
|
||||
|
||||
queueCommonDownload(fileName: file.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
.tint(.primary)
|
||||
|
|
|
|||
Loading…
Reference in a new issue