This is the multi-page printable view of this section. Click here to print.
Blog
Version 2.8.2
Version 2.8.2 (build 173) - not yet released
Why are these blogs about RsyncUI? I am writing these blogs primarily to document my work, but I believe it is important to be transparent about how I develop RsyncUI. I am not sure who reads these blogs, but I know that the changelogs are frequently read when there are new releases. Additionally, I am learning every day and consider myself an average competent Swift developer. I commenced use Visual Studio Code and MCP because I “stumbled” upon an YouTube video about them. In essence, my primary method of staying informed about Swift development involves reading blogs and watching videos created by other developers.
I have commenced a comprehensive code formatting cleanup utilizing SwiftLint, SwiftFormat, and periphery. Despite using these tools for several years, some cleanup was still required.
All of the aforementioned tools are widely recognized and utilized by numerous developers.
The periphery application is employed to eliminate unused code. SwiftFormat is utilized for code formatting and SwiftLint code quality. All instructions for SwiftFormat in code are removed, and both tools now have a single configuration file each containing instructions. They work very well together.
As a result, numerous code modifications have been made, primarily due to the formatting adjustments implemented using the aforementioned tools.
Visual Studio Code (VSC) and GitHub MCP services
Model Context Protocol (MCP) is an open standard, introduced in November 2024 by Anthropic. Quote Google “The Model Context Protocol (MCP) is an open standard designed to solve this. Introduced by Anthropic in November 2024, MCP provides a secure and standardized “language” for LLMs to communicate with external data, applications, and services. It acts as a bridge, allowing AI to move beyond static knowledge and become a dynamic agent that can retrieve current information and take action, making it more accurate, useful, and automated.”
I have recently begun utilizing Visual Studio Code (VSC) and its integration with GitHub MCP services. I initiated a request to VSC, utilizing the MCP services, to analyze the codebase for RsyncUI and identify issues in naming conventions. I am highly impressed by the capabilities of AI in supporting developers. The aforementioned request resulted in several code updates, and two reports have been generated as a result. These reports are authored by AI and are accessible from the root directory of the RsyncUI repository.
Although Xcode remains my primary development tool, Visual Studio Code (VSC) is also officially supported by swift.org. Utilizing Xcode ensures that the latest Swift toolchain is always up-to-date. VSC can also utilize the Xcode-installed toolchain, and it offers a wide range of extensions.
Version 2.8.1
Version 2.8.1 (build 172) - Dec 5, 2025
An AI-generated changelog is available at the bottom of this page.
This maintenance release addresses a few issues.
All annoying messages regarding missing files are resolved.
Empty stats file
Please see the note about no stats in the changelog (blog) for version 2.8.0.
However, version 2.8.2 will introduce an option to enable it. If RsyncUI detects an empty statistics file, it will append a default log record stating 0 files : 0.00 MB in 0.00 seconds.
If logging fails due to the aforementioned reason, this error will be generated, if enabled in version 2.8.2.

Release Notes: v2.8.0 β v2.8.1
Overview
This release cycle includes 25 commits across 32 files, focusing on stability improvements, architectural refactoring, and enhanced error handling. The release emphasizes code maintainability and robustness.
Stats: 526 insertions(+), 386 deletions(-)
ποΈ Architectural Improvements
Execution Logic Refactoring
- Split
EstimateExecute.swiftinto separateEstimate.swiftandExecute.swiftfiles - Cleaned up initialization logic and removed convenience initializers
- Improved separation of concerns between estimation and execution phases
- Refactored EstimateExecute initialization logic
Error Handling & Robustness
- Enhanced error handling in
RemoteDataNumbersinitialization - Added fallback mechanisms for missing stats with default values
- Improved error catching in log record handling
- Added conditional error handling when appending default stats to logs
- Refactored error handling and logging method names
Code Cleanup
- Removed network monitoring feature and related code
- Removed sleeptime setting and configuration (after initial addition, then reverted)
- Removed WidgetVerify extension and related files
- Removed verify URL support and related code
- Cleaned up unused code in estimation initialization
π Logging & Monitoring Enhancements
Advanced Logging System
- Refactored log record handling with improved error catching
- Added conditional logging for summary log records
- Added debug logging for
getstats()success and failure tracking - Added commented-out function for permanent log storage (future feature)
- Improved rsync output parsing to use
getstats()with comprehensive error handling - Updated
Logging.swiftwith enhanced functionality
Log Settings
- Updated
ObservableLogSettings.swiftwith refined configurations - Enhanced log file handling in storage actors
- Improved
ActorReadLogRecordsJSON.swifterror handling
π¨ UI & Progress Tracking
Progress View Improvements
- Extracted
SynchronizeProgressViewto separate file for better modularity - Added
RestoreProgressViewfor improved progress display during restore operations - Set max value on dry run in restore and quicktask operations
- Refactored progress view with max value support
- Improved UI progress indicators and logging
View Enhancements
- Increased minHeight for sheet views in
TasksView - Refactored URL estimate button logic in
AddTaskView - Updated query item handling in
SidebarTasksView - Updated estimation in progress view
π§ Process & Configuration Management
Path & Catalog Handling
- Fixed config path concatenation in
Homepath - Refactored catalog and volume models and services
- Updated catalog path logic and progress handling
- Added
sharedPathForRestoreto Params with formatted restore arguments - Refactored path concatenation and alert handling
Process Management
- Refactored RsyncProcess termination configuration
- Improved TCPconnections handling
- Enhanced execution flow in estimation and task views
- Updated
Execute.swiftwith improved process handling
π¦ Dependencies & Project Structure
- Updated
Package.resolvedand RsyncUIDeepLinks package revision - Updated Makefile configuration
- Updated
project.pbxprojwith structural changes - Added version 2.8.1 JSON configuration files
- Updated README.md and Sonoma JSON to reference v2.8.0
π§Ή Code Quality Improvements
- Consistent code formatting improvements across multiple files
- Enhanced
ReadAllTasksutility with better error management - Improved alert handling patterns in views
- Refactored
ActorReadSynchronizeConfigurationJSONfor better readability - Updated
EditValueErrorSchemewith refined error handling
Key Files Modified
- EstimateExecute (split into Estimate.swift and Execute.swift)
- Logging.swift
- RemoteDataNumbers.swift
- SynchronizeProgressView.swift (extracted)
- RestoreTableView.swift
- Multiple view files in Views
Testing Focus
- Verify estimation and execution separation works correctly
- Test error handling for missing stats and log records
- Validate progress tracking in restore operations
- Confirm all removed features (network monitoring, verify widgets) don’t cause issues
Release Target: v2.8.1rc1
Branch: version-2.8.1
Previous Release: v2.8.0
Version 2.8.0
Version 2.8.0 (build 171) - Dec 2, 2025
An AI-generated changelog is available at the bottom of this page.
The following summarizes changes in this release:
- The major update within this release is that all seven Swift Packages (SPM) are updated to version 2.0.
- All SPM are validated by their own test, using the new Swift Testing framework.
- Several cleanups and refactorings have been made in the code.
- There are approximately 17,000 lines of Swift code, and some code has not been reviewed for some time.
- It has been about five years since I commenced development of RsyncUI, based on code end experience from my previous project.
- I have learned a lot during these years, and now most of the code has been reviewed at least once since the start of development.
- Almost every time I review old code, there are some refactorings, simplifications, and cleaning of the code.
- There are approximately 17,000 lines of Swift code, and some code has not been reviewed for some time.
- A bug in the restore data functionality has been resolved.
- Only one Widget is now available, Estimate and Execute.
- All synchronization, such as quicktask and restore, now includes a progress bar if there has been an estimate ahead.
- Real-time capture of rsync includes capturing to a file. Users can view either the RsyncUI logfile or the rsync capture to file in the view logfile.
- A gesture has been added to indicate when buttons are pressed.
Please be aware that real-time logging to file and view incurs a performance penalty. In certain instances, the termination signal may precede the complete draining of input from the filehandle, resulting in the absence of data for logging. While data synchronization is ensured, there may be no logging regarding the synchronization process.
My recommendation is to utilize this feature exclusively during the testing of new tasks and βdry-run tasks.
This feature is automatically disabled when the view is closed or switched off by buttons in the view.
No stats
There is more info about the upcoming maintenance release, version 2.8.1, about this issue in the blog about version 2.8.1
The process termination signal indicates that the external rsync process has completed and the process has been terminated. All tasks within the main synchronize view are updated with the latest run, but there is also a separate logging that records the main result of each task with a timestamp.
Occasionally, when synchronizing a small amount of data to fast SSDs, the termination signal is detected before all output from rsync has been received. In such cases, the separate logging may be missing. After data synchronization and the absence of a log, you can verify the synchronization of data by recalculating the estimate.
The process termination signal serves as a message to perform logging, but if the last summarized rsync output is missing, there is nothing to log.
Output from rsync refers to the information that rsync provides to the terminal during the execution of a task.
Normally, the logging works as expected.
Swift Packages
All SPM packages include their own testing mechanisms, and all tests have been successfully passed. SPM packages are generally small and focused on a specific purpose, which simplifies testing for edge cases and typical usage scenarios.
Main Repository
- RsyncUI (https://github.com/rsyncOSX/RsyncUI) - the main repository for RsyncUI
Swift Packages used by RsyncUI
All SPM packages are refactored, updated, and checked into the main branch. RsyncUI is a depended on all packages, but the last one is not mandatory. SSH keys can be generated via command line.
- 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
- RsyncArguments (https://github.com/rsyncOSX/RsyncArguments) - Generate parameters for
rsyncbased on configurations - 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
- 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
Development Summary: v2.7.8 β v2.8.0
Overview
This issue summarizes the major development work completed since the release of version 2.7.8, including architectural improvements, UI enhancements, and new features leading toward version 2.8.0.
π Development Statistics
pie title "Commits by Category (70 total commits)"
"Refactoring" : 25
"UI/UX Improvements" : 18
"Feature Additions" : 15
"Package Updates" : 12ποΈ Major Feature Areas
mindmap
root((v2.7.8 β v2.8.0))
Progress Tracking
RestoreProgressView
SynchronizeProgressView
Max value support
Real-time updates
Logging System
Dedicated log window
Actor-based concurrency
File output option
Delete optimization
Architecture
Params struct
SSHParams refactor
Actor concurrency
Path handling
UI Refinements
Glass button style
Dynamic text colors
Toolbar layouts
Progress indicators
Widget System
Remove WidgetVerify
URL handling updates
DeepLinks improvementsπ Development Flow
gitGraph
commit id: "v2.7.8 Release"
branch version-2.7.9
checkout version-2.7.9
commit id: "Add log window"
commit id: "Actor concurrency"
commit id: "File output option"
commit id: "Glass button style"
commit tag: "v2.7.9rc1"
commit id: "Process termination"
commit id: "Widget refinements"
commit id: "Restore progress"
commit tag: "v2.7.9rc2"
checkout main
merge version-2.7.9
branch version-2.8.0
checkout version-2.8.0
commit id: "Catalog refactor"
commit id: "Progress improvements"
commit id: "Path concatenation fix"
commit id: "EstimateExecute refactor"
commit id: "Current HEAD"π¦ Major Refactoring Initiatives
1. Architecture & Code Organization
graph TD
A[Monolithic Code] --> B[Structured Architecture]
B --> C[Params Struct]
B --> D[SSHParams Struct]
B --> E[Actor-based Logging]
B --> F[Service Layer]
C --> C1[Centralized Parameters]
D --> D1[SSH Handling]
E --> E1[DeleteLogrecords Actor]
E --> E2[ActorReadLogRecordsJSON]
F --> F1[AttachedVolumesService]
F --> F2[HomeCatalogsService]
style B fill:#90EE90
style C fill:#87CEEB
style D fill:#87CEEB
style E fill:#87CEEB
style F fill:#87CEEB2. Progress Tracking System Enhancement
sequenceDiagram
participant User
participant UI
participant Process
participant Progress
User->>UI: Initiate Task
UI->>Process: Start rsync
Process->>Progress: Report progress
Progress->>UI: Update RestoreProgressView
Progress->>UI: Update SynchronizeProgressView
UI->>User: Real-time feedback
Process->>Progress: Set max value
Progress->>UI: Display completion %
UI->>User: Task complete3. Logging System Modernization
graph LR
A[Old Logging] --> B[New Logging System]
B --> C[Dedicated Window]
B --> D[Actor Concurrency]
B --> E[File Output]
B --> F[Optimized Deletion]
C --> C1[Separate Log View]
D --> D1[Thread-safe Operations]
E --> E1[Persistent Logs]
F --> F1[Batch Processing]
style A fill:#FFB6C6
style B fill:#90EE90
style C fill:#87CEEB
style D fill:#87CEEB
style E fill:#87CEEB
style F fill:#87CEEBπ¨ UI/UX Improvements
graph TD
A[UI Enhancements] --> B[Visual Polish]
A --> C[Component Updates]
A --> D[Layout Improvements]
B --> B1[Glass Button Style]
B --> B2[Sustained Pressure Animation]
B --> B3[Dynamic Text Colors]
C --> C1[ConditionalGlassButton]
C --> C2[RestoreProgressView]
C --> C3[SynchronizeProgressView]
D --> D1[Toolbar Layouts]
D --> D2[Sheet View Heights]
D --> D3[Progress Indicators]
style A fill:#FFD700
style B fill:#87CEEB
style C fill:#87CEEB
style D fill:#87CEEBπ§ Technical Improvements
Parameter Handling Refactor
Before:
- Parameters scattered across multiple classes
- Inconsistent SSH parameter creation
- Duplicated argument construction logic
After:
- Centralized
Paramsstruct - Dedicated
SSHParamsstruct - Consistent parameter construction
- Better code reusability
Concurrency Model
Key Changes:
- Introduction of Actor-based concurrency for log management
DeleteLogrecordsactor for safe log deletionActorReadLogRecordsJSONoptimization- Thread-safe state management
Path Handling
Improvements:
- URL extension for home directory access
- Fixed config path concatenation
- Better catalog path logic
- Unified path handling approach
ποΈ Cleanup & Removal
graph LR
A[Removed Components] --> B[WidgetVerifyExtension]
A --> C[Verify URL Support]
A --> D[AllOutputView]
A --> E[Unused Environment Variables]
A --> F[Convenience Initializers]
B --> B1[Simplified widget system]
C --> C1[Streamlined URL handling]
D --> D1[Replaced with RsyncRealtimeView]
E --> E1[Cleaner codebase]
F --> F1[Explicit initialization]
style A fill:#FFB6C6π Commit Activity Timeline
gantt
title Development Timeline (v2.7.8 β v2.8.0)
dateFormat YYYY-MM-DD
section Logging System
Actor concurrency & log management :done, 2024-10-01, 2024-10-15
Dedicated log window :done, 2024-10-15, 2024-10-25
File output & optimization :done, 2024-10-25, 2024-11-05
section Architecture
Params & SSHParams refactor :done, 2024-10-20, 2024-11-10
Service layer improvements :done, 2024-11-15, 2024-11-25
section UI/UX
Glass button & animations :done, 2024-10-28, 2024-11-08
Progress tracking enhancements :done, 2024-11-10, 2024-11-25
section Progress System
Restore progress view :done, 2024-11-12, 2024-11-20
Synchronize progress extraction :done, 2024-11-22, 2024-11-28
Max value support :done, 2024-11-28, 2024-12-02
section Cleanup
Widget removal & refinements :done, 2024-11-05, 2024-11-15
Code cleanup & optimization :done, 2024-11-20, 2024-12-02π― Key Achievements
β Enhanced Progress Tracking
- New
RestoreProgressViewfor better restore operation visibility - Extracted
SynchronizeProgressViewto separate file - Added max value support for accurate progress percentages
- Improved UI progress indicators across all views
- New
β Modern Logging System
- Dedicated log window for better log management
- Actor-based concurrency for thread-safe operations
- File output option for persistent logging
- Optimized log deletion with batch processing
β Architectural Improvements
- Introduced
Paramsstruct for centralized parameter management - Created
SSHParamsstruct for SSH handling - Refactored catalog and volume models with service layer
- Improved error handling and method naming consistency
- Introduced
β UI Polish
- Glass button style with sustained pressure animations
- Dynamic text colors for better visual feedback
- Refined toolbar layouts across multiple views
- Increased sheet view heights for better content display
β Code Quality
- Removed deprecated widget components
- Fixed path concatenation issues
- Eliminated unused code and environment variables
- Improved code organization and modularity
π Code Quality Metrics
graph LR
A[Code Improvements] --> B[+2500 lines added]
A --> C[-1800 lines removed]
A --> D[Net: +700 lines]
B --> B1[New features]
B --> B2[Better structure]
C --> C1[Removed redundancy]
C --> C2[Cleaned deprecated code]
D --> D1[More functionality]
D --> D2[Better maintainability]
style A fill:#90EE90
style B fill:#87CEEB
style C fill:#FFB6C6
style D fill:#FFD700π Next Steps for v2.8.0
- Final testing of all refactored components
- Performance benchmarking of actor-based logging
- User acceptance testing of new progress views
- Documentation updates
- Release notes preparation
π Notes
- Two release candidates (v2.7.9rc1 and v2.7.9rc2) were created during development
- The current branch
version-2.8.0is actively being developed - All changes maintain backward compatibility with existing configurations
- Swift package dependencies have been updated to their latest stable versions
Total Commits: 70
Files Changed: ~150+
Primary Focus Areas: Architecture, UI/UX, Logging, Progress Tracking
Development Period: Post v2.7.8 release β Present
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 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: December 2, 2025, version 2.8.0
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 DecodeEncodeGeneric/Sources ParseRsyncOutput/Sources RsyncArguments/Sources RsyncUI/RsyncUI RsyncUIDeepLinks/Sources
SSHCreateKey/Sources RsyncProcess/Sources ProcessCommand/Sources
203 text files.
203 unique files.
16 files ignored.
github.com/AlDanial/cloc v 2.06 T=0.05 s (4239.5 files/s, 475137.0 lines/s)
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
Swift 197 2518 2750 17058
C 2 36 72 254
XML 2 0 0 53
JSON 1 0 0 6
C/C++ Header 1 1 3 0
-------------------------------------------------------------------------------
SUM: 203 2555 2825 17371
-------------------------------------------------------------------------------
Main Repository
- RsyncUI (https://github.com/rsyncOSX/RsyncUI) - the main repository for RsyncUI
Swift Packages used by RsyncUI
All SPM packages are refactored, updated, and checked into the main branch. RsyncUI is a depended on all packages, but the last one is not mandatory. SSH keys can be generated via command line.
- 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
- RsyncArguments (https://github.com/rsyncOSX/RsyncArguments) - Generate parameters for
rsyncbased on configurations - 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
- 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
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.
However, it is important to note that there are several compiler directives, configured within Xcode, that pertain to concurrency. I strongly recommend researching these directives and their respective meanings. Additionally, if you are migrating an existing project to the new concurrency settings, I suggest researching the process of migrating projects. I recommend reading blog posts about Swift concurrency from Matt Massicotte and Antoine van der Lee.
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 RsyncUI 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
- delete 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.
Example of structured concurrency
Approximately 800 log records, comprising the total of 1500, are selected for deletion. Two operations are concurrently executed on a background thread: the actual deletion of log records and the updating of non-deleted records for display.
The deletion of logs commences on the main thread. The user selects the logs to be deleted, and the delete button either initiates the deletion process or aborts it. The deletelogs() function, an asynchronous function, is initiated on the main thread.
Button("Delete", role: .destructive) {
Task {
await deletelogs(selectedloguuids)
}
}
The deletelogs starts on the main thread and jumps off to an background thread inside the Task {}.
func deletelogs(_ uuids: Set<UUID>) async {
Task {
print("(1) start async let updatedRecords deletelogs")
async let updatedRecords: [LogRecords]? = ActorReadLogRecordsJSON().deletelogs(
uuids,
logrecords: logrecords,
profile: rsyncUIdata.profile,
validhiddenIDs: validhiddenIDs
)
let records = await updatedRecords
print("(2) awaited updatedRecords deletelogs from (1))")
print("(3) start async let updatedRecords updatelogsbyhiddenID)")
async let updatedLogs: [Log]? = ActorReadLogRecordsJSON().updatelogsbyhiddenID(records, hiddenID)
logrecords = records
logs = await (updatedLogs ?? [])
print("(4) awaited updatedLogs from (3)")
WriteLogRecordsJSON(rsyncUIdata.profile, records)
selectedloguuids.removeAll()
}
}
The debug windows in Xcode display the following:
The actors also print whether they execute on the main thread. The first async let statement initiates execution, and the subsequent await statement for the result above (2) suspends the function’s execution until the asynchronous result is computed. The await statement is crucial for suspending the execution of the function until the asynchronous result is available. And then the next (3) and (4).
(1) start async let updatedRecords deletelogs
ActorReadLogRecordsJSON: deletelogs() NOT on main thread, currently on <NSThread: 0xa49e3c200>{number = 18}
ActorReadLogRecordsJSON: DEINIT
(2) awaited updatedRecords deletelogs from (1))
(3) start async let updatedRecords updatelogsbyhiddenID)
ActorReadLogRecordsJSON: updatelogsbyhiddenID() NOT on main thread, currently on <NSThread: 0xa49e3c280>{number = 17}
ActorReadLogRecordsJSON: DEINIT
(4) awaited updatedLogs from (3)
WriteLogRecordsJSON: writeJSONToPersistentStore file:///Users/thomas/.rsyncosx/VPxxxxxxxx/WDBackup/logrecords.json
WriteLogRecordsJSON DEINIT
ActorReadLogRecordsJSON: updatelogsbyhiddenID() NOT on main thread, currently on <NSThread: 0xa4bb72800>{number = 19}
ActorReadLogRecordsJSON: DEINIT
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.
The tagging of data to be synchronized is computed within the ParseRsyncOutput package, a local Swift Package for RsyncUI.
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.
Version 3.4.x
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)
Openrsync
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).