Actions: Add OS-specific checks

Plugin lists are universal across clients and OS-specific cases
should be appropriately handled.

OSes can now be added to the deeplink action case and Ferrite scans
for either iOS-specific or fallthrough actions.

There must be 1 action that corresponds to an OS and/or 1 fallback
case, otherwise the action will not show in the plugins list.

Also remove some extraneous files.

Signed-off-by: kingbri <bdashore3@proton.me>
This commit is contained in:
kingbri 2023-03-19 21:24:24 -04:00
parent 20c55316b0
commit 69d2f6babe
9 changed files with 137 additions and 103 deletions

View file

@ -52,7 +52,6 @@
0C50B7D0299DF63C00A9FA3C /* UIDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C50B7CF299DF63C00A9FA3C /* UIDevice.swift */; };
0C54D36328C5086E00BFEEE2 /* History+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C54D36128C5086E00BFEEE2 /* History+CoreDataClass.swift */; };
0C54D36428C5086E00BFEEE2 /* History+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C54D36228C5086E00BFEEE2 /* History+CoreDataProperties.swift */; };
0C5708E929B8E61C00BE07F9 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C5708E829B8E61C00BE07F9 /* Logger.swift */; };
0C5708EB29B8F89300BE07F9 /* SettingsLogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C5708EA29B8F89300BE07F9 /* SettingsLogView.swift */; };
0C572D4C2993FC2A003EEC05 /* ViewDidAppearHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C572D4B2993FC2A003EEC05 /* ViewDidAppearHandler.swift */; };
0C572D4E299403B7003EEC05 /* ViewDidAppear.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C572D4D299403B7003EEC05 /* ViewDidAppear.swift */; };
@ -188,7 +187,6 @@
0C50B7CF299DF63C00A9FA3C /* UIDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIDevice.swift; sourceTree = "<group>"; };
0C54D36128C5086E00BFEEE2 /* History+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "History+CoreDataClass.swift"; sourceTree = "<group>"; };
0C54D36228C5086E00BFEEE2 /* History+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "History+CoreDataProperties.swift"; sourceTree = "<group>"; };
0C5708E829B8E61C00BE07F9 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
0C5708EA29B8F89300BE07F9 /* SettingsLogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsLogView.swift; sourceTree = "<group>"; };
0C572D4B2993FC2A003EEC05 /* ViewDidAppearHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewDidAppearHandler.swift; sourceTree = "<group>"; };
0C572D4D299403B7003EEC05 /* ViewDidAppear.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewDidAppear.swift; sourceTree = "<group>"; };
@ -420,7 +418,6 @@
isa = PBXGroup;
children = (
0C44E2A728D4DDDC007711AE /* Application.swift */,
0C5708E829B8E61C00BE07F9 /* Logger.swift */,
);
path = Classes;
sourceTree = "<group>";
@ -788,7 +785,6 @@
0C5005522992B6750064606A /* PluginTagsView.swift in Sources */,
0CB0AB5F29BD2A200015422C /* KodiServerView.swift in Sources */,
0CE66B3A28E640D200F69346 /* Backport.swift in Sources */,
0C5708E929B8E61C00BE07F9 /* Logger.swift in Sources */,
0C42B5962932F2D5008057A0 /* DebridPickerView.swift in Sources */,
0C2886D72960C50900D6FC16 /* RealDebridCloudView.swift in Sources */,
0C3DD44229B6ACD9006429DB /* KodiServer+CoreDataClass.swift in Sources */,

View file

@ -12,8 +12,16 @@ import Foundation
public class Application {
static let shared = Application()
// OS name for Plugins to read. Lowercase for ease of use
let os = "ios"
// Minimum OS version that Ferrite runs on
let minVersion = OperatingSystemVersion(majorVersion: 14, minorVersion: 0, patchVersion: 0)
// Grabs the current user's OS version
let osVersion: OperatingSystemVersion = ProcessInfo().operatingSystemVersion
// Application version and build variables
var appVersion: String {
Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "0.0.0"
}
@ -34,6 +42,4 @@ public class Application {
}
#endif
}
let osVersion: OperatingSystemVersion = ProcessInfo().operatingSystemVersion
}

View file

@ -1,65 +0,0 @@
//
// Logger.swift
// Ferrite
//
// Created by Brian Dashore on 3/8/23.
//
import Foundation
public class Logger {
var messageArray: [Log] = []
struct Log: Hashable {
let level: LogLevel
let description: String
let timeStamp: Date = .init()
func toMessage() -> String {
"[\(level.rawValue)]: \(description)"
}
}
enum LogLevel: String, Identifiable {
var id: Int {
hashValue
}
case info = "INFO"
case warn = "WARN"
case error = "ERROR"
}
func info(_ message: String) {
let log = Log(
level: .info,
description: message
)
messageArray.append(log)
print("LOG: \(log.toMessage())")
}
func warn(_ message: String) {
let log = Log(
level: .warn,
description: message
)
messageArray.append(log)
print("LOG: \(log.toMessage())")
}
func error(_ message: String) {
let log = Log(
level: .error,
description: message
)
messageArray.append(log)
print("LOG: \(log.toMessage())")
}
}

