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
+37 -4
View File
@@ -38,6 +38,8 @@ struct JobEditorView: View {
@State private var watchEnabled: Bool
@State private var watchPath: String
@State private var watchDebounceSeconds: Int
@State private var allowUnattendedDelete: Bool
@State private var validationMessage: String?
init(job: SyncJob? = nil, initialSection: JobEditorSection? = nil) {
self.existingJob = job
@@ -68,6 +70,8 @@ struct JobEditorView: View {
_watchPath = State(initialValue: job?.watchPath ?? "")
let defaultDebounce = UserDefaults.standard.integer(forKey: "defaultWatchDebounceSeconds")
_watchDebounceSeconds = State(initialValue: job?.watchDebounceSeconds ?? (defaultDebounce == 0 ? 3 : defaultDebounce))
_allowUnattendedDelete = State(initialValue: job?.allowUnattendedDelete ?? false)
_validationMessage = State(initialValue: nil)
}
var body: some View {
@@ -174,6 +178,12 @@ struct JobEditorView: View {
Toggle("Archive mode (-a)", isOn: $archive)
Toggle("Compress during transfer (-z)", isOn: $compress)
Toggle("Delete files missing from source (--delete)", isOn: $deleteExtraneous)
if deleteExtraneous && (scheduleEnabled || watchEnabled) {
Toggle("Allow unattended delete", isOn: $allowUnattendedDelete)
Text("Required before scheduled or file-watch runs may use --delete. Enable only after validating the job with Dry Run.")
.font(.caption)
.foregroundStyle(.orange)
}
Toggle("Preserve permissions", isOn: $preservePermissions)
Toggle("Preserve extended attributes", isOn: $preserveExtendedAttributes)
Toggle("Show transfer progress", isOn: $showProgress)
@@ -191,6 +201,9 @@ struct JobEditorView: View {
Section("Advanced") {
TextField("Additional rsync arguments", text: $additionalArguments)
.font(.system(.body, design: .monospaced))
Text("Transport, password, remote-command and destructive options are blocked here. Use the dedicated controls above for SSH and --delete.")
.font(.caption)
.foregroundStyle(.secondary)
}
}
.formStyle(.grouped)
@@ -213,6 +226,14 @@ struct JobEditorView: View {
}
}
}
.alert("Invalid Sync Job", isPresented: Binding(
get: { validationMessage != nil },
set: { if !$0 { validationMessage = nil } }
)) {
Button("OK", role: .cancel) { validationMessage = nil }
} message: {
Text(validationMessage ?? "The job configuration is invalid.")
}
.frame(width: 690, height: 820)
}
@@ -262,10 +283,22 @@ struct JobEditorView: View {
job.watchEnabled = watchEnabled
job.watchPath = watchPath
job.watchDebounceSeconds = watchDebounceSeconds
job.allowUnattendedDelete = deleteExtraneous ? allowUnattendedDelete : false
job.updatedAt = Date()
if existingJob == nil { modelContext.insert(job) }
try? modelContext.save()
NotificationCenter.default.post(name: .syncJobsChanged, object: nil)
dismiss()
let validationTrigger: SyncRunTrigger = (job.scheduleEnabled || job.watchEnabled) ? .scheduled : .manual
if let message = RsyncSecurityValidator.validationMessage(for: job, trigger: validationTrigger) {
validationMessage = message
return
}
do {
if existingJob == nil { modelContext.insert(job) }
try modelContext.save()
NotificationCenter.default.post(name: .syncJobsChanged, object: nil)
dismiss()
} catch {
validationMessage = "Could not save the job: \(error.localizedDescription)"
}
}
}