This is the multi-page printable view of this section. Click here to print.
Blog
Version 2.7.7
Version 2.7.7 (build 168) - Release Candidate (RC4)
Note: I initially believed that the next release, version 2.7.7, would be a minor update. However, it appears to be a more significant release.
On November 16th, a new release candidate has been uploaded. Since the previous release candidate, there have been some updates to the Swift Packages, and the logging section has been cleaned up and several unnecessary log statements have been removed. Additionally, the release build does not contain any log statements, the make procedure (building a new release) omits all logger DEBUG statements.
All changes in code since version 2.7.6 can be viewed here. The release is scheduled for late November. I personally use this version daily and frequently compile new versions when there are code updates.
Logger
Within RsyncUI, I utilize Swift Logger to display in Xcode Console whether a call is on the main thread or a background thread. Upon reviewing the logging, I discovered an excessive number of log statements. Consequently, in the next release build, logging statements are disabled for the release build, but they are enabled (by DEBUG flag) when running RsyncUI in Xcode only.
I have removed numerous “boilerplate code” for log statements and instead enhanced the Logger (by extension Logger) with new functions. Replacing all logging statements in code applies to numerous files.
Real-Time Output
Real-time output from rsync is now included. You can find it within the “hidden” menu, accessible using the shortcut ⌘S to reveal concealed toolbar options. The real-time view captures all output from rsync as it happens. The capture requires some additional CPU when activated, and it is automatically enabled and disabled when opening and closing the view. Additionally, the captured output is stored in memory and is cleared when the view is closed or by using the Clear button.
The real-time output captures the rsync output by an actor without blocking the @MainActor and UI updates. However, the @Observable object monitoring updates is executed on the @MainActor and updates the real-time view.
Schedules
A bug related to deleting schedules has been fixed, and upon request, the weekly schedule has been reinstated. Schedules can only be added for the current and the next month. This restriction is implemented to manage memory usage effectively, as every schedule includes the callback action, which is stored in memory. Schedules are automatically saved to a file and reloaded into memory upon startup.
Charts
A bug in the charts has been fixed. The bug could cause RsyncUI to crash.
Concealing Distractions
By using the shortcut ⌘S (for showing or hiding) or by accessing the Task main menu, the Charts and Quick task will be displayed. They are concealed by default. To reveal them, simply toggle the show or hide option using the shortcut.
Internal Refactor
Two minor but crucial objects have been refactored as Swift Package Manager (SPM) packages. Refactoring to SPM isolates the code, making it easier to test. The two objects are responsible for executing tasks, such as the actual rsync command with arguments outside of RsyncUI. Both objects listen for output from the tasks and the termination signal. If they fail, RsyncUI also fails.
A significant modification introduced by using SPM is the relocation of any local calls within the object to the outside of the SPM. This is achieved through Dependency Injection (DI). The process object depends on calling other objects within RsyncUI, and these objects are subsequently fed into the process object.
Swift Packages for RsyncUI
The two latest SPM packages are RsyncProcess and ProcessCommand.
Main Repository
- RsyncUI (https://github.com/rsyncOSX/RsyncUI) - the main repository for RsyncUI
Local RsyncUI Packages (by SPM)
SPM makes it easy to create local packages. Each package contains its own tests using Swift Testing, the new framework for creating tests.
- RsyncArguments (https://github.com/rsyncOSX/RsyncArguments) - Generate parameters for
rsyncbased on configurations - sshCreateKey (https://github.com/rsyncOSX/sshCreateKey) - Assist in creating an SSH identity file and key using RsyncUI
- generate an RSA-based SSH key for default and user-defined keys, including the SSH port number
- DecodeEncodeGeneric (https://github.com/rsyncOSX/DecodeEncodeGeneric) - Generic code for decoding and encoding JSON data
- ParseRsyncOutput (https://github.com/rsyncOSX/ParseRsyncOutput) - Parse and extract numerical values from the output of
rsync- this data is used to display details and log results for synchronized tasks
- RsyncUIDeepLinks (https://github.com/rsyncOSX/RsyncUIDeepLinks) - Parse and return valid URL deeplinks to execute tasks directly within RsyncUI
- RsyncProcess (https://github.com/rsyncOSX/RsyncProcess) - A minor package but a core function of RsyncUI
- listens for output from the rsync process as well as termination signal
- ProcessCommand (https://github.com/rsyncOSX/ProcessCommand) - As above, but for commands other than rsync
Version 2.7.6
Version 2.7.6 (build 167) - 3 November, 2025
A few users have experienced a crash when estimating tasks. The release fixes the issue.
The update does the following:
- preventing RsyncUI from crashing when the termination signal is received before all data is read from the datahandle
- the process object also now determines if there is still data to be retrieved after the termination signal is discovered
- although the reason for this is unknown, it does sometimes happen
- the estimation speed is increased
- the speed increase is due to more responsive handling of output from rsync and termination signal
- the actual speed of synchronization of data is not changed, that is outside RsyncUI
Verify remote
Overview
This post describes how I use the Verify remote function.
The Remote Setup
I back up my bird photography to multiple locations:
- a Raspberry Pi 5 server configured with two WD Red SA500 2.5" SSD 1TB drives set up as a mirrored ZFS pool
- a remote cloud service (JottaCloud)
- two 1TB NVMe external SSD drives attached to my computers
The first two locations are updated whenever data changes. JottaCloud updates are managed by a service running on my Macs. Updates to the Raspberry Pi 5 server are performed using RsyncUI.
Currently, my bird photography consists of 140GB and 8,000 files, including RAW and sidecar files. During travel, I use my MacBook Pro for photo editing, while at home, I use my Mac Mini M4. I always keep track of which Mac has been updated and stores the most recent files.
For this post, I am using my MacBook Pro as the local device, while the remote server contains more recently updated data.
Requirements for Using Verify Remote
The user is solely responsible for determining the appropriate action. RsyncUI provides only advisory guidance based on a basic evaluation of push and pull data comparison. Additionally, this function requires rsync version 3.x to be installed and enabled.
The Verify process requires that one of the Macs be synchronized with the remote. If both Macs have local unique updates, the Verify outcome is likely inaccurate, potentially resulting in data loss.
This function is not intended to be automated. Users must verify their subsequent actions.
The Procedure
Select the Pictures profile that stores my task to synchronize my local picture raw catalog with my remote server. After selecting the task, open the Verify remote function.

During the initial run, the Adjust output toggle is disabled. When enabled, the function eliminates the last 16 lines of output from both pull and push operations, as well as all other lines that are identical in both. To initiate the evaluation, select the upward arrow.

The remote server has less data than my local Mac because I deleted some photos locally that were previously synced to the remote. The output from rsync is tagged with information that is evaluated by looking at the first characters of each line. The push data in the left table indicates that my local Mac contains more data, which makes sense because the remote needs to be updated to reflect the deleted photos.

The Adjust output toggle is enabled, and a new evaluation begins.

The remote table on the right indicates that there are updates to sidecars and local data that need to be deleted.

I perform a pull of data from the remote to update my local Mac.

Following the pull, Verify remote displays that my local Mac and remote device are in sync.

Number of files
Numbers updated: November 15, 2025, version 2.7.7 rc4
There is a very nice and excellent tool, cloc, for counting of files and lines of code. Below are the numbers for Swift files which are part of the repository for compiling RsyncUI. RsyncUI does not rely on external libraries; it is constructed using default Swift libraries and Swift/SwiftUI code exclusively.
cloc RsyncUI/RsyncUI DecodeEncodeGeneric/Sources ParseRsyncOutput/Sources RsyncArguments/Sources RsyncUIDeepLinks/Sources SSHCreateKey/Sources RsyncProcess/Sources ProcessCommand/Sources
201 text files.
201 unique files.
8 files ignored.
github.com/AlDanial/cloc v 2.06 T=0.06 s (3319.3 files/s, 371034.7 lines/s)
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
Swift 195 2324 2489 17230
C 2 36 72 254
XML 2 0 0 53
JSON 1 0 0 6
C/C++ Header 1 1 3 0
-------------------------------------------------------------------------------
SUM: 201 2361 2564 17543
-------------------------------------------------------------------------------
Main Repository
- RsyncUI (https://github.com/rsyncOSX/RsyncUI) - the main repository for RsyncUI
Local RsyncUI packages (by SPM)
SPM makes it easy to create local packages. And each package containes their own tests by Swift Testing, the new framwork for creating tests. All packages are created by me.
- RsyncArguments (https://github.com/rsyncOSX/RsyncArguments) - Generate parameters for
rsyncbased on configurations - sshCreateKey (https://github.com/rsyncOSX/sshCreateKey) - Assist in creating an SSH identity file and key using RsyncUI
- generate an RSA-based SSH key for default and user-defined keys, including the SSH port number
- DecodeEncodeGeneric (https://github.com/rsyncOSX/DecodeEncodeGeneric) - Generic code for decoding and encoding JSON data
- ParseRsyncOutput (https://github.com/rsyncOSX/ParseRsyncOutput) - Parse and extract numerical values from the output of
rsync- this data is used to display details and log results for synchronized tasks
- RsyncUIDeepLinks (https://github.com/rsyncOSX/RsyncUIDeepLinks) - parse end return valid URL deeplink for execute tasks direct within RsyncUI
- RsyncProcess (https://github.com/rsyncOSX/RsyncProcess) - one minor package, but a core function of RsyncUI
- listens for output from the rsync process as well as termination signal
- ProcessCommand (https://github.com/rsyncOSX/ProcessCommand) - as above, but for other commands than rsync
Swift concurrency
First, I must acknowledge that my understanding of Swift concurrency is limited. While I have a basic grasp of the subject, if you are reading this and seeking more detailed information, I strongly recommend searching for and reading articles from other sources that provide a more comprehensive explanation of Swift concurrency.
RsyncUI is a graphical user interface (GUI) application; the majority of its operations are executed on the main thread. However, some resource-intensive tasks are performed on other threads managed by the cooperative thread pool (CTP), excluding and not blocking the main thread. How the executors and CTP work and interact are details I don’t know about—they are managed by the Swift runtime. There are three kinds of executors:
- the main executor manages jobs on the main thread
- the global concurrent executor and the serial executor, both execute jobs on threads from the CTP
Most work in RsyncUI is executed on the main thread. By default, SwiftUI ensures all UI updates are performed on the main thread. Below are some tasks within RsyncUI that are executed on the main thread:
- preparing and executing
rsyncsynchronization tasks (preparing involves computing the correct arguments for rsync) - monitoring progress and termination of the actual
rsynctasks- monitoring progress is an asynchronous sequence running on the main thread, created by an asynchronous sequence of two specific notifications generated by the notification center
- some write and read operations
Swift Version 6 and the New Concurrency Model
Swift version 6 introduced strict concurrency checking. By enabling Swift 6 language mode and strict concurrency checking, Xcode assists in identifying and resolving possible data races at compile time.
Quote from swift.org: “More formally, a data race occurs when one thread accesses memory while the same memory is being modified by another thread. The Swift 6 language mode eliminates these issues by preventing data races at compile time.”
RsyncUI adheres to the new concurrency model of Swift 6.
Swift Concurrency and Asynchronous Execution
Concurrency and asynchronous execution are important parts of Swift. The latest version of Swift simplifies writing asynchronous code using the async and await keywords, as well as the actor protocol. While RsyncUI doesn’t inherently require concurrency, it is automatically introduced through the use of actors, async, and await keywords. The objective is to execute most work synchronously on the main thread, provided it doesn’t block the GUI.
Asynchronous execution can be performed on both the main thread and background threads from the CTP. When executing asynchronous operations on the main thread, it is crucial to use Swift’s structured concurrency, specifically the async/await keywords. The await keyword serves as a suspension point, allowing other and more critical tasks to access the main thread.
Cooperative Thread Pool (CTP)
The following tasks are executed asynchronously on threads from the CTP, adhering to the actor protocol:
- read synchronization tasks from file
- JSON data decoding: asynchronous decoding that inherits the thread from the actor reading data
- JSON data encoding: synchronous encoding on the main thread
- read and sort log records
- preparing output from rsync for display
- preparing data from the log file for display
- checking for updates to RsyncUI
Adhering to the actor protocol, all access to properties within an actor must be performed asynchronously. There are only five actors in RsyncUI, but there are more asynchronous functions, some of which run on the main thread as well.
Structured Concurrency
Some concurrent functions within RsyncUI are structured using async let. You may have several async let statements, and they will all execute in parallel or concurrently. When all async let tasks are completed, the root task or parent task will continue execution.
Structured concurrency might also dictate the order of execution. The keyword await is a suspension point where execution waits until the asynchronous function is completed before continuing. If there are several await statements in sequence, the next one will execute when the current asynchronous task is completed.
Tagging of data
Overview
It is imperative that RsyncUI correctly tags tasks with data to be synchronized. If the tagging fails, some source data may not be synchronized. RsyncUI supports both the latest version of rsync and the older default version included in macOS 14 and macOS 15.
The tagging of data to be synchronized is computed within the ParseRsyncOutput package, a local Swift Package for RsyncUI.
From version 2.4.0, there is verification of the tagging process. If the output from rsync exceeds 20 lines but no data is tagged for synchronization, RsyncUI displays an alert. Normally, if there is no data to synchronize, output from rsync is about 20 lines.
As of version 2.4.0, extracting numbers from a string containing letters and digits is now a single-line operation.
Example:
- Input string:
Number of created files: 7,191 (reg: 6,846, dir: 345) - Converted to:
[7191, 6846, 345](the thousands separator is also removed from the string before parsing)
The function below extracts only numbers from the input:
public func returnIntNumber(_ input: String) -> [Int] {
var numbers: [Int] = []
let str = input.replacingOccurrences(of: ",", with: "")
let stringArray = str.components(separatedBy: CharacterSet.decimalDigits.inverted)
.compactMap { $0.isEmpty == true ? nil : $0 }
for item in stringArray where item.isEmpty == false {
if let number = Int(item) {
numbers.append(number)
}
}
if numbers.count == 0 {
return [0]
} else {
return numbers
}
}
The parsing of rsync output is not particularly complex, though it differs somewhat between the latest version of rsync and the default versions.
Latest Version of rsync
The trailing output from the latest version of rsync (version 3.4.1) looks like:
....
Number of files: 7,192 (reg: 6,846, dir: 346)
Number of created files: 7,191 (reg: 6,846, dir: 345)
Number of deleted files: 0
Number of regular files transferred: 6,846
Total file size: 24,788,299 bytes
Total transferred file size: 24,788,299 bytes
Literal data: 0 bytes
Matched data: 0 bytes
File list size: 0
File list generation time: 0.003 seconds
File list transfer time: 0.000 seconds
Total bytes sent: 394,304
Total bytes received: 22,226
sent 394,304 bytes received 22,226 bytes 833,060.00 bytes/sec
total size is 24,788,299 speedup is 59.51 (DRY RUN)
Default Version of rsync
The trailing output from the default version of rsync looks like:
....
Number of files: 7192
Number of files transferred: 6846
Total file size: 24788299 bytes
Total transferred file size: 24788299 bytes
Literal data: 0 bytes
Matched data: 0 bytes
File list size: 336861
File list generation time: 0.052 seconds
File list transfer time: 0.000 seconds
Total bytes sent: 380178
Total bytes received: 43172
sent 380178 bytes received 43172 bytes 169340.00 bytes/sec
total size is 24788299 speedup is 58.55
How the Tagging Works
The output from rsync is parsed and numbers are extracted. After parsing, these numbers determine whether data should be tagged for synchronization.
Latest Version of rsync
Three numbers determine whether data needs to be synchronized: the number of updates (regular files transferred), new files, and deleted files. Each can be either 0 or a positive number, and all three must be verified.
Default Versions
Only one number determines whether data needs to be synchronized: the number of updates (files transferred).