View file

@ -12,10 +12,73 @@ public struct ActionJson: Codable, Hashable, PluginJson {
public let version: Int16
let minVersion: String?
let requires: [ActionRequirement]
let deeplink: String?
public var author: String?
public var listId: UUID?
public var tags: [PluginTagJson]?
let deeplink: [DeeplinkActionJson]?
public let author: String?
public let listId: UUID?
public let tags: [PluginTagJson]?
public init(
name: String,
version: Int16,
minVersion: String?,
requires: [ActionRequirement],
deeplink: [DeeplinkActionJson]?,
author: String?,
listId: UUID?,
tags: [PluginTagJson]?
) {
self.name = name
self.version = version
self.minVersion = minVersion
self.requires = requires
self.deeplink = deeplink
self.author = author
self.listId = listId
self.tags = tags
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.version = try container.decode(Int16.self, forKey: .version)
self.minVersion = try container.decodeIfPresent(String.self, forKey: .minVersion)
self.requires = try container.decode([ActionRequirement].self, forKey: .requires)
self.author = try container.decodeIfPresent(String.self, forKey: .author)
self.listId = try container.decodeIfPresent(UUID.self, forKey: .listId)
self.tags = try container.decodeIfPresent([PluginTagJson].self, forKey: .tags)
if let deeplinkString = try? container.decode(String.self, forKey: .deeplink) {
self.deeplink = [DeeplinkActionJson(os: [], scheme: deeplinkString)]
} else if let deeplinkAction = try? container.decode([DeeplinkActionJson].self, forKey: .deeplink) {
self.deeplink = deeplinkAction
} else {
self.deeplink = nil
}
}
}
public struct DeeplinkActionJson: Codable, Hashable {
let os: [String]
let scheme: String
init(os: [String], scheme: String) {
self.os = os
self.scheme = scheme
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let os = try? container.decode(String.self, forKey: .os) {
self.os = [os]
} else if let os = try? container.decode([String].self, forKey: .os) {
self.os = os
} else {
self.os = []
}
self.scheme = try container.decode(String.self, forKey: .scheme)
}
}
public extension ActionJson {

View file

@ -28,6 +28,7 @@ public struct PluginTagJson: Codable, Hashable, Sendable {
extension PluginManager {
enum PluginManagerError: Error {
case ListAddition(description: String)
case ActionAddition(description: String)
}
struct AvailablePlugins {

View file

@ -24,9 +24,9 @@ public struct SourceJson: Codable, Hashable, Sendable, PluginJson {
let jsonParser: SourceJsonParserJson?
let rssParser: SourceRssParserJson?
let htmlParser: SourceHtmlParserJson?
public var author: String?
public var listId: UUID?
public var tags: [PluginTagJson]?
public let author: String?
public let listId: UUID?
public let tags: [PluginTagJson]?
}
public extension SourceJson {

View file

@ -28,8 +28,8 @@ extension Plugin {
public protocol PluginJson: Hashable {
var name: String { get }
var version: Int16 { get }
var author: String? { get set }
var listId: UUID? { get set }
var tags: [PluginTagJson]? { get set }
var author: String? { get }
var listId: UUID? { get }
var tags: [PluginTagJson]? { get }
func getTags() -> [PluginTagJson]
}

View file

@ -52,7 +52,7 @@ class LoggingManager: ObservableObject {
@Published var showToast: Bool = false
// Default the toast type to error since the majority of toasts are errors
@Published var toastType: Logger.LogLevel = .error
@Published var toastType: LogLevel = .error
var showErrorToasts: Bool {
UserDefaults.standard.bool(forKey: "Debug.ShowErrorToasts")
}

View file

@ -132,13 +132,17 @@ public class PluginManager: ObservableObject {
if let actions = pluginResponse.actions {
tempActions += actions.compactMap { inputJson in
if checkAppVersion(minVersion: inputJson.minVersion) {
if
let deeplink = inputJson.deeplink,
checkAppVersion(minVersion: inputJson.minVersion),
let filteredDeeplinks = getFilteredDeeplinks(deeplink)
{
return ActionJson(
name: inputJson.name,
version: inputJson.version,
minVersion: inputJson.minVersion,
requires: inputJson.requires,
deeplink: inputJson.deeplink,
deeplink: filteredDeeplinks,
author: pluginList.author,
listId: pluginList.id,
tags: inputJson.tags
@ -152,6 +156,27 @@ public class PluginManager: ObservableObject {
return AvailablePlugins(availableSources: tempSources, availableActions: tempActions)
}
// Checks if a deeplink action is present and if there's a single action for the OS (or fallback)
func getFilteredDeeplinks(_ deeplinks: [DeeplinkActionJson]) -> [DeeplinkActionJson]? {
let osArray = deeplinks.filter { deeplink in
deeplink.os.contains(where: { $0.lowercased() == Application.shared.os.lowercased() })
}
if osArray.count == 1 {
return osArray
} else {
let universalArray = deeplinks.filter { deeplink in
deeplink.os.isEmpty
}
if universalArray.count == 1 {
return universalArray
} else {
return nil
}
}
}
// forType required to guide generic inferences
func fetchFilteredPlugins<PJ: PluginJson>(forType: PJ.Type,
installedPlugins: FetchedResults<some Plugin>,
@ -163,8 +188,8 @@ public class PluginManager: ObservableObject {
.filter { availablePlugin in
let pluginExists = installedPlugins.contains(where: {
availablePlugin.name == $0.name &&
availablePlugin.listId == $0.listId &&
availablePlugin.author == $0.author
availablePlugin.listId == $0.listId &&
availablePlugin.author == $0.author
})
if searchText.isEmpty {
@ -185,10 +210,10 @@ public class PluginManager: ObservableObject {
for plugin in installedPlugins {
if let availablePlugin = availablePlugins.first(where: {
plugin.listId == $0.listId &&
plugin.name == $0.name &&
plugin.author == $0.author
plugin.name == $0.name &&
plugin.author == $0.author
}),
availablePlugin.version > plugin.version
availablePlugin.version > plugin.version
{
updatedPlugins.append(availablePlugin)
}
@ -252,8 +277,8 @@ public class PluginManager: ObservableObject {
UserDefaults.standard.set(nil, forKey: "Action.DefaultDebridList")
actionErrorAlertMessage =
"The default action could not be run. The action choice sheet has been opened. \n\n" +
"Please check your default actions in Settings"
"The default action could not be run. The action choice sheet has been opened. \n\n" +
"Please check your default actions in Settings"
showActionErrorAlert.toggle()
}
} else {
@ -281,8 +306,8 @@ public class PluginManager: ObservableObject {
UserDefaults.standard.set(nil, forKey: "Actions.DefaultMagnetList")
actionErrorAlertMessage =
"The default action could not be run. The action choice sheet has been opened. \n\n" +
"Please check your default actions in Settings"
"The default action could not be run. The action choice sheet has been opened. \n\n" +
"Please check your default actions in Settings"
showActionErrorAlert.toggle()
}
} else {
@ -296,9 +321,9 @@ public class PluginManager: ObservableObject {
guard let deeplink = action.deeplink, let urlString else {
actionErrorAlertMessage = "Could not run action: \(action.name) since there is no deeplink to execute. Contact the action dev!"
showActionErrorAlert.toggle()
print("Could not run action: \(action.name) since there is no deeplink to execute.")
logManager?.error("Could not run action: \(action.name) since there is no deeplink to execute.")
return
}
@ -309,8 +334,8 @@ public class PluginManager: ObservableObject {
} else {
actionErrorAlertMessage = "Could not run action: \(action.name) because the created deeplink was invalid. Contact the action dev!"
showActionErrorAlert.toggle()
print("Could not run action: \(action.name) because the created deeplink (\(String(describing: playbackUrl))) was invalid")
logManager?.error("Could not run action: \(action.name) because the created deeplink (\(String(describing: playbackUrl))) was invalid")
}
}
@ -320,7 +345,7 @@ public class PluginManager: ObservableObject {
actionErrorAlertMessage = "Could not send URL to Kodi since there is no playback URL to send"
showActionErrorAlert.toggle()
print("Could not send URL to Kodi since there is no playback URL to send")
logManager?.error("Kodi action: Could not send URL to Kodi since there is no playback URL to send")
return
}
@ -330,11 +355,13 @@ public class PluginManager: ObservableObject {
actionSuccessAlertMessage = "Your URL should be playing on Kodi"
showActionSuccessAlert.toggle()
logManager?.info("URL \(urlString) is playing on Kodi")
} catch {
actionErrorAlertMessage = "Kodi Error: \(error)"
showActionErrorAlert.toggle()
print("Kodi action error: \(error)")
logManager?.error("Kodi action: \(error)")
}
}
@ -351,7 +378,7 @@ public class PluginManager: ObservableObject {
return
}
guard let deeplink = actionJson.deeplink else {
guard let deeplinks = actionJson.deeplink else {
await logManager?.error("Action addition: only deeplink actions can be added to Ferrite iOS. Please contact the action dev!")
return
}
@ -389,7 +416,13 @@ public class PluginManager: ObservableObject {
}
}
newAction.deeplink = deeplink
// Only one deeplink is left in this action JSON because of the previous filtering logic
guard let deeplinkJson = deeplinks.first else {
await logManager?.error("Action addition: No deeplink was present in action with name \(actionJson.name). Contact the action dev!")
return
}
newAction.deeplink = deeplinkJson.scheme
do {
try backgroundContext.save()