Skip to content

Commit dd791fd

Browse files
authored
Merge pull request #223 from datlechin/refactor/plugin-install-tech-debt
refactor: centralize plugin install state and fix reinstall edge cases
2 parents 58deb81 + 549e8ce commit dd791fd

3 files changed

Lines changed: 32 additions & 15 deletions

File tree

TablePro/Core/Plugins/PluginManager.swift

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ final class PluginManager {
1515

1616
private(set) var plugins: [PluginEntry] = []
1717

18+
private(set) var isInstalling = false
19+
1820
private(set) var needsRestart = false
1921

2022
private(set) var driverPlugins: [String: any DriverPlugin] = [:]
@@ -182,6 +184,14 @@ final class PluginManager {
182184
}
183185
}
184186

187+
private func replaceExistingPlugin(bundleId: String) {
188+
guard let existingIndex = plugins.firstIndex(where: { $0.id == bundleId }) else { return }
189+
// Order matters: unregisterCapabilities reads from `plugins` to find the principal class
190+
unregisterCapabilities(pluginId: bundleId)
191+
plugins[existingIndex].bundle.unload()
192+
plugins.remove(at: existingIndex)
193+
}
194+
185195
private func unregisterCapabilities(pluginId: String) {
186196
driverPlugins = driverPlugins.filter { _, value in
187197
guard let entry = plugins.first(where: { $0.id == pluginId }) else { return true }
@@ -232,14 +242,20 @@ final class PluginManager {
232242
// MARK: - Install / Uninstall
233243

234244
func installPlugin(from url: URL) async throws -> PluginEntry {
245+
guard !isInstalling else {
246+
throw PluginError.installFailed("Another plugin installation is already in progress")
247+
}
248+
isInstalling = true
249+
defer { isInstalling = false }
250+
235251
if url.pathExtension == "tableplugin" {
236-
return try await installBundle(from: url)
252+
return try installBundle(from: url)
237253
} else {
238254
return try await installFromZip(from: url)
239255
}
240256
}
241257

242-
private func installBundle(from url: URL) async throws -> PluginEntry {
258+
private func installBundle(from url: URL) throws -> PluginEntry {
243259
guard let sourceBundle = Bundle(url: url) else {
244260
throw PluginError.invalidBundle("Cannot create bundle from \(url.lastPathComponent)")
245261
}
@@ -251,19 +267,17 @@ final class PluginManager {
251267
throw PluginError.pluginConflict(existingName: existing.name)
252268
}
253269

254-
if let existingIndex = plugins.firstIndex(where: { $0.id == newBundleId }) {
255-
unregisterCapabilities(pluginId: newBundleId)
256-
plugins[existingIndex].bundle.unload()
257-
plugins.remove(at: existingIndex)
258-
}
270+
replaceExistingPlugin(bundleId: newBundleId)
259271

260272
let fm = FileManager.default
261273
let destURL = userPluginsDir.appendingPathComponent(url.lastPathComponent)
262274

263-
if fm.fileExists(atPath: destURL.path) {
264-
try fm.removeItem(at: destURL)
275+
if url.standardizedFileURL != destURL.standardizedFileURL {
276+
if fm.fileExists(atPath: destURL.path) {
277+
try fm.removeItem(at: destURL)
278+
}
279+
try fm.copyItem(at: url, to: destURL)
265280
}
266-
try fm.copyItem(at: url, to: destURL)
267281

268282
let entry = try loadPlugin(at: destURL, source: .userInstalled)
269283

@@ -321,6 +335,8 @@ final class PluginManager {
321335
throw PluginError.pluginConflict(existingName: existing.name)
322336
}
323337

338+
replaceExistingPlugin(bundleId: newBundleId)
339+
324340
let destURL = userPluginsDir.appendingPathComponent(extracted.lastPathComponent)
325341

326342
if fm.fileExists(atPath: destURL.path) {

TablePro/Info.plist

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
</array>
3030
</dict>
3131
<dict>
32+
<key>CFBundleTypeExtensions</key>
33+
<array>
34+
<string>tableplugin</string>
35+
</array>
3236
<key>CFBundleTypeName</key>
3337
<string>TablePro Plugin</string>
3438
<key>CFBundleTypeRole</key>

TablePro/Views/Settings/Plugins/InstalledPluginsView.swift

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ struct InstalledPluginsView: View {
1212
private let pluginManager = PluginManager.shared
1313

1414
@State private var selectedPluginId: String?
15-
@State private var isInstalling = false
1615
@State private var showErrorAlert = false
1716
@State private var errorAlertTitle = ""
1817
@State private var errorAlertMessage = ""
@@ -51,9 +50,9 @@ struct InstalledPluginsView: View {
5150
Button("Install from File...") {
5251
installFromFile()
5352
}
54-
.disabled(isInstalling)
53+
.disabled(pluginManager.isInstalling)
5554

56-
if isInstalling {
55+
if pluginManager.isInstalling {
5756
ProgressView()
5857
.controlSize(.small)
5958
}
@@ -205,9 +204,7 @@ struct InstalledPluginsView: View {
205204
}
206205

207206
private func installPlugin(from url: URL) {
208-
isInstalling = true
209207
Task {
210-
defer { isInstalling = false }
211208
do {
212209
let entry = try await pluginManager.installPlugin(from: url)
213210
selectedPluginId = entry.id

0 commit comments

Comments
 (0)