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:
kingbri 2023-01-02 11:29:30 -05:00
parent b0850d43d7
commit 9b7bc55a25
20 changed files with 434 additions and 146 deletions

View file

@ -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 */,

View file

@ -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)
}
}

View file

@ -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? {

View file

@ -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

View file

@ -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?
}

View file

@ -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
}

View file

@ -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

View file

@ -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)
}
}
}

View file

@ -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(

View file

@ -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)
}
*/
}

View file

@ -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)
}
}
}
}

View file

@ -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
}

View file

@ -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()
}
}

View 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()
}
}

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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()

View file

@ -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

View file

@ -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)