import Foundation final class RemotePush: ObservableObject { @Published var isTransmittingToRemote = false @Published var lastPushWasSuccessful = true func transmitToRemote(settings: GeneralSettings, outputFolder: String, outputHandler: @escaping (String) -> Void) { guard !isTransmittingToRemote else { return } DispatchQueue.main.async { self.isTransmittingToRemote = true } DispatchQueue.global().async { let success = self.transmit( outputFolder: outputFolder, remoteUser: settings.remoteUserForUpload, remotePort: settings.remotePortForUpload, remoteDomain: settings.url, remotePath: settings.remotePathForUpload, excludedItems: [".git"], outputHandler: outputHandler) DispatchQueue.main.async { self.isTransmittingToRemote = false self.lastPushWasSuccessful = success } } } private func transmit( outputFolder: String, remoteUser: String, remotePort: Int, remoteDomain: String, remotePath: String, excludedItems: [String], outputHandler: @escaping (String) -> Void ) -> Bool { let remoteDomain = remoteDomain.withHttpPrefixRemoved let remotePath = remotePath.withLeadingSlash.withTrailingSlash let outputFolder = outputFolder.withLeadingSlash.withTrailingSlash let process = Process() process.executableURL = URL(fileURLWithPath: "/bin/bash") let arguments = [ "/opt/homebrew/bin/rsync", "-hrutv", "--info=progress2"] + excludedItems.reduce(into: []) { $0 += ["--exclude", $1] } + [ "-e", "\"/opt/homebrew/bin/ssh -p \(remotePort)\"", outputFolder, "\(remoteUser)@\(remoteDomain):\(remotePath)" ] let argument = arguments.joined(separator: " ") process.arguments = ["-c", argument] let pipe = Pipe() process.standardOutput = pipe process.standardError = pipe let fileHandle = pipe.fileHandleForReading // Use a DispatchQueue to read output asynchronously fileHandle.readabilityHandler = { fileHandle in if let output = String(data: fileHandle.availableData, encoding: .utf8), !output.isEmpty { outputHandler(output) } } process.launch() process.waitUntilExit() if process.terminationStatus == 0 { return true } return false } }