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 = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* 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 */; };
|
0C0D50E5288DFE7F0035ECC8 /* SourceModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D50E4288DFE7F0035ECC8 /* SourceModels.swift */; };
|
||||||
0C0D50E7288DFF850035ECC8 /* SourceListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D50E6288DFF850035ECC8 /* SourceListView.swift */; };
|
0C0D50E7288DFF850035ECC8 /* SourceListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0D50E6288DFF850035ECC8 /* SourceListView.swift */; };
|
||||||
0C32FB532890D19D002BD219 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C32FB522890D19D002BD219 /* AboutView.swift */; };
|
0C32FB532890D19D002BD219 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C32FB522890D19D002BD219 /* AboutView.swift */; };
|
||||||
0C32FB552890D1BF002BD219 /* UIApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C32FB542890D1BF002BD219 /* UIApplication.swift */; };
|
0C32FB552890D1BF002BD219 /* UIApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C32FB542890D1BF002BD219 /* UIApplication.swift */; };
|
||||||
0C32FB572890D1F2002BD219 /* ListRowViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C32FB562890D1F2002BD219 /* ListRowViews.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 */; };
|
0C57D4CC289032ED008534E8 /* SearchResultRDView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C57D4CB289032ED008534E8 /* SearchResultRDView.swift */; };
|
||||||
0C64A4B4288903680079976D /* Base32 in Frameworks */ = {isa = PBXBuildFile; productRef = 0C64A4B3288903680079976D /* Base32 */; };
|
0C64A4B4288903680079976D /* Base32 in Frameworks */ = {isa = PBXBuildFile; productRef = 0C64A4B3288903680079976D /* Base32 */; };
|
||||||
0C64A4B7288903880079976D /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 0C64A4B6288903880079976D /* KeychainSwift */; };
|
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 */; };
|
0C90E32C2888E5D000C0BC89 /* ActivityView in Frameworks */ = {isa = PBXBuildFile; productRef = 0C90E32B2888E5D000C0BC89 /* ActivityView */; };
|
||||||
0CA05457288EE58200850554 /* SettingsSourceUrlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA05456288EE58200850554 /* SettingsSourceUrlView.swift */; };
|
0CA05457288EE58200850554 /* SettingsSourceUrlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA05456288EE58200850554 /* SettingsSourceUrlView.swift */; };
|
||||||
0CA05459288EE9E600850554 /* SourceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA05458288EE9E600850554 /* SourceManager.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 */; };
|
0CAF1C7B286F5C8600296F86 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = 0CAF1C7A286F5C8600296F86 /* SwiftSoup */; };
|
||||||
0CBC76FD288D914F0054BE44 /* BatchChoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CBC76FC288D914F0054BE44 /* BatchChoiceView.swift */; };
|
0CBC76FD288D914F0054BE44 /* BatchChoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CBC76FC288D914F0054BE44 /* BatchChoiceView.swift */; };
|
||||||
0CBC76FF288DAAD00054BE44 /* NavigationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CBC76FE288DAAD00054BE44 /* NavigationViewModel.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 */; };
|
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 */; };
|
0CFEFCFD288A006200B3F490 /* GroupBoxStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CFEFCFC288A006200B3F490 /* GroupBoxStyle.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference 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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
0CFEFCFC288A006200B3F490 /* GroupBoxStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupBoxStyle.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
|
@ -100,6 +113,7 @@
|
||||||
files = (
|
files = (
|
||||||
0C90E32C2888E5D000C0BC89 /* ActivityView in Frameworks */,
|
0C90E32C2888E5D000C0BC89 /* ActivityView in Frameworks */,
|
||||||
0C64A4B4288903680079976D /* Base32 in Frameworks */,
|
0C64A4B4288903680079976D /* Base32 in Frameworks */,
|
||||||
|
0C4CFC462897030D00AD9FAD /* Regex in Frameworks */,
|
||||||
0C64A4B7288903880079976D /* KeychainSwift in Frameworks */,
|
0C64A4B7288903880079976D /* KeychainSwift in Frameworks */,
|
||||||
0CAF1C7B286F5C8600296F86 /* SwiftSoup in Frameworks */,
|
0CAF1C7B286F5C8600296F86 /* SwiftSoup in Frameworks */,
|
||||||
);
|
);
|
||||||
|
|
@ -111,10 +125,16 @@
|
||||||
0C0D50DE288DF72D0035ECC8 /* Classes */ = {
|
0C0D50DE288DF72D0035ECC8 /* Classes */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
0CE37AB8288E4AE900428C2D /* TorrentSourceUrl+CoreDataClass.swift */,
|
0C79DC052899AF3C003F1C5A /* SourceSeedLeech+CoreDataClass.swift */,
|
||||||
0CE37AB9288E4AE900428C2D /* TorrentSourceUrl+CoreDataProperties.swift */,
|
0C79DC062899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift */,
|
||||||
0C0D50DF288DF7700035ECC8 /* TorrentSource+CoreDataClass.swift */,
|
0C4CFC4728970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift */,
|
||||||
0C0D50E0288DF7700035ECC8 /* TorrentSource+CoreDataProperties.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;
|
path = Classes;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -252,7 +272,7 @@
|
||||||
children = (
|
children = (
|
||||||
0C0D50DE288DF72D0035ECC8 /* Classes */,
|
0C0D50DE288DF72D0035ECC8 /* Classes */,
|
||||||
0CBC7704288DE7F40054BE44 /* PersistenceController.swift */,
|
0CBC7704288DE7F40054BE44 /* PersistenceController.swift */,
|
||||||
0CBC7700288DE4400054BE44 /* FerriteDB.xcdatamodeld */,
|
0C84F4752895BE680074B7C9 /* FerriteDB.xcdatamodeld */,
|
||||||
);
|
);
|
||||||
path = DataManagement;
|
path = DataManagement;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -278,6 +298,7 @@
|
||||||
0C90E32B2888E5D000C0BC89 /* ActivityView */,
|
0C90E32B2888E5D000C0BC89 /* ActivityView */,
|
||||||
0C64A4B3288903680079976D /* Base32 */,
|
0C64A4B3288903680079976D /* Base32 */,
|
||||||
0C64A4B6288903880079976D /* KeychainSwift */,
|
0C64A4B6288903880079976D /* KeychainSwift */,
|
||||||
|
0C4CFC452897030D00AD9FAD /* Regex */,
|
||||||
);
|
);
|
||||||
productName = Torrenter;
|
productName = Torrenter;
|
||||||
productReference = 0CAF1C68286F5C0E00296F86 /* Ferrite.app */;
|
productReference = 0CAF1C68286F5C0E00296F86 /* Ferrite.app */;
|
||||||
|
|
@ -312,6 +333,7 @@
|
||||||
0C90E32A2888E5D000C0BC89 /* XCRemoteSwiftPackageReference "ActivityView" */,
|
0C90E32A2888E5D000C0BC89 /* XCRemoteSwiftPackageReference "ActivityView" */,
|
||||||
0C64A4B2288903680079976D /* XCRemoteSwiftPackageReference "Base32" */,
|
0C64A4B2288903680079976D /* XCRemoteSwiftPackageReference "Base32" */,
|
||||||
0C64A4B5288903880079976D /* XCRemoteSwiftPackageReference "keychain-swift" */,
|
0C64A4B5288903880079976D /* XCRemoteSwiftPackageReference "keychain-swift" */,
|
||||||
|
0C4CFC442897030D00AD9FAD /* XCRemoteSwiftPackageReference "Regex" */,
|
||||||
);
|
);
|
||||||
productRefGroup = 0CAF1C69286F5C0E00296F86 /* Products */;
|
productRefGroup = 0CAF1C69286F5C0E00296F86 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
|
|
@ -339,41 +361,47 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
0CE37ABB288E4AE900428C2D /* TorrentSourceUrl+CoreDataProperties.swift in Sources */,
|
|
||||||
0C0D50E5288DFE7F0035ECC8 /* SourceModels.swift in Sources */,
|
0C0D50E5288DFE7F0035ECC8 /* SourceModels.swift in Sources */,
|
||||||
0C32FB532890D19D002BD219 /* AboutView.swift in Sources */,
|
0C32FB532890D19D002BD219 /* AboutView.swift in Sources */,
|
||||||
|
0C84F4832895BFED0074B7C9 /* Source+CoreDataProperties.swift in Sources */,
|
||||||
0CA148DB288903F000DE2211 /* NavView.swift in Sources */,
|
0CA148DB288903F000DE2211 /* NavView.swift in Sources */,
|
||||||
0CBC7705288DE7F40054BE44 /* PersistenceController.swift in Sources */,
|
0CBC7705288DE7F40054BE44 /* PersistenceController.swift in Sources */,
|
||||||
0CA0545B288EEA4E00850554 /* SourceListEditorView.swift in Sources */,
|
0CA0545B288EEA4E00850554 /* SourceListEditorView.swift in Sources */,
|
||||||
|
0C84F4872895BFED0074B7C9 /* SourceList+CoreDataProperties.swift in Sources */,
|
||||||
0CA148E9288903F000DE2211 /* MainView.swift in Sources */,
|
0CA148E9288903F000DE2211 /* MainView.swift in Sources */,
|
||||||
0CBC76FD288D914F0054BE44 /* BatchChoiceView.swift in Sources */,
|
0CBC76FD288D914F0054BE44 /* BatchChoiceView.swift in Sources */,
|
||||||
0C32FB552890D1BF002BD219 /* UIApplication.swift in Sources */,
|
0C32FB552890D1BF002BD219 /* UIApplication.swift in Sources */,
|
||||||
0C0D50E7288DFF850035ECC8 /* SourceListView.swift in Sources */,
|
0C0D50E7288DFF850035ECC8 /* SourceListView.swift in Sources */,
|
||||||
0CA148EC288903F000DE2211 /* ContentView.swift in Sources */,
|
0CA148EC288903F000DE2211 /* ContentView.swift in Sources */,
|
||||||
0CA148E1288903F000DE2211 /* Collection.swift in Sources */,
|
0CA148E1288903F000DE2211 /* Collection.swift in Sources */,
|
||||||
|
0C79DC082899AF3C003F1C5A /* SourceSeedLeech+CoreDataProperties.swift in Sources */,
|
||||||
0CA148DD288903F000DE2211 /* ScrapingViewModel.swift in Sources */,
|
0CA148DD288903F000DE2211 /* ScrapingViewModel.swift in Sources */,
|
||||||
0CA148D8288903F000DE2211 /* MagnetChoiceView.swift in Sources */,
|
0CA148D8288903F000DE2211 /* MagnetChoiceView.swift in Sources */,
|
||||||
|
0C84F4862895BFED0074B7C9 /* SourceList+CoreDataClass.swift in Sources */,
|
||||||
0CA148E3288903F000DE2211 /* Task.swift in Sources */,
|
0CA148E3288903F000DE2211 /* Task.swift in Sources */,
|
||||||
0CA148E7288903F000DE2211 /* ToastViewModel.swift in Sources */,
|
0CA148E7288903F000DE2211 /* ToastViewModel.swift in Sources */,
|
||||||
0CFEFCFD288A006200B3F490 /* GroupBoxStyle.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 */,
|
0CA148E6288903F000DE2211 /* WebView.swift in Sources */,
|
||||||
|
0C4CFC4E28970C8B00AD9FAD /* SourceComplexQuery+CoreDataProperties.swift in Sources */,
|
||||||
0CA148E2288903F000DE2211 /* Data.swift in Sources */,
|
0CA148E2288903F000DE2211 /* Data.swift in Sources */,
|
||||||
0C0D50E1288DF7700035ECC8 /* TorrentSource+CoreDataClass.swift in Sources */,
|
|
||||||
0C57D4CC289032ED008534E8 /* SearchResultRDView.swift in Sources */,
|
0C57D4CC289032ED008534E8 /* SearchResultRDView.swift in Sources */,
|
||||||
0CA05459288EE9E600850554 /* SourceManager.swift in Sources */,
|
0CA05459288EE9E600850554 /* SourceManager.swift in Sources */,
|
||||||
|
0C84F4772895BE680074B7C9 /* FerriteDB.xcdatamodeld in Sources */,
|
||||||
0CA05457288EE58200850554 /* SettingsSourceUrlView.swift in Sources */,
|
0CA05457288EE58200850554 /* SettingsSourceUrlView.swift in Sources */,
|
||||||
0CA148DE288903F000DE2211 /* RealDebridModels.swift in Sources */,
|
0CA148DE288903F000DE2211 /* RealDebridModels.swift in Sources */,
|
||||||
0CBC76FF288DAAD00054BE44 /* NavigationViewModel.swift in Sources */,
|
0CBC76FF288DAAD00054BE44 /* NavigationViewModel.swift in Sources */,
|
||||||
|
0C4CFC4D28970C8B00AD9FAD /* SourceComplexQuery+CoreDataClass.swift in Sources */,
|
||||||
0CA148E8288903F000DE2211 /* RealDebridWrapper.swift in Sources */,
|
0CA148E8288903F000DE2211 /* RealDebridWrapper.swift in Sources */,
|
||||||
0CA148D6288903F000DE2211 /* SettingsView.swift in Sources */,
|
0CA148D6288903F000DE2211 /* SettingsView.swift in Sources */,
|
||||||
0CA148E5288903F000DE2211 /* DebridManager.swift in Sources */,
|
0CA148E5288903F000DE2211 /* DebridManager.swift in Sources */,
|
||||||
0CBC7702288DE4400054BE44 /* FerriteDB.xcdatamodeld in Sources */,
|
0C84F4842895BFED0074B7C9 /* SourceHtmlParser+CoreDataClass.swift in Sources */,
|
||||||
0CA148D9288903F000DE2211 /* CardView.swift in Sources */,
|
0CA148D9288903F000DE2211 /* CardView.swift in Sources */,
|
||||||
0C32FB572890D1F2002BD219 /* ListRowViews.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 */,
|
0CA148EB288903F000DE2211 /* SearchResultsView.swift in Sources */,
|
||||||
0CA148D7288903F000DE2211 /* LoginWebView.swift in Sources */,
|
0CA148D7288903F000DE2211 /* LoginWebView.swift in Sources */,
|
||||||
0C0D50E2288DF7700035ECC8 /* TorrentSource+CoreDataProperties.swift in Sources */,
|
|
||||||
0CA148E0288903F000DE2211 /* FerriteApp.swift in Sources */,
|
0CA148E0288903F000DE2211 /* FerriteApp.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|
@ -581,6 +609,14 @@
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCRemoteSwiftPackageReference section */
|
/* Begin XCRemoteSwiftPackageReference section */
|
||||||
|
0C4CFC442897030D00AD9FAD /* XCRemoteSwiftPackageReference "Regex" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/sindresorhus/Regex";
|
||||||
|
requirement = {
|
||||||
|
branch = main;
|
||||||
|
kind = branch;
|
||||||
|
};
|
||||||
|
};
|
||||||
0C64A4B2288903680079976D /* XCRemoteSwiftPackageReference "Base32" */ = {
|
0C64A4B2288903680079976D /* XCRemoteSwiftPackageReference "Base32" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/norio-nomura/Base32";
|
repositoryURL = "https://github.com/norio-nomura/Base32";
|
||||||
|
|
@ -616,6 +652,11 @@
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
|
0C4CFC452897030D00AD9FAD /* Regex */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = 0C4CFC442897030D00AD9FAD /* XCRemoteSwiftPackageReference "Regex" */;
|
||||||
|
productName = Regex;
|
||||||
|
};
|
||||||
0C64A4B3288903680079976D /* Base32 */ = {
|
0C64A4B3288903680079976D /* Base32 */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = 0C64A4B2288903680079976D /* XCRemoteSwiftPackageReference "Base32" */;
|
package = 0C64A4B2288903680079976D /* XCRemoteSwiftPackageReference "Base32" */;
|
||||||
|
|
@ -639,12 +680,12 @@
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
|
|
||||||
/* Begin XCVersionGroup section */
|
/* Begin XCVersionGroup section */
|
||||||
0CBC7700288DE4400054BE44 /* FerriteDB.xcdatamodeld */ = {
|
0C84F4752895BE680074B7C9 /* FerriteDB.xcdatamodeld */ = {
|
||||||
isa = XCVersionGroup;
|
isa = XCVersionGroup;
|
||||||
children = (
|
children = (
|
||||||
0CBC7701288DE4400054BE44 /* FerriteDB.xcdatamodel */,
|
0C84F4762895BE680074B7C9 /* FerriteDB.xcdatamodel */,
|
||||||
);
|
);
|
||||||
currentVersion = 0CBC7701288DE4400054BE44 /* FerriteDB.xcdatamodel */;
|
currentVersion = 0C84F4762895BE680074B7C9 /* FerriteDB.xcdatamodel */;
|
||||||
path = FerriteDB.xcdatamodeld;
|
path = FerriteDB.xcdatamodeld;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
versionGroupType = wrapper.xcdatamodel;
|
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"?>
|
<?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="">
|
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21271" systemVersion="21F79" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||||
<entity name="TorrentSource" representedClassName="TorrentSource" syncable="YES">
|
<entity name="Source" representedClassName="Source" syncable="YES">
|
||||||
|
<attribute name="baseUrl" attributeType="String" defaultValueString=""/>
|
||||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||||
<attribute name="linkQuery" attributeType="String"/>
|
<attribute name="name" attributeType="String" defaultValueString=""/>
|
||||||
<attribute name="name" optional="YES" attributeType="String"/>
|
<attribute name="version" attributeType="String" defaultValueString=""/>
|
||||||
<attribute name="rowQuery" attributeType="String"/>
|
<relationship name="htmlParser" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="SourceHtmlParser" inverseName="parentSource" inverseEntity="SourceHtmlParser"/>
|
||||||
<attribute name="sizeQuery" optional="YES" attributeType="String"/>
|
|
||||||
<attribute name="titleQuery" optional="YES" attributeType="String"/>
|
|
||||||
<attribute name="url" attributeType="String"/>
|
|
||||||
</entity>
|
</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="repoAuthor" optional="YES" attributeType="String"/>
|
||||||
<attribute name="repoName" 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>
|
</entity>
|
||||||
</model>
|
</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
|
// No iCloud until finalized sources
|
||||||
struct PersistenceController {
|
struct PersistenceController {
|
||||||
static let shared = PersistenceController()
|
static var shared = PersistenceController()
|
||||||
|
|
||||||
// Coredata storage
|
// Coredata storage
|
||||||
let container: NSPersistentContainer
|
let container: NSPersistentContainer
|
||||||
|
|
@ -40,10 +40,9 @@ struct PersistenceController {
|
||||||
backgroundContext.automaticallyMergesChangesFromParent = true
|
backgroundContext.automaticallyMergesChangesFromParent = true
|
||||||
backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
|
backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
|
||||||
try? backgroundContext.setQueryGenerationFrom(.current)
|
try? backgroundContext.setQueryGenerationFrom(.current)
|
||||||
|
|
||||||
container.loadPersistentStores { _, error in
|
container.loadPersistentStores { _, error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
fatalError("Error: \(error.localizedDescription)")
|
fatalError("CoreData init error: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct SourceJson: Codable {
|
public struct SourceListJson: Codable {
|
||||||
let repoName: String?
|
let repoName: String?
|
||||||
let repoAuthor: String?
|
let repoAuthor: String?
|
||||||
let sources: [TorrentSourceJson]
|
let sources: [SourceJson]
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
case repoName = "name"
|
case repoName = "name"
|
||||||
|
|
@ -19,11 +19,40 @@ public struct SourceJson: Codable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct TorrentSourceJson: Codable, Hashable {
|
public struct SourceJson: Codable, Hashable {
|
||||||
let name: String?
|
let name: String
|
||||||
let url: String
|
let version: String
|
||||||
let rowQuery: String
|
let baseUrl: String
|
||||||
let linkQuery: String
|
let htmlParser: SourceHtmlParserJson?
|
||||||
let titleQuery: String?
|
}
|
||||||
let sizeQuery: String?
|
|
||||||
|
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 Base32
|
||||||
|
import Regex
|
||||||
import SwiftSoup
|
import SwiftSoup
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
|
@ -15,6 +16,8 @@ public struct SearchResult: Hashable, Codable {
|
||||||
let size: String
|
let size: String
|
||||||
let magnetLink: String
|
let magnetLink: String
|
||||||
let magnetHash: String?
|
let magnetHash: String?
|
||||||
|
let seeders: String?
|
||||||
|
let leechers: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScrapingViewModel: ObservableObject {
|
class ScrapingViewModel: ObservableObject {
|
||||||
|
|
@ -24,44 +27,48 @@ class ScrapingViewModel: ObservableObject {
|
||||||
var toastModel: ToastViewModel?
|
var toastModel: ToastViewModel?
|
||||||
|
|
||||||
@Published var searchResults: [SearchResult] = []
|
@Published var searchResults: [SearchResult] = []
|
||||||
@Published var debridHashes: [String] = []
|
|
||||||
@Published var searchText: String = ""
|
@Published var searchText: String = ""
|
||||||
@Published var selectedSearchResult: SearchResult?
|
@Published var selectedSearchResult: SearchResult?
|
||||||
@Published var filteredSource: TorrentSource?
|
@Published var filteredSource: Source?
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public func scanSources(sources: [TorrentSource]) async {
|
public func scanSources(sources: [Source]) async {
|
||||||
if sources.isEmpty {
|
if sources.isEmpty {
|
||||||
print("Sources empty")
|
print("Sources empty")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var tempResults: [SearchResult] = []
|
var tempResults: [SearchResult] = []
|
||||||
|
|
||||||
for source in sources {
|
for source in sources {
|
||||||
if source.enabled {
|
if source.enabled {
|
||||||
guard let html = await fetchWebsiteHtml(source: source) else {
|
if let htmlParser = source.htmlParser {
|
||||||
continue
|
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)
|
continue
|
||||||
tempResults += sourceResults
|
}
|
||||||
|
|
||||||
|
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
|
searchResults = tempResults
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetches the HTML body for the source website
|
// Fetches the HTML for a URL
|
||||||
@MainActor
|
@MainActor
|
||||||
public func fetchWebsiteHtml(source: TorrentSource) async -> String? {
|
public func fetchWebsiteHtml(urlString: String) async -> String? {
|
||||||
guard let encodedQuery = searchText.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else {
|
guard let url = URL(string: urlString) 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 {
|
|
||||||
toastModel?.toastDescription = "Source doesn't contain a valid URL, contact the source dev!"
|
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!")
|
print("Source doesn't contain a valid URL, contact the source dev!")
|
||||||
|
|
||||||
|
|
@ -83,58 +90,179 @@ class ScrapingViewModel: ObservableObject {
|
||||||
// Returns results to UI
|
// Returns results to UI
|
||||||
// Results must have a link and title, but other parameters aren't required
|
// Results must have a link and title, but other parameters aren't required
|
||||||
@MainActor
|
@MainActor
|
||||||
public func scrapeWebsite(source: TorrentSource, html: String) async -> [SearchResult] {
|
public func scrapeWebsite(source: Source, html: String) async -> [SearchResult] {
|
||||||
var tempResults: [SearchResult] = []
|
guard let htmlParser = source.htmlParser else {
|
||||||
var hashes: [String] = []
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
var rows = Elements()
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let document = try SwiftSoup.parse(html)
|
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 {
|
var tempResults: [SearchResult] = []
|
||||||
guard let link = try row.select(source.linkQuery).first() else {
|
|
||||||
|
// 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
|
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:") {
|
if !href.starts(with: "magnet:") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetches the magnet hash
|
||||||
let magnetHash = fetchMagnetHash(magnetLink: href)
|
let magnetHash = fetchMagnetHash(magnetLink: href)
|
||||||
|
|
||||||
|
// Fetches the episode/movie title
|
||||||
var title: String?
|
var title: String?
|
||||||
if let titleQuery = source.titleQuery {
|
if let titleParser = htmlParser.title {
|
||||||
title = try row.select(titleQuery).first()?.text()
|
title = try? runComplexQuery(
|
||||||
|
row: row,
|
||||||
|
query: titleParser.query,
|
||||||
|
attribute: titleParser.attribute,
|
||||||
|
regexString: titleParser.regex
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let size = try row.select(source.sizeQuery ?? "").first()
|
// Fetches the torrent's size
|
||||||
let sizeText = try size?.text()
|
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(
|
let result = SearchResult(
|
||||||
title: title ?? "No title",
|
title: title ?? "No title",
|
||||||
source: source.name ?? "N/A",
|
source: source.name,
|
||||||
size: sizeText ?? "?B",
|
size: size ?? "",
|
||||||
magnetLink: href,
|
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)
|
tempResults.append(result)
|
||||||
|
} catch {
|
||||||
|
toastModel?.toastDescription = "Scraping error: \(error)"
|
||||||
|
print("Scraping error: \(error)")
|
||||||
|
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return tempResults
|
return tempResults
|
||||||
} catch {
|
}
|
||||||
toastModel?.toastDescription = "Error while scraping: \(error)"
|
|
||||||
print("Error while scraping: \(error)")
|
|
||||||
|
|
||||||
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))
|
let decryptedMagnetHash = base32DecodeToData(String(magnetHash))
|
||||||
return decryptedMagnetHash?.hexEncodedString()
|
return decryptedMagnetHash?.hexEncodedString()
|
||||||
} else {
|
} else {
|
||||||
return String(magnetHash)
|
return String(magnetHash).lowercased()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,22 +5,23 @@
|
||||||
// Created by Brian Dashore on 7/25/22.
|
// Created by Brian Dashore on 7/25/22.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import CoreData
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public class SourceManager: ObservableObject {
|
public class SourceManager: ObservableObject {
|
||||||
var toastModel: ToastViewModel?
|
var toastModel: ToastViewModel?
|
||||||
|
|
||||||
@Published var availableSources: [TorrentSourceJson] = []
|
@Published var availableSources: [SourceJson] = []
|
||||||
|
|
||||||
@Published var urlErrorAlertText = ""
|
@Published var urlErrorAlertText = ""
|
||||||
@Published var showUrlErrorAlert = false
|
@Published var showUrlErrorAlert = false
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public func fetchSourcesFromUrl() async {
|
public func fetchSourcesFromUrl() async {
|
||||||
let sourceUrlRequest = TorrentSourceUrl.fetchRequest()
|
let sourceUrlRequest = SourceList.fetchRequest()
|
||||||
do {
|
do {
|
||||||
let sourceUrls = try PersistenceController.shared.backgroundContext.fetch(sourceUrlRequest)
|
let sourceUrls = try PersistenceController.shared.backgroundContext.fetch(sourceUrlRequest)
|
||||||
var tempSourceUrls: [TorrentSourceJson] = []
|
var tempSourceUrls: [SourceJson] = []
|
||||||
|
|
||||||
for sourceUrl in sourceUrls {
|
for sourceUrl in sourceUrls {
|
||||||
guard let url = URL(string: sourceUrl.urlString) else {
|
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 (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
|
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
|
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||||
|
|
||||||
// If a source exists, don't add the new one
|
// If a source exists, don't add the new one
|
||||||
if let name = sourceJson.name {
|
let existingSourceRequest = Source.fetchRequest()
|
||||||
let existingSourceRequest = TorrentSource.fetchRequest()
|
existingSourceRequest.predicate = NSPredicate(format: "name == %@", sourceJson.name)
|
||||||
existingSourceRequest.predicate = NSPredicate(format: "name == %@", name)
|
existingSourceRequest.fetchLimit = 1
|
||||||
existingSourceRequest.fetchLimit = 1
|
|
||||||
|
|
||||||
let existingSource = try? backgroundContext.fetch(existingSourceRequest).first
|
let existingSource = try? backgroundContext.fetch(existingSourceRequest).first
|
||||||
if existingSource != nil {
|
if existingSource != nil {
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
toastModel?.toastDescription = "Could not install source \(sourceJson.name ?? "Unknown source") because it is already installed."
|
toastModel?.toastDescription = "Could not install source with name \(sourceJson.name) because it is already installed."
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let newTorrentSource = TorrentSource(context: backgroundContext)
|
let newSource = Source(context: backgroundContext)
|
||||||
newTorrentSource.name = sourceJson.name
|
newSource.name = sourceJson.name
|
||||||
newTorrentSource.url = sourceJson.url
|
newSource.version = sourceJson.version
|
||||||
newTorrentSource.rowQuery = sourceJson.rowQuery
|
newSource.baseUrl = sourceJson.baseUrl
|
||||||
newTorrentSource.linkQuery = sourceJson.linkQuery
|
|
||||||
newTorrentSource.titleQuery = sourceJson.titleQuery
|
|
||||||
newTorrentSource.sizeQuery = sourceJson.sizeQuery
|
|
||||||
|
|
||||||
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 {
|
do {
|
||||||
try backgroundContext.save()
|
try backgroundContext.save()
|
||||||
|
|
@ -88,7 +134,7 @@ public class SourceManager: ObservableObject {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
let sourceUrlRequest = TorrentSourceUrl.fetchRequest()
|
let sourceUrlRequest = SourceList.fetchRequest()
|
||||||
sourceUrlRequest.predicate = NSPredicate(format: "urlString == %@", sourceUrl)
|
sourceUrlRequest.predicate = NSPredicate(format: "urlString == %@", sourceUrl)
|
||||||
sourceUrlRequest.fetchLimit = 1
|
sourceUrlRequest.fetchLimit = 1
|
||||||
|
|
||||||
|
|
@ -97,12 +143,12 @@ public class SourceManager: ObservableObject {
|
||||||
PersistenceController.shared.delete(existingSourceUrl, context: backgroundContext)
|
PersistenceController.shared.delete(existingSourceUrl, context: backgroundContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
let newSourceUrl = TorrentSourceUrl(context: backgroundContext)
|
let newSourceUrl = SourceList(context: backgroundContext)
|
||||||
newSourceUrl.urlString = sourceUrl
|
newSourceUrl.urlString = sourceUrl
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let (data, _) = try await URLSession.shared.data(for: URLRequest(url: URL(string: sourceUrl)!))
|
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
|
newSourceUrl.repoName = rawResponse.repoName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,11 @@ struct ContentView: View {
|
||||||
@AppStorage("RealDebrid.Enabled") var realDebridEnabled = false
|
@AppStorage("RealDebrid.Enabled") var realDebridEnabled = false
|
||||||
|
|
||||||
@FetchRequest(
|
@FetchRequest(
|
||||||
entity: TorrentSource.entity(),
|
entity: Source.entity(),
|
||||||
sortDescriptors: []
|
sortDescriptors: []
|
||||||
) var sources: FetchedResults<TorrentSource>
|
) var sources: FetchedResults<Source>
|
||||||
|
|
||||||
@State private var selectedSource: TorrentSource? {
|
@State private var selectedSource: Source? {
|
||||||
didSet {
|
didSet {
|
||||||
scrapingModel.filteredSource = selectedSource
|
scrapingModel.filteredSource = selectedSource
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,14 @@ struct SearchResultRDView: View {
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
if let seeders = result.seeders {
|
||||||
|
Text("S: \(seeders)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if let leechers = result.leechers {
|
||||||
|
Text("L: \(leechers)")
|
||||||
|
}
|
||||||
|
|
||||||
Text(result.size)
|
Text(result.size)
|
||||||
|
|
||||||
if realDebridEnabled {
|
if realDebridEnabled {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import SwiftUI
|
||||||
|
|
||||||
struct SettingsView: View {
|
struct SettingsView: View {
|
||||||
@EnvironmentObject var debridManager: DebridManager
|
@EnvironmentObject var debridManager: DebridManager
|
||||||
|
@EnvironmentObject var sourceManager: SourceManager
|
||||||
|
|
||||||
let backgroundContext = PersistenceController.shared.backgroundContext
|
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@ struct SettingsSourceListView: View {
|
||||||
let backgroundContext = PersistenceController.shared.backgroundContext
|
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||||
|
|
||||||
@FetchRequest(
|
@FetchRequest(
|
||||||
entity: TorrentSourceUrl.entity(),
|
entity: SourceList.entity(),
|
||||||
sortDescriptors: []
|
sortDescriptors: []
|
||||||
) var sourceUrls: FetchedResults<TorrentSourceUrl>
|
) var sourceUrls: FetchedResults<SourceList>
|
||||||
|
|
||||||
@State private var presentSourceSheet = false
|
@State private var presentSourceSheet = false
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,9 @@ struct SourceListView: View {
|
||||||
let backgroundContext = PersistenceController.shared.backgroundContext
|
let backgroundContext = PersistenceController.shared.backgroundContext
|
||||||
|
|
||||||
@FetchRequest(
|
@FetchRequest(
|
||||||
entity: TorrentSource.entity(),
|
entity: Source.entity(),
|
||||||
sortDescriptors: []
|
sortDescriptors: []
|
||||||
) var sources: FetchedResults<TorrentSource>
|
) var sources: FetchedResults<Source>
|
||||||
|
|
||||||
@State private var availableSourceLength = 0
|
@State private var availableSourceLength = 0
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@ struct SourceListView: View {
|
||||||
PersistenceController.shared.save()
|
PersistenceController.shared.save()
|
||||||
}
|
}
|
||||||
)) {
|
)) {
|
||||||
Text(source.name ?? "Unknown Source")
|
Text(source.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onDelete { offsets in
|
.onDelete { offsets in
|
||||||
|
|
@ -52,7 +52,7 @@ struct SourceListView: View {
|
||||||
ForEach(sourceManager.availableSources, id: \.self) { availableSource in
|
ForEach(sourceManager.availableSources, id: \.self) { availableSource in
|
||||||
if !sources.contains(where: { availableSource.name == $0.name }) {
|
if !sources.contains(where: { availableSource.name == $0.name }) {
|
||||||
HStack {
|
HStack {
|
||||||
Text(availableSource.name ?? "Unnamed source")
|
Text(availableSource.name)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue