Improved progress display

Previously, the progress bar updated only after completing an entire table.

For large tables such as LIBARC, this could make the application appear to have stopped responding.

Phase 10 updates the interface while rows are being exported.
This commit is contained in:
Mark Gregory
2026-10-18 14:43:13 +01:00
parent 0891230b39
commit ebd0bd1e29
9 changed files with 76 additions and 38 deletions
@@ -12,19 +12,19 @@
000000000000000000000015 /* DatabaseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000012 /* DatabaseModel.swift */; };
000000000000000000000017 /* MySQLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000016 /* MySQLProtocol.swift */; };
A10000000000000000000003 /* WorkbenchReplacement.icns in Resources */ = {isa = PBXBuildFile; fileRef = A10000000000000000000001 /* WorkbenchReplacement.icns */; };
A10000000000000000000004 /* WorkbenchBrand.png in Resources */ = {isa = PBXBuildFile; fileRef = A10000000000000000000002 /* WorkbenchBrand.png */; };
A10000000000000000000003 /* DBSyn.icns in Resources */ = {isa = PBXBuildFile; fileRef = A10000000000000000000001 /* DBSyn.icns */; };
A10000000000000000000004 /* DBSynBrand.png in Resources */ = {isa = PBXBuildFile; fileRef = A10000000000000000000002 /* DBSynBrand.png */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
00000000000000000000000F /* WorkbenchReplacement.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WorkbenchReplacement.app; sourceTree = BUILT_PRODUCTS_DIR; };
00000000000000000000000F /* DBSyn.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DBSyn.app; sourceTree = BUILT_PRODUCTS_DIR; };
000000000000000000000010 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
000000000000000000000011 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
000000000000000000000012 /* DatabaseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseModel.swift; sourceTree = "<group>"; };
000000000000000000000016 /* MySQLProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MySQLProtocol.swift; sourceTree = "<group>"; };
A10000000000000000000001 /* WorkbenchReplacement.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = WorkbenchReplacement.icns; sourceTree = "<group>"; };
A10000000000000000000002 /* WorkbenchBrand.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = WorkbenchBrand.png; sourceTree = "<group>"; };
A10000000000000000000001 /* DBSyn.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = DBSyn.icns; sourceTree = "<group>"; };
A10000000000000000000002 /* DBSynBrand.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = DBSynBrand.png; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -41,12 +41,12 @@
000000000000000000000002 = {
isa = PBXGroup;
children = (
000000000000000000000003 /* WorkbenchReplacement */,
000000000000000000000003 /* DBSyn */,
000000000000000000000004 /* Products */,
);
sourceTree = "<group>";
};
000000000000000000000003 /* WorkbenchReplacement */ = {
000000000000000000000003 /* DBSyn */ = {
isa = PBXGroup;
children = (
000000000000000000000010 /* App.swift */,
@@ -55,13 +55,13 @@
000000000000000000000016 /* MySQLProtocol.swift */,
A10000000000000000000005 /* Resources */,);
path = WorkbenchReplacement;
path = DBSyn;
sourceTree = "<group>";
};
000000000000000000000004 /* Products */ = {
isa = PBXGroup;
children = (
00000000000000000000000F /* WorkbenchReplacement.app */,
00000000000000000000000F /* DBSyn.app */,
);
name = Products;
sourceTree = "<group>";
@@ -70,8 +70,8 @@
A10000000000000000000005 /* Resources */ = {
isa = PBXGroup;
children = (
A10000000000000000000001 /* WorkbenchReplacement.icns */,
A10000000000000000000002 /* WorkbenchBrand.png */,
A10000000000000000000001 /* DBSyn.icns */,
A10000000000000000000002 /* DBSynBrand.png */,
);
path = Resources;
sourceTree = "<group>";
@@ -79,9 +79,9 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
000000000000000000000005 /* WorkbenchReplacement */ = {
000000000000000000000005 /* DBSyn */ = {
isa = PBXNativeTarget;
buildConfigurationList = 00000000000000000000000A /* Build configuration list for PBXNativeTarget "WorkbenchReplacement" */;
buildConfigurationList = 00000000000000000000000A /* Build configuration list for PBXNativeTarget "DBSyn" */;
buildPhases = (
000000000000000000000006 /* Sources */,
000000000000000000000008 /* Frameworks */,
@@ -91,9 +91,9 @@
);
dependencies = (
);
name = WorkbenchReplacement;
productName = WorkbenchReplacement;
productReference = 00000000000000000000000F /* WorkbenchReplacement.app */;
name = DBSyn;
productName = DBSyn;
productReference = 00000000000000000000000F /* DBSyn.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
@@ -109,7 +109,7 @@
};
};
};
buildConfigurationList = 000000000000000000000009 /* Build configuration list for PBXProject "WorkbenchReplacement" */;
buildConfigurationList = 000000000000000000000009 /* Build configuration list for PBXProject "DBSyn" */;
compatibilityVersion = "Xcode 14.0";
developmentRegion = en;
hasScannedForEncodings = 0;
@@ -122,7 +122,7 @@
projectDirPath = "";
projectRoot = "";
targets = (
000000000000000000000005 /* WorkbenchReplacement */,
000000000000000000000005 /* DBSyn */,
);
};
/* End PBXProject section */
@@ -133,8 +133,8 @@
buildActionMask = 2147483647;
files = (
A10000000000000000000003 /* WorkbenchReplacement.icns in Resources */,
A10000000000000000000004 /* WorkbenchBrand.png in Resources */,);
A10000000000000000000003 /* DBSyn.icns in Resources */,
A10000000000000000000004 /* DBSynBrand.png in Resources */,);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
@@ -184,10 +184,10 @@
DEVELOPMENT_TEAM = 39R6U3437D;
ENABLE_APP_SANDBOX = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "Workbench Replacement";
INFOPLIST_KEY_CFBundleIconFile = WorkbenchReplacement;
INFOPLIST_KEY_CFBundleDisplayName = "DBSyn";
INFOPLIST_KEY_CFBundleIconFile = DBSyn;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
PRODUCT_BUNDLE_IDENTIFIER = net.absdev.WorkbenchReplacement;
PRODUCT_BUNDLE_IDENTIFIER = net.absdev.DBSyn;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
@@ -202,10 +202,10 @@
DEVELOPMENT_TEAM = 39R6U3437D;
ENABLE_APP_SANDBOX = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "Workbench Replacement";
INFOPLIST_KEY_CFBundleIconFile = WorkbenchReplacement;
INFOPLIST_KEY_CFBundleDisplayName = "DBSyn";
INFOPLIST_KEY_CFBundleIconFile = DBSyn;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
PRODUCT_BUNDLE_IDENTIFIER = net.absdev.WorkbenchReplacement;
PRODUCT_BUNDLE_IDENTIFIER = net.absdev.DBSyn;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
@@ -215,7 +215,7 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
000000000000000000000009 /* Build configuration list for PBXProject "WorkbenchReplacement" */ = {
000000000000000000000009 /* Build configuration list for PBXProject "DBSyn" */ = {
isa = XCConfigurationList;
buildConfigurations = (
00000000000000000000000B /* Debug */,
@@ -224,7 +224,7 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
00000000000000000000000A /* Build configuration list for PBXNativeTarget "WorkbenchReplacement" */ = {
00000000000000000000000A /* Build configuration list for PBXNativeTarget "DBSyn" */ = {
isa = XCConfigurationList;
buildConfigurations = (
00000000000000000000000D /* Debug */,
@@ -2,18 +2,18 @@ import SwiftUI
import AppKit
extension Notification.Name {
static let workbenchRevealSQLEditor = Notification.Name("WorkbenchReplacement.RevealSQLEditor")
static let workbenchRevealSQLEditor = Notification.Name("DBSyn.RevealSQLEditor")
}
@main
struct WorkbenchReplacementApp: App {
struct DBSynApp: App {
@StateObject private var model = DatabaseModel()
var body: some Scene {
WindowGroup("Workbench Replacement") {
WindowGroup("DBSyn") {
WorkbenchLaunchView()
.onAppear {
if let iconURL = Bundle.main.url(forResource: "WorkbenchReplacement", withExtension: "icns"),
if let iconURL = Bundle.main.url(forResource: "DBSyn", withExtension: "icns"),
let icon = NSImage(contentsOf: iconURL) {
NSApplication.shared.applicationIconImage = icon
}
@@ -24,6 +24,17 @@ struct WorkbenchReplacementApp: App {
.defaultSize(width: 1200, height: 800)
.windowResizability(.contentSize)
.commands {
CommandGroup(replacing: .appInfo) {
Button("About DBSyn") {
let icon = NSImage(contentsOfFile: Bundle.main.path(forResource: "DBSynBrand", ofType: "png") ?? "")
NSApplication.shared.orderFrontStandardAboutPanel(options: [
.applicationName: "DBSyn",
.applicationIcon: icon ?? NSApplication.shared.applicationIconImage,
.version: "Phase 59.2",
.credits: NSAttributedString(string: "Native MySQL client for macOS")
])
}
}
WorkbenchFileCommands(model: model)
}
@@ -343,7 +354,7 @@ struct WorkbenchSplashView: View {
Spacer()
if let path = Bundle.main.path(
forResource: "WorkbenchBrand",
forResource: "DBSynBrand",
ofType: "png"
), let image = NSImage(contentsOfFile: path) {
Image(nsImage: image)
@@ -352,7 +363,7 @@ struct WorkbenchSplashView: View {
.frame(width: 160, height: 160)
}
Text("Workbench Replacement")
Text("DBSyn")
.font(.system(size: 30, weight: .bold))
Text("Modern MySQL Client for macOS")
@@ -372,7 +383,7 @@ struct WorkbenchSplashView: View {
ProgressView()
.frame(width: 260)
Text("Starting Workbench Replacement…")
Text("Starting DBSyn…")
.font(.caption)
.foregroundStyle(.secondary)
.padding(.bottom, 30)
@@ -175,7 +175,7 @@ struct MainView: View {
private var mainToolbar: some View {
HStack(spacing: 16) {
Image(nsImage: NSImage(named: "WorkbenchBrand") ?? NSImage(contentsOfFile: Bundle.main.path(forResource: "WorkbenchBrand", ofType: "png") ?? "") ?? NSImage())
Image(nsImage: NSImage(named: "DBSynBrand") ?? NSImage(contentsOfFile: Bundle.main.path(forResource: "DBSynBrand", ofType: "png") ?? "") ?? NSImage())
.resizable()
.scaledToFit()
.frame(width: 30, height: 30)
@@ -3463,7 +3463,7 @@ struct WorkbenchDatabaseTransferView: View {
func write(_ value: String) throws {
try handle?.write(contentsOf: Data(value.utf8))
}
try write("-- WorkbenchReplacement native export\n-- Database: \(database)\n-- Generated: \(Date())\nSET NAMES utf8mb4;\n\n")
try write("-- DBSyn native export\n-- Database: \(database)\n-- Generated: \(Date())\nSET NAMES utf8mb4;\n\n")
let db = quoteIdentifier(database)
for (index, table) in tables.enumerated() {
try Task.checkCancellation()
@@ -358,7 +358,7 @@ private final class PersistentMySQLSessions {
let connection: MySQLWireConnection
}
private let queue = DispatchQueue(label: "WorkbenchReplacement.MySQLSessions")
private let queue = DispatchQueue(label: "DBSyn.MySQLSessions")
private var connections: [UUID: Entry] = [:]
func execute(profile: ConnectionProfile, database: String,

Before

Width:  |  Height:  |  Size: 460 KiB

After

Width:  |  Height:  |  Size: 460 KiB

+10
View File
@@ -0,0 +1,10 @@
# Phase 59.2 — DBSyn rebrand
- Renamed Xcode project, app target, product, source directory, icon resource, image resource and release script to DBSyn.
- Updated bundle display name and identifier to `net.absdev.DBSyn`.
- Replaced the default About menu with an explicit About DBSyn action and branded About panel.
- Preserved original icon artwork (now `DBSyn.icns` and `DBSynBrand.png`).
- Kept six legacy UserDefaults/Keychain service identifiers intentionally to preserve previously saved connections, SQL history, scripts, credentials and snapshots. These are internal compatibility keys, not displayed branding.
- Xcode project needs to be opened using `DBSyn.xcodeproj`; old project path no longer exists.
- Because the bundle identifier changes, existing macOS Keychain access may require credentials to be re-entered depending on code-signing identity.
- Swift parser verified on all four Swift files; Xcode build requires macOS.
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
PHASE="${1:?Usage: release-dbsyn.sh PHASE NAME}"
NAME="${2:?Usage: release-dbsyn.sh PHASE NAME}"
cd "$ROOT"
mkdir -p releases
xcodebuild -project DBSyn.xcodeproj -scheme DBSyn -configuration Release -destination 'platform=macOS' -derivedDataPath "$ROOT/build" clean build
APP="$ROOT/build/Build/Products/Release/DBSyn.app"
test -d "$APP"
SOURCE="DBSyn-Phase${PHASE}-${NAME}-Source.zip"
BINARY="DBSyn-Phase${PHASE}-${NAME}-App-Unsigned.zip"
/usr/bin/zip -qr "releases/$SOURCE" DBSyn DBSyn.xcodeproj docs scripts -x '*.DS_Store'
(cd "$ROOT/build/Build/Products/Release" && /usr/bin/zip -qr "$ROOT/releases/$BINARY" DBSyn.app)
/usr/bin/unzip -tq "releases/$SOURCE" >/dev/null
/usr/bin/unzip -tq "releases/$BINARY" >/dev/null
printf 'Created:\n %s\n %s\n' "releases/$SOURCE" "releases/$BINARY"