From 23afdac020c5a234cc1e45494ec94cf0082679a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ng=C3=B4=20Qu=E1=BB=91c=20=C4=90=E1=BA=A1t?= Date: Wed, 6 May 2026 14:18:58 +0700 Subject: [PATCH] perf(plugins): defer bundle.load() until first connection or format use --- Plugins/CSVExportPlugin/Info.plist | 4 + Plugins/ClickHouseDriverPlugin/Info.plist | 4 + Plugins/JSONExportPlugin/Info.plist | 4 + Plugins/MQLExportPlugin/Info.plist | 4 + Plugins/MySQLDriverPlugin/Info.plist | 5 + Plugins/PostgreSQLDriverPlugin/Info.plist | 5 + Plugins/RedisDriverPlugin/Info.plist | 8 +- Plugins/SQLExportPlugin/Info.plist | 4 + Plugins/SQLImportPlugin/Info.plist | 4 + Plugins/SQLiteDriverPlugin/Info.plist | 4 + Plugins/XLSXExportPlugin/Info.plist | 4 + TablePro/Core/Database/DatabaseDriver.swift | 4 +- .../Plugins/PluginManager+Registration.swift | 31 ++- .../Plugins/PluginManager+Validation.swift | 1 + TablePro/Core/Plugins/PluginManager.swift | 181 +++++++++++++++++- TablePro/Core/Plugins/PluginManifest.swift | 28 +++ .../Core/Services/Export/ExportService.swift | 6 +- .../Core/Services/Export/ImportService.swift | 2 +- TablePro/Models/Export/ExportModels.swift | 4 +- TablePro/Views/Export/ExportDialog.swift | 6 +- .../Views/Export/ExportTableTreeView.swift | 4 +- TablePro/Views/Import/ImportDialog.swift | 4 +- ...ainContentCoordinator+SidebarActions.swift | 2 +- 23 files changed, 300 insertions(+), 23 deletions(-) create mode 100644 TablePro/Core/Plugins/PluginManifest.swift diff --git a/Plugins/CSVExportPlugin/Info.plist b/Plugins/CSVExportPlugin/Info.plist index f6d13edb8..f50f93f63 100644 --- a/Plugins/CSVExportPlugin/Info.plist +++ b/Plugins/CSVExportPlugin/Info.plist @@ -4,5 +4,9 @@ TableProPluginKitVersion 9 + TableProProvidesExportFormatIds + + csv + diff --git a/Plugins/ClickHouseDriverPlugin/Info.plist b/Plugins/ClickHouseDriverPlugin/Info.plist index f6d13edb8..6b37ff721 100644 --- a/Plugins/ClickHouseDriverPlugin/Info.plist +++ b/Plugins/ClickHouseDriverPlugin/Info.plist @@ -4,5 +4,9 @@ TableProPluginKitVersion 9 + TableProProvidesDatabaseTypeIds + + ClickHouse + diff --git a/Plugins/JSONExportPlugin/Info.plist b/Plugins/JSONExportPlugin/Info.plist index f6d13edb8..f06c7ab70 100644 --- a/Plugins/JSONExportPlugin/Info.plist +++ b/Plugins/JSONExportPlugin/Info.plist @@ -4,5 +4,9 @@ TableProPluginKitVersion 9 + TableProProvidesExportFormatIds + + json + diff --git a/Plugins/MQLExportPlugin/Info.plist b/Plugins/MQLExportPlugin/Info.plist index f6d13edb8..712fd7466 100644 --- a/Plugins/MQLExportPlugin/Info.plist +++ b/Plugins/MQLExportPlugin/Info.plist @@ -4,5 +4,9 @@ TableProPluginKitVersion 9 + TableProProvidesExportFormatIds + + mql + diff --git a/Plugins/MySQLDriverPlugin/Info.plist b/Plugins/MySQLDriverPlugin/Info.plist index f6d13edb8..6da0a44b2 100644 --- a/Plugins/MySQLDriverPlugin/Info.plist +++ b/Plugins/MySQLDriverPlugin/Info.plist @@ -4,5 +4,10 @@ TableProPluginKitVersion 9 + TableProProvidesDatabaseTypeIds + + MySQL + MariaDB + diff --git a/Plugins/PostgreSQLDriverPlugin/Info.plist b/Plugins/PostgreSQLDriverPlugin/Info.plist index f6d13edb8..35bc385e1 100644 --- a/Plugins/PostgreSQLDriverPlugin/Info.plist +++ b/Plugins/PostgreSQLDriverPlugin/Info.plist @@ -4,5 +4,10 @@ TableProPluginKitVersion 9 + TableProProvidesDatabaseTypeIds + + PostgreSQL + Redshift + diff --git a/Plugins/RedisDriverPlugin/Info.plist b/Plugins/RedisDriverPlugin/Info.plist index 2b8a3c0bb..4c234f85f 100644 --- a/Plugins/RedisDriverPlugin/Info.plist +++ b/Plugins/RedisDriverPlugin/Info.plist @@ -18,9 +18,13 @@ $(MARKETING_VERSION) CFBundleVersion $(CURRENT_PROJECT_VERSION) - TableProPluginKitVersion - 9 NSPrincipalClass $(PRODUCT_MODULE_NAME).RedisPlugin + TableProPluginKitVersion + 9 + TableProProvidesDatabaseTypeIds + + Redis + diff --git a/Plugins/SQLExportPlugin/Info.plist b/Plugins/SQLExportPlugin/Info.plist index f6d13edb8..f69ea8ee0 100644 --- a/Plugins/SQLExportPlugin/Info.plist +++ b/Plugins/SQLExportPlugin/Info.plist @@ -4,5 +4,9 @@ TableProPluginKitVersion 9 + TableProProvidesExportFormatIds + + sql + diff --git a/Plugins/SQLImportPlugin/Info.plist b/Plugins/SQLImportPlugin/Info.plist index f6d13edb8..a3bc134ae 100644 --- a/Plugins/SQLImportPlugin/Info.plist +++ b/Plugins/SQLImportPlugin/Info.plist @@ -4,5 +4,9 @@ TableProPluginKitVersion 9 + TableProProvidesImportFormatIds + + sql + diff --git a/Plugins/SQLiteDriverPlugin/Info.plist b/Plugins/SQLiteDriverPlugin/Info.plist index f6d13edb8..8332ca837 100644 --- a/Plugins/SQLiteDriverPlugin/Info.plist +++ b/Plugins/SQLiteDriverPlugin/Info.plist @@ -4,5 +4,9 @@ TableProPluginKitVersion 9 + TableProProvidesDatabaseTypeIds + + SQLite + diff --git a/Plugins/XLSXExportPlugin/Info.plist b/Plugins/XLSXExportPlugin/Info.plist index f6d13edb8..99937c938 100644 --- a/Plugins/XLSXExportPlugin/Info.plist +++ b/Plugins/XLSXExportPlugin/Info.plist @@ -4,5 +4,9 @@ TableProPluginKitVersion 9 + TableProProvidesExportFormatIds + + xlsx + diff --git a/TablePro/Core/Database/DatabaseDriver.swift b/TablePro/Core/Database/DatabaseDriver.swift index 9708f8eee..7755e9bfd 100644 --- a/TablePro/Core/Database/DatabaseDriver.swift +++ b/TablePro/Core/Database/DatabaseDriver.swift @@ -378,7 +378,7 @@ enum DatabaseDriverFactory { awaitPlugins: Bool ) async throws -> DatabaseDriver { let pluginId = connection.type.pluginTypeId - if PluginManager.shared.driverPlugins[pluginId] == nil, + if PluginManager.shared.driverPlugin(for: connection.type) == nil, !PluginManager.shared.hasFinishedInitialLoad { logger.info("Plugin '\(pluginId)' not loaded yet — waiting for background load") await PluginManager.shared.waitForInitialLoad() @@ -391,7 +391,7 @@ enum DatabaseDriverFactory { passwordOverride: String? = nil ) throws -> DatabaseDriver { let pluginId = connection.type.pluginTypeId - guard let plugin = PluginManager.shared.driverPlugins[pluginId] else { + guard let plugin = PluginManager.shared.driverPlugin(for: connection.type) else { if connection.type.isDownloadablePlugin { throw PluginError.pluginNotInstalled(connection.type.rawValue) } diff --git a/TablePro/Core/Plugins/PluginManager+Registration.swift b/TablePro/Core/Plugins/PluginManager+Registration.swift index 7a665f240..3bc0d3e3e 100644 --- a/TablePro/Core/Plugins/PluginManager+Registration.swift +++ b/TablePro/Core/Plugins/PluginManager+Registration.swift @@ -214,7 +214,36 @@ extension PluginManager { // MARK: - Plugin Property Lookups func driverPlugin(for databaseType: DatabaseType) -> (any DriverPlugin)? { - driverPlugins[databaseType.pluginTypeId] + let typeId = databaseType.pluginTypeId + if let driver = driverPlugins[typeId] { return driver } + activateDriver(databaseTypeId: typeId) + return driverPlugins[typeId] + } + + func exportPlugin(forFormat formatId: String) -> (any ExportFormatPlugin)? { + if let plugin = exportPlugins[formatId] { return plugin } + activateExportFormat(formatId) + return exportPlugins[formatId] + } + + func importPlugin(forFormat formatId: String) -> (any ImportFormatPlugin)? { + if let plugin = importPlugins[formatId] { return plugin } + activateImportFormat(formatId) + return importPlugins[formatId] + } + + func allExportPlugins() -> [any ExportFormatPlugin] { + for formatId in allLazyExportFormatIds() { + activateExportFormat(formatId) + } + return Array(exportPlugins.values) + } + + func allImportPlugins() -> [any ImportFormatPlugin] { + for formatId in allLazyImportFormatIds() { + activateImportFormat(formatId) + } + return Array(importPlugins.values) } /// Returns a temporary plugin driver for query building (buildBrowseQuery), or nil diff --git a/TablePro/Core/Plugins/PluginManager+Validation.swift b/TablePro/Core/Plugins/PluginManager+Validation.swift index 76fc8cdd2..f28fddce0 100644 --- a/TablePro/Core/Plugins/PluginManager+Validation.swift +++ b/TablePro/Core/Plugins/PluginManager+Validation.swift @@ -15,6 +15,7 @@ extension PluginManager { func validateDependencies() { let loadedIds = Set(plugins.map(\.id)) for plugin in plugins where plugin.isEnabled { + 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 { diff --git a/TablePro/Core/Plugins/PluginManager.swift b/TablePro/Core/Plugins/PluginManager.swift index d15d329d1..f38c32b30 100644 --- a/TablePro/Core/Plugins/PluginManager.swift +++ b/TablePro/Core/Plugins/PluginManager.swift @@ -84,6 +84,11 @@ final class PluginManager { private var pendingPluginURLs: [(url: URL, source: PluginSource)] = [] + @ObservationIgnored private var lazyDriverURLs: [String: URL] = [:] + @ObservationIgnored private var lazyExportURLs: [String: URL] = [:] + @ObservationIgnored private var lazyImportURLs: [String: URL] = [:] + @ObservationIgnored private var activatedBundleIds: Set = [] + var queryBuildingDriverCache: [String: (any PluginDatabaseDriver)?] = [:] init( @@ -156,24 +161,192 @@ final class PluginManager { func loadPlugins() { migrateDisabledPluginsKey() discoverAllPlugins() - let pending = pendingPluginURLs + var lazyPending: [(url: URL, source: PluginSource, manifest: PluginManifest)] = [] + var eagerPending: [(url: URL, source: PluginSource)] = [] + for entry in pendingPluginURLs { + if let bundle = Bundle(url: entry.url), + let manifest = PluginManifest(bundle: bundle), + manifest.supportsLazyLoad { + lazyPending.append((url: entry.url, source: entry.source, manifest: manifest)) + } else { + eagerPending.append(entry) + } + } + pendingPluginURLs.removeAll() + + for entry in lazyPending { + registerLazyManifest(at: entry.url, source: entry.source, manifest: entry.manifest) + } + Task { if !self.rejectedPlugins.isEmpty { await self.autoUpdateRejectedPlugins() } - let validated = await Self.validateAndLoadBundles(pending) - self.pendingPluginURLs.removeAll() + let validated = await Self.validateAndLoadBundles(eagerPending) self.needsRestartStorage = false self.registerValidatedBundles(validated) self.validateDependencies() self.hasFinishedInitialLoad = true - Self.logger.info("Loaded \(self.plugins.count) plugin(s): \(self.driverPlugins.count) driver(s), \(self.exportPlugins.count) export format(s), \(self.importPlugins.count) import format(s)") + let lazyCount = lazyPending.count + let eagerCount = validated.count + Self.logger.info("Loaded \(self.plugins.count) plugin(s): \(lazyCount) lazy + \(eagerCount) eager (\(self.driverPlugins.count) driver(s) active, \(self.exportPlugins.count) export(s) active, \(self.importPlugins.count) import(s) active)") if !self.rejectedPlugins.isEmpty { NotificationCenter.default.post(name: .pluginsRejected, object: self.rejectedPlugins) } } } + // MARK: - Lazy Plugin Activation + + private func registerLazyManifest(at url: URL, source: PluginSource, manifest: PluginManifest) { + guard let bundle = Bundle(url: url) else { return } + do { + try Self.validateBundleVersions(bundle, source: source) + } catch { + Self.logger.error("Lazy plugin '\(manifest.bundleId)' failed version check: \(error.localizedDescription)") + if source == .userInstalled { + rejectedPlugins.append(RejectedPlugin( + url: url, + bundleId: manifest.bundleId, + registryId: Self.readRegistryMetadata(for: url)?.pluginId, + name: manifest.bundleId, + reason: error.localizedDescription, + isOutdated: (error as? PluginError)?.isOutdated ?? false + )) + } + return + } + if source == .userInstalled { + do { + try verifyCodeSignature(bundle: bundle) + } catch { + Self.logger.error("Lazy plugin '\(manifest.bundleId)' failed code-sign check: \(error.localizedDescription)") + rejectedPlugins.append(RejectedPlugin( + url: url, + bundleId: manifest.bundleId, + registryId: Self.readRegistryMetadata(for: url)?.pluginId, + name: manifest.bundleId, + reason: error.localizedDescription, + isOutdated: false + )) + return + } + } + + let bundleId = manifest.bundleId + if source == .userInstalled, + let existing = plugins.first(where: { $0.id == bundleId }), + existing.source == .builtIn + { + Self.logger.info("Skipping user-installed lazy '\(bundleId)': built-in version already registered") + return + } + + let primaryTypeId = manifest.providedDatabaseTypeIds.first + let additionalTypeIds = Array(manifest.providedDatabaseTypeIds.dropFirst()) + let registrySnapshot = primaryTypeId.flatMap { + PluginMetadataRegistry.shared.snapshot(forTypeId: $0) + } + + var capabilities: [PluginCapability] = [] + if !manifest.providedDatabaseTypeIds.isEmpty { capabilities.append(.databaseDriver) } + if !manifest.providedExportFormatIds.isEmpty { capabilities.append(.exportFormat) } + if !manifest.providedImportFormatIds.isEmpty { capabilities.append(.importFormat) } + + let info = bundle.infoDictionary ?? [:] + let version = Self.readRegistryMetadata(for: url)?.version + ?? (info["CFBundleShortVersionString"] as? String) + ?? "1.0.0" + let displayName = registrySnapshot?.displayName + ?? bundleId.split(separator: ".").last.map(String.init) + ?? bundleId + let pluginIconName = registrySnapshot?.iconName ?? "puzzlepiece" + let defaultPort = registrySnapshot?.defaultPort + let pluginDescription = registrySnapshot?.connection.tagline ?? "" + + let entry = PluginEntry( + id: bundleId, + bundle: bundle, + url: url, + source: source, + name: displayName, + version: version, + pluginDescription: pluginDescription, + capabilities: capabilities, + isEnabled: !disabledPluginIds.contains(bundleId), + databaseTypeId: primaryTypeId, + additionalTypeIds: additionalTypeIds, + pluginIconName: pluginIconName, + defaultPort: defaultPort + ) + plugins.append(entry) + + for typeId in manifest.providedDatabaseTypeIds { + lazyDriverURLs[typeId] = url + } + for formatId in manifest.providedExportFormatIds { + lazyExportURLs[formatId] = url + } + for formatId in manifest.providedImportFormatIds { + lazyImportURLs[formatId] = url + } + Self.logger.debug("Registered lazy plugin '\(bundleId)': drivers=\(manifest.providedDatabaseTypeIds), exports=\(manifest.providedExportFormatIds), imports=\(manifest.providedImportFormatIds)") + } + + func activateDriver(databaseTypeId typeId: String) { + guard driverPlugins[typeId] == nil else { return } + guard let url = lazyDriverURLs[typeId] else { return } + activateLazyBundle(at: url) + } + + func activateExportFormat(_ formatId: String) { + guard exportPlugins[formatId] == nil else { return } + guard let url = lazyExportURLs[formatId] else { return } + activateLazyBundle(at: url) + } + + func activateImportFormat(_ formatId: String) { + guard importPlugins[formatId] == nil else { return } + guard let url = lazyImportURLs[formatId] else { return } + activateLazyBundle(at: url) + } + + func allLazyExportFormatIds() -> [String] { + Array(lazyExportURLs.keys) + } + + func allLazyImportFormatIds() -> [String] { + Array(lazyImportURLs.keys) + } + + private func activateLazyBundle(at url: URL) { + guard let bundle = Bundle(url: url) else { return } + let bundleId = bundle.bundleIdentifier ?? url.lastPathComponent + guard !activatedBundleIds.contains(bundleId) else { return } + + guard bundle.load() else { + Self.logger.error("Failed to load lazy bundle '\(bundleId)' at \(url.lastPathComponent)") + return + } + + guard let principalClass = bundle.principalClass as? any TableProPlugin.Type else { + Self.logger.error("Lazy plugin '\(bundleId)' has no TableProPlugin principal class") + return + } + + validateCapabilityDeclarations(principalClass, pluginId: bundleId) + + let isEnabled = plugins.first(where: { $0.id == bundleId })?.isEnabled ?? false + if isEnabled { + let instance = principalClass.init() + registerCapabilities(instance, pluginId: bundleId) + } + + activatedBundleIds.insert(bundleId) + queryBuildingDriverCache.removeAll() + Self.logger.info("Activated plugin '\(bundleId)' on demand") + } + private struct ValidatedBundle: @unchecked Sendable { let url: URL let source: PluginSource diff --git a/TablePro/Core/Plugins/PluginManifest.swift b/TablePro/Core/Plugins/PluginManifest.swift new file mode 100644 index 000000000..667406026 --- /dev/null +++ b/TablePro/Core/Plugins/PluginManifest.swift @@ -0,0 +1,28 @@ +// +// PluginManifest.swift +// TablePro +// + +import Foundation + +internal struct PluginManifest { + let bundleId: String + let providedDatabaseTypeIds: [String] + let providedExportFormatIds: [String] + let providedImportFormatIds: [String] + + var supportsLazyLoad: Bool { + !providedDatabaseTypeIds.isEmpty + || !providedExportFormatIds.isEmpty + || !providedImportFormatIds.isEmpty + } + + init?(bundle: Bundle) { + guard let id = bundle.bundleIdentifier else { return nil } + let info = bundle.infoDictionary ?? [:] + bundleId = id + providedDatabaseTypeIds = info["TableProProvidesDatabaseTypeIds"] as? [String] ?? [] + providedExportFormatIds = info["TableProProvidesExportFormatIds"] as? [String] ?? [] + providedImportFormatIds = info["TableProProvidesImportFormatIds"] as? [String] ?? [] + } +} diff --git a/TablePro/Core/Services/Export/ExportService.swift b/TablePro/Core/Services/Export/ExportService.swift index 4c1d21863..ff0f96f7e 100644 --- a/TablePro/Core/Services/Export/ExportService.swift +++ b/TablePro/Core/Services/Export/ExportService.swift @@ -97,7 +97,7 @@ final class ExportService { throw ExportError.noTablesSelected } - guard let plugin = PluginManager.shared.exportPlugins[config.formatId] else { + guard let plugin = PluginManager.shared.exportPlugin(forFormat: config.formatId) else { throw ExportError.formatNotFound(config.formatId) } @@ -185,7 +185,7 @@ final class ExportService { config: ExportConfiguration, to url: URL ) async throws { - guard let plugin = PluginManager.shared.exportPlugins[config.formatId] else { + guard let plugin = PluginManager.shared.exportPlugin(forFormat: config.formatId) else { throw ExportError.formatNotFound(config.formatId) } @@ -261,7 +261,7 @@ final class ExportService { config: ExportConfiguration, to url: URL ) async throws { - guard let plugin = PluginManager.shared.exportPlugins[config.formatId] else { + guard let plugin = PluginManager.shared.exportPlugin(forFormat: config.formatId) else { throw ExportError.formatNotFound(config.formatId) } guard let driver else { diff --git a/TablePro/Core/Services/Export/ImportService.swift b/TablePro/Core/Services/Export/ImportService.swift index 7810791e3..4a0cd5e58 100644 --- a/TablePro/Core/Services/Export/ImportService.swift +++ b/TablePro/Core/Services/Export/ImportService.swift @@ -54,7 +54,7 @@ final class ImportService { ownsDecompressedFile: Bool = false, knownStatementCount: Int? = nil ) async throws -> PluginImportResult { - guard let plugin = PluginManager.shared.importPlugins[formatId] else { + guard let plugin = PluginManager.shared.importPlugin(forFormat: formatId) else { throw PluginImportError.importFailed("Import format '\(formatId)' not found") } diff --git a/TablePro/Models/Export/ExportModels.swift b/TablePro/Models/Export/ExportModels.swift index 9902724c7..87763dc5e 100644 --- a/TablePro/Models/Export/ExportModels.swift +++ b/TablePro/Models/Export/ExportModels.swift @@ -23,14 +23,14 @@ struct ExportConfiguration { var fileName: String = "export" var fullFileName: String { - guard let plugin = PluginManager.shared.exportPlugins[formatId] else { + guard let plugin = PluginManager.shared.exportPlugin(forFormat: formatId) else { return "\(fileName).\(formatId)" } return "\(fileName).\(plugin.currentFileExtension)" } var fileExtension: String { - guard let plugin = PluginManager.shared.exportPlugins[formatId] else { + guard let plugin = PluginManager.shared.exportPlugin(forFormat: formatId) else { return formatId } return plugin.currentFileExtension diff --git a/TablePro/Views/Export/ExportDialog.swift b/TablePro/Views/Export/ExportDialog.swift index 87acdef6f..44f0c962e 100644 --- a/TablePro/Views/Export/ExportDialog.swift +++ b/TablePro/Views/Export/ExportDialog.swift @@ -162,7 +162,7 @@ struct ExportDialog: View { private var availableFormats: [any ExportFormatPlugin] { let dbTypeId = connection.type.rawValue - return PluginManager.shared.exportPlugins.values + return PluginManager.shared.allExportPlugins() .filter { plugin in let pluginType = type(of: plugin) if !pluginType.supportedDatabaseTypeIds.isEmpty { @@ -185,7 +185,7 @@ struct ExportDialog: View { } private var currentPlugin: (any ExportFormatPlugin)? { - PluginManager.shared.exportPlugins[config.formatId] + PluginManager.shared.exportPlugin(forFormat: config.formatId) } // MARK: - Layout Constants @@ -280,7 +280,7 @@ struct ExportDialog: View { Picker("", selection: $config.formatId) { ForEach(availableFormatIds, id: \.self) { formatId in - if let plugin = PluginManager.shared.exportPlugins[formatId] { + if let plugin = PluginManager.shared.exportPlugin(forFormat: formatId) { if isProGatedFormat(formatId) { Text("\(type(of: plugin).formatDisplayName) (Pro)").tag(formatId) } else { diff --git a/TablePro/Views/Export/ExportTableTreeView.swift b/TablePro/Views/Export/ExportTableTreeView.swift index 3bc4929ea..e01c07599 100644 --- a/TablePro/Views/Export/ExportTableTreeView.swift +++ b/TablePro/Views/Export/ExportTableTreeView.swift @@ -15,12 +15,12 @@ struct ExportTableTreeView: View { let formatId: String private var optionColumns: [PluginExportOptionColumn] { - guard let plugin = PluginManager.shared.exportPlugins[formatId] else { return [] } + guard let plugin = PluginManager.shared.exportPlugin(forFormat: formatId) else { return [] } return type(of: plugin).perTableOptionColumns } private var currentPlugin: (any ExportFormatPlugin)? { - PluginManager.shared.exportPlugins[formatId] + PluginManager.shared.exportPlugin(forFormat: formatId) } var body: some View { diff --git a/TablePro/Views/Import/ImportDialog.swift b/TablePro/Views/Import/ImportDialog.swift index 2ea685f8f..318e81a74 100644 --- a/TablePro/Views/Import/ImportDialog.swift +++ b/TablePro/Views/Import/ImportDialog.swift @@ -122,7 +122,7 @@ struct ImportDialog: View { private var availableFormats: [any ImportFormatPlugin] { let dbTypeId = connection.type.rawValue - return PluginManager.shared.importPlugins.values + return PluginManager.shared.allImportPlugins() .filter { plugin in let supported = type(of: plugin).supportedDatabaseTypeIds let excluded = type(of: plugin).excludedDatabaseTypeIds @@ -138,7 +138,7 @@ struct ImportDialog: View { } private var currentPlugin: (any ImportFormatPlugin)? { - PluginManager.shared.importPlugins[selectedFormatId] + PluginManager.shared.importPlugin(forFormat: selectedFormatId) } // MARK: - View Components diff --git a/TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarActions.swift b/TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarActions.swift index 96eb56084..a5b3ba9fc 100644 --- a/TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarActions.swift +++ b/TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarActions.swift @@ -123,7 +123,7 @@ extension MainContentCoordinator { } let panel = NSOpenPanel() var contentTypes: [UTType] = [] - for (_, plugin) in PluginManager.shared.importPlugins { + for plugin in PluginManager.shared.allImportPlugins() { for ext in type(of: plugin).acceptedFileExtensions { if let utType = UTType(filenameExtension: ext) { contentTypes.append(utType)