How to upload results to label in shell from swift code? - swift

Everything is working fine, but I don't understand why the wrong label "loading" doesn't update the percentage. while i print the output value is continuously changing
do {
#discardableResult
func shell(_ args: String...) -> (String?, Int32) {
let task = Process()
task.launchPath = "/usr/bin/env"
task.arguments = args
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)
task.waitUntilExit()
return (output, task.terminationStatus)
}
shell(....)
let strURL = "http://download.com/load.zip"
let url = URL(string: strURL)
FileDownloader(url! as NSObject).download(url: url!)
var result = ""
func checkdl() {
let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
let fileURL = dir!.appendingPathComponent("load.txt")
result = try! String(contentsOf: fileURL, encoding: .utf8)
print(result)
if result.contains("100.0") {
try! "".write(to: fileURL, atomically: true, encoding: String.Encoding.utf8)
print("download done")
} else {
sleep(2)
loading.stringValue = result+"%"
checkdl()
}
}
checkdl()
shell(....)
}
Everything is working fine, but I don't understand why the wrong label "loading" doesn't update the percentage. while i print the output value is continuously changing

Related

Swift start Process and read continously changed output

I have this code to continously read the output of a started process:
let task = Process()
task.arguments = ["-c", command]
task.launchPath = "/bin/zsh"
let pipe = Pipe()
let errorPipe = Pipe()
task.standardOutput = pipe
task.standardError = errorPipe
let outHandle = pipe.fileHandleForReading
let errorHandle = errorPipe.fileHandleForReading
outHandle.waitForDataInBackgroundAndNotify()
errorHandle.waitForDataInBackgroundAndNotify()
var updateObserver: NSObjectProtocol!
updateObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.NSFileHandleDataAvailable, object: outHandle, queue: nil, using: { notification in
let data = outHandle.availableData
if !data.isEmpty {
if let str = String(data: data, encoding: .utf8) {
print(str) // This is differently here.
}
outHandle.waitForDataInBackgroundAndNotify()
} else {
NotificationCenter.default.removeObserver(updateObserver!)
}
})
var errorObserver: NSObjectProtocol!
errorObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.NSFileHandleDataAvailable, object: errorHandle, queue: nil, using: { notification in
let data = errorHandle.availableData
if !data.isEmpty {
if let str = String(data: data, encoding: .utf8) {
print(str) // This is differently here.
}
errorHandle.waitForDataInBackgroundAndNotify()
} else {
NotificationCenter.default.removeObserver(errorObserver!)
}
})
var taskObserver : NSObjectProtocol!
taskObserver = NotificationCenter.default.addObserver(forName: Process.didTerminateNotification, object: task, queue: nil, using: { notification in
print("terminated")
NotificationCenter.default.removeObserver(taskObserver!)
})
task.launch()
Now this works for processes that print a new line with every change.
What does not work is to get outputs of processes that edit already printed lines (changin percentage or something like that).
In that case, the pipe is not pushing anything.
How would I handle that case. I thought about doing the output into a file, read that and at the end deleting it. Is that a possible solution?

More Buffer for FileHandler Output?

I have the following Code:
func syncShellExec(path: String, args: [String] = []) {
let process = Process()
process.launchPath = "/bin/bash"
process.arguments = [path] + args
let outputPipe = Pipe()
let filelHandler = outputPipe.fileHandleForReading
process.standardOutput = outputPipe
process.launch()
filelHandler.readabilityHandler = { pipe in
let data = pipe.availableData
if let line = String(data: data, encoding: String.Encoding.utf8) {
DispatchQueue.main.sync {
self.output_window.string += line
self.output_window.scrollToEndOfDocument(nil)
}
} else {
print("Error decoding data: \(data.base64EncodedString())")
}
}
process.waitUntilExit()
filelHandler.readabilityHandler = nil
}
If the amount of round about 330.000 Characters is reached the output stopped immediately. Is there a way to increase the Buffer for this Operation?
There are two problems:
The process may terminate before all data has been read from the pipe.
Your function blocks the main thread, so that the UI eventually freezes.
Similarly as in How can I tell when a FileHandle has nothing left to be read? you should wait asynchronously for the process to terminate, and also wait for “end-of-file” on the pipe:
func asyncShellExec(path: String, args: [String] = []) {
let process = Process()
process.launchPath = "/bin/bash"
process.arguments = [path] + args
let outputPipe = Pipe()
let filelHandler = outputPipe.fileHandleForReading
process.standardOutput = outputPipe
process.launch()
let group = DispatchGroup()
group.enter()
filelHandler.readabilityHandler = { pipe in
let data = pipe.availableData
if data.isEmpty { // EOF
filelHandler.readabilityHandler = nil
group.leave()
return
}
if let line = String(data: data, encoding: String.Encoding.utf8) {
DispatchQueue.main.sync {
self.output_window.string += line
self.output_window.scrollToEndOfDocument(nil)
}
} else {
print("Error decoding data: \(data.base64EncodedString())")
}
}
process.terminationHandler = { process in
group.wait()
DispatchQueue.main.sync {
// Update UI that process has finished.
}
}
}

Execute a Swift script in terminal

I have built a command line tool, at some point, I need to execute a curl command. I'm creating the script that should be executed, but I don't know how.
I'm able to create the script and printing it out, but I'm not being able to execute it.
It looks something like: curl https://api.github.com/zen
Please ask me anything if it's not clear. I appreciate your help.
#!/usr/bin/env swift
import Foundation
func run(_ args: String...) -> Int32 {
let task = Process()
task.launchPath = "/usr/bin/env"
task.arguments = args
task.launch()
task.waitUntilExit()
return task.terminationStatus
}
run("curl", "https://api.github.com/zen")
You can run a Terminal command from Swift using NSTask (now called Process in Swift 3): If you need output, add let output = handle.readDataToEndOfFile() at the end. Here's the whole thing wrapped in a function (the launchPath would be /usr/bin/curl):
func runTask(launchPath: String, flags: [String]) -> String {
let task = Process()
let pipe = Pipe()
task.launchPath = launchPath
task.arguments = flags
task.standardOutput = pipe
let handle = pipe.fileHandleForReading
task.launch()
return String(data: handle.readDataToEndOfFile(), encoding: .utf8) ?? ""
}
In your case though, you might want to have a look at URLSession and URLRequest (superseding NSURLRequest). To create a request to your URL and credentials, you would simply do:
var request = URLRequest(url:URL(string: "https://api.github.com/zen")!)
request.setValue("application/vnd.github.v3.raw", forHTTPHeaderField: "Accept")
request.setValue("token USERTOKEN", forHTTPHeaderField: "Authorization")
let session = URLSession(configuration: .default)
session.dataTask(with: request, completionHandler: {(data, response, error) in
guard let data = data, error == nil else {
print("Error: \(error.debugDescription)")
return
}
guard let output = String(data: data, encoding: .utf8) as String? else {
print("Unable to format output data")
return
}
print(output)
}).resume()

Swift NStask function

I'm a complete swift noob. Using this code in xcode I get the result I need. I created a command line binary "menubar" that takes several arguments. I normally run it in the terminal "/bin/menubar getip", "/bin/menubar/getuser". I want to create a function based on the following working code.
import Cocoa
import Foundation
var task:NSTask = NSTask()
var pipe:NSPipe = NSPipe()
task.launchPath = "/bin/menubar"
task.arguments = ["getip"]
task.standardOutput = pipe
task.launch()
var handle = pipe.fileHandleForReading
var data = handle.readDataToEndOfFile()
var result_s = NSString(data: data, encoding: NSUTF8StringEncoding)
print(result_s)
I want to convert it to a function.
func commmand (argument: String) -> String
{
let task:NSTask = NSTask()
let pipe:NSPipe = NSPipe()
task.launchPath = "/bin/menubar"
task.arguments = ["argument"]
task.standardOutput = pipe
task.launch()
let handle = pipe.fileHandleForReading
let data = handle.readDataToEndOfFile()
let result_s = NSString(data: data, encoding: NSUTF8StringEncoding)
return result_s
}
commmand getip
Try this:
func commmand(argument: String) -> String
{
let task:NSTask = NSTask()
let pipe:NSPipe = NSPipe()
task.launchPath = "/bin/menubar"
task.arguments = [argument]
task.standardOutput = pipe
task.launch()
let handle = pipe.fileHandleForReading
let data = handle.readDataToEndOfFile()
let result_s = String(data: data, encoding: NSUTF8StringEncoding)!
return result_s
}
print(commmand("getip"))

Using Pandoc with Swift

I'm trying to use Pandoc to convert LaTeX to Markdown. I need to create a file and then run the pandoc terminal command. The problem is the file I create isn't in the same directory that I'm running the terminal commands in.
I tried using shell("cd") but it doesn't move you to the user's folder.
Any ideas?
import Cocoa
class ViewController: NSViewController {
func shell(args: String...) -> Int32 {
let task = NSTask()
task.launchPath = "/usr/bin/env"
task.arguments = args
task.launch()
task.waitUntilExit()
return task.terminationStatus
}
override func viewDidLoad() {
super.viewDidLoad()
shell("pwd")
let file = "input.txt"
let text = "\\emph{test}"
if let dir : NSString = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.AllDomainsMask, true).first {
let inputPath = dir.stringByAppendingPathComponent(file)
//writing
do {
try text.writeToFile(inputPath, atomically: false, encoding: NSUTF8StringEncoding)
shell("pandoc","-f","latex","-t","markdown","input.txt","-o","output.txt")
}
catch {/* error handling here */}
let outputPath = dir.stringByAppendingPathComponent("output.txt")
//reading
do {
let inputText = try NSString(contentsOfFile: inputPath, encoding: NSUTF8StringEncoding)
print(inputText)
let convertedText = try NSString(contentsOfFile: outputPath, encoding: NSUTF8StringEncoding)
print(convertedText)
}
catch {/* error handling here */}
}
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
}
Here is the output
/Users/james/Library/Developer/Xcode/DerivedData/FlashCardPreview-gqzwutewnxspazcdloxqruaikvel/Build/Products/Debug
env: pandoc: No such file or directory
\emph{test}
This is partly because the task's executable is set to env which itself executes pandoc; but in the meantime it loses the working directory.
The solution to this is to set launchPath to the Pandoc executable.
This is also because we have to use inputPath and outputPath in the task arguments instead of just the filenames, or to set a currentDirectoryPath for the task.
Working version 1:
func shell(args: String...) -> Int32 {
let task = NSTask()
task.launchPath = "/usr/local/bin/pandoc"
task.arguments = args
task.launch()
task.waitUntilExit()
return task.terminationStatus
}
and
let file = "input.txt"
let text = "\\emph{test}"
if let dir : NSString = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.AllDomainsMask, true).first {
let inputPath = dir.stringByAppendingPathComponent(file)
let outputPath = dir.stringByAppendingPathComponent("output.txt")
//writing
do {
try text.writeToFile(inputPath, atomically: false, encoding: NSUTF8StringEncoding)
shell("-f","latex","-t","markdown", inputPath, "-o", outputPath)
}
catch { print(error) }
//reading
do {
let inputText = try NSString(contentsOfFile: inputPath, encoding: NSUTF8StringEncoding)
print(inputText)
let convertedText = try NSString(contentsOfFile: outputPath, encoding: NSUTF8StringEncoding)
print(convertedText)
}
catch { print(error) }
}
Working version 2:
func shell(args: String...) -> Int32 {
let task = NSTask()
task.launchPath = "/usr/local/bin/pandoc"
if let dir = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.AllDomainsMask, true).first {
task.currentDirectoryPath = dir
}
task.arguments = args
task.launch()
task.waitUntilExit()
return task.terminationStatus
}
and
let file = "input.txt"
let text = "\\emph{test}"
if let dir : NSString = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.AllDomainsMask, true).first {
let inputPath = dir.stringByAppendingPathComponent(file)
let outputPath = dir.stringByAppendingPathComponent("output.txt")
//writing
do {
try text.writeToFile(inputPath, atomically: false, encoding: NSUTF8StringEncoding)
shell("-f","latex","-t","markdown","input.txt","-o","output.txt")
}
catch { print(error) }
//reading
do {
let inputText = try NSString(contentsOfFile: inputPath, encoding: NSUTF8StringEncoding)
print(inputText)
let convertedText = try NSString(contentsOfFile: outputPath, encoding: NSUTF8StringEncoding)
print(convertedText)
}
catch { print(error) }
}
Prints:
\emph{test}
*test*