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:
parent
20c55316b0
commit
69d2f6babe
9 changed files with 137 additions and 103 deletions
|
|
@ -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 */,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())")
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in a new issue