Ferrite: Overhaul sources
Sources are now completely changed to use a more flexible API. This uses a fully native source system, so there will be 0 overhead on resource usage and performance. JSON objects specify what is fetched and displayed by Ferrite when searching torrents. Sources now include sizes, seeders, and leechers for any site that specifies them. The versioning and repo naming framework has been added, but will be displayed in another update. API support will be included in another update. Signed-off-by: kingbri <bdashore3@gmail.com>
This commit is contained in:
parent
64798a172a
commit
940f8337a5
27 changed files with 617 additions and 198 deletions
|
|
@ -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 = "<group>"; };
|
||||
0C0D50E0288DF7700035ECC8 /* TorrentSource+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TorrentSource+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
0C0D50E4288DFE7F0035ECC8 /* SourceModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceModels.swift; sourceTree = "<group>"; };
|
||||
0C0D50E6288DFF850035ECC8 /* SourceListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceListView.swift; sourceTree = "<group>"; };
|
||||
0C32FB522890D19D002BD219 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
|
||||
0C32FB542890D1BF002BD219 /* UIApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIApplication.swift; sourceTree = "<group>"; };
|
||||
0C32FB562890D1F2002BD219 /* ListRowViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRowViews.swift; sourceTree = "<group>"; };
|
||||
0C4CFC4728970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceComplexQuery+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
0C4CFC4828970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceComplexQuery+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
0C57D4CB289032ED008534E8 /* SearchResultRDView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultRDView.swift; sourceTree = "<group>"; };
|
||||
0C79DC052899AF3C003F1C5A /* SourceSeedLeech+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceSeedLeech+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
0C79DC062899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceSeedLeech+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
0C84F4762895BE680074B7C9 /* FerriteDB.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = FerriteDB.xcdatamodel; sourceTree = "<group>"; };
|
||||
0C84F47A2895BFED0074B7C9 /* Source+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Source+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
0C84F47B2895BFED0074B7C9 /* Source+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Source+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
0C84F47C2895BFED0074B7C9 /* SourceHtmlParser+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceHtmlParser+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
0C84F47D2895BFED0074B7C9 /* SourceHtmlParser+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceHtmlParser+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
0C84F47E2895BFED0074B7C9 /* SourceList+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceList+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
0C84F47F2895BFED0074B7C9 /* SourceList+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceList+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
0CA05456288EE58200850554 /* SettingsSourceUrlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSourceUrlView.swift; sourceTree = "<group>"; };
|
||||
0CA05458288EE9E600850554 /* SourceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceManager.swift; sourceTree = "<group>"; };
|
||||
0CA0545A288EEA4E00850554 /* SourceListEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceListEditorView.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -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 = "<group>"; };
|
||||
0CBC76FE288DAAD00054BE44 /* NavigationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationViewModel.swift; sourceTree = "<group>"; };
|
||||
0CBC7701288DE4400054BE44 /* FerriteDB.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = FerriteDB.xcdatamodel; sourceTree = "<group>"; };
|
||||
0CBC7704288DE7F40054BE44 /* PersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceController.swift; sourceTree = "<group>"; };
|
||||
0CE37AB8288E4AE900428C2D /* TorrentSourceUrl+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TorrentSourceUrl+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
0CE37AB9288E4AE900428C2D /* TorrentSourceUrl+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TorrentSourceUrl+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
0CFEFCFC288A006200B3F490 /* GroupBoxStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupBoxStyle.swift; sourceTree = "<group>"; };
|
||||
/* 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 = "<group>";
|
||||
|
|
@ -252,7 +272,7 @@
|
|||
children = (
|
||||
0C0D50DE288DF72D0035ECC8 /* Classes */,
|
||||
0CBC7704288DE7F40054BE44 /* PersistenceController.swift */,
|
||||
0CBC7700288DE4400054BE44 /* FerriteDB.xcdatamodeld */,
|
||||
0C84F4752895BE680074B7C9 /* FerriteDB.xcdatamodeld */,
|
||||
);
|
||||
path = DataManagement;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -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 = "<group>";
|
||||
versionGroupType = wrapper.xcdatamodel;
|
||||
|
|
|
|||
13
Ferrite/DataManagement/Classes/Source+CoreDataClass.swift
Normal file
13
Ferrite/DataManagement/Classes/Source+CoreDataClass.swift
Normal file
|
|
@ -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 {}
|
||||
|
|
@ -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<Source> {
|
||||
NSFetchRequest<Source>(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 {}
|
||||
|
|
@ -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 {}
|
||||
|
|
@ -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<SourceComplexQuery> {
|
||||
NSFetchRequest<SourceComplexQuery>(entityName: "SourceComplexQuery")
|
||||
}
|
||||
|
||||
@NSManaged var attribute: String
|
||||
@NSManaged var query: String
|
||||
@NSManaged var regex: String?
|
||||
}
|
||||
|
||||
extension SourceComplexQuery: Identifiable {}
|
||||
|
|
@ -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 {}
|
||||
|
|
@ -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<SourceHtmlParser> {
|
||||
NSFetchRequest<SourceHtmlParser>(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 {}
|
||||
|
|
@ -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 {}
|
||||
|
|
@ -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<SourceList> {
|
||||
NSFetchRequest<SourceList>(entityName: "SourceList")
|
||||
}
|
||||
|
||||
@NSManaged var repoAuthor: String?
|
||||
@NSManaged var repoName: String?
|
||||
@NSManaged var urlString: String
|
||||
}
|
||||
|
||||
extension SourceList: Identifiable {}
|
||||
|
|
@ -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 {}
|
||||
|
|
@ -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<SourceSeedLeech> {
|
||||
NSFetchRequest<SourceSeedLeech>(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 {}
|
||||
|
|
@ -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 {}
|
||||
|
|
@ -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<TorrentSource> {
|
||||
NSFetchRequest<TorrentSource>(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 {}
|
||||
|
|
@ -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 {}
|
||||
|
|
@ -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<TorrentSourceUrl> {
|
||||
NSFetchRequest<TorrentSourceUrl>(entityName: "TorrentSourceUrl")
|
||||
}
|
||||
|
||||
@NSManaged var urlString: String
|
||||
@NSManaged var repoName: String?
|
||||
@NSManaged var repoAuthor: String?
|
||||
}
|
||||
|
||||
extension TorrentSourceUrl: Identifiable {}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>FerriteDB.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -1,17 +1,48 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21256.2" systemVersion="21F79" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="TorrentSource" representedClassName="TorrentSource" syncable="YES">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21271" systemVersion="21F79" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Source" representedClassName="Source" syncable="YES">
|
||||
<attribute name="baseUrl" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="linkQuery" attributeType="String"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="rowQuery" attributeType="String"/>
|
||||
<attribute name="sizeQuery" optional="YES" attributeType="String"/>
|
||||
<attribute name="titleQuery" optional="YES" attributeType="String"/>
|
||||
<attribute name="url" attributeType="String"/>
|
||||
<attribute name="name" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="version" attributeType="String" defaultValueString=""/>
|
||||
<relationship name="htmlParser" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="SourceHtmlParser" inverseName="parentSource" inverseEntity="SourceHtmlParser"/>
|
||||
</entity>
|
||||
<entity name="TorrentSourceUrl" representedClassName="TorrentSourceUrl" syncable="YES">
|
||||
<entity name="SourceComplexQuery" representedClassName="SourceComplexQuery" isAbstract="YES" syncable="YES">
|
||||
<attribute name="attribute" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="query" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="regex" optional="YES" attributeType="String"/>
|
||||
</entity>
|
||||
<entity name="SourceHtmlParser" representedClassName="SourceHtmlParser" syncable="YES">
|
||||
<attribute name="rows" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="searchUrl" attributeType="String" defaultValueString=""/>
|
||||
<relationship name="magnet" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="SourceMagnet" inverseName="parentParser" inverseEntity="SourceMagnet"/>
|
||||
<relationship name="parentSource" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Source" inverseName="htmlParser" inverseEntity="Source"/>
|
||||
<relationship name="seedLeech" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SourceSeedLeech" inverseName="parentParser" inverseEntity="SourceSeedLeech"/>
|
||||
<relationship name="size" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="SourceSize" inverseName="parentParser" inverseEntity="SourceSize"/>
|
||||
<relationship name="title" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="SourceTitle" inverseName="parentParser" inverseEntity="SourceTitle"/>
|
||||
</entity>
|
||||
<entity name="SourceList" representedClassName="SourceList" syncable="YES">
|
||||
<attribute name="repoAuthor" optional="YES" attributeType="String"/>
|
||||
<attribute name="repoName" optional="YES" attributeType="String"/>
|
||||
<attribute name="urlString" attributeType="String"/>
|
||||
<attribute name="urlString" attributeType="String" defaultValueString=""/>
|
||||
</entity>
|
||||
<entity name="SourceMagnet" representedClassName="SourceMagnet" parentEntity="SourceComplexQuery" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="externalLinkQuery" optional="YES" attributeType="String"/>
|
||||
<relationship name="parentParser" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SourceHtmlParser" inverseName="magnet" inverseEntity="SourceHtmlParser"/>
|
||||
</entity>
|
||||
<entity name="SourceSeedLeech" representedClassName="SourceSeedLeech" syncable="YES">
|
||||
<attribute name="attribute" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="combined" optional="YES" attributeType="String"/>
|
||||
<attribute name="leecherRegex" optional="YES" attributeType="String"/>
|
||||
<attribute name="leechers" optional="YES" attributeType="String"/>
|
||||
<attribute name="seederRegex" optional="YES" attributeType="String"/>
|
||||
<attribute name="seeders" optional="YES" attributeType="String"/>
|
||||
<relationship name="parentParser" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SourceHtmlParser" inverseName="seedLeech" inverseEntity="SourceHtmlParser"/>
|
||||
</entity>
|
||||
<entity name="SourceSize" representedClassName="SourceSize" parentEntity="SourceComplexQuery" syncable="YES" codeGenerationType="class">
|
||||
<relationship name="parentParser" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SourceHtmlParser" inverseName="size" inverseEntity="SourceHtmlParser"/>
|
||||
</entity>
|
||||
<entity name="SourceTitle" representedClassName="SourceTitle" parentEntity="SourceComplexQuery" syncable="YES" codeGenerationType="class">
|
||||
<relationship name="parentParser" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SourceHtmlParser" inverseName="title" inverseEntity="SourceHtmlParser"/>
|
||||
</entity>
|
||||
</model>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21256.2" systemVersion="21F79" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="TorrentSource" representedClassName="TorrentSource" syncable="YES">
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="linkQuery" attributeType="String"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="rowQuery" attributeType="String"/>
|
||||
<attribute name="sizeQuery" optional="YES" attributeType="String"/>
|
||||
<attribute name="titleQuery" optional="YES" attributeType="String"/>
|
||||
<attribute name="url" attributeType="String"/>
|
||||
</entity>
|
||||
<entity name="TorrentSourceUrl" representedClassName="TorrentSourceUrl" syncable="YES">
|
||||
<attribute name="repoAuthor" optional="YES" attributeType="String"/>
|
||||
<attribute name="repoName" optional="YES" attributeType="String"/>
|
||||
<attribute name="urlString" attributeType="String"/>
|
||||
</entity>
|
||||
</model>
|
||||
|
|
@ -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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@ struct ContentView: View {
|
|||
@AppStorage("RealDebrid.Enabled") var realDebridEnabled = false
|
||||
|
||||
@FetchRequest(
|
||||
entity: TorrentSource.entity(),
|
||||
entity: Source.entity(),
|
||||
sortDescriptors: []
|
||||
) var sources: FetchedResults<TorrentSource>
|
||||
) var sources: FetchedResults<Source>
|
||||
|
||||
@State private var selectedSource: TorrentSource? {
|
||||
@State private var selectedSource: Source? {
|
||||
didSet {
|
||||
scrapingModel.filteredSource = selectedSource
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import SwiftUI
|
|||
|
||||
struct SettingsView: View {
|
||||
@EnvironmentObject var debridManager: DebridManager
|
||||
@EnvironmentObject var sourceManager: SourceManager
|
||||
|
||||
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ struct SettingsSourceListView: View {
|
|||
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||
|
||||
@FetchRequest(
|
||||
entity: TorrentSourceUrl.entity(),
|
||||
entity: SourceList.entity(),
|
||||
sortDescriptors: []
|
||||
) var sourceUrls: FetchedResults<TorrentSourceUrl>
|
||||
) var sourceUrls: FetchedResults<SourceList>
|
||||
|
||||
@State private var presentSourceSheet = false
|
||||
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ struct SourceListView: View {
|
|||
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||
|
||||
@FetchRequest(
|
||||
entity: TorrentSource.entity(),
|
||||
entity: Source.entity(),
|
||||
sortDescriptors: []
|
||||
) var sources: FetchedResults<TorrentSource>
|
||||
) var sources: FetchedResults<Source>
|
||||
|
||||
@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()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue