Ferrite-backup/Ferrite/ViewModels/SourceManager.swift
kingbri efecfa3236 Sources: Add RSS, descriptions, and settings
RSS parsing has been added as a method to parse source since they're
easier on the website's end to parse.

Source settings have been added. The only current setting is the fetch
mode which selects which parser/scraper to use. By default, if an RSS
parser is found, it's selected.

A source now has info shown regarding versioning and authorship. A source
list's repository name and author string are now required.

Signed-off-by: kingbri <bdashore3@gmail.com>
2022-08-05 11:17:51 -04:00

257 lines
10 KiB
Swift

//
// SourceViewModel.swift
// Ferrite
//
// Created by Brian Dashore on 7/25/22.
//
import CoreData
import Foundation
public class SourceManager: ObservableObject {
var toastModel: ToastViewModel?
@Published var availableSources: [SourceJson] = []
@Published var urlErrorAlertText = ""
@Published var showUrlErrorAlert = false
@MainActor
public func fetchSourcesFromUrl() async {
let sourceListRequest = SourceList.fetchRequest()
do {
let sourceLists = try PersistenceController.shared.backgroundContext.fetch(sourceListRequest)
var tempSourceUrls: [SourceJson] = []
for sourceList in sourceLists {
guard let url = URL(string: sourceList.urlString) else {
return
}
let (data, _) = try await URLSession.shared.data(for: URLRequest(url: url))
var sourceResponse = try JSONDecoder().decode(SourceListJson.self, from: data)
for index in sourceResponse.sources.indices {
sourceResponse.sources[index].author = sourceList.author
}
tempSourceUrls += sourceResponse.sources
}
availableSources = tempSourceUrls
} catch {
print(error)
}
}
public func installSource(sourceJson: SourceJson) {
let backgroundContext = PersistenceController.shared.backgroundContext
// If a source exists, don't add the new one
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 with name \(sourceJson.name) because it is already installed."
}
return
}
let newSource = Source(context: backgroundContext)
newSource.name = sourceJson.name
newSource.version = sourceJson.version
newSource.baseUrl = sourceJson.baseUrl
newSource.author = sourceJson.author
// Adds an RSS parser if present
if let rssParserJson = sourceJson.rssParser {
addRssParser(newSource: newSource, rssParserJson: rssParserJson)
}
// Adds an HTML parser if present
if let htmlParserJson = sourceJson.htmlParser {
addHtmlParser(newSource: newSource, htmlParserJson: htmlParserJson)
}
// Add an API condition as well
if newSource.rssParser != nil {
newSource.preferredParser = Int16(SourcePreferredParser.rss.rawValue)
} else {
newSource.preferredParser = Int16(SourcePreferredParser.scraping.rawValue)
}
newSource.enabled = true
do {
try backgroundContext.save()
} catch {
Task { @MainActor in
toastModel?.toastDescription = error.localizedDescription
}
}
}
func addRssParser(newSource: Source, rssParserJson: SourceRssParserJson) {
let backgroundContext = PersistenceController.shared.backgroundContext
let newSourceRssParser = SourceRssParser(context: backgroundContext)
newSourceRssParser.rssUrl = rssParserJson.rssUrl
newSourceRssParser.searchUrl = rssParserJson.searchUrl
newSourceRssParser.items = rssParserJson.items
if let magnetLinkJson = rssParserJson.magnetLink {
let newSourceMagnetLink = SourceMagnetLink(context: backgroundContext)
newSourceMagnetLink.query = magnetLinkJson.query
newSourceMagnetLink.attribute = magnetLinkJson.attribute ?? "text"
newSourceMagnetLink.lookupAttribute = magnetLinkJson.lookupAttribute
newSourceRssParser.magnetLink = newSourceMagnetLink
}
if let magnetHashJson = rssParserJson.magnetHash {
let newSourceMagnetHash = SourceMagnetHash(context: backgroundContext)
newSourceMagnetHash.query = magnetHashJson.query
newSourceMagnetHash.attribute = magnetHashJson.attribute ?? "text"
newSourceMagnetHash.lookupAttribute = magnetHashJson.lookupAttribute
newSourceRssParser.magnetHash = newSourceMagnetHash
}
if let titleJson = rssParserJson.title {
let newSourceTitle = SourceTitle(context: backgroundContext)
newSourceTitle.query = titleJson.query
newSourceTitle.attribute = titleJson.attribute ?? "text"
newSourceTitle.lookupAttribute = newSourceTitle.lookupAttribute
newSourceRssParser.title = newSourceTitle
}
if let sizeJson = rssParserJson.size {
let newSourceSize = SourceSize(context: backgroundContext)
newSourceSize.query = sizeJson.query
newSourceSize.attribute = sizeJson.attribute ?? "text"
newSourceSize.lookupAttribute = sizeJson.lookupAttribute
newSourceRssParser.size = newSourceSize
}
if let seedLeechJson = rssParserJson.sl {
let newSourceSeedLeech = SourceSeedLeech(context: backgroundContext)
newSourceSeedLeech.seeders = seedLeechJson.seeders
newSourceSeedLeech.leechers = seedLeechJson.leechers
newSourceSeedLeech.combined = seedLeechJson.combined
newSourceSeedLeech.attribute = seedLeechJson.attribute ?? "text"
newSourceSeedLeech.lookupAttribute = seedLeechJson.lookupAttribute
newSourceSeedLeech.seederRegex = seedLeechJson.seederRegex
newSourceSeedLeech.leecherRegex = seedLeechJson.leecherRegex
newSourceRssParser.seedLeech = newSourceSeedLeech
}
if let trackerJson = rssParserJson.trackers {
for urlString in trackerJson {
let newSourceTracker = SourceTracker(context: backgroundContext)
newSourceTracker.urlString = urlString
newSourceTracker.parentRssParser = newSourceRssParser
}
}
newSource.rssParser = newSourceRssParser
}
func addHtmlParser(newSource: Source, htmlParserJson: SourceHtmlParserJson) {
let backgroundContext = PersistenceController.shared.backgroundContext
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 ?? "text"
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 ?? "text"
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 ?? "text"
newSourceSeedLeech.seederRegex = seedLeechJson.seederRegex
newSourceSeedLeech.leecherRegex = seedLeechJson.leecherRegex
newSourceHtmlParser.seedLeech = newSourceSeedLeech
}
// Adds a magnet complex query and its unique properties
let newSourceMagnet = SourceMagnetLink(context: backgroundContext)
newSourceMagnet.externalLinkQuery = htmlParserJson.magnet.externalLinkQuery
newSourceMagnet.query = htmlParserJson.magnet.query
newSourceMagnet.attribute = htmlParserJson.magnet.attribute
newSourceMagnet.regex = htmlParserJson.magnet.regex
newSourceHtmlParser.magnetLink = newSourceMagnet
newSource.htmlParser = newSourceHtmlParser
}
@MainActor
public func addSourceList(sourceUrl: String) async -> Bool {
let backgroundContext = PersistenceController.shared.backgroundContext
if sourceUrl.isEmpty || URL(string: sourceUrl) == nil {
urlErrorAlertText = "The provided source list is invalid. Please check if the URL is formatted properly."
showUrlErrorAlert.toggle()
return false
}
do {
let (data, _) = try await URLSession.shared.data(for: URLRequest(url: URL(string: sourceUrl)!))
let rawResponse = try JSONDecoder().decode(SourceListJson.self, from: data)
let sourceListRequest = SourceList.fetchRequest()
sourceListRequest.predicate = NSPredicate(format: "urlString == %@ OR author == %@", sourceUrl, rawResponse.author)
sourceListRequest.fetchLimit = 1
if (try? backgroundContext.fetch(sourceListRequest).first) != nil {
urlErrorAlertText = "A source with the same URL or author exists. Please remove it and try again."
showUrlErrorAlert.toggle()
return false
}
let newSourceUrl = SourceList(context: backgroundContext)
newSourceUrl.urlString = sourceUrl
newSourceUrl.name = rawResponse.name
newSourceUrl.author = rawResponse.author
try backgroundContext.save()
return true
} catch {
urlErrorAlertText = error.localizedDescription
showUrlErrorAlert.toggle()
return false
}
}
}