mirror of
https://github.com/Ferrite-iOS/Ferrite.git
synced 2026-01-11 20:10:27 +00:00
Library: Add support for RealDebrid cloud
RealDebrid saves a user's unrestricted links and "torrents" (magnet links in this case). Add the ability to see and queue a user's RD library in Ferrite itself. This required a further abstraction of the debrid manager to allow for more types other than search results to be passed to various functions. Deleting an item from RD's cloud list deletes the item from RD as well. NOTE: This does not track download progress, but it does show if a magnet is currently being downloaded or not. Signed-off-by: kingbri <bdashore3@proton.me>
This commit is contained in:
parent
b0850d43d7
commit
9b7bc55a25
20 changed files with 434 additions and 146 deletions
|
|
@ -14,6 +14,8 @@
|
|||
0C0D50E7288DFF850035ECC8 /* SourcesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D50E6288DFF850035ECC8 /* SourcesView.swift */; };
|
||||
0C10848B28BD9A38008F0BA6 /* SettingsAppVersionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C10848A28BD9A38008F0BA6 /* SettingsAppVersionView.swift */; };
|
||||
0C12D43D28CC332A000195BF /* HistoryButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C12D43C28CC332A000195BF /* HistoryButtonView.swift */; };
|
||||
0C2886D22960AC2800D6FC16 /* DebridCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2886D12960AC2800D6FC16 /* DebridCloudView.swift */; };
|
||||
0C2886D72960C50900D6FC16 /* RealDebridCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2886D62960C50900D6FC16 /* RealDebridCloudView.swift */; };
|
||||
0C31133C28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C31133A28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift */; };
|
||||
0C31133D28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C31133B28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift */; };
|
||||
0C32FB532890D19D002BD219 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C32FB522890D19D002BD219 /* AboutView.swift */; };
|
||||
|
|
@ -122,6 +124,8 @@
|
|||
0C0D50E6288DFF850035ECC8 /* SourcesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourcesView.swift; sourceTree = "<group>"; };
|
||||
0C10848A28BD9A38008F0BA6 /* SettingsAppVersionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAppVersionView.swift; sourceTree = "<group>"; };
|
||||
0C12D43C28CC332A000195BF /* HistoryButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryButtonView.swift; sourceTree = "<group>"; };
|
||||
0C2886D12960AC2800D6FC16 /* DebridCloudView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebridCloudView.swift; sourceTree = "<group>"; };
|
||||
0C2886D62960C50900D6FC16 /* RealDebridCloudView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealDebridCloudView.swift; sourceTree = "<group>"; };
|
||||
0C31133A28B1ABFA004DCB0D /* SourceJsonParser+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceJsonParser+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
0C31133B28B1ABFA004DCB0D /* SourceJsonParser+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceJsonParser+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
0C32FB522890D19D002BD219 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -306,6 +310,14 @@
|
|||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0C2886D52960C4F800D6FC16 /* Cloud */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0C2886D62960C50900D6FC16 /* RealDebridCloudView.swift */,
|
||||
);
|
||||
path = Cloud;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0C44E2A628D4DDC6007711AE /* Classes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -482,7 +494,9 @@
|
|||
0CA3B23528C265FD00616D3A /* Library */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0C2886D52960C4F800D6FC16 /* Cloud */,
|
||||
0CA3B23828C2660D00616D3A /* BookmarksView.swift */,
|
||||
0C2886D12960AC2800D6FC16 /* DebridCloudView.swift */,
|
||||
0CA3B23628C2660700616D3A /* HistoryView.swift */,
|
||||
0CD4CAC528C980EB0046E1DC /* HistoryActionsView.swift */,
|
||||
0C12D43C28CC332A000195BF /* HistoryButtonView.swift */,
|
||||
|
|
@ -651,6 +665,7 @@
|
|||
0CA429F828C5098D000D0610 /* DateFormatter.swift in Sources */,
|
||||
0CE66B3A28E640D200F69346 /* Backport.swift in Sources */,
|
||||
0C42B5962932F2D5008057A0 /* DebridChoiceView.swift in Sources */,
|
||||
0C2886D72960C50900D6FC16 /* RealDebridCloudView.swift in Sources */,
|
||||
0C84F4872895BFED0074B7C9 /* SourceList+CoreDataProperties.swift in Sources */,
|
||||
0C794B6B289DACF100DD1CC8 /* SourceCatalogButtonView.swift in Sources */,
|
||||
0C54D36428C5086E00BFEEE2 /* History+CoreDataProperties.swift in Sources */,
|
||||
|
|
@ -722,6 +737,7 @@
|
|||
0C391ECB28CAA44B009F1CA1 /* AlertButton.swift in Sources */,
|
||||
0C7C128628DAA3CD00381CD1 /* URL.swift in Sources */,
|
||||
0C84F4852895BFED0074B7C9 /* SourceHtmlParser+CoreDataProperties.swift in Sources */,
|
||||
0C2886D22960AC2800D6FC16 /* DebridCloudView.swift in Sources */,
|
||||
0CA148EB288903F000DE2211 /* SearchResultsView.swift in Sources */,
|
||||
0CA148D7288903F000DE2211 /* LoginWebView.swift in Sources */,
|
||||
0CA148E0288903F000DE2211 /* FerriteApp.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -358,4 +358,11 @@ public class RealDebrid {
|
|||
|
||||
return rawResponse
|
||||
}
|
||||
|
||||
public func deleteDownload(debridID: String) async throws {
|
||||
var request = URLRequest(url: URL(string: "\(baseApiUrl)/downloads/delete/\(debridID)")!)
|
||||
request.httpMethod = "DELETE"
|
||||
|
||||
try await performRequest(request: &request, requestName: #function)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,9 +112,11 @@ struct PersistenceController {
|
|||
newBookmark.magnetLink = bookmarkJson.magnetLink
|
||||
newBookmark.seeders = bookmarkJson.seeders
|
||||
newBookmark.leechers = bookmarkJson.leechers
|
||||
|
||||
save(backgroundContext)
|
||||
}
|
||||
|
||||
func createHistory(entryJson: HistoryEntryJson, date: Double?) {
|
||||
func createHistory(_ entryJson: HistoryEntryJson, date: Double? = nil) {
|
||||
let historyDate = date.map { Date(timeIntervalSince1970: $0) } ?? Date()
|
||||
let historyDateString = DateFormatter.historyDateFormatter.string(from: historyDate)
|
||||
|
||||
|
|
@ -153,6 +155,8 @@ struct PersistenceController {
|
|||
|
||||
newHistoryEntry.parentHistory?.dateString = historyDateString
|
||||
newHistoryEntry.parentHistory?.date = historyDate
|
||||
|
||||
save(backgroundContext)
|
||||
}
|
||||
|
||||
func getHistoryPredicate(range: HistoryDeleteRange) -> NSPredicate? {
|
||||
|
|
|
|||
|
|
@ -4,12 +4,21 @@
|
|||
//
|
||||
// Created by Brian Dashore on 8/31/22.
|
||||
//
|
||||
// From https://stackoverflow.com/a/59307884
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
// From https://www.hackingwithswift.com/example-code/strings/how-to-capitalize-the-first-letter-of-a-string
|
||||
func capitalizingFirstLetter() -> String {
|
||||
return prefix(1).capitalized + dropFirst()
|
||||
}
|
||||
|
||||
mutating func capitalizeFirstLetter() {
|
||||
self = self.capitalizingFirstLetter()
|
||||
}
|
||||
|
||||
// From https://stackoverflow.com/a/59307884
|
||||
private func compare(toVersion targetVersion: String) -> ComparisonResult {
|
||||
let versionDelimiter = "."
|
||||
var result: ComparisonResult = .orderedSame
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@ struct HistoryJson: Codable {
|
|||
}
|
||||
|
||||
struct HistoryEntryJson: Codable {
|
||||
let name: String
|
||||
let subName: String?
|
||||
let url: String
|
||||
let timeStamp: Double?
|
||||
var name: String? = nil
|
||||
var subName: String? = nil
|
||||
var url: String? = nil
|
||||
var timeStamp: Double? = nil
|
||||
let source: String?
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,6 @@ public enum DebridType: Int, Codable, Hashable, CaseIterable {
|
|||
|
||||
// Wrapper struct for magnet links to contain both the link and hash for easy access
|
||||
public struct Magnet: Codable, Hashable, Sendable {
|
||||
let link: String
|
||||
let link: String?
|
||||
let hash: String
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ public extension RealDebrid {
|
|||
let bytes, selected: Int
|
||||
}
|
||||
|
||||
struct UserTorrentsResponse: Codable, Sendable {
|
||||
struct UserTorrentsResponse: Codable, Hashable, Sendable {
|
||||
let id, filename, hash: String
|
||||
let bytes: Int
|
||||
let host: String
|
||||
|
|
@ -183,7 +183,7 @@ public extension RealDebrid {
|
|||
|
||||
// MARK: - User downloads list
|
||||
|
||||
struct UserDownloadsResponse: Codable, Sendable {
|
||||
struct UserDownloadsResponse: Codable, Hashable, Sendable {
|
||||
let id, filename: String
|
||||
let mimeType: String?
|
||||
let filesize: Int
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ public class BackupManager: ObservableObject {
|
|||
if let storedHistories = backup.history {
|
||||
for storedHistory in storedHistories {
|
||||
for storedEntry in storedHistory.entries {
|
||||
PersistenceController.shared.createHistory(entryJson: storedEntry, date: storedHistory.date)
|
||||
PersistenceController.shared.createHistory(storedEntry, date: storedHistory.date)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,11 @@ public class DebridManager: ObservableObject {
|
|||
var selectedRealDebridFile: RealDebrid.IAFile?
|
||||
var selectedRealDebridID: String?
|
||||
|
||||
// RealDebrid cloud variables
|
||||
@Published var realDebridCloudTorrents: [RealDebrid.UserTorrentsResponse] = []
|
||||
@Published var realDebridCloudDownloads: [RealDebrid.UserDownloadsResponse] = []
|
||||
var realDebridCloudTTL: Double = 0.0
|
||||
|
||||
// AllDebrid auth variables
|
||||
@Published var allDebridAuthProcessing: Bool = false
|
||||
|
||||
|
|
@ -107,6 +112,30 @@ public class DebridManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
// Cleans all cached IA values in the event of a full IA refresh
|
||||
public func clearIAValues() {
|
||||
realDebridIAValues = []
|
||||
allDebridIAValues = []
|
||||
premiumizeIAValues = []
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// Common function to populate hashes for debrid services
|
||||
public func populateDebridIA(_ resultMagnets: [Magnet]) async {
|
||||
do {
|
||||
|
|
@ -153,7 +182,16 @@ public class DebridManager: ObservableObject {
|
|||
}
|
||||
|
||||
if enabledDebrids.contains(.premiumize) {
|
||||
let availableMagnets = try await premiumize.divideCacheRequests(magnets: sendMagnets)
|
||||
// Only strip magnets that don't have an associated link for PM
|
||||
let strippedResultMagnets: [Magnet] = resultMagnets.compactMap {
|
||||
if let magnetLink = $0.link {
|
||||
return Magnet(link: magnetLink, hash: $0.hash)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
let availableMagnets = try await premiumize.divideCacheRequests(magnets: strippedResultMagnets)
|
||||
|
||||
// Split DDL requests into chunks of 10
|
||||
for chunk in availableMagnets.chunked(into: 10) {
|
||||
|
|
@ -174,15 +212,15 @@ public class DebridManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
// Common function to match search results with a provided debrid service
|
||||
public func matchSearchResult(result: SearchResult?) -> IAStatus {
|
||||
guard let result else {
|
||||
// Common function to match a magnet hash with a provided debrid service
|
||||
public func matchMagnetHash(_ magnetHash: String?) -> IAStatus {
|
||||
guard let magnetHash else {
|
||||
return .none
|
||||
}
|
||||
|
||||
switch selectedDebridType {
|
||||
case .realDebrid:
|
||||
guard let realDebridMatch = realDebridIAValues.first(where: { result.magnetHash == $0.hash }) else {
|
||||
guard let realDebridMatch = realDebridIAValues.first(where: { magnetHash == $0.hash }) else {
|
||||
return .none
|
||||
}
|
||||
|
||||
|
|
@ -192,7 +230,7 @@ public class DebridManager: ObservableObject {
|
|||
return .partial
|
||||
}
|
||||
case .allDebrid:
|
||||
guard let allDebridMatch = allDebridIAValues.first(where: { result.magnetHash == $0.hash }) else {
|
||||
guard let allDebridMatch = allDebridIAValues.first(where: { magnetHash == $0.hash }) else {
|
||||
return .none
|
||||
}
|
||||
|
||||
|
|
@ -202,7 +240,7 @@ public class DebridManager: ObservableObject {
|
|||
return .full
|
||||
}
|
||||
case .premiumize:
|
||||
guard let premiumizeMatch = premiumizeIAValues.first(where: { result.magnetHash == $0.hash }) else {
|
||||
guard let premiumizeMatch = premiumizeIAValues.first(where: { magnetHash == $0.hash }) else {
|
||||
return .none
|
||||
}
|
||||
|
||||
|
|
@ -216,8 +254,8 @@ public class DebridManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
public func selectDebridResult(result: SearchResult) -> Bool {
|
||||
guard let magnetHash = result.magnetHash else {
|
||||
public func selectDebridResult(magnetHash: String?) -> Bool {
|
||||
guard let magnetHash = magnetHash else {
|
||||
toastModel?.updateToastDescription("Could not find the torrent magnet hash")
|
||||
return false
|
||||
}
|
||||
|
|
@ -429,7 +467,7 @@ public class DebridManager: ObservableObject {
|
|||
// MARK: - Debrid fetch UI linked functions
|
||||
|
||||
// Common function to delegate what debrid service to fetch from
|
||||
public func fetchDebridDownload(searchResult: SearchResult) async {
|
||||
public func fetchDebridDownload(magnetLink: String?) async {
|
||||
defer {
|
||||
currentDebridTask = nil
|
||||
showLoadingProgress = false
|
||||
|
|
@ -437,21 +475,11 @@ public class DebridManager: ObservableObject {
|
|||
|
||||
showLoadingProgress = true
|
||||
|
||||
// Premiumize doesn't need a magnet link
|
||||
guard searchResult.magnetLink != nil || selectedDebridType == .premiumize else {
|
||||
toastModel?.updateToastDescription("Could not run your action because the magnet link is invalid.")
|
||||
print("Debrid error: Invalid magnet link")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Force unwrap is OK for debrid types that aren't ignored since the magnet link was already checked
|
||||
// Do not force unwrap for Premiumize!
|
||||
switch selectedDebridType {
|
||||
case .realDebrid:
|
||||
await fetchRdDownload(magnetLink: searchResult.magnetLink!)
|
||||
await fetchRdDownload(magnetLink: magnetLink)
|
||||
case .allDebrid:
|
||||
await fetchAdDownload(magnetLink: searchResult.magnetLink!)
|
||||
await fetchAdDownload(magnetLink: magnetLink)
|
||||
case .premiumize:
|
||||
fetchPmDownload()
|
||||
case .none:
|
||||
|
|
@ -459,38 +487,32 @@ public class DebridManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func fetchRdDownload(magnetLink: String) async {
|
||||
func fetchRdDownload(magnetLink: String?) async {
|
||||
do {
|
||||
var fileIds: [Int] = []
|
||||
|
||||
if let iaFile = selectedRealDebridFile {
|
||||
guard let iaBatchFromFile = selectedRealDebridItem?.batches[safe: iaFile.batchIndex] else {
|
||||
return
|
||||
}
|
||||
|
||||
fileIds = iaBatchFromFile.files.map(\.id)
|
||||
}
|
||||
// Bypass the TTL since a download needs to be queried
|
||||
await fetchRdCloud(bypassTTL: true)
|
||||
|
||||
// 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 }
|
||||
let existingTorrents = realDebridCloudTorrents.filter { $0.hash == selectedRealDebridItem?.hash && $0.status == "downloaded" }
|
||||
|
||||
// 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 {
|
||||
downloadUrl = existingLink
|
||||
} else {
|
||||
let downloadLink = try await realDebrid.unrestrictLink(debridDownloadLink: torrentLink)
|
||||
|
||||
downloadUrl = downloadLink
|
||||
}
|
||||
|
||||
} else {
|
||||
try await checkRdUserDownloads(userTorrentLink: torrentLink)
|
||||
} else if let magnetLink = magnetLink {
|
||||
// Add a magnet after all the cache checks fail
|
||||
selectedRealDebridID = try await realDebrid.addMagnet(magnetLink: magnetLink)
|
||||
|
||||
var fileIds: [Int] = []
|
||||
if let iaFile = selectedRealDebridFile {
|
||||
guard let iaBatchFromFile = selectedRealDebridItem?.batches[safe: iaFile.batchIndex] else {
|
||||
return
|
||||
}
|
||||
|
||||
fileIds = iaBatchFromFile.files.map(\.id)
|
||||
}
|
||||
|
||||
if let realDebridId = selectedRealDebridID {
|
||||
try await realDebrid.selectFiles(debridID: realDebridId, fileIds: fileIds)
|
||||
|
||||
|
|
@ -504,6 +526,9 @@ public class DebridManager: ObservableObject {
|
|||
} else {
|
||||
toastModel?.updateToastDescription("Could not cache this torrent. Aborting.")
|
||||
}
|
||||
} else {
|
||||
toastModel?.updateToastDescription("Could not fetch your file from RealDebrid's cache or API")
|
||||
print("RealDebrid error: No magnet link or cached file found")
|
||||
}
|
||||
} catch {
|
||||
switch error {
|
||||
|
|
@ -528,6 +553,21 @@ public class DebridManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
// Refreshes torrents and downloads from a RD user's account
|
||||
public func fetchRdCloud(bypassTTL: Bool = false) async {
|
||||
if bypassTTL || Date().timeIntervalSince1970 > realDebridCloudTTL {
|
||||
do {
|
||||
realDebridCloudTorrents = try await realDebrid.userTorrents()
|
||||
realDebridCloudDownloads = try await realDebrid.userDownloads()
|
||||
|
||||
// 5 minutes
|
||||
realDebridCloudTTL = Date().timeIntervalSince1970 + 300
|
||||
} catch {
|
||||
toastModel?.updateToastDescription("RealDebrid cloud fetch error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func deleteRdTorrent() async {
|
||||
if let realDebridId = selectedRealDebridID {
|
||||
try? await realDebrid.deleteTorrent(debridID: realDebridId)
|
||||
|
|
@ -536,7 +576,25 @@ public class DebridManager: ObservableObject {
|
|||
selectedRealDebridID = nil
|
||||
}
|
||||
|
||||
func fetchAdDownload(magnetLink: String) async {
|
||||
func checkRdUserDownloads(userTorrentLink: String) async throws {
|
||||
let existingLinks = realDebridCloudDownloads.filter { $0.link == userTorrentLink }
|
||||
if let existingLink = existingLinks[safe: 0]?.download {
|
||||
downloadUrl = existingLink
|
||||
} else {
|
||||
let downloadLink = try await realDebrid.unrestrictLink(debridDownloadLink: userTorrentLink)
|
||||
|
||||
downloadUrl = downloadLink
|
||||
}
|
||||
}
|
||||
|
||||
func fetchAdDownload(magnetLink: String?) async {
|
||||
guard let magnetLink = magnetLink else {
|
||||
toastModel?.updateToastDescription("Could not run your action because the magnet link is invalid.")
|
||||
print("AllDebrid error: Invalid magnet link")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let magnetID = try await allDebrid.addMagnet(magnetLink: magnetLink)
|
||||
let lockedLink = try await allDebrid.fetchMagnetStatus(
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ class NavigationViewModel: ObservableObject {
|
|||
@Published var isSearching: Bool = false
|
||||
|
||||
@Published var selectedSearchResult: SearchResult?
|
||||
@Published var selectedMagnetLink: String?
|
||||
@Published var selectedHistoryInfo: HistoryEntryJson?
|
||||
@Published var resultFromCloud: Bool = false
|
||||
|
||||
// For giving information in magnet choice sheet
|
||||
@Published var selectedTitle: String = ""
|
||||
|
|
@ -124,6 +127,7 @@ class NavigationViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
public func addToHistory(name: String?, source: String?, url: String?, subName: String? = nil) {
|
||||
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||
|
||||
|
|
@ -141,4 +145,5 @@ class NavigationViewModel: ObservableObject {
|
|||
|
||||
PersistenceController.shared.save(backgroundContext)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,27 +10,36 @@ import SwiftUI
|
|||
struct DebridLabelView: View {
|
||||
@EnvironmentObject var debridManager: DebridManager
|
||||
|
||||
var result: SearchResult
|
||||
|
||||
let debridAbbreviation: String
|
||||
@State var cloudLinks: [String] = []
|
||||
var magnetHash: String?
|
||||
|
||||
var body: some View {
|
||||
Text(debridAbbreviation)
|
||||
.fontWeight(.bold)
|
||||
.padding(2)
|
||||
.background {
|
||||
Group {
|
||||
switch debridManager.matchSearchResult(result: result) {
|
||||
case .full:
|
||||
Color.green
|
||||
case .partial:
|
||||
Color.orange
|
||||
case .none:
|
||||
Color.red
|
||||
if let selectedDebridType = debridManager.selectedDebridType {
|
||||
Text(selectedDebridType.toString(abbreviated: true))
|
||||
.fontWeight(.bold)
|
||||
.padding(2)
|
||||
.background {
|
||||
Group {
|
||||
if cloudLinks.isEmpty {
|
||||
switch debridManager.matchMagnetHash(magnetHash) {
|
||||
case .full:
|
||||
Color.green
|
||||
case .partial:
|
||||
Color.orange
|
||||
case .none:
|
||||
Color.red
|
||||
}
|
||||
} else if cloudLinks.count == 1 {
|
||||
Color.green
|
||||
} else if cloudLinks.count > 1 {
|
||||
Color.orange
|
||||
} else {
|
||||
Color.red
|
||||
}
|
||||
}
|
||||
.cornerRadius(4)
|
||||
.opacity(0.5)
|
||||
}
|
||||
.cornerRadius(4)
|
||||
.opacity(0.5)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ struct BookmarksView: View {
|
|||
if let bookmark = bookmarks[safe: index] {
|
||||
PersistenceController.shared.delete(bookmark, context: backgroundContext)
|
||||
|
||||
NotificationCenter.default.post(name: .didDeleteBookmark, object: nil)
|
||||
NotificationCenter.default.post(name: .didDeleteBookmark, object: bookmark)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -55,8 +55,8 @@ struct BookmarksView: View {
|
|||
if debridManager.enabledDebrids.count > 0 {
|
||||
viewTask = Task {
|
||||
let magnets = bookmarks.compactMap {
|
||||
if let magnetLink = $0.magnetLink, let magnetHash = $0.magnetHash {
|
||||
return Magnet(link: magnetLink, hash: magnetHash)
|
||||
if let magnetHash = $0.magnetHash {
|
||||
return Magnet(link: $0.magnetLink, hash: magnetHash)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,143 @@
|
|||
//
|
||||
// RealDebridCloudView.swift
|
||||
// Ferrite
|
||||
//
|
||||
// Created by Brian Dashore on 12/31/22.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct RealDebridCloudView: View {
|
||||
@EnvironmentObject var navModel: NavigationViewModel
|
||||
@EnvironmentObject var debridManager: DebridManager
|
||||
|
||||
@State private var viewTask: Task<Void, Never>?
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
DisclosureGroup("Downloads") {
|
||||
ForEach(debridManager.realDebridCloudDownloads, id: \.self) { downloadResponse in
|
||||
Button(downloadResponse.filename) {
|
||||
navModel.resultFromCloud = true
|
||||
navModel.selectedTitle = downloadResponse.filename
|
||||
debridManager.downloadUrl = downloadResponse.link
|
||||
|
||||
PersistenceController.shared.createHistory(
|
||||
HistoryEntryJson(
|
||||
name: downloadResponse.filename,
|
||||
url: downloadResponse.link,
|
||||
source: DebridType.realDebrid.toString()
|
||||
)
|
||||
)
|
||||
|
||||
navModel.runDebridAction(urlString: debridManager.downloadUrl)
|
||||
}
|
||||
.backport.tint(.primary)
|
||||
}
|
||||
.onDelete { offsets in
|
||||
for index in offsets {
|
||||
if let downloadResponse = debridManager.realDebridCloudDownloads[safe: index] {
|
||||
Task {
|
||||
do {
|
||||
try await debridManager.realDebrid.deleteDownload(debridID: downloadResponse.id)
|
||||
|
||||
// Bypass TTL to get current RD values
|
||||
await debridManager.fetchRdCloud(bypassTTL: true)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DisclosureGroup("Torrents") {
|
||||
ForEach(debridManager.realDebridCloudTorrents, id: \.self) { torrentResponse in
|
||||
Button {
|
||||
Task {
|
||||
if torrentResponse.status == "downloaded" && !torrentResponse.links.isEmpty {
|
||||
navModel.resultFromCloud = true
|
||||
navModel.selectedTitle = torrentResponse.filename
|
||||
|
||||
var historyInfo = HistoryEntryJson(
|
||||
name: torrentResponse.filename,
|
||||
source: DebridType.realDebrid.toString()
|
||||
)
|
||||
|
||||
if torrentResponse.links.count == 1 {
|
||||
if let downloadLink = torrentResponse.links[safe: 0] {
|
||||
do {
|
||||
try await debridManager.checkRdUserDownloads(userTorrentLink: downloadLink)
|
||||
navModel.selectedTitle = torrentResponse.filename
|
||||
historyInfo.url = downloadLink
|
||||
|
||||
PersistenceController.shared.createHistory(historyInfo)
|
||||
navModel.currentChoiceSheet = .magnet
|
||||
} catch {
|
||||
debridManager.toastModel?.updateToastDescription("RealDebrid cloud fetch error: \(error)")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debridManager.clearIAValues()
|
||||
await debridManager.populateDebridIA([Magnet(link: nil, hash: torrentResponse.hash)])
|
||||
|
||||
if debridManager.selectDebridResult(magnetHash: torrentResponse.hash) {
|
||||
navModel.selectedHistoryInfo = historyInfo
|
||||
navModel.currentChoiceSheet = .batch
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
Text(torrentResponse.filename)
|
||||
.font(.callout)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.lineLimit(4)
|
||||
|
||||
HStack {
|
||||
Text(torrentResponse.status.capitalizingFirstLetter())
|
||||
Spacer()
|
||||
DebridLabelView(cloudLinks: torrentResponse.links)
|
||||
}
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
.disabledAppearance(navModel.currentChoiceSheet != nil, dimmedOpacity: 0.7, animation: .easeOut(duration: 0.2))
|
||||
.backport.tint(.primary)
|
||||
}
|
||||
.onDelete { offsets in
|
||||
for index in offsets {
|
||||
if let torrentResponse = debridManager.realDebridCloudTorrents[safe: index] {
|
||||
Task {
|
||||
do {
|
||||
try await debridManager.realDebrid.deleteTorrent(debridID: torrentResponse.id)
|
||||
|
||||
// Bypass TTL to get current RD values
|
||||
await debridManager.fetchRdCloud(bypassTTL: true)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
viewTask = Task {
|
||||
await debridManager.fetchRdCloud()
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
viewTask?.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RealDebridCloudView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
RealDebridCloudView()
|
||||
}
|
||||
}
|
||||
31
Ferrite/Views/ComponentViews/Library/DebridCloudView.swift
Normal file
31
Ferrite/Views/ComponentViews/Library/DebridCloudView.swift
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// DebridCloudView.swift
|
||||
// Ferrite
|
||||
//
|
||||
// Created by Brian Dashore on 12/31/22.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct DebridCloudView: View {
|
||||
@EnvironmentObject var debridManager: DebridManager
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
switch debridManager.selectedDebridType {
|
||||
case .realDebrid:
|
||||
RealDebridCloudView()
|
||||
case .allDebrid, .premiumize, .none:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
.inlinedList()
|
||||
.listStyle(.insetGrouped)
|
||||
}
|
||||
}
|
||||
|
||||
struct DebridCloudView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
DebridCloudView()
|
||||
}
|
||||
}
|
||||
|
|
@ -24,15 +24,23 @@ struct SearchResultButtonView: View {
|
|||
if debridManager.currentDebridTask == nil {
|
||||
navModel.selectedSearchResult = result
|
||||
navModel.selectedTitle = result.title ?? ""
|
||||
navModel.resultFromCloud = false
|
||||
|
||||
switch debridManager.matchSearchResult(result: result) {
|
||||
switch debridManager.matchMagnetHash(result.magnetHash) {
|
||||
case .full:
|
||||
if debridManager.selectDebridResult(result: result) {
|
||||
if debridManager.selectDebridResult(magnetHash: result.magnetHash) {
|
||||
debridManager.currentDebridTask = Task {
|
||||
await debridManager.fetchDebridDownload(searchResult: result)
|
||||
await debridManager.fetchDebridDownload(magnetLink: result.magnetLink)
|
||||
|
||||
if !debridManager.downloadUrl.isEmpty {
|
||||
navModel.addToHistory(name: result.title, source: result.source, url: debridManager.downloadUrl)
|
||||
PersistenceController.shared.createHistory(
|
||||
HistoryEntryJson(
|
||||
name: result.title,
|
||||
url: debridManager.downloadUrl,
|
||||
source: result.source
|
||||
)
|
||||
)
|
||||
|
||||
navModel.runDebridAction(urlString: debridManager.downloadUrl)
|
||||
|
||||
if navModel.currentChoiceSheet != .magnet {
|
||||
|
|
@ -42,11 +50,18 @@ struct SearchResultButtonView: View {
|
|||
}
|
||||
}
|
||||
case .partial:
|
||||
if debridManager.selectDebridResult(result: result) {
|
||||
if debridManager.selectDebridResult(magnetHash: result.magnetHash) {
|
||||
navModel.currentChoiceSheet = .batch
|
||||
}
|
||||
case .none:
|
||||
navModel.addToHistory(name: result.title, source: result.source, url: result.magnetLink)
|
||||
PersistenceController.shared.createHistory(
|
||||
HistoryEntryJson(
|
||||
name: result.title,
|
||||
url: result.magnetLink,
|
||||
source: result.source
|
||||
)
|
||||
)
|
||||
|
||||
navModel.runMagnetAction(magnetString: result.magnetLink)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,17 +30,7 @@ struct SearchResultInfoView: View {
|
|||
Text(size)
|
||||
}
|
||||
|
||||
if debridManager.selectedDebridType == .realDebrid {
|
||||
DebridLabelView(result: result, debridAbbreviation: "RD")
|
||||
}
|
||||
|
||||
if debridManager.selectedDebridType == .allDebrid {
|
||||
DebridLabelView(result: result, debridAbbreviation: "AD")
|
||||
}
|
||||
|
||||
if debridManager.selectedDebridType == .premiumize {
|
||||
DebridLabelView(result: result, debridAbbreviation: "PM")
|
||||
}
|
||||
DebridLabelView(magnetHash: result.magnetHash)
|
||||
}
|
||||
.font(.caption)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,12 +87,12 @@ struct ContentView: View {
|
|||
await scrapingModel.scanSources(sources: sources)
|
||||
|
||||
if debridManager.enabledDebrids.count > 0, !scrapingModel.searchResults.isEmpty {
|
||||
debridManager.realDebridIAValues = []
|
||||
debridManager.allDebridIAValues = []
|
||||
debridManager.clearIAValues()
|
||||
|
||||
// Remove magnets that don't have a hash
|
||||
let magnets = scrapingModel.searchResults.compactMap {
|
||||
if let magnetLink = $0.magnetLink, let magnetHash = $0.magnetHash {
|
||||
return Magnet(link: magnetLink, hash: magnetHash)
|
||||
if let magnetHash = $0.magnetHash {
|
||||
return Magnet(link: $0.magnetLink, hash: magnetHash)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,9 +11,11 @@ struct LibraryView: View {
|
|||
enum LibraryPickerSegment {
|
||||
case bookmarks
|
||||
case history
|
||||
case debridCloud
|
||||
}
|
||||
|
||||
@EnvironmentObject var navModel: NavigationViewModel
|
||||
@EnvironmentObject var debridManager: DebridManager
|
||||
|
||||
@FetchRequest(
|
||||
entity: Bookmark.entity(),
|
||||
|
|
@ -40,6 +42,10 @@ struct LibraryView: View {
|
|||
Picker("Segments", selection: $selectedSegment) {
|
||||
Text("Bookmarks").tag(LibraryPickerSegment.bookmarks)
|
||||
Text("History").tag(LibraryPickerSegment.history)
|
||||
|
||||
if !debridManager.enabledDebrids.isEmpty {
|
||||
Text("Cloud").tag(LibraryPickerSegment.debridCloud)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
.padding()
|
||||
|
|
@ -49,6 +55,8 @@ struct LibraryView: View {
|
|||
BookmarksView(bookmarks: bookmarks)
|
||||
case .history:
|
||||
HistoryView(history: history)
|
||||
case .debridCloud:
|
||||
DebridCloudView()
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
|
@ -63,6 +71,10 @@ struct LibraryView: View {
|
|||
if history.isEmpty {
|
||||
EmptyInstructionView(title: "No History", message: "Start watching to build history")
|
||||
}
|
||||
case .debridCloud:
|
||||
if debridManager.selectedDebridType != .realDebrid {
|
||||
EmptyInstructionView(title: "Cloud Unavailable", message: "Listing is not available for this service")
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Library")
|
||||
|
|
@ -72,7 +84,7 @@ struct LibraryView: View {
|
|||
EditButton()
|
||||
|
||||
switch selectedSegment {
|
||||
case .bookmarks:
|
||||
case .bookmarks, .debridCloud:
|
||||
DebridChoiceView()
|
||||
case .history:
|
||||
HistoryActionsView()
|
||||
|
|
|
|||
|
|
@ -58,7 +58,8 @@ struct BatchChoiceView: View {
|
|||
|
||||
Task {
|
||||
try? await Task.sleep(seconds: 1)
|
||||
debridManager.selectedRealDebridItem = nil
|
||||
|
||||
debridManager.clearSelectedDebridItems()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -68,36 +69,22 @@ struct BatchChoiceView: View {
|
|||
|
||||
// Common function to communicate betwen VMs and queue/display a download
|
||||
func queueCommonDownload(fileName: String) {
|
||||
if let searchResult = navModel.selectedSearchResult {
|
||||
debridManager.currentDebridTask = Task {
|
||||
await debridManager.fetchDebridDownload(searchResult: searchResult)
|
||||
debridManager.currentDebridTask = Task {
|
||||
await debridManager.fetchDebridDownload(magnetLink: navModel.resultFromCloud ? nil : navModel.selectedMagnetLink)
|
||||
|
||||
if !debridManager.downloadUrl.isEmpty {
|
||||
try? await Task.sleep(seconds: 1)
|
||||
navModel.selectedBatchTitle = fileName
|
||||
navModel.addToHistory(
|
||||
name: searchResult.title,
|
||||
source: searchResult.source,
|
||||
url: debridManager.downloadUrl,
|
||||
subName: fileName
|
||||
)
|
||||
navModel.runDebridAction(urlString: debridManager.downloadUrl)
|
||||
if !debridManager.downloadUrl.isEmpty {
|
||||
try? await Task.sleep(seconds: 1)
|
||||
navModel.selectedBatchTitle = fileName
|
||||
|
||||
if var selectedHistoryInfo = navModel.selectedHistoryInfo {
|
||||
selectedHistoryInfo.url = debridManager.downloadUrl
|
||||
PersistenceController.shared.createHistory(selectedHistoryInfo)
|
||||
}
|
||||
|
||||
switch debridManager.selectedDebridType {
|
||||
case .realDebrid:
|
||||
debridManager.selectedRealDebridFile = nil
|
||||
debridManager.selectedRealDebridItem = nil
|
||||
case .allDebrid:
|
||||
debridManager.selectedAllDebridFile = nil
|
||||
debridManager.selectedAllDebridItem = nil
|
||||
case .premiumize:
|
||||
debridManager.selectedPremiumizeFile = nil
|
||||
debridManager.selectedPremiumizeItem = nil
|
||||
case .none:
|
||||
break
|
||||
}
|
||||
navModel.runDebridAction(urlString: debridManager.downloadUrl)
|
||||
}
|
||||
|
||||
debridManager.clearSelectedDebridItems()
|
||||
}
|
||||
|
||||
navModel.currentChoiceSheet = nil
|
||||
|
|
|
|||
|
|
@ -71,30 +71,31 @@ struct MagnetChoiceView: View {
|
|||
}
|
||||
}
|
||||
|
||||
Section(header: "Magnet options") {
|
||||
ListRowButtonView("Copy magnet", systemImage: "doc.on.doc.fill") {
|
||||
UIPasteboard.general.string = navModel.selectedSearchResult?.magnetLink
|
||||
showMagnetCopyAlert.toggle()
|
||||
}
|
||||
.backport.alert(
|
||||
isPresented: $showMagnetCopyAlert,
|
||||
title: "Copied",
|
||||
message: "Magnet link copied successfully",
|
||||
buttons: [AlertButton("OK")]
|
||||
)
|
||||
|
||||
ListRowButtonView("Share magnet", systemImage: "square.and.arrow.up.fill") {
|
||||
if let result = navModel.selectedSearchResult,
|
||||
let magnetLink = result.magnetLink,
|
||||
let url = URL(string: magnetLink)
|
||||
{
|
||||
navModel.activityItems = [url]
|
||||
navModel.showLocalActivitySheet.toggle()
|
||||
if !navModel.resultFromCloud {
|
||||
Section(header: "Magnet options") {
|
||||
ListRowButtonView("Copy magnet", systemImage: "doc.on.doc.fill") {
|
||||
UIPasteboard.general.string = navModel.selectedMagnetLink
|
||||
showMagnetCopyAlert.toggle()
|
||||
}
|
||||
}
|
||||
.backport.alert(
|
||||
isPresented: $showMagnetCopyAlert,
|
||||
title: "Copied",
|
||||
message: "Magnet link copied successfully",
|
||||
buttons: [AlertButton("OK")]
|
||||
)
|
||||
|
||||
ListRowButtonView("Open in WebTor", systemImage: "arrow.up.forward.app.fill") {
|
||||
navModel.runMagnetAction(magnetString: navModel.selectedSearchResult?.magnetLink, .webtor)
|
||||
ListRowButtonView("Share magnet", systemImage: "square.and.arrow.up.fill") {
|
||||
if let magnetLink = navModel.selectedMagnetLink,
|
||||
let url = URL(string: magnetLink)
|
||||
{
|
||||
navModel.activityItems = [url]
|
||||
navModel.showLocalActivitySheet.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
ListRowButtonView("Open in WebTor", systemImage: "arrow.up.forward.app.fill") {
|
||||
navModel.runMagnetAction(magnetString: navModel.selectedMagnetLink, .webtor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -111,6 +112,7 @@ struct MagnetChoiceView: View {
|
|||
debridManager.downloadUrl = ""
|
||||
navModel.selectedTitle = ""
|
||||
navModel.selectedBatchTitle = ""
|
||||
navModel.resultFromCloud = false
|
||||
}
|
||||
.navigationTitle("Link actions")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
|
|
|
|||
Loading…
Reference in a new issue