Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build-plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ jobs:
Plugin release for TablePro (PluginKit $PKV).

### Installation
TablePro will prompt you to install this plugin automatically when you select the database type. You can also install manually via Settings > Plugins > Browse.
TablePro offers to install this plugin where it is needed. To install it by hand, add the zip below in Settings > Plugins.

### SHA-256
- ARM64: \`$ARM64_SHA\`
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Interface, Sidebar and Toolbar color groups from the theme editor and the theme file.
- Fonts from the theme file.
- Loading plugins signed by other developers, and Trusted Plugin Developers in Settings > Plugins.
- Browse tab and plugin download counts in Settings > Plugins.
- On/off switch for each plugin in Settings > Plugins.
- Theme install from the plugin registry.

### Fixed

Expand Down
5 changes: 0 additions & 5 deletions TablePro/Core/Plugins/PluginError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ enum PluginError: LocalizedError {
case incompatibleWithCurrentApp(minimumRequired: String)
case invalidDescriptor(pluginId: String, reason: String)
case unknownDatabaseType(String)
case pluginDisabled(pluginId: String, pluginName: String)
case pluginLoadFailed(pluginId: String?, pluginName: String, reason: String?)
case pluginInstallFailed(databaseType: String, reason: String)

Expand Down Expand Up @@ -70,8 +69,6 @@ enum PluginError: LocalizedError {
return String(format: String(localized: "Plugin '%@' has an invalid descriptor: %@"), pluginId, reason)
case .unknownDatabaseType(let databaseType):
return String(format: String(localized: "TablePro doesn't recognize the database type “%@”."), databaseType)
case .pluginDisabled(_, let pluginName):
return String(format: String(localized: "The %@ plugin is turned off."), pluginName)
case .pluginLoadFailed(_, let pluginName, _):
return String(format: String(localized: "The %@ plugin could not be loaded."), pluginName)
case .pluginInstallFailed(let databaseType, let reason):
Expand All @@ -92,8 +89,6 @@ enum PluginError: LocalizedError {
switch self {
case .unknownDatabaseType:
return String(localized: "Edit the connection and choose its database type.")
case .pluginDisabled:
return String(localized: "Turn the plugin on to connect.")
case .pluginLoadFailed:
return String(localized: "Update or reinstall the plugin in Settings > Plugins.")
case .pluginInstallFailed:
Expand Down
1 change: 0 additions & 1 deletion TablePro/Core/Plugins/PluginManager+AutoUpdate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,6 @@ extension PluginManager {
guard let installed = plugins.first(where: { $0.id == pluginId }) else { return nil }
guard installed.source == .userInstalled else { return nil }
guard let registryPlugin = manifest.plugins.first(where: { $0.id == pluginId }) else { return nil }
guard registryPlugin.category != .theme else { return nil }
return registryPlugin.version.compare(installed.version, options: .numeric) == .orderedDescending
? registryPlugin : nil
}
Expand Down
6 changes: 0 additions & 6 deletions TablePro/Core/Plugins/PluginManager+DriverAvailability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import TableProPluginKit

internal enum DriverUnavailability: Equatable, Sendable {
case unknownType
case disabled(pluginId: String, pluginName: String)
case failedToLoad(pluginId: String?, pluginName: String, reason: String?)
case outdated(reason: String)
case notInstalled
Expand All @@ -27,9 +26,6 @@ extension PluginManager {
) -> DriverUnavailability {
let typeId = databaseType.pluginTypeId
if let entry = installedEntry(servingTypeId: typeId) {
guard entry.isEnabled else {
return .disabled(pluginId: entry.id, pluginName: entry.name)
}
let rejection = rejectedPlugins.first { $0.providedDatabaseTypeIds.contains(typeId) }
return .failedToLoad(pluginId: entry.id, pluginName: entry.name, reason: rejection?.reason)
}
Expand All @@ -56,8 +52,6 @@ extension PluginManager {
switch driverUnavailability(for: databaseType) {
case .unknownType:
return .unknownDatabaseType(databaseType.rawValue)
case .disabled(let pluginId, let pluginName):
return .pluginDisabled(pluginId: pluginId, pluginName: pluginName)
case .failedToLoad(let pluginId, let pluginName, let reason):
return .pluginLoadFailed(pluginId: pluginId, pluginName: pluginName, reason: reason)
case .outdated(let reason):
Expand Down
45 changes: 0 additions & 45 deletions TablePro/Core/Plugins/PluginManager+Lifecycle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,47 +10,6 @@ import SwiftUI
import TableProPluginKit

extension PluginManager {
func setEnabled(_ enabled: Bool, pluginId: String) {
guard let index = plugins.firstIndex(where: { $0.id == pluginId }) else { return }

plugins[index].isEnabled = enabled

var disabled = disabledPluginIds
if enabled {
disabled.remove(pluginId)
} else {
disabled.insert(pluginId)
}
disabledPluginIds = disabled

if enabled {
/// `principalClass` loads the bundle's executable, so enabling is a code-loading path
/// and takes the same gate every other one does. A plugin that fails it stays disabled
/// and is withdrawn rather than published as an installed one that cannot be used.
do {
try assertLoadable(plugins[index].bundle, source: plugins[index].source)
} catch {
Self.logger.error(
"Refusing to enable plugin '\(pluginId, privacy: .public)': failed the load gate: \(error.publicLogShape, privacy: .public)"
)
plugins[index].isEnabled = false
disabled.insert(pluginId)
disabledPluginIds = disabled
withdrawPlugin(at: plugins[index].url, reason: error)
return
}
if let principalClass = plugins[index].bundle.principalClass as? any TableProPlugin.Type {
let instance = principalClass.init()
registerCapabilities(instance, pluginId: pluginId)
}
} else {
unregisterCapabilities(pluginId: pluginId)
}

queryBuildingDriverCache.removeAll()
Self.logger.info("Plugin '\(pluginId)' \(enabled ? "enabled" : "disabled")")
}

func uninstallPlugin(id: String) async throws {
guard let index = plugins.firstIndex(where: { $0.id == id }) else {
throw PluginError.notFound
Expand All @@ -75,10 +34,6 @@ extension PluginManager {

PluginSettingsStorage(pluginId: id).removeAll()

var disabled = disabledPluginIds
disabled.remove(id)
disabledPluginIds = disabled

if stagedUpdates[id] != nil {
await discardStagedUpdate(pluginId: id)
}
Expand Down
8 changes: 2 additions & 6 deletions TablePro/Core/Plugins/PluginManager+Registration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ extension PluginManager {
/// All database types with loaded plugins, ordered by display name.
var availableDatabaseTypes: [DatabaseType] {
var types: [DatabaseType] = []
for entry in plugins where entry.isEnabled {
for entry in plugins {
if let typeId = entry.databaseTypeId {
types.append(DatabaseType(rawValue: typeId))
}
Expand Down Expand Up @@ -757,11 +757,7 @@ extension PluginManager {
if let existingEntry = plugins.first(where: { entry in
entry.databaseTypeId == pluginTypeId || entry.additionalTypeIds.contains(pluginTypeId)
}) {
if !existingEntry.isEnabled {
setEnabled(true, pluginId: existingEntry.id)
}
if driverPlugins[pluginTypeId] != nil {
Self.logger.info("Re-enabled existing plugin '\(existingEntry.name)' for '\(databaseType.rawValue)'")
if driverPlugin(for: databaseType) != nil {
return
}
Self.logger.warning("Plugin '\(existingEntry.id)' exists but driver not registered, reinstalling")
Expand Down
4 changes: 1 addition & 3 deletions TablePro/Core/Plugins/PluginManager+Validation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,13 @@ import TableProPluginKit
extension PluginManager {
func validateDependencies() {
let loadedIds = Set(plugins.map(\.id))
for plugin in plugins where plugin.isEnabled {
for plugin in plugins {
guard plugin.bundle.isLoaded else { continue }
guard let principalClass = plugin.bundle.principalClass as? any TableProPlugin.Type else { continue }
let deps = principalClass.dependencies
for dep in deps {
if !loadedIds.contains(dep) {
Self.logger.warning("Plugin '\(plugin.id)' requires '\(dep)' which is not installed")
} else if let depEntry = plugins.first(where: { $0.id == dep }), !depEntry.isEnabled {
Self.logger.warning("Plugin '\(plugin.id)' requires '\(dep)' which is disabled")
}
}
}
Expand Down
32 changes: 2 additions & 30 deletions TablePro/Core/Plugins/PluginManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,7 @@ final class PluginManager: ObservableObject {

/// Still 19, so every plugin already published for the previous release keeps loading.
nonisolated static let minimumCompatiblePluginKitVersion = 19
private static let disabledPluginsKey = "com.TablePro.disabledPlugins"
private static let legacyDisabledPluginsKey = "disabledPlugins"

private let defaults: UserDefaults
private let builtInPluginsURL: URL?
internal let userPluginsDir: URL

Expand Down Expand Up @@ -214,11 +211,6 @@ final class PluginManager: ObservableObject {

@Published internal(set) var pluginInstances: [String: any TableProPlugin] = [:]

var disabledPluginIds: Set<String> {
get { Set(defaults.stringArray(forKey: Self.disabledPluginsKey) ?? []) }
set { defaults.set(Array(newValue), forKey: Self.disabledPluginsKey) }
}

nonisolated static let logger = Logger(subsystem: "com.TablePro", category: "PluginManager")

@Published private var pendingPluginURLs: [(url: URL, source: PluginSource)] = []
Expand Down Expand Up @@ -248,7 +240,6 @@ final class PluginManager: ObservableObject {
builtInPluginsURL: URL? = Bundle.main.builtInPlugInsURL,
userPluginsDir: URL = PluginManager.defaultUserPluginsDir()
) {
self.defaults = userDefaults
self.builtInPluginsURL = builtInPluginsURL
self.userPluginsDir = userPluginsDir
Self.clearLegacyNeedsRestartKey(in: userDefaults)
Expand Down Expand Up @@ -312,19 +303,9 @@ final class PluginManager: ObservableObject {
}
}

private func migrateDisabledPluginsKey() {
if let legacy = defaults.stringArray(forKey: Self.legacyDisabledPluginsKey) {
if defaults.stringArray(forKey: Self.disabledPluginsKey) == nil {
defaults.set(legacy, forKey: Self.disabledPluginsKey)
}
defaults.removeObject(forKey: Self.legacyDisabledPluginsKey)
}
}

// MARK: - Loading

func loadPlugins() {
migrateDisabledPluginsKey()
cleanStaleStagingArtifacts()
discoverAllPlugins()

Expand Down Expand Up @@ -455,7 +436,6 @@ final class PluginManager: ObservableObject {
version: version,
pluginDescription: pluginDescription,
capabilities: capabilities,
isEnabled: !disabledPluginIds.contains(bundleId),
databaseTypeId: primaryTypeId,
additionalTypeIds: additionalTypeIds,
pluginIconName: pluginIconName,
Expand Down Expand Up @@ -557,7 +537,6 @@ final class PluginManager: ObservableObject {
guard !activatedBundleIds.contains(bundleId) else { return }

let entry = plugins.first(where: { $0.id == bundleId })
if let entry, !entry.isEnabled { return }

do {
try assertLoadable(bundle, source: entry?.source ?? .userInstalled)
Expand All @@ -582,8 +561,7 @@ final class PluginManager: ObservableObject {

validateCapabilityDeclarations(principalClass, pluginId: bundleId)

let isEnabled = entry?.isEnabled ?? false
if isEnabled {
if entry != nil {
let instance = principalClass.init()
registerCapabilities(instance, pluginId: bundleId)
}
Expand Down Expand Up @@ -744,7 +722,6 @@ final class PluginManager: ObservableObject {
let exportType = principalClass as? any ExportFormatPlugin.Type
let importType = principalClass as? any ImportFormatPlugin.Type

let disabled = disabledPluginIds
let version: String
if let declared = Self.bundleShortVersion(at: url) {
version = declared
Expand All @@ -761,7 +738,6 @@ final class PluginManager: ObservableObject {
version: version,
pluginDescription: principalClass.pluginDescription,
capabilities: principalClass.capabilities,
isEnabled: !disabled.contains(bundleId),
databaseTypeId: driverType?.databaseTypeId,
additionalTypeIds: driverType?.additionalDatabaseTypeIds ?? [],
pluginIconName: driverType?.iconName ?? "puzzlepiece",
Expand All @@ -772,11 +748,7 @@ final class PluginManager: ObservableObject {

plugins.append(entry)
validateCapabilityDeclarations(principalClass, pluginId: bundleId)

if entry.isEnabled {
let instance = principalClass.init()
registerCapabilities(instance, pluginId: bundleId)
}
registerCapabilities(principalClass.init(), pluginId: bundleId)

Self.logger.info("Loaded plugin '\(entry.name)' v\(entry.version) [\(source == .builtIn ? "built-in" : "user")]")
return entry
Expand Down
1 change: 0 additions & 1 deletion TablePro/Core/Plugins/PluginModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ struct PluginEntry: Identifiable {
var version: String
let pluginDescription: String
let capabilities: [PluginCapability]
var isEnabled: Bool

let databaseTypeId: String?
let additionalTypeIds: [String]
Expand Down
Loading
Loading