diff --git a/Ferrite.xcodeproj/project.pbxproj b/Ferrite.xcodeproj/project.pbxproj index 601c669..4afcce9 100644 --- a/Ferrite.xcodeproj/project.pbxproj +++ b/Ferrite.xcodeproj/project.pbxproj @@ -7,16 +7,26 @@ objects = { /* Begin PBXBuildFile section */ - 0C0D50E1288DF7700035ECC8 /* TorrentSource+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D50DF288DF7700035ECC8 /* TorrentSource+CoreDataClass.swift */; }; - 0C0D50E2288DF7700035ECC8 /* TorrentSource+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D50E0288DF7700035ECC8 /* TorrentSource+CoreDataProperties.swift */; }; 0C0D50E5288DFE7F0035ECC8 /* SourceModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D50E4288DFE7F0035ECC8 /* SourceModels.swift */; }; 0C0D50E7288DFF850035ECC8 /* SourceListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D50E6288DFF850035ECC8 /* SourceListView.swift */; }; 0C32FB532890D19D002BD219 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C32FB522890D19D002BD219 /* AboutView.swift */; }; 0C32FB552890D1BF002BD219 /* UIApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C32FB542890D1BF002BD219 /* UIApplication.swift */; }; 0C32FB572890D1F2002BD219 /* ListRowViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C32FB562890D1F2002BD219 /* ListRowViews.swift */; }; + 0C4CFC462897030D00AD9FAD /* Regex in Frameworks */ = {isa = PBXBuildFile; productRef = 0C4CFC452897030D00AD9FAD /* Regex */; }; + 0C4CFC4D28970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C4CFC4728970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift */; }; + 0C4CFC4E28970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C4CFC4828970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift */; }; 0C57D4CC289032ED008534E8 /* SearchResultRDView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C57D4CB289032ED008534E8 /* SearchResultRDView.swift */; }; 0C64A4B4288903680079976D /* Base32 in Frameworks */ = {isa = PBXBuildFile; productRef = 0C64A4B3288903680079976D /* Base32 */; }; 0C64A4B7288903880079976D /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 0C64A4B6288903880079976D /* KeychainSwift */; }; + 0C79DC072899AF3C003F1C5A /* SourceSeedLeech+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C79DC052899AF3C003F1C5A /* SourceSeedLeech+CoreDataClass.swift */; }; + 0C79DC082899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C79DC062899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift */; }; + 0C84F4772895BE680074B7C9 /* FerriteDB.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 0C84F4752895BE680074B7C9 /* FerriteDB.xcdatamodeld */; }; + 0C84F4822895BFED0074B7C9 /* Source+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84F47A2895BFED0074B7C9 /* Source+CoreDataClass.swift */; }; + 0C84F4832895BFED0074B7C9 /* Source+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84F47B2895BFED0074B7C9 /* Source+CoreDataProperties.swift */; }; + 0C84F4842895BFED0074B7C9 /* SourceHtmlParser+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84F47C2895BFED0074B7C9 /* SourceHtmlParser+CoreDataClass.swift */; }; + 0C84F4852895BFED0074B7C9 /* SourceHtmlParser+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84F47D2895BFED0074B7C9 /* SourceHtmlParser+CoreDataProperties.swift */; }; + 0C84F4862895BFED0074B7C9 /* SourceList+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84F47E2895BFED0074B7C9 /* SourceList+CoreDataClass.swift */; }; + 0C84F4872895BFED0074B7C9 /* SourceList+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C84F47F2895BFED0074B7C9 /* SourceList+CoreDataProperties.swift */; }; 0C90E32C2888E5D000C0BC89 /* ActivityView in Frameworks */ = {isa = PBXBuildFile; productRef = 0C90E32B2888E5D000C0BC89 /* ActivityView */; }; 0CA05457288EE58200850554 /* SettingsSourceUrlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA05456288EE58200850554 /* SettingsSourceUrlView.swift */; }; 0CA05459288EE9E600850554 /* SourceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA05458288EE9E600850554 /* SourceManager.swift */; }; @@ -44,22 +54,28 @@ 0CAF1C7B286F5C8600296F86 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = 0CAF1C7A286F5C8600296F86 /* SwiftSoup */; }; 0CBC76FD288D914F0054BE44 /* BatchChoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CBC76FC288D914F0054BE44 /* BatchChoiceView.swift */; }; 0CBC76FF288DAAD00054BE44 /* NavigationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CBC76FE288DAAD00054BE44 /* NavigationViewModel.swift */; }; - 0CBC7702288DE4400054BE44 /* FerriteDB.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 0CBC7700288DE4400054BE44 /* FerriteDB.xcdatamodeld */; }; 0CBC7705288DE7F40054BE44 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CBC7704288DE7F40054BE44 /* PersistenceController.swift */; }; - 0CE37ABA288E4AE900428C2D /* TorrentSourceUrl+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE37AB8288E4AE900428C2D /* TorrentSourceUrl+CoreDataClass.swift */; }; - 0CE37ABB288E4AE900428C2D /* TorrentSourceUrl+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE37AB9288E4AE900428C2D /* TorrentSourceUrl+CoreDataProperties.swift */; }; 0CFEFCFD288A006200B3F490 /* GroupBoxStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CFEFCFC288A006200B3F490 /* GroupBoxStyle.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 0C0D50DF288DF7700035ECC8 /* TorrentSource+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TorrentSource+CoreDataClass.swift"; sourceTree = ""; }; - 0C0D50E0288DF7700035ECC8 /* TorrentSource+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TorrentSource+CoreDataProperties.swift"; sourceTree = ""; }; 0C0D50E4288DFE7F0035ECC8 /* SourceModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceModels.swift; sourceTree = ""; }; 0C0D50E6288DFF850035ECC8 /* SourceListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceListView.swift; sourceTree = ""; }; 0C32FB522890D19D002BD219 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; 0C32FB542890D1BF002BD219 /* UIApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIApplication.swift; sourceTree = ""; }; 0C32FB562890D1F2002BD219 /* ListRowViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRowViews.swift; sourceTree = ""; }; + 0C4CFC4728970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceComplexQuery+CoreDataClass.swift"; sourceTree = ""; }; + 0C4CFC4828970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceComplexQuery+CoreDataProperties.swift"; sourceTree = ""; }; 0C57D4CB289032ED008534E8 /* SearchResultRDView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultRDView.swift; sourceTree = ""; }; + 0C79DC052899AF3C003F1C5A /* SourceSeedLeech+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceSeedLeech+CoreDataClass.swift"; sourceTree = ""; }; + 0C79DC062899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceSeedLeech+CoreDataProperties.swift"; sourceTree = ""; }; + 0C84F4762895BE680074B7C9 /* FerriteDB.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = FerriteDB.xcdatamodel; sourceTree = ""; }; + 0C84F47A2895BFED0074B7C9 /* Source+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Source+CoreDataClass.swift"; sourceTree = ""; }; + 0C84F47B2895BFED0074B7C9 /* Source+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Source+CoreDataProperties.swift"; sourceTree = ""; }; + 0C84F47C2895BFED0074B7C9 /* SourceHtmlParser+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceHtmlParser+CoreDataClass.swift"; sourceTree = ""; }; + 0C84F47D2895BFED0074B7C9 /* SourceHtmlParser+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceHtmlParser+CoreDataProperties.swift"; sourceTree = ""; }; + 0C84F47E2895BFED0074B7C9 /* SourceList+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceList+CoreDataClass.swift"; sourceTree = ""; }; + 0C84F47F2895BFED0074B7C9 /* SourceList+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceList+CoreDataProperties.swift"; sourceTree = ""; }; 0CA05456288EE58200850554 /* SettingsSourceUrlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSourceUrlView.swift; sourceTree = ""; }; 0CA05458288EE9E600850554 /* SourceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceManager.swift; sourceTree = ""; }; 0CA0545A288EEA4E00850554 /* SourceListEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceListEditorView.swift; sourceTree = ""; }; @@ -86,10 +102,7 @@ 0CAF1C68286F5C0E00296F86 /* Ferrite.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ferrite.app; sourceTree = BUILT_PRODUCTS_DIR; }; 0CBC76FC288D914F0054BE44 /* BatchChoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchChoiceView.swift; sourceTree = ""; }; 0CBC76FE288DAAD00054BE44 /* NavigationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationViewModel.swift; sourceTree = ""; }; - 0CBC7701288DE4400054BE44 /* FerriteDB.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = FerriteDB.xcdatamodel; sourceTree = ""; }; 0CBC7704288DE7F40054BE44 /* PersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceController.swift; sourceTree = ""; }; - 0CE37AB8288E4AE900428C2D /* TorrentSourceUrl+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TorrentSourceUrl+CoreDataClass.swift"; sourceTree = ""; }; - 0CE37AB9288E4AE900428C2D /* TorrentSourceUrl+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TorrentSourceUrl+CoreDataProperties.swift"; sourceTree = ""; }; 0CFEFCFC288A006200B3F490 /* GroupBoxStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupBoxStyle.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -100,6 +113,7 @@ files = ( 0C90E32C2888E5D000C0BC89 /* ActivityView in Frameworks */, 0C64A4B4288903680079976D /* Base32 in Frameworks */, + 0C4CFC462897030D00AD9FAD /* Regex in Frameworks */, 0C64A4B7288903880079976D /* KeychainSwift in Frameworks */, 0CAF1C7B286F5C8600296F86 /* SwiftSoup in Frameworks */, ); @@ -111,10 +125,16 @@ 0C0D50DE288DF72D0035ECC8 /* Classes */ = { isa = PBXGroup; children = ( - 0CE37AB8288E4AE900428C2D /* TorrentSourceUrl+CoreDataClass.swift */, - 0CE37AB9288E4AE900428C2D /* TorrentSourceUrl+CoreDataProperties.swift */, - 0C0D50DF288DF7700035ECC8 /* TorrentSource+CoreDataClass.swift */, - 0C0D50E0288DF7700035ECC8 /* TorrentSource+CoreDataProperties.swift */, + 0C79DC052899AF3C003F1C5A /* SourceSeedLeech+CoreDataClass.swift */, + 0C79DC062899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift */, + 0C4CFC4728970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift */, + 0C4CFC4828970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift */, + 0C84F47A2895BFED0074B7C9 /* Source+CoreDataClass.swift */, + 0C84F47B2895BFED0074B7C9 /* Source+CoreDataProperties.swift */, + 0C84F47C2895BFED0074B7C9 /* SourceHtmlParser+CoreDataClass.swift */, + 0C84F47D2895BFED0074B7C9 /* SourceHtmlParser+CoreDataProperties.swift */, + 0C84F47E2895BFED0074B7C9 /* SourceList+CoreDataClass.swift */, + 0C84F47F2895BFED0074B7C9 /* SourceList+CoreDataProperties.swift */, ); path = Classes; sourceTree = ""; @@ -252,7 +272,7 @@ children = ( 0C0D50DE288DF72D0035ECC8 /* Classes */, 0CBC7704288DE7F40054BE44 /* PersistenceController.swift */, - 0CBC7700288DE4400054BE44 /* FerriteDB.xcdatamodeld */, + 0C84F4752895BE680074B7C9 /* FerriteDB.xcdatamodeld */, ); path = DataManagement; sourceTree = ""; @@ -278,6 +298,7 @@ 0C90E32B2888E5D000C0BC89 /* ActivityView */, 0C64A4B3288903680079976D /* Base32 */, 0C64A4B6288903880079976D /* KeychainSwift */, + 0C4CFC452897030D00AD9FAD /* Regex */, ); productName = Torrenter; productReference = 0CAF1C68286F5C0E00296F86 /* Ferrite.app */; @@ -312,6 +333,7 @@ 0C90E32A2888E5D000C0BC89 /* XCRemoteSwiftPackageReference "ActivityView" */, 0C64A4B2288903680079976D /* XCRemoteSwiftPackageReference "Base32" */, 0C64A4B5288903880079976D /* XCRemoteSwiftPackageReference "keychain-swift" */, + 0C4CFC442897030D00AD9FAD /* XCRemoteSwiftPackageReference "Regex" */, ); productRefGroup = 0CAF1C69286F5C0E00296F86 /* Products */; projectDirPath = ""; @@ -339,41 +361,47 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0CE37ABB288E4AE900428C2D /* TorrentSourceUrl+CoreDataProperties.swift in Sources */, 0C0D50E5288DFE7F0035ECC8 /* SourceModels.swift in Sources */, 0C32FB532890D19D002BD219 /* AboutView.swift in Sources */, + 0C84F4832895BFED0074B7C9 /* Source+CoreDataProperties.swift in Sources */, 0CA148DB288903F000DE2211 /* NavView.swift in Sources */, 0CBC7705288DE7F40054BE44 /* PersistenceController.swift in Sources */, 0CA0545B288EEA4E00850554 /* SourceListEditorView.swift in Sources */, + 0C84F4872895BFED0074B7C9 /* SourceList+CoreDataProperties.swift in Sources */, 0CA148E9288903F000DE2211 /* MainView.swift in Sources */, 0CBC76FD288D914F0054BE44 /* BatchChoiceView.swift in Sources */, 0C32FB552890D1BF002BD219 /* UIApplication.swift in Sources */, 0C0D50E7288DFF850035ECC8 /* SourceListView.swift in Sources */, 0CA148EC288903F000DE2211 /* ContentView.swift in Sources */, 0CA148E1288903F000DE2211 /* Collection.swift in Sources */, + 0C79DC082899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift in Sources */, 0CA148DD288903F000DE2211 /* ScrapingViewModel.swift in Sources */, 0CA148D8288903F000DE2211 /* MagnetChoiceView.swift in Sources */, + 0C84F4862895BFED0074B7C9 /* SourceList+CoreDataClass.swift in Sources */, 0CA148E3288903F000DE2211 /* Task.swift in Sources */, 0CA148E7288903F000DE2211 /* ToastViewModel.swift in Sources */, 0CFEFCFD288A006200B3F490 /* GroupBoxStyle.swift in Sources */, - 0CE37ABA288E4AE900428C2D /* TorrentSourceUrl+CoreDataClass.swift in Sources */, + 0C79DC072899AF3C003F1C5A /* SourceSeedLeech+CoreDataClass.swift in Sources */, 0CA148E6288903F000DE2211 /* WebView.swift in Sources */, + 0C4CFC4E28970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift in Sources */, 0CA148E2288903F000DE2211 /* Data.swift in Sources */, - 0C0D50E1288DF7700035ECC8 /* TorrentSource+CoreDataClass.swift in Sources */, 0C57D4CC289032ED008534E8 /* SearchResultRDView.swift in Sources */, 0CA05459288EE9E600850554 /* SourceManager.swift in Sources */, + 0C84F4772895BE680074B7C9 /* FerriteDB.xcdatamodeld in Sources */, 0CA05457288EE58200850554 /* SettingsSourceUrlView.swift in Sources */, 0CA148DE288903F000DE2211 /* RealDebridModels.swift in Sources */, 0CBC76FF288DAAD00054BE44 /* NavigationViewModel.swift in Sources */, + 0C4CFC4D28970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift in Sources */, 0CA148E8288903F000DE2211 /* RealDebridWrapper.swift in Sources */, 0CA148D6288903F000DE2211 /* SettingsView.swift in Sources */, 0CA148E5288903F000DE2211 /* DebridManager.swift in Sources */, - 0CBC7702288DE4400054BE44 /* FerriteDB.xcdatamodeld in Sources */, + 0C84F4842895BFED0074B7C9 /* SourceHtmlParser+CoreDataClass.swift in Sources */, 0CA148D9288903F000DE2211 /* CardView.swift in Sources */, 0C32FB572890D1F2002BD219 /* ListRowViews.swift in Sources */, + 0C84F4822895BFED0074B7C9 /* Source+CoreDataClass.swift in Sources */, + 0C84F4852895BFED0074B7C9 /* SourceHtmlParser+CoreDataProperties.swift in Sources */, 0CA148EB288903F000DE2211 /* SearchResultsView.swift in Sources */, 0CA148D7288903F000DE2211 /* LoginWebView.swift in Sources */, - 0C0D50E2288DF7700035ECC8 /* TorrentSource+CoreDataProperties.swift in Sources */, 0CA148E0288903F000DE2211 /* FerriteApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -581,6 +609,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 0C4CFC442897030D00AD9FAD /* XCRemoteSwiftPackageReference "Regex" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/sindresorhus/Regex"; + requirement = { + branch = main; + kind = branch; + }; + }; 0C64A4B2288903680079976D /* XCRemoteSwiftPackageReference "Base32" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/norio-nomura/Base32"; @@ -616,6 +652,11 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 0C4CFC452897030D00AD9FAD /* Regex */ = { + isa = XCSwiftPackageProductDependency; + package = 0C4CFC442897030D00AD9FAD /* XCRemoteSwiftPackageReference "Regex" */; + productName = Regex; + }; 0C64A4B3288903680079976D /* Base32 */ = { isa = XCSwiftPackageProductDependency; package = 0C64A4B2288903680079976D /* XCRemoteSwiftPackageReference "Base32" */; @@ -639,12 +680,12 @@ /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ - 0CBC7700288DE4400054BE44 /* FerriteDB.xcdatamodeld */ = { + 0C84F4752895BE680074B7C9 /* FerriteDB.xcdatamodeld */ = { isa = XCVersionGroup; children = ( - 0CBC7701288DE4400054BE44 /* FerriteDB.xcdatamodel */, + 0C84F4762895BE680074B7C9 /* FerriteDB.xcdatamodel */, ); - currentVersion = 0CBC7701288DE4400054BE44 /* FerriteDB.xcdatamodel */; + currentVersion = 0C84F4762895BE680074B7C9 /* FerriteDB.xcdatamodel */; path = FerriteDB.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; diff --git a/Ferrite/DataManagement/Classes/Source+CoreDataClass.swift b/Ferrite/DataManagement/Classes/Source+CoreDataClass.swift new file mode 100644 index 0000000..1196354 --- /dev/null +++ b/Ferrite/DataManagement/Classes/Source+CoreDataClass.swift @@ -0,0 +1,13 @@ +// +// Source+CoreDataClass.swift +// Ferrite +// +// Created by Brian Dashore on 7/30/22. +// +// + +import CoreData +import Foundation + +@objc(Source) +public class Source: NSManagedObject {} diff --git a/Ferrite/DataManagement/Classes/Source+CoreDataProperties.swift b/Ferrite/DataManagement/Classes/Source+CoreDataProperties.swift new file mode 100644 index 0000000..1213858 --- /dev/null +++ b/Ferrite/DataManagement/Classes/Source+CoreDataProperties.swift @@ -0,0 +1,24 @@ +// +// Source+CoreDataProperties.swift +// Ferrite +// +// Created by Brian Dashore on 7/30/22. +// +// + +import CoreData +import Foundation + +public extension Source { + @nonobjc class func fetchRequest() -> NSFetchRequest { + NSFetchRequest(entityName: "Source") + } + + @NSManaged var name: String + @NSManaged var enabled: Bool + @NSManaged var version: String + @NSManaged var baseUrl: String + @NSManaged var htmlParser: SourceHtmlParser? +} + +extension Source: Identifiable {} diff --git a/Ferrite/DataManagement/Classes/SourceComplexQuery+CoreDataClass.swift b/Ferrite/DataManagement/Classes/SourceComplexQuery+CoreDataClass.swift new file mode 100644 index 0000000..3362732 --- /dev/null +++ b/Ferrite/DataManagement/Classes/SourceComplexQuery+CoreDataClass.swift @@ -0,0 +1,13 @@ +// +// SourceComplexQuery+CoreDataClass.swift +// Ferrite +// +// Created by Brian Dashore on 7/31/22. +// +// + +import CoreData +import Foundation + +@objc(SourceComplexQuery) +public class SourceComplexQuery: NSManagedObject {} diff --git a/Ferrite/DataManagement/Classes/SourceComplexQuery+CoreDataProperties.swift b/Ferrite/DataManagement/Classes/SourceComplexQuery+CoreDataProperties.swift new file mode 100644 index 0000000..eaa9c56 --- /dev/null +++ b/Ferrite/DataManagement/Classes/SourceComplexQuery+CoreDataProperties.swift @@ -0,0 +1,22 @@ +// +// SourceComplexQuery+CoreDataProperties.swift +// Ferrite +// +// Created by Brian Dashore on 7/31/22. +// +// + +import CoreData +import Foundation + +public extension SourceComplexQuery { + @nonobjc class func fetchRequest() -> NSFetchRequest { + NSFetchRequest(entityName: "SourceComplexQuery") + } + + @NSManaged var attribute: String + @NSManaged var query: String + @NSManaged var regex: String? +} + +extension SourceComplexQuery: Identifiable {} diff --git a/Ferrite/DataManagement/Classes/SourceHtmlParser+CoreDataClass.swift b/Ferrite/DataManagement/Classes/SourceHtmlParser+CoreDataClass.swift new file mode 100644 index 0000000..ff712ba --- /dev/null +++ b/Ferrite/DataManagement/Classes/SourceHtmlParser+CoreDataClass.swift @@ -0,0 +1,13 @@ +// +// SourceHtmlParser+CoreDataClass.swift +// Ferrite +// +// Created by Brian Dashore on 7/30/22. +// +// + +import CoreData +import Foundation + +@objc(SourceHtmlParser) +public class SourceHtmlParser: NSManagedObject {} diff --git a/Ferrite/DataManagement/Classes/SourceHtmlParser+CoreDataProperties.swift b/Ferrite/DataManagement/Classes/SourceHtmlParser+CoreDataProperties.swift new file mode 100644 index 0000000..e7e6268 --- /dev/null +++ b/Ferrite/DataManagement/Classes/SourceHtmlParser+CoreDataProperties.swift @@ -0,0 +1,26 @@ +// +// SourceHtmlParser+CoreDataProperties.swift +// Ferrite +// +// Created by Brian Dashore on 8/2/22. +// +// + +import CoreData +import Foundation + +public extension SourceHtmlParser { + @nonobjc class func fetchRequest() -> NSFetchRequest { + NSFetchRequest(entityName: "SourceHtmlParser") + } + + @NSManaged var rows: String + @NSManaged var searchUrl: String + @NSManaged var magnet: SourceMagnet? + @NSManaged var parentSource: Source? + @NSManaged var size: SourceSize? + @NSManaged var title: SourceTitle? + @NSManaged var seedLeech: SourceSeedLeech? +} + +extension SourceHtmlParser: Identifiable {} diff --git a/Ferrite/DataManagement/Classes/SourceList+CoreDataClass.swift b/Ferrite/DataManagement/Classes/SourceList+CoreDataClass.swift new file mode 100644 index 0000000..c6f26e5 --- /dev/null +++ b/Ferrite/DataManagement/Classes/SourceList+CoreDataClass.swift @@ -0,0 +1,13 @@ +// +// SourceList+CoreDataClass.swift +// Ferrite +// +// Created by Brian Dashore on 7/30/22. +// +// + +import CoreData +import Foundation + +@objc(SourceList) +public class SourceList: NSManagedObject {} diff --git a/Ferrite/DataManagement/Classes/SourceList+CoreDataProperties.swift b/Ferrite/DataManagement/Classes/SourceList+CoreDataProperties.swift new file mode 100644 index 0000000..aa85143 --- /dev/null +++ b/Ferrite/DataManagement/Classes/SourceList+CoreDataProperties.swift @@ -0,0 +1,22 @@ +// +// SourceList+CoreDataProperties.swift +// Ferrite +// +// Created by Brian Dashore on 7/30/22. +// +// + +import CoreData +import Foundation + +public extension SourceList { + @nonobjc class func fetchRequest() -> NSFetchRequest { + NSFetchRequest(entityName: "SourceList") + } + + @NSManaged var repoAuthor: String? + @NSManaged var repoName: String? + @NSManaged var urlString: String +} + +extension SourceList: Identifiable {} diff --git a/Ferrite/DataManagement/Classes/SourceSeedLeech+CoreDataClass.swift b/Ferrite/DataManagement/Classes/SourceSeedLeech+CoreDataClass.swift new file mode 100644 index 0000000..236706a --- /dev/null +++ b/Ferrite/DataManagement/Classes/SourceSeedLeech+CoreDataClass.swift @@ -0,0 +1,13 @@ +// +// SourceSeedLeech+CoreDataClass.swift +// Ferrite +// +// Created by Brian Dashore on 8/2/22. +// +// + +import CoreData +import Foundation + +@objc(SourceSeedLeech) +public class SourceSeedLeech: NSManagedObject {} diff --git a/Ferrite/DataManagement/Classes/SourceSeedLeech+CoreDataProperties.swift b/Ferrite/DataManagement/Classes/SourceSeedLeech+CoreDataProperties.swift new file mode 100644 index 0000000..365bb59 --- /dev/null +++ b/Ferrite/DataManagement/Classes/SourceSeedLeech+CoreDataProperties.swift @@ -0,0 +1,26 @@ +// +// SourceSeedLeech+CoreDataProperties.swift +// Ferrite +// +// Created by Brian Dashore on 8/2/22. +// +// + +import CoreData +import Foundation + +public extension SourceSeedLeech { + @nonobjc class func fetchRequest() -> NSFetchRequest { + NSFetchRequest(entityName: "SourceSeedLeech") + } + + @NSManaged var combined: String? + @NSManaged var leecherRegex: String? + @NSManaged var leechers: String? + @NSManaged var seederRegex: String? + @NSManaged var seeders: String? + @NSManaged var attribute: String + @NSManaged var parentParser: SourceHtmlParser? +} + +extension SourceSeedLeech: Identifiable {} diff --git a/Ferrite/DataManagement/Classes/TorrentSource+CoreDataClass.swift b/Ferrite/DataManagement/Classes/TorrentSource+CoreDataClass.swift deleted file mode 100644 index bc789b4..0000000 --- a/Ferrite/DataManagement/Classes/TorrentSource+CoreDataClass.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// TorrentSource+CoreDataClass.swift -// Ferrite -// -// Created by Brian Dashore on 7/24/22. -// -// - -import CoreData -import Foundation - -@objc(TorrentSource) -public class TorrentSource: NSManagedObject {} diff --git a/Ferrite/DataManagement/Classes/TorrentSource+CoreDataProperties.swift b/Ferrite/DataManagement/Classes/TorrentSource+CoreDataProperties.swift deleted file mode 100644 index 9040ec7..0000000 --- a/Ferrite/DataManagement/Classes/TorrentSource+CoreDataProperties.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// TorrentSource+CoreDataProperties.swift -// Ferrite -// -// Created by Brian Dashore on 7/24/22. -// -// - -import CoreData -import Foundation - -public extension TorrentSource { - @nonobjc class func fetchRequest() -> NSFetchRequest { - NSFetchRequest(entityName: "TorrentSource") - } - - @NSManaged var enabled: Bool - @NSManaged var linkQuery: String - @NSManaged var name: String? - @NSManaged var rowQuery: String - @NSManaged var sizeQuery: String? - @NSManaged var titleQuery: String? - @NSManaged var url: String -} - -extension TorrentSource: Identifiable {} diff --git a/Ferrite/DataManagement/Classes/TorrentSourceUrl+CoreDataClass.swift b/Ferrite/DataManagement/Classes/TorrentSourceUrl+CoreDataClass.swift deleted file mode 100644 index f6ec04c..0000000 --- a/Ferrite/DataManagement/Classes/TorrentSourceUrl+CoreDataClass.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// TorrentSourceUrl+CoreDataClass.swift -// Ferrite -// -// Created by Brian Dashore on 7/24/22. -// -// - -import CoreData -import Foundation - -@objc(TorrentSourceUrl) -public class TorrentSourceUrl: NSManagedObject {} diff --git a/Ferrite/DataManagement/Classes/TorrentSourceUrl+CoreDataProperties.swift b/Ferrite/DataManagement/Classes/TorrentSourceUrl+CoreDataProperties.swift deleted file mode 100644 index 265ce24..0000000 --- a/Ferrite/DataManagement/Classes/TorrentSourceUrl+CoreDataProperties.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// TorrentSourceUrl+CoreDataProperties.swift -// Ferrite -// -// Created by Brian Dashore on 7/25/22. -// -// - -import CoreData -import Foundation - -public extension TorrentSourceUrl { - @nonobjc class func fetchRequest() -> NSFetchRequest { - NSFetchRequest(entityName: "TorrentSourceUrl") - } - - @NSManaged var urlString: String - @NSManaged var repoName: String? - @NSManaged var repoAuthor: String? -} - -extension TorrentSourceUrl: Identifiable {} diff --git a/Ferrite/DataManagement/FerriteDB.xcdatamodeld/.xccurrentversion b/Ferrite/DataManagement/FerriteDB.xcdatamodeld/.xccurrentversion new file mode 100644 index 0000000..c089bb1 --- /dev/null +++ b/Ferrite/DataManagement/FerriteDB.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + FerriteDB.xcdatamodel + + diff --git a/Ferrite/DataManagement/FerriteDB.xcdatamodeld/FerriteDB.xcdatamodel/contents b/Ferrite/DataManagement/FerriteDB.xcdatamodeld/FerriteDB.xcdatamodel/contents index 84c1bc5..7696a03 100644 --- a/Ferrite/DataManagement/FerriteDB.xcdatamodeld/FerriteDB.xcdatamodel/contents +++ b/Ferrite/DataManagement/FerriteDB.xcdatamodeld/FerriteDB.xcdatamodel/contents @@ -1,17 +1,48 @@ - - + + + - - - - - - + + + - + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Ferrite/DataManagement/FerriteDB.xcdatamodeld/FerriteDB_v2.xcdatamodel/contents b/Ferrite/DataManagement/FerriteDB.xcdatamodeld/FerriteDB_v2.xcdatamodel/contents new file mode 100644 index 0000000..84c1bc5 --- /dev/null +++ b/Ferrite/DataManagement/FerriteDB.xcdatamodeld/FerriteDB_v2.xcdatamodel/contents @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Ferrite/DataManagement/PersistenceController.swift b/Ferrite/DataManagement/PersistenceController.swift index dd7bcb7..2dacbe7 100644 --- a/Ferrite/DataManagement/PersistenceController.swift +++ b/Ferrite/DataManagement/PersistenceController.swift @@ -9,7 +9,7 @@ import CoreData // No iCloud until finalized sources struct PersistenceController { - static let shared = PersistenceController() + static var shared = PersistenceController() // Coredata storage let container: NSPersistentContainer @@ -40,10 +40,9 @@ struct PersistenceController { backgroundContext.automaticallyMergesChangesFromParent = true backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy try? backgroundContext.setQueryGenerationFrom(.current) - container.loadPersistentStores { _, error in if let error = error { - fatalError("Error: \(error.localizedDescription)") + fatalError("CoreData init error: \(error)") } } } diff --git a/Ferrite/Models/SourceModels.swift b/Ferrite/Models/SourceModels.swift index 6110ec4..e2d99ef 100644 --- a/Ferrite/Models/SourceModels.swift +++ b/Ferrite/Models/SourceModels.swift @@ -7,10 +7,10 @@ import Foundation -public struct SourceJson: Codable { +public struct SourceListJson: Codable { let repoName: String? let repoAuthor: String? - let sources: [TorrentSourceJson] + let sources: [SourceJson] enum CodingKeys: String, CodingKey { case repoName = "name" @@ -19,11 +19,40 @@ public struct SourceJson: Codable { } } -public struct TorrentSourceJson: Codable, Hashable { - let name: String? - let url: String - let rowQuery: String - let linkQuery: String - let titleQuery: String? - let sizeQuery: String? +public struct SourceJson: Codable, Hashable { + let name: String + let version: String + let baseUrl: String + let htmlParser: SourceHtmlParserJson? +} + +public struct SourceHtmlParserJson: Codable, Hashable { + let searchUrl: String + let rows: String + let magnet: SourceMagnetJson + let title: SouceComplexQuery? + let size: SouceComplexQuery? + let sl: SourceSLJson? +} + +public struct SouceComplexQuery: Codable, Hashable { + let query: String + let attribute: String + let regex: String? +} + +public struct SourceMagnetJson: Codable, Hashable { + let query: String + let attribute: String + let regex: String? + let externalLinkQuery: String? +} + +public struct SourceSLJson: Codable, Hashable { + let seeders: String? + let leechers: String? + let combined: String? + let attribute: String + let seederRegex: String? + let leecherRegex: String? } diff --git a/Ferrite/ViewModels/ScrapingViewModel.swift b/Ferrite/ViewModels/ScrapingViewModel.swift index 8e8cf1a..38b4c73 100644 --- a/Ferrite/ViewModels/ScrapingViewModel.swift +++ b/Ferrite/ViewModels/ScrapingViewModel.swift @@ -6,6 +6,7 @@ // import Base32 +import Regex import SwiftSoup import SwiftUI @@ -15,6 +16,8 @@ public struct SearchResult: Hashable, Codable { let size: String let magnetLink: String let magnetHash: String? + let seeders: String? + let leechers: String? } class ScrapingViewModel: ObservableObject { @@ -24,44 +27,48 @@ class ScrapingViewModel: ObservableObject { var toastModel: ToastViewModel? @Published var searchResults: [SearchResult] = [] - @Published var debridHashes: [String] = [] @Published var searchText: String = "" @Published var selectedSearchResult: SearchResult? - @Published var filteredSource: TorrentSource? + @Published var filteredSource: Source? @MainActor - public func scanSources(sources: [TorrentSource]) async { + public func scanSources(sources: [Source]) async { if sources.isEmpty { print("Sources empty") + return } var tempResults: [SearchResult] = [] for source in sources { if source.enabled { - guard let html = await fetchWebsiteHtml(source: source) else { - continue - } + if let htmlParser = source.htmlParser { + guard let encodedQuery = searchText.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { + toastModel?.toastDescription = "Could not process search query, invalid characters present." + print("Could not process search query, invalid characters present") - let sourceResults = await scrapeWebsite(source: source, html: html) - tempResults += sourceResults + continue + } + + let urlString = source.baseUrl + htmlParser.searchUrl.replacingOccurrences(of: "{query}", with: encodedQuery) + + guard let html = await fetchWebsiteHtml(urlString: urlString) else { + continue + } + + let sourceResults = await scrapeWebsite(source: source, html: html) + tempResults += sourceResults + } } } searchResults = tempResults } - // Fetches the HTML body for the source website + // Fetches the HTML for a URL @MainActor - public func fetchWebsiteHtml(source: TorrentSource) async -> String? { - guard let encodedQuery = searchText.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else { - toastModel?.toastDescription = "Could not process search query, invalid characters present." - print("Could not process search query, invalid characters present") - - return nil - } - - guard let url = URL(string: source.url + encodedQuery) else { + public func fetchWebsiteHtml(urlString: String) async -> String? { + guard let url = URL(string: urlString) else { toastModel?.toastDescription = "Source doesn't contain a valid URL, contact the source dev!" print("Source doesn't contain a valid URL, contact the source dev!") @@ -83,58 +90,179 @@ class ScrapingViewModel: ObservableObject { // Returns results to UI // Results must have a link and title, but other parameters aren't required @MainActor - public func scrapeWebsite(source: TorrentSource, html: String) async -> [SearchResult] { - var tempResults: [SearchResult] = [] - var hashes: [String] = [] + public func scrapeWebsite(source: Source, html: String) async -> [SearchResult] { + guard let htmlParser = source.htmlParser else { + return [] + } + + var rows = Elements() do { let document = try SwiftSoup.parse(html) + rows = try document.select(htmlParser.rows) + } catch { + toastModel?.toastDescription = "Scraping error, couldn't fetch rows: \(error)" + print("Scraping error, couldn't fetch rows: \(error)") - let rows = try document.select(source.rowQuery) + return [] + } - for row in rows { - guard let link = try row.select(source.linkQuery).first() else { + var tempResults: [SearchResult] = [] + + // If there's an error, continue instead of returning with nothing + for row in rows { + do { + // Fetches the magnet link + // If the magnet is located on an external page, fetch the external page and grab the magnet link + // External page fetching affects source performance + guard let magnetParser = htmlParser.magnet else { continue } - let href = try link.attr("href") + var href: String + if let externalMagnetQuery = magnetParser.externalLinkQuery, !externalMagnetQuery.isEmpty { + guard let externalMagnetLink = try row.select(externalMagnetQuery).first()?.attr("href") else { + continue + } + + guard let magnetHtml = await fetchWebsiteHtml(urlString: source.baseUrl + externalMagnetLink) else { + continue + } + + let magnetDocument = try SwiftSoup.parse(magnetHtml) + guard let linkResult = try magnetDocument.select(magnetParser.query).first() else { + continue + } + + if magnetParser.attribute == "text" { + href = try linkResult.text() + } else { + href = try linkResult.attr(magnetParser.attribute) + } + } else { + guard let link = try runComplexQuery( + row: row, + query: magnetParser.query, + attribute: magnetParser.attribute, + regexString: magnetParser.regex + ) else { + continue + } + + href = link + } if !href.starts(with: "magnet:") { continue } + // Fetches the magnet hash let magnetHash = fetchMagnetHash(magnetLink: href) + // Fetches the episode/movie title var title: String? - if let titleQuery = source.titleQuery { - title = try row.select(titleQuery).first()?.text() + if let titleParser = htmlParser.title { + title = try? runComplexQuery( + row: row, + query: titleParser.query, + attribute: titleParser.attribute, + regexString: titleParser.regex + ) } - let size = try row.select(source.sizeQuery ?? "").first() - let sizeText = try size?.text() + // Fetches the torrent's size + var size: String? + if let sizeParser = htmlParser.size { + size = try? runComplexQuery( + row: row, + query: sizeParser.query, + attribute: sizeParser.attribute, + regexString: sizeParser.regex + ) + } + + // Fetches seeders and leechers if there are any + var seeders: String? + var leechers: String? + if let seederLeecher = htmlParser.seedLeech { + if let combinedQuery = seederLeecher.combined { + if let combinedString = try? runComplexQuery( + row: row, + query: combinedQuery, + attribute: seederLeecher.attribute, + regexString: nil + ) { + if let seederRegex = seederLeecher.seederRegex, let leecherRegex = seederLeecher.leecherRegex { + // Seeder regex matching + seeders = try? Regex(seederRegex).firstMatch(in: combinedString)?.groups[safe: 0]?.value + + // Leecher regex matching + leechers = try? Regex(leecherRegex).firstMatch(in: combinedString)?.groups[safe: 0]?.value + } + } + } else { + if let seederQuery = seederLeecher.seeders { + seeders = try? runComplexQuery( + row: row, + query: seederQuery, + attribute: seederLeecher.attribute, + regexString: seederLeecher.seederRegex + ) + } + + if let leecherQuery = seederLeecher.seeders { + leechers = try? runComplexQuery( + row: row, + query: leecherQuery, + attribute: seederLeecher.attribute, + regexString: seederLeecher.leecherRegex + ) + } + } + } let result = SearchResult( title: title ?? "No title", - source: source.name ?? "N/A", - size: sizeText ?? "?B", + source: source.name, + size: size ?? "", magnetLink: href, - magnetHash: magnetHash + magnetHash: magnetHash, + seeders: seeders, + leechers: leechers ) - // Change to bulk request to speed up UI - if let hash = magnetHash { - hashes.append(hash) - } - tempResults.append(result) + } catch { + toastModel?.toastDescription = "Scraping error: \(error)" + print("Scraping error: \(error)") + + continue } + } - return tempResults - } catch { - toastModel?.toastDescription = "Error while scraping: \(error)" - print("Error while scraping: \(error)") + return tempResults + } - return [] + func runComplexQuery(row: Element, query: String, attribute: String, regexString: String?) throws -> String? { + var parsedValue: String? + + let result = try row.select(query).first() + + switch attribute { + case "text": + parsedValue = try result?.text() + default: + parsedValue = try result?.attr(attribute) + } + + // A capture group must be used in the provided regex + if let regexString = regexString, + let parsedValue = parsedValue, + let regexValue = try Regex(regexString).firstMatch(in: parsedValue)?.groups[safe: 0]?.value + { + return regexValue + } else { + return parsedValue } } @@ -153,7 +281,7 @@ class ScrapingViewModel: ObservableObject { let decryptedMagnetHash = base32DecodeToData(String(magnetHash)) return decryptedMagnetHash?.hexEncodedString() } else { - return String(magnetHash) + return String(magnetHash).lowercased() } } } diff --git a/Ferrite/ViewModels/SourceManager.swift b/Ferrite/ViewModels/SourceManager.swift index d8e148d..dd99f4b 100644 --- a/Ferrite/ViewModels/SourceManager.swift +++ b/Ferrite/ViewModels/SourceManager.swift @@ -5,22 +5,23 @@ // Created by Brian Dashore on 7/25/22. // +import CoreData import Foundation public class SourceManager: ObservableObject { var toastModel: ToastViewModel? - @Published var availableSources: [TorrentSourceJson] = [] + @Published var availableSources: [SourceJson] = [] @Published var urlErrorAlertText = "" @Published var showUrlErrorAlert = false @MainActor public func fetchSourcesFromUrl() async { - let sourceUrlRequest = TorrentSourceUrl.fetchRequest() + let sourceUrlRequest = SourceList.fetchRequest() do { let sourceUrls = try PersistenceController.shared.backgroundContext.fetch(sourceUrlRequest) - var tempSourceUrls: [TorrentSourceJson] = [] + var tempSourceUrls: [SourceJson] = [] for sourceUrl in sourceUrls { guard let url = URL(string: sourceUrl.urlString) else { @@ -28,7 +29,7 @@ public class SourceManager: ObservableObject { } let (data, _) = try await URLSession.shared.data(for: URLRequest(url: url)) - let sourceResponse = try JSONDecoder().decode(SourceJson.self, from: data) + let sourceResponse = try JSONDecoder().decode(SourceListJson.self, from: data) tempSourceUrls += sourceResponse.sources } @@ -39,34 +40,79 @@ public class SourceManager: ObservableObject { } } - public func installSource(sourceJson: TorrentSourceJson) { + public func installSource(sourceJson: SourceJson) { let backgroundContext = PersistenceController.shared.backgroundContext // If a source exists, don't add the new one - if let name = sourceJson.name { - let existingSourceRequest = TorrentSource.fetchRequest() - existingSourceRequest.predicate = NSPredicate(format: "name == %@", name) - existingSourceRequest.fetchLimit = 1 + let existingSourceRequest = Source.fetchRequest() + existingSourceRequest.predicate = NSPredicate(format: "name == %@", sourceJson.name) + existingSourceRequest.fetchLimit = 1 - let existingSource = try? backgroundContext.fetch(existingSourceRequest).first - if existingSource != nil { - Task { @MainActor in - toastModel?.toastDescription = "Could not install source \(sourceJson.name ?? "Unknown source") because it is already installed." - } - - return + let existingSource = try? backgroundContext.fetch(existingSourceRequest).first + if existingSource != nil { + Task { @MainActor in + toastModel?.toastDescription = "Could not install source with name \(sourceJson.name) because it is already installed." } + + return } - let newTorrentSource = TorrentSource(context: backgroundContext) - newTorrentSource.name = sourceJson.name - newTorrentSource.url = sourceJson.url - newTorrentSource.rowQuery = sourceJson.rowQuery - newTorrentSource.linkQuery = sourceJson.linkQuery - newTorrentSource.titleQuery = sourceJson.titleQuery - newTorrentSource.sizeQuery = sourceJson.sizeQuery + let newSource = Source(context: backgroundContext) + newSource.name = sourceJson.name + newSource.version = sourceJson.version + newSource.baseUrl = sourceJson.baseUrl - newTorrentSource.enabled = true + // Adds an HTML parser if present + if let htmlParserJson = sourceJson.htmlParser { + let newSourceHtmlParser = SourceHtmlParser(context: backgroundContext) + newSourceHtmlParser.searchUrl = htmlParserJson.searchUrl + newSourceHtmlParser.rows = htmlParserJson.rows + + // Adds a title complex query if present + if let titleJson = htmlParserJson.title { + let newSourceTitle = SourceTitle(context: backgroundContext) + newSourceTitle.query = titleJson.query + newSourceTitle.attribute = titleJson.attribute + newSourceTitle.regex = titleJson.regex + + newSourceHtmlParser.title = newSourceTitle + } + + // Adds a size complex query if present + if let sizeJson = htmlParserJson.size { + let newSourceSize = SourceSize(context: backgroundContext) + newSourceSize.query = sizeJson.query + newSourceSize.attribute = sizeJson.attribute + newSourceSize.regex = sizeJson.regex + + newSourceHtmlParser.size = newSourceSize + } + + if let seedLeechJson = htmlParserJson.sl { + let newSourceSeedLeech = SourceSeedLeech(context: backgroundContext) + newSourceSeedLeech.seeders = seedLeechJson.seeders + newSourceSeedLeech.leechers = seedLeechJson.leechers + newSourceSeedLeech.combined = seedLeechJson.combined + newSourceSeedLeech.attribute = seedLeechJson.attribute + newSourceSeedLeech.seederRegex = seedLeechJson.seederRegex + newSourceSeedLeech.leecherRegex = seedLeechJson.leecherRegex + + newSourceHtmlParser.seedLeech = newSourceSeedLeech + } + + // Adds a magnet complex query and its unique properties + let newSourceMagnet = SourceMagnet(context: backgroundContext) + newSourceMagnet.externalLinkQuery = htmlParserJson.magnet.externalLinkQuery + newSourceMagnet.query = htmlParserJson.magnet.query + newSourceMagnet.attribute = htmlParserJson.magnet.attribute + newSourceMagnet.regex = htmlParserJson.magnet.regex + + newSourceHtmlParser.magnet = newSourceMagnet + + newSource.htmlParser = newSourceHtmlParser + } + + newSource.enabled = true do { try backgroundContext.save() @@ -88,7 +134,7 @@ public class SourceManager: ObservableObject { return false } - let sourceUrlRequest = TorrentSourceUrl.fetchRequest() + let sourceUrlRequest = SourceList.fetchRequest() sourceUrlRequest.predicate = NSPredicate(format: "urlString == %@", sourceUrl) sourceUrlRequest.fetchLimit = 1 @@ -97,12 +143,12 @@ public class SourceManager: ObservableObject { PersistenceController.shared.delete(existingSourceUrl, context: backgroundContext) } - let newSourceUrl = TorrentSourceUrl(context: backgroundContext) + let newSourceUrl = SourceList(context: backgroundContext) newSourceUrl.urlString = sourceUrl do { let (data, _) = try await URLSession.shared.data(for: URLRequest(url: URL(string: sourceUrl)!)) - if let rawResponse = try? JSONDecoder().decode(SourceJson.self, from: data) { + if let rawResponse = try? JSONDecoder().decode(SourceListJson.self, from: data) { newSourceUrl.repoName = rawResponse.repoName } diff --git a/Ferrite/Views/ContentView.swift b/Ferrite/Views/ContentView.swift index 5e0010f..72e3226 100644 --- a/Ferrite/Views/ContentView.swift +++ b/Ferrite/Views/ContentView.swift @@ -15,11 +15,11 @@ struct ContentView: View { @AppStorage("RealDebrid.Enabled") var realDebridEnabled = false @FetchRequest( - entity: TorrentSource.entity(), + entity: Source.entity(), sortDescriptors: [] - ) var sources: FetchedResults + ) var sources: FetchedResults - @State private var selectedSource: TorrentSource? { + @State private var selectedSource: Source? { didSet { scrapingModel.filteredSource = selectedSource } diff --git a/Ferrite/Views/SearchResultRDView.swift b/Ferrite/Views/SearchResultRDView.swift index 0183285..f422a7c 100644 --- a/Ferrite/Views/SearchResultRDView.swift +++ b/Ferrite/Views/SearchResultRDView.swift @@ -20,6 +20,14 @@ struct SearchResultRDView: View { Spacer() + if let seeders = result.seeders { + Text("S: \(seeders)") + } + + if let leechers = result.leechers { + Text("L: \(leechers)") + } + Text(result.size) if realDebridEnabled { diff --git a/Ferrite/Views/SettingsView.swift b/Ferrite/Views/SettingsView.swift index 19adb2a..dae6a4c 100644 --- a/Ferrite/Views/SettingsView.swift +++ b/Ferrite/Views/SettingsView.swift @@ -9,6 +9,7 @@ import SwiftUI struct SettingsView: View { @EnvironmentObject var debridManager: DebridManager + @EnvironmentObject var sourceManager: SourceManager let backgroundContext = PersistenceController.shared.backgroundContext diff --git a/Ferrite/Views/SettingsViews/SettingsSourceUrlView.swift b/Ferrite/Views/SettingsViews/SettingsSourceUrlView.swift index b1619da..dfc34ef 100644 --- a/Ferrite/Views/SettingsViews/SettingsSourceUrlView.swift +++ b/Ferrite/Views/SettingsViews/SettingsSourceUrlView.swift @@ -11,9 +11,9 @@ struct SettingsSourceListView: View { let backgroundContext = PersistenceController.shared.backgroundContext @FetchRequest( - entity: TorrentSourceUrl.entity(), + entity: SourceList.entity(), sortDescriptors: [] - ) var sourceUrls: FetchedResults + ) var sourceUrls: FetchedResults @State private var presentSourceSheet = false diff --git a/Ferrite/Views/SourceListView.swift b/Ferrite/Views/SourceListView.swift index bd7b1d9..0bad7cc 100644 --- a/Ferrite/Views/SourceListView.swift +++ b/Ferrite/Views/SourceListView.swift @@ -13,9 +13,9 @@ struct SourceListView: View { let backgroundContext = PersistenceController.shared.backgroundContext @FetchRequest( - entity: TorrentSource.entity(), + entity: Source.entity(), sortDescriptors: [] - ) var sources: FetchedResults + ) var sources: FetchedResults @State private var availableSourceLength = 0 @@ -32,7 +32,7 @@ struct SourceListView: View { PersistenceController.shared.save() } )) { - Text(source.name ?? "Unknown Source") + Text(source.name) } } .onDelete { offsets in @@ -52,7 +52,7 @@ struct SourceListView: View { ForEach(sourceManager.availableSources, id: \.self) { availableSource in if !sources.contains(where: { availableSource.name == $0.name }) { HStack { - Text(availableSource.name ?? "Unnamed source") + Text(availableSource.name) Spacer()