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
18 changes: 10 additions & 8 deletions TablePro/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//

import AppKit
import Combine
import os
import SwiftUI

Expand All @@ -13,6 +14,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
static let lifecycleLogger = Logger(subsystem: "com.TablePro", category: "NativeTabLifecycle")

private var hasRunPostLaunchActivation = false
private var pluginsRejectedCancellable: AnyCancellable?

// MARK: - URL & File Open

Expand Down Expand Up @@ -76,10 +78,11 @@ class AppDelegate: NSObject, NSApplicationDelegate {
self, selector: #selector(windowWillClose(_:)),
name: NSWindow.willCloseNotification, object: nil
)
NotificationCenter.default.addObserver(
self, selector: #selector(handlePluginsRejected(_:)),
name: .pluginsRejected, object: nil
)
pluginsRejectedCancellable = AppEvents.shared.pluginsRejected
.receive(on: RunLoop.main)
.sink { [weak self] rejected in
self?.handlePluginsRejected(rejected)
}
NotificationCenter.default.addObserver(
self, selector: #selector(handleFocusConnectionForm),
name: .focusConnectionFormWindowRequested, object: nil
Expand Down Expand Up @@ -153,9 +156,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {

// MARK: - Plugin Rejection Alert

@objc private func handlePluginsRejected(_ notification: Notification) {
guard let rejected = notification.object as? [RejectedPlugin],
!rejected.isEmpty else { return }
private func handlePluginsRejected(_ rejected: [RejectedPlugin]) {
guard !rejected.isEmpty else { return }
let details = rejected.map { "\($0.name): \($0.reason)" }.joined(separator: "\n")
Task {
let alert = NSAlert()
Expand Down Expand Up @@ -198,7 +200,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
$0 !== window && AppLaunchCoordinator.isMainWindow($0) && $0.isVisible
}.count
if remaining == 0 {
NotificationCenter.default.post(name: .mainWindowWillClose, object: nil)
AppEvents.shared.mainWindowWillClose.send(())
WindowOpener.shared.openWelcome()
}
}
Expand Down
4 changes: 2 additions & 2 deletions TablePro/Core/Database/DatabaseManager+Health.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import AppKit
import Combine
import Foundation
import os
import TableProPluginKit
Expand Down Expand Up @@ -297,8 +298,7 @@ extension DatabaseManager {
await startHealthMonitor(for: sessionId)
}

// Post connection notification for schema reload
NotificationCenter.default.post(name: .databaseDidConnect, object: nil)
AppEvents.shared.databaseDidConnect.send(DatabaseDidConnect(connectionId: sessionId))

Self.logger.info("Manual reconnect succeeded for: \(session.connection.name)")
} catch {
Expand Down
2 changes: 1 addition & 1 deletion TablePro/Core/Database/DatabaseManager+Sessions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ extension DatabaseManager {
appSettingsStorage.saveLastConnectionId(connection.id)

MacAnalyticsProvider.shared.markConnectionSucceeded()
NotificationCenter.default.post(name: .databaseDidConnect, object: nil)
AppEvents.shared.databaseDidConnect.send(DatabaseDidConnect(connectionId: connection.id))

let supportsHealth = PluginMetadataRegistry.shared.snapshot(
forTypeId: connection.type.pluginTypeId
Expand Down
52 changes: 50 additions & 2 deletions TablePro/Core/Events/AppEvents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ import Foundation
final class AppEvents {
static let shared = AppEvents()

// MARK: - Theme & Accessibility

let themeChanged = PassthroughSubject<Void, Never>()

let connectionStatusChanged = PassthroughSubject<ConnectionStatusChange, Never>()
let accessibilityTextSizeChanged = PassthroughSubject<Void, Never>()

// MARK: - Settings

let editorSettingsChanged = PassthroughSubject<Void, Never>()

Expand All @@ -22,7 +26,43 @@ final class AppEvents {

let terminalSettingsChanged = PassthroughSubject<Void, Never>()

let accessibilityTextSizeChanged = PassthroughSubject<Void, Never>()
// MARK: - Connections

let connectionStatusChanged = PassthroughSubject<ConnectionStatusChange, Never>()

let connectionUpdated = PassthroughSubject<Void, Never>()

let databaseDidConnect = PassthroughSubject<DatabaseDidConnect, Never>()

let mainCoordinatorTeardown = PassthroughSubject<MainCoordinatorTeardown, Never>()

// MARK: - Window

let mainWindowWillClose = PassthroughSubject<Void, Never>()

// MARK: - Data Sources

let queryHistoryDidUpdate = PassthroughSubject<Void, Never>()

let sqlFavoritesDidUpdate = PassthroughSubject<Void, Never>()

let linkedFoldersDidUpdate = PassthroughSubject<Void, Never>()

let linkedSQLFoldersDidUpdate = PassthroughSubject<Void, Never>()

// MARK: - License & Sync

let licenseStatusDidChange = PassthroughSubject<Void, Never>()

let syncChangeTracked = PassthroughSubject<Void, Never>()

// MARK: - MCP

let mcpAuditLogChanged = PassthroughSubject<Void, Never>()

// MARK: - Plugins

let pluginsRejected = PassthroughSubject<[RejectedPlugin], Never>()

private init() {}
}
Expand All @@ -31,3 +71,11 @@ struct ConnectionStatusChange: Sendable {
let connectionId: UUID
let status: ConnectionStatus
}

struct DatabaseDidConnect: Sendable {
let connectionId: UUID
}

struct MainCoordinatorTeardown: Sendable {
let connectionId: UUID
}
7 changes: 2 additions & 5 deletions TablePro/Core/MCP/MCPAuditLogStorage.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import Combine
import Foundation
import os
import SQLite3

extension Notification.Name {
static let mcpAuditLogChanged = Notification.Name("com.TablePro.mcp.auditLogChanged")
}

actor MCPAuditLogStorage {
static let shared = MCPAuditLogStorage()
private static let logger = Logger(subsystem: "com.TablePro", category: "MCPAuditLogStorage")
Expand Down Expand Up @@ -160,7 +157,7 @@ actor MCPAuditLogStorage {
let inserted = sqlite3_step(statement) == SQLITE_DONE
if inserted {
Task { @MainActor in
NotificationCenter.default.post(name: .mcpAuditLogChanged, object: nil)
AppEvents.shared.mcpAuditLogChanged.send(())
}
}
return inserted
Expand Down
3 changes: 2 additions & 1 deletion TablePro/Core/Plugins/PluginManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// TablePro
//

import Combine
import Foundation
import os
import Security
Expand Down Expand Up @@ -191,7 +192,7 @@ final class PluginManager {
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)
AppEvents.shared.pluginsRejected.send(self.rejectedPlugins)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion TablePro/Core/Plugins/PluginModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ enum PluginSource {
case userInstalled
}

struct RejectedPlugin {
struct RejectedPlugin: Sendable {
let url: URL
let bundleId: String?
let registryId: String?
Expand Down
3 changes: 2 additions & 1 deletion TablePro/Core/Services/Export/ConnectionExportService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// TablePro
//

import Combine
import Foundation
import os
import UniformTypeIdentifiers
Expand Down Expand Up @@ -565,7 +566,7 @@ enum ConnectionExportService {
}

if importedCount > 0 {
NotificationCenter.default.post(name: .connectionUpdated, object: nil)
AppEvents.shared.connectionUpdated.send(())
logger.info("Imported \(importedCount) connections")
}

Expand Down
5 changes: 3 additions & 2 deletions TablePro/Core/Services/Export/LinkedFolderWatcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// Rescans on filesystem changes with 1s debounce.
//

import Combine
import CryptoKit
import Foundation
import os
Expand Down Expand Up @@ -58,7 +59,7 @@ final class LinkedFolderWatcher {
debounceTask = Task { @MainActor [weak self] in
let results = await Self.scanFoldersAsync(folders)
self?.linkedConnections = results
NotificationCenter.default.post(name: .linkedFoldersDidUpdate, object: nil)
AppEvents.shared.linkedFoldersDidUpdate.send(())
}
}

Expand All @@ -70,7 +71,7 @@ final class LinkedFolderWatcher {
let folders = LinkedFolderStorage.shared.loadFolders()
let results = await Self.scanFoldersAsync(folders)
self?.linkedConnections = results
NotificationCenter.default.post(name: .linkedFoldersDidUpdate, object: nil)
AppEvents.shared.linkedFoldersDidUpdate.send(())
}
}

Expand Down
16 changes: 0 additions & 16 deletions TablePro/Core/Services/Infrastructure/AppNotifications.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,23 @@
import Foundation

extension Notification.Name {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update remaining query history notification consumers

This migration removes the Notification.Name.queryHistoryDidUpdate declaration, but the test target still observes .queryHistoryDidUpdate in TableProTests/Core/Storage/QueryHistoryManagerTests.swift when verifying delete/clear notifications. As a result, compiling or running the test target will fail until those tests are migrated to AppEvents.shared.queryHistoryDidUpdate or a compatibility notification is kept.

Useful? React with 👍 / 👎.

// MARK: - Query History

static let queryHistoryDidUpdate = Notification.Name("queryHistoryDidUpdate")

// MARK: - Connections

static let connectionUpdated = Notification.Name("connectionUpdated")
static let databaseDidConnect = Notification.Name("databaseDidConnect")
static let exportConnections = Notification.Name("exportConnections")
static let importConnections = Notification.Name("importConnections")
static let importConnectionsFromApp = Notification.Name("importConnectionsFromApp")
static let linkedFoldersDidUpdate = Notification.Name("linkedFoldersDidUpdate")
static let focusConnectionFormWindowRequested = Notification.Name("focusConnectionFormWindowRequested")
static let openSampleDatabaseRequested = Notification.Name("openSampleDatabaseRequested")
static let resetSampleDatabaseRequested = Notification.Name("resetSampleDatabaseRequested")

// MARK: - License

static let licenseStatusDidChange = Notification.Name("licenseStatusDidChange")

// MARK: - Export

static let exportQueryResults = Notification.Name("exportQueryResults")

// MARK: - SQL Favorites

static let sqlFavoritesDidUpdate = Notification.Name("sqlFavoritesDidUpdate")
static let saveAsFavoriteRequested = Notification.Name("saveAsFavoriteRequested")

// MARK: - Plugins

static let pluginsRejected = Notification.Name("pluginsRejected")

// MARK: - Feedback

static let showFeedbackWindow = Notification.Name("com.TablePro.showFeedbackWindow")
Expand Down
11 changes: 6 additions & 5 deletions TablePro/Core/Services/Infrastructure/WelcomeRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//

import AppKit
import Combine
import Foundation
import Observation

Expand All @@ -16,14 +17,14 @@ internal final class WelcomeRouter {
private(set) var pendingConnectionShare: URL?
private(set) var pendingSQLFiles: [URL] = []

@ObservationIgnored private var databaseDidConnectCancellable: AnyCancellable?

private init() {
NotificationCenter.default.addObserver(
forName: .databaseDidConnect, object: nil, queue: .main
) { _ in
MainActor.assumeIsolated {
databaseDidConnectCancellable = AppEvents.shared.databaseDidConnect
.receive(on: RunLoop.main)
.sink { _ in
WelcomeRouter.shared.drainPendingSQLFiles()
}
}
}

private func drainPendingSQLFiles() {
Expand Down
3 changes: 2 additions & 1 deletion TablePro/Core/Services/Licensing/LicenseManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Orchestrates license activation, offline verification, and periodic re-validation
//

import Combine
import Foundation
import Observation
import os
Expand Down Expand Up @@ -289,7 +290,7 @@ final class LicenseManager {

private func notifyIfChanged(from previousStatus: LicenseStatus) {
if status != previousStatus {
NotificationCenter.default.post(name: .licenseStatusDidChange, object: nil)
AppEvents.shared.licenseStatusDidChange.send(())
}
}
}
9 changes: 3 additions & 6 deletions TablePro/Core/Services/SQL/SQLFolderWatcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@
// TablePro
//

import Combine
import CoreServices
import Foundation
import os

internal extension Notification.Name {
static let linkedSQLFoldersDidUpdate = Notification.Name("com.TablePro.linkedSQLFoldersDidUpdate")
}

@MainActor
@Observable
internal final class SQLFolderWatcher {
Expand Down Expand Up @@ -105,7 +102,7 @@ internal final class SQLFolderWatcher {
debounceTask = Task { @MainActor [weak self] in
await Self.rescan(folders: folders)
self?.lastScanCompletedAt = Date()
NotificationCenter.default.post(name: .linkedSQLFoldersDidUpdate, object: nil)
AppEvents.shared.linkedSQLFoldersDidUpdate.send(())
}
}

Expand All @@ -120,7 +117,7 @@ internal final class SQLFolderWatcher {
let folders = LinkedSQLFolderStorage.shared.loadFolders().filter(\.isEnabled)
await Self.rescan(folders: folders)
self?.lastScanCompletedAt = Date()
NotificationCenter.default.post(name: .linkedSQLFoldersDidUpdate, object: nil)
AppEvents.shared.linkedSQLFoldersDidUpdate.send(())
}
}

Expand Down
10 changes: 4 additions & 6 deletions TablePro/Core/Storage/QueryHistoryManager.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Combine
import Foundation

final class QueryHistoryManager {
Expand Down Expand Up @@ -59,10 +60,7 @@ final class QueryHistoryManager {
let success = await storage.addHistory(entry)
if success {
await MainActor.run {
NotificationCenter.default.post(
name: .queryHistoryDidUpdate,
object: nil
)
AppEvents.shared.queryHistoryDidUpdate.send(())
}
}
}
Expand Down Expand Up @@ -97,7 +95,7 @@ final class QueryHistoryManager {
let success = await storage.deleteHistory(id: id)
if success {
await MainActor.run {
NotificationCenter.default.post(name: .queryHistoryDidUpdate, object: nil)
AppEvents.shared.queryHistoryDidUpdate.send(())
}
}
return success
Expand All @@ -111,7 +109,7 @@ final class QueryHistoryManager {
let success = await storage.clearAllHistory()
if success {
await MainActor.run {
NotificationCenter.default.post(name: .queryHistoryDidUpdate, object: nil)
AppEvents.shared.queryHistoryDidUpdate.send(())
}
}
return success
Expand Down
Loading
Loading