Ferrite-backup/Ferrite/ViewModels/SourceManager.swift
kingbri b7c9c75e3b 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>
2022-08-04 21:33:59 -04:00

165 lines
6 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 sourceUrlRequest = SourceList.fetchRequest()
do {
let sourceUrls = try PersistenceController.shared.backgroundContext.fetch(sourceUrlRequest)
var tempSourceUrls: [SourceJson] = []
for sourceUrl in sourceUrls {
guard let url = URL(string: sourceUrl.urlString) else {
return
}
let (data, _) = try await URLSession.shared.data(for: URLRequest(url: url))
let sourceResponse = try JSONDecoder().decode(SourceListJson.self, from: data)
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
// 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()
} catch {
Task { @MainActor in
toastModel?.toastDescription = error.localizedDescription
}
}
}
@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
}
let sourceUrlRequest = SourceList.fetchRequest()
sourceUrlRequest.predicate = NSPredicate(format: "urlString == %@", sourceUrl)
sourceUrlRequest.fetchLimit = 1
if let existingSourceUrl = try? backgroundContext.fetch(sourceUrlRequest).first {
print("Existing source URL found")
PersistenceController.shared.delete(existingSourceUrl, 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(SourceListJson.self, from: data) {
newSourceUrl.repoName = rawResponse.repoName
}
try backgroundContext.save()
return true
} catch {
urlErrorAlertText = error.localizedDescription
showUrlErrorAlert.toggle()
return false
}
}
}