Plugins: Add option to use YAML for lists

YAML is a modern configuration format which is human readable
and supports more conventions than JSON.

However, some people may still prefer to write in JSON, so add the
option for users to write legacy JSON if they want to and to provide
backwards compatability with older plugins.

Signed-off-by: kingbri <bdashore3@proton.me>
This commit is contained in:
kingbri 2023-04-01 23:11:30 -04:00
parent c8c7732575
commit 22bec5da52
3 changed files with 54 additions and 8 deletions

View file

@ -72,6 +72,7 @@
0C70E40228C3CE9C00A5C72D /* ConditionalContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C70E40128C3CE9C00A5C72D /* ConditionalContextMenu.swift */; }; 0C70E40228C3CE9C00A5C72D /* ConditionalContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C70E40128C3CE9C00A5C72D /* ConditionalContextMenu.swift */; };
0C70E40628C40C4E00A5C72D /* NotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C70E40528C40C4E00A5C72D /* NotificationCenter.swift */; }; 0C70E40628C40C4E00A5C72D /* NotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C70E40528C40C4E00A5C72D /* NotificationCenter.swift */; };
0C733287289C4C820058D1FE /* SourceSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C733286289C4C820058D1FE /* SourceSettingsView.swift */; }; 0C733287289C4C820058D1FE /* SourceSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C733286289C4C820058D1FE /* SourceSettingsView.swift */; };
0C748EDA29D9256D0049B8BE /* Yams in Frameworks */ = {isa = PBXBuildFile; productRef = 0C748ED929D9256D0049B8BE /* Yams */; };
0C7506D728B1AC9A008BEE38 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 0C7506D628B1AC9A008BEE38 /* SwiftyJSON */; }; 0C7506D728B1AC9A008BEE38 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 0C7506D628B1AC9A008BEE38 /* SwiftyJSON */; };
0C750744289B003E004B3906 /* SourceRssParser+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C750742289B003E004B3906 /* SourceRssParser+CoreDataClass.swift */; }; 0C750744289B003E004B3906 /* SourceRssParser+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C750742289B003E004B3906 /* SourceRssParser+CoreDataClass.swift */; };
0C750745289B003E004B3906 /* SourceRssParser+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C750743289B003E004B3906 /* SourceRssParser+CoreDataProperties.swift */; }; 0C750745289B003E004B3906 /* SourceRssParser+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C750743289B003E004B3906 /* SourceRssParser+CoreDataProperties.swift */; };
@ -299,6 +300,7 @@
0C4CFC462897030D00AD9FAD /* Regex in Frameworks */, 0C4CFC462897030D00AD9FAD /* Regex in Frameworks */,
0C64A4B7288903880079976D /* KeychainSwift in Frameworks */, 0C64A4B7288903880079976D /* KeychainSwift in Frameworks */,
0CAF1C7B286F5C8600296F86 /* SwiftSoup in Frameworks */, 0CAF1C7B286F5C8600296F86 /* SwiftSoup in Frameworks */,
0C748EDA29D9256D0049B8BE /* Yams in Frameworks */,
0CDDDE052935235E006810B1 /* BetterSafariView in Frameworks */, 0CDDDE052935235E006810B1 /* BetterSafariView in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -688,6 +690,7 @@
0C7506D628B1AC9A008BEE38 /* SwiftyJSON */, 0C7506D628B1AC9A008BEE38 /* SwiftyJSON */,
0CDDDE042935235E006810B1 /* BetterSafariView */, 0CDDDE042935235E006810B1 /* BetterSafariView */,
0C448BE829A135F100F4E266 /* Introspect-Static */, 0C448BE829A135F100F4E266 /* Introspect-Static */,
0C748ED929D9256D0049B8BE /* Yams */,
); );
productName = Torrenter; productName = Torrenter;
productReference = 0CAF1C68286F5C0E00296F86 /* Ferrite.app */; productReference = 0CAF1C68286F5C0E00296F86 /* Ferrite.app */;
@ -725,6 +728,7 @@
0C7506D528B1AC9A008BEE38 /* XCRemoteSwiftPackageReference "SwiftyJSON" */, 0C7506D528B1AC9A008BEE38 /* XCRemoteSwiftPackageReference "SwiftyJSON" */,
0CDDDE032935235E006810B1 /* XCRemoteSwiftPackageReference "BetterSafariView" */, 0CDDDE032935235E006810B1 /* XCRemoteSwiftPackageReference "BetterSafariView" */,
0C448BE729A135F100F4E266 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */, 0C448BE729A135F100F4E266 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */,
0C748ED829D9256D0049B8BE /* XCRemoteSwiftPackageReference "Yams" */,
); );
productRefGroup = 0CAF1C69286F5C0E00296F86 /* Products */; productRefGroup = 0CAF1C69286F5C0E00296F86 /* Products */;
projectDirPath = ""; projectDirPath = "";
@ -1152,6 +1156,14 @@
kind = branch; kind = branch;
}; };
}; };
0C748ED829D9256D0049B8BE /* XCRemoteSwiftPackageReference "Yams" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/jpsim/Yams";
requirement = {
kind = upToNextMinorVersion;
minimumVersion = 5.0.5;
};
};
0C7506D528B1AC9A008BEE38 /* XCRemoteSwiftPackageReference "SwiftyJSON" */ = { 0C7506D528B1AC9A008BEE38 /* XCRemoteSwiftPackageReference "SwiftyJSON" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/SwiftyJSON/SwiftyJSON"; repositoryURL = "https://github.com/SwiftyJSON/SwiftyJSON";
@ -1199,6 +1211,11 @@
package = 0C64A4B5288903880079976D /* XCRemoteSwiftPackageReference "keychain-swift" */; package = 0C64A4B5288903880079976D /* XCRemoteSwiftPackageReference "keychain-swift" */;
productName = KeychainSwift; productName = KeychainSwift;
}; };
0C748ED929D9256D0049B8BE /* Yams */ = {
isa = XCSwiftPackageProductDependency;
package = 0C748ED829D9256D0049B8BE /* XCRemoteSwiftPackageReference "Yams" */;
productName = Yams;
};
0C7506D628B1AC9A008BEE38 /* SwiftyJSON */ = { 0C7506D628B1AC9A008BEE38 /* SwiftyJSON */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = 0C7506D528B1AC9A008BEE38 /* XCRemoteSwiftPackageReference "SwiftyJSON" */; package = 0C7506D528B1AC9A008BEE38 /* XCRemoteSwiftPackageReference "SwiftyJSON" */;

View file

@ -29,6 +29,7 @@ extension PluginManager {
enum PluginManagerError: Error { enum PluginManagerError: Error {
case ListAddition(description: String) case ListAddition(description: String)
case ActionAddition(description: String) case ActionAddition(description: String)
case PluginFetch(description: String)
} }
struct AvailablePlugins { struct AvailablePlugins {

View file

@ -7,6 +7,7 @@
import Foundation import Foundation
import SwiftUI import SwiftUI
import Yams
public class PluginManager: ObservableObject { public class PluginManager: ObservableObject {
var logManager: LoggingManager? var logManager: LoggingManager?
@ -102,7 +103,18 @@ public class PluginManager: ObservableObject {
let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData) let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData)
let (data, _) = try await URLSession.shared.data(for: request) let (data, _) = try await URLSession.shared.data(for: request)
let pluginResponse = try JSONDecoder().decode(PluginListJson.self, from: data) let pluginResponse: PluginListJson?
// If the URL is a yaml file, decode as such. Otherwise assume legacy JSON
if url.pathExtension == "yaml" || url.pathExtension == "yml" {
pluginResponse = try YAMLDecoder().decode(PluginListJson.self, from: data)
} else {
pluginResponse = try JSONDecoder().decode(PluginListJson.self, from: data)
}
guard let pluginResponse else {
throw PluginManagerError.PluginFetch(description: "Could not decode plugin list data")
}
if let sources = pluginResponse.sources { if let sources = pluginResponse.sources {
// Faster and more performant to map instead of a for loop // Faster and more performant to map instead of a for loop
@ -748,25 +760,41 @@ public class PluginManager: ObservableObject {
// Adds a plugin list // Adds a plugin list
// Can move this to PersistenceController if needed // Can move this to PersistenceController if needed
public func addPluginList(_ url: String, isSheet: Bool = false, existingPluginList: PluginList? = nil) async throws { public func addPluginList(_ urlString: String, isSheet: Bool = false, existingPluginList: PluginList? = nil) async throws {
let backgroundContext = PersistenceController.shared.backgroundContext let backgroundContext = PersistenceController.shared.backgroundContext
if url.isEmpty || !url.starts(with: "https://") && !url.starts(with: "http://") { if urlString.isEmpty || !urlString.starts(with: "https://") && !urlString.starts(with: "http://") {
throw PluginManagerError.ListAddition(description: "The provided source list is invalid. Please check if the URL is formatted properly.") throw PluginManagerError.ListAddition(description: "The provided source list is invalid. Please check if the URL is formatted properly.")
} }
let (data, _) = try await URLSession.shared.data(for: URLRequest(url: URL(string: url)!)) guard let url = URL(string: urlString) else {
let rawResponse = try JSONDecoder().decode(PluginListJson.self, from: data) throw PluginManagerError.ListAddition(description: "The provided source list is invalid. Please check if the URL is formatted properly.")
}
let (data, _) = try await URLSession.shared.data(for: URLRequest(url: url))
let rawResponse: PluginListJson?
// If the URL is a yaml file, decode as such. Otherwise assume legacy JSON
if url.pathExtension == "yaml" || url.pathExtension == "yml" {
rawResponse = try YAMLDecoder().decode(PluginListJson.self, from: data)
} else {
rawResponse = try JSONDecoder().decode(PluginListJson.self, from: data)
}
guard let rawResponse else {
throw PluginManagerError.ListAddition(description: "Could not decode the plugin list from URL \(urlString)")
}
if let existingPluginList { if let existingPluginList {
existingPluginList.urlString = url existingPluginList.urlString = urlString
existingPluginList.name = rawResponse.name existingPluginList.name = rawResponse.name
existingPluginList.author = rawResponse.author existingPluginList.author = rawResponse.author
try PersistenceController.shared.container.viewContext.save() try PersistenceController.shared.container.viewContext.save()
} else { } else {
let pluginListRequest = PluginList.fetchRequest() let pluginListRequest = PluginList.fetchRequest()
let urlPredicate = NSPredicate(format: "urlString == %@", url) let urlPredicate = NSPredicate(format: "urlString == %@", urlString)
let infoPredicate = NSPredicate(format: "author == %@ AND name == %@", rawResponse.author, rawResponse.name) let infoPredicate = NSPredicate(format: "author == %@ AND name == %@", rawResponse.author, rawResponse.name)
pluginListRequest.predicate = NSCompoundPredicate(type: .or, subpredicates: [urlPredicate, infoPredicate]) pluginListRequest.predicate = NSCompoundPredicate(type: .or, subpredicates: [urlPredicate, infoPredicate])
pluginListRequest.fetchLimit = 1 pluginListRequest.fetchLimit = 1
@ -779,7 +807,7 @@ public class PluginManager: ObservableObject {
let newPluginList = PluginList(context: backgroundContext) let newPluginList = PluginList(context: backgroundContext)
newPluginList.id = UUID() newPluginList.id = UUID()
newPluginList.urlString = url newPluginList.urlString = urlString
newPluginList.name = rawResponse.name newPluginList.name = rawResponse.name
newPluginList.author = rawResponse.author newPluginList.author = rawResponse.author