Skip to content

Commit e0ec9b2

Browse files
kochj23claude
andcommitted
fix(ux): Fix SMB/USB destination picker, path checks, and logs folder (v1.7.3)
Three bugs reported in issue #2 (JimmysDev): - browseForDestination: set panel.prompt="Select" and add message hint. On macOS 15, clicking "Open" on a directory in NSOpenPanel navigates into it rather than selecting it, making external volumes impossible to set as a destination without knowing this quirk. - performConnectionTest: use isReadableFile for /Volumes/ source paths; destination check now passes when path doesn't exist (rsync creates it) and only fails if the volume root itself is not mounted. - openLogsFolder: create ~/Library/Application Support/RsyncGUI/Logs/ before opening it. NSWorkspace.open silently does nothing on a nonexistent URL, making the button appear broken. Bumps version to 1.7.3 (build 18). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
1 parent 30bdd67 commit e0ec9b2

4 files changed

Lines changed: 59 additions & 11 deletions

File tree

README.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# RsyncGUI v1.7.2
1+
# RsyncGUI v1.7.3
22

33
![Build](https://github.com/kochj23/RsyncGUI/actions/workflows/build.yml/badge.svg)
44

@@ -20,7 +20,7 @@ A modern, open-source alternative to the discontinued [RsyncOSX](https://github.
2020

2121
## Download
2222

23-
Download the latest release: [RsyncGUI v1.7.2](https://github.com/kochj23/RsyncGUI/releases/latest)
23+
Download the latest release: [RsyncGUI v1.7.3](https://github.com/kochj23/RsyncGUI/releases/latest)
2424

2525
Or build from source (see below).
2626

@@ -37,7 +37,23 @@ If you're coming from RsyncOSX (discontinued), RsyncGUI offers:
3737

3838
---
3939

40-
## 🆕 What's New in v1.7.2 (March 2026)
40+
## 🆕 What's New in v1.7.3 (March 2026)
41+
42+
### Bug Fix Release — SMB/USB Destination, Path Checks, Logs
43+
44+
**SMB share → USB drive destination picker — fixed:**
45+
- The folder browser now shows a "Select" button instead of "Open" and displays a hint message. On macOS 15, clicking "Open" on a directory navigated *into* it rather than selecting it, making it impossible to set an SMB or USB destination without knowing to navigate inside the target folder first.
46+
47+
**"Some checks failed" on valid external/network paths — fixed:**
48+
- Source path check now uses `isReadableFile` for `/Volumes/` paths. `fileExists` can return `false` on SMB shares and USB drives even when mounted and accessible, causing a false failure.
49+
- Destination path check no longer fails when the path doesn't exist. rsync creates destination directories — a missing path is expected on first run. The check now only fails if the volume itself is not mounted.
50+
51+
**"Open Logs Folder" button does nothing — fixed:**
52+
- `~/Library/Application Support/RsyncGUI/Logs/` is now created if it doesn't exist before trying to open it. `NSWorkspace.open()` silently does nothing on a nonexistent URL.
53+
54+
---
55+
56+
## What's New in v1.7.2 (March 2026)
4157

4258
### Bug Fix Release — Large Jobs, iCloud, and Reliability
4359

RsyncGUI.xcodeproj/project.pbxproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,7 @@
599599
CODE_SIGN_ENTITLEMENTS = "RsyncGUI Widget/RsyncGUI_Widget.entitlements";
600600
CODE_SIGN_IDENTITY = "-";
601601
CODE_SIGN_STYLE = Automatic;
602-
CURRENT_PROJECT_VERSION = 17;
602+
CURRENT_PROJECT_VERSION = 18;
603603
DEVELOPMENT_TEAM = "";
604604
GENERATE_INFOPLIST_FILE = YES;
605605
INFOPLIST_FILE = "RsyncGUI Widget/Info.plist";
@@ -611,7 +611,7 @@
611611
"@executable_path/../../../../Frameworks",
612612
);
613613
MACOSX_DEPLOYMENT_TARGET = 14.0;
614-
MARKETING_VERSION = 1.7.2;
614+
MARKETING_VERSION = 1.7.3;
615615
PRODUCT_BUNDLE_IDENTIFIER = com.jordankoch.rsyncgui.widget;
616616
PRODUCT_NAME = "$(TARGET_NAME)";
617617
SDKROOT = macosx;
@@ -629,7 +629,7 @@
629629
CODE_SIGN_ENTITLEMENTS = "RsyncGUI Widget/RsyncGUI_Widget.entitlements";
630630
CODE_SIGN_IDENTITY = "-";
631631
CODE_SIGN_STYLE = Automatic;
632-
CURRENT_PROJECT_VERSION = 17;
632+
CURRENT_PROJECT_VERSION = 18;
633633
DEVELOPMENT_TEAM = "";
634634
GENERATE_INFOPLIST_FILE = YES;
635635
INFOPLIST_FILE = "RsyncGUI Widget/Info.plist";
@@ -641,7 +641,7 @@
641641
"@executable_path/../../../../Frameworks",
642642
);
643643
MACOSX_DEPLOYMENT_TARGET = 14.0;
644-
MARKETING_VERSION = 1.7.2;
644+
MARKETING_VERSION = 1.7.3;
645645
PRODUCT_BUNDLE_IDENTIFIER = com.jordankoch.rsyncgui.widget;
646646
PRODUCT_NAME = "$(TARGET_NAME)";
647647
SDKROOT = macosx;

RsyncGUI/Views/JobEditorView.swift

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -521,11 +521,17 @@ struct JobEditorView: View {
521521
panel.canChooseFiles = false
522522
panel.canChooseDirectories = true
523523
panel.allowsMultipleSelection = false
524+
// "Select" instead of "Open" avoids the macOS quirk where clicking
525+
// "Open" on a directory navigates into it rather than selecting it.
526+
// This is especially confusing on SMB shares and USB drives.
527+
panel.prompt = "Select"
528+
panel.message = "Navigate inside the destination folder, then click Select."
524529

525530
guard panel.runModal() == .OK, let url = panel.url else { return }
531+
let selectedPath = url.path
526532
DispatchQueue.main.async {
527533
if let index = self.job.destinations.firstIndex(where: { $0.id == destinationId }) {
528-
self.job.destinations[index].path = url.path
534+
self.job.destinations[index].path = selectedPath
529535
}
530536
}
531537
}
@@ -1334,7 +1340,11 @@ struct JobEditorView: View {
13341340

13351341
// 1. Check source path
13361342
let sourcePath = job.source.replacingOccurrences(of: "~", with: FileManager.default.homeDirectoryForCurrentUser.path)
1337-
let sourceExists = FileManager.default.fileExists(atPath: sourcePath)
1343+
// Use isReadableFile for /Volumes/ paths (SMB/USB) — fileExists can return
1344+
// false on network/external volumes even when the path is valid and mounted.
1345+
let sourceExists = sourcePath.hasPrefix("/Volumes/")
1346+
? FileManager.default.isReadableFile(atPath: sourcePath)
1347+
: FileManager.default.fileExists(atPath: sourcePath)
13381348
checks.append(ConnectionCheck(
13391349
name: "Source Path",
13401350
passed: sourceExists,
@@ -1344,11 +1354,28 @@ struct JobEditorView: View {
13441354
// 2. Check destination path (if local)
13451355
if !job.isRemote {
13461356
let destPath = job.destination.replacingOccurrences(of: "~", with: FileManager.default.homeDirectoryForCurrentUser.path)
1357+
// Destination not existing is not a failure — rsync creates it.
1358+
// Only fail if the path's parent volume is unreachable.
13471359
let destExists = FileManager.default.fileExists(atPath: destPath)
1360+
let destReachable: Bool
1361+
if destExists {
1362+
destReachable = true
1363+
} else if destPath.hasPrefix("/Volumes/") {
1364+
// For external/network volumes, check if the volume root is mounted
1365+
let components = destPath.split(separator: "/", maxSplits: 3)
1366+
let volumeRoot = components.count >= 3 ? "/Volumes/\(components[2])" : destPath
1367+
destReachable = FileManager.default.fileExists(atPath: volumeRoot)
1368+
} else {
1369+
destReachable = true // rsync will create local paths
1370+
}
13481371
checks.append(ConnectionCheck(
13491372
name: "Destination Path",
1350-
passed: destExists,
1351-
message: destExists ? "\(destPath)" : "⚠️ Path not found (will be created): \(destPath)"
1373+
passed: destReachable,
1374+
message: destExists
1375+
? "\(destPath)"
1376+
: destReachable
1377+
? "⚠️ Path not found (will be created): \(destPath)"
1378+
: "❌ Volume not mounted: \(destPath)"
13521379
))
13531380
}
13541381

RsyncGUI/Views/SettingsView.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,11 @@ struct AdvancedSettings: View {
187187
return
188188
}
189189
let logsURL = appSupport.appendingPathComponent("RsyncGUI/Logs")
190+
// Create the directory if it doesn't exist yet — NSWorkspace.open silently
191+
// does nothing on a nonexistent URL, which caused the button to appear broken.
192+
if !FileManager.default.fileExists(atPath: logsURL.path) {
193+
try? FileManager.default.createDirectory(at: logsURL, withIntermediateDirectories: true)
194+
}
190195
NSWorkspace.shared.open(logsURL)
191196
}
192197

0 commit comments

Comments
 (0)