Major update for stability

This commit is contained in:
Mark Gregory
2026-09-08 15:22:19 +01:00
parent 3b95f53a25
commit b2f3c5aef6
24 changed files with 1059 additions and 212 deletions
+15 -13
View File
@@ -14,17 +14,16 @@ final class RsyncRunner: ObservableObject {
let wasCancelled: Bool
}
func run(job: SyncJob, dryRun: Bool) async throws -> Result {
func run(job: SyncJob, dryRun: Bool, trigger: SyncRunTrigger = .manual) async throws -> Result {
guard !isRunning else { throw RunnerError.alreadyRunning }
guard !job.sourcePath.isEmpty, !job.destinationPath.isEmpty else { throw RunnerError.invalidPaths }
if job.isRemote && job.remoteHost.isEmpty { throw RunnerError.missingRemoteHost }
if job.isRemote && remoteDestinationLooksLikeHost(job) { throw RunnerError.invalidRemoteDestination }
if job.isRemote && job.sshKeyPath.trimmingCharacters(in: .whitespacesAndNewlines).hasSuffix(".pub") {
let privatePath = RsyncCommandBuilder.normalizedPrivateKeyPath(job.sshKeyPath)
if privatePath.hasSuffix(".pub") { throw RunnerError.publicSSHKeySelected }
if let message = RsyncSecurityValidator.validationMessage(for: job, trigger: trigger) {
throw RunnerError.securityValidation(message)
}
let executable = RsyncLocator.locate()
guard RsyncLocator.isTrustedExecutableCandidate(executable) else {
throw RunnerError.invalidExecutable(executable)
}
let arguments = RsyncCommandBuilder.build(job: job, dryRun: dryRun, executable: executable)
isRunning = true
@@ -106,15 +105,18 @@ final class RsyncRunner: ObservableObject {
}
enum RunnerError: LocalizedError {
case alreadyRunning, invalidPaths, missingRemoteHost, invalidRemoteDestination, publicSSHKeySelected
case alreadyRunning
case securityValidation(String)
case invalidExecutable(String)
var errorDescription: String? {
switch self {
case .alreadyRunning: return "Another rsync job is already running."
case .invalidPaths: return "Source and destination are required."
case .missingRemoteHost: return "Remote host is required for an SSH job."
case .invalidRemoteDestination: return "Remote destination must be a path on the remote machine, for example /home/mag/backup or ~/backup — not the host address."
case .publicSSHKeySelected: return "The SSH key must be the private key (for example ~/.ssh/id_rsa or ~/.ssh/id_ed25519), not the .pub file."
case .alreadyRunning:
return "Another rsync job is already running."
case .securityValidation(let message):
return message
case .invalidExecutable(let path):
return "The rsync executable at \(path) is unavailable or is not a trusted regular executable file."
}
}
}