Swift NSTask How to Receive Output - swift

I currently have:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
var task = NSTask()
task.launchPath = "/usr/bin/sudo"
task.arguments = ["tcpdump"]
var pipe = NSPipe()
task.standardOutput = pipe
var handle = pipe.fileHandleForReading
handle.waitForDataInBackgroundAndNotify()
var observer = NSNotificationCenter.defaultCenter().addObserverForName(NSFileHandleDataAvailableNotification, object: handle, queue: nil, usingBlock: { (note: NSNotification!) -> Void in
var dataRead = handle.availableData
var str = NSString(data: dataRead, encoding: NSUTF8StringEncoding)
println("debug \(str)")
})
task.launch()
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
}
I have been looking for hours on this, and I can't find anything. Also, weirdly, sudo runs without requiring a password, but no packets are intercepted by tcpdump, hence I also need to know how to input a password. Thank you!

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?

'WeatherManagerDelegate' cannot be constructed because it has no accessible initializers I'm getting this error, when I'm trying to run my code

I'm getting this error on line var delegate = WeatherManagerDelegate()
import Foundation
protocol WeatherManagerDelegate {
func didUpdateWeather(weather:WeatherModel)
}
struct WeatherManager {
let weatherURL = "https://api.openweathermap.org/data/2.5/weather?&appid=d73ab8784f3b294976fc6189b3e6eba2&units=metric"
var delegate = WeatherManagerDelegate()
func fetchWeather(cityName: String) {
let urlString = "\(weatherURL)&q=\(cityName)"
performRequest(urlString: urlString)
}
func performRequest(urlString: String)
{
//Create URL
if let url = URL(string: urlString){
//Create a URL Session.
let session = URLSession(configuration: .default)
//Give session a task
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil{ //on selecting url 2nd option the seletors you get select from data onwards to error and press enter and it will be set to closure format.
print(error!)
return
}
if let safeData = data {
/* let dataString = String(data: safeData, encoding: .utf8)
print(dataString!)*/
if let weather = parseJSON(weatherData: safeData) {
self.delegate.didUpdateWeather(weather:weather)
}
}
}
//Start task
task.resume()
}
}
func parseJSON(weatherData: Data) ->WeatherModel? {
let decoder = JSONDecoder()
do{
let decodeData = try decoder.decode(WeatherData.self, from: weatherData)
let name = decodeData.name
let temp = decodeData.main.temp
print(decodeData.main.temp_max)
print(decodeData.main.temp_min)
print(decodeData.sys.country)
print(decodeData.weather[0].description)
let id = decodeData.weather[0].id
let weather = WeatherModel(conditionId: id, cityName: name, temperature: temp)
print(weather.conditionName)
print(weather.temperatureString)
}
catch{
print(error)
return nil
}
}
}
and when I'm trying to make it an optional
var delegate = WeatherManagerDelegate?()
I'm getting this error
No exact matches in call to initializer
Replace
var delegate = WeatherManagerDelegate()
with
weak var delegate: WeatherManagerDelegate?
and update the calls to read self.delegate?.didUpdateWeather()
WeatherManager should not be responsible for creating its own delegate, that is something that should come from wherever it is begin used.
The weak attribute is almost always necessary when using delegates to avoid retain cycles.
Since weak can only be applied to class objects, you also need to indicate that in the protocol definition:
protocol WeatherManagerDelegate: AnyObject { ... }

Running terminal commands in in cocoa app

I facing a problem when running my code on cocoa app to run some command line scripts
This function run smoothly when using Command line tool but when using full cocoa app with some On Off UI it not working at all
My script should turn on/off the http & https proxy
Here is my function:
private func runTask(_ cmd: String) {
// Create a Task instance
let task = Process()
// Set the task parameters
task.launchPath = "/bin/sh"
task.arguments = ["-c", String(format:"%#", cmd)]
// Create a Pipe and make the task
// put all the output there
let pipe = Pipe()
task.standardOutput = pipe
// Launch the task
task.launch()
// Get the data
let data = pipe.fileHandleForReading.readDataToEndOfFile()
guard let output = NSString(data: data, encoding: String.Encoding.utf8.rawValue) else { return }
print(output)
}
And here is my full ViewController class:
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func onButtonTapped(_ sender: NSButton) {
print("onButtonTapped")
let selected: Switch = .on
let listOfNetworkCommands: String = [
#"networksetup -setwebproxystate "Wi-fi" \#(selected)"#, // switch http proxy
#"networksetup -setsecurewebproxystate "Wi-fi" \#(selected)"#, // switch https proxy
#"networksetup -setpassiveftp "Wi-fi" \#(selected)"# // switch passive ftp
].joined(separator: " && ")
runTask(listOfNetworkCommands)
}
#IBAction func offButtonTapped(_ sender: NSButton) {
print("onButtonTapped")
let selected: Switch = .off
let listOfNetworkCommands: String = [
#"networksetup -setwebproxystate "Wi-fi" \#(selected)"#, // switch http proxy
#"networksetup -setsecurewebproxystate "Wi-fi" \#(selected)"#, // switch https proxy
#"networksetup -setpassiveftp "Wi-fi" \#(selected)"# // switch passive ftp
].joined(separator: " && ")
runTask(listOfNetworkCommands)
}
enum Switch: String {
case on, off
}
private func runTask(_ cmd: String) {
// Create a Task instance
let task = Process()
// Set the task parameters
task.launchPath = "/bin/sh"
task.arguments = ["-c", String(format:"%#", cmd)]
// Create a Pipe and make the task
// put all the output there
let pipe = Pipe()
task.standardOutput = pipe
// Launch the task
task.launch()
// Get the data
let data = pipe.fileHandleForReading.readDataToEndOfFile()
guard let output = NSString(data: data, encoding: String.Encoding.utf8.rawValue) else { return }
print(output)
}
}
Any idea why my function not triggered in the cocoa app?
Simple answer is found by disabling App Sandbox in your Cocoa Application (found under your Project app target > Capabilities tab > App Sandbox switch). You'll find that you're being blocked by a sandbox exception. Disabling sandboxing should fix your issue.
You can also see this in Console.app if you filter for your app name or the sandboxd process. You'll likely have an entry like this when sandboxing is enabled:
error 00:21:57.502273 +0000 sandboxd Sandbox: sh(17363) deny(1) file-read-data /dev/ttys003

Swift GDC background process freezes app

I've written the function that launches Tor process. It's a process that won't stop until SIGTERM is sent to it, so, to avoid app freezing, I run this process in a background queue (Tor needs to be launched when the application is started and finished when application is terminated, and user needs to do some other things meanwhile). That's my code for Tor launching:
func launchTor(hashedPassword hash : String) {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) {
let task = NSTask()
task.launchPath = "/bin/bash"
print("Hashed password : \(hash)")
task.arguments = (["-c", "/usr/local/bin/tor HashedControlPassword \(hash)"])
let pipe = NSPipe()
task.standardOutput = pipe
let handle = pipe.fileHandleForReading
handle.waitForDataInBackgroundAndNotify()
let errPipe = NSPipe()
task.standardError = errPipe
let errHandle = errPipe.fileHandleForReading
errHandle.waitForDataInBackgroundAndNotify()
var startObserver : NSObjectProtocol!
startObserver = NSNotificationCenter.defaultCenter().addObserverForName(NSFileHandleDataAvailableNotification, object: nil, queue: nil) { notification -> Void in
let data = handle.availableData
if data.length > 0 {
if let output = String(data: data, encoding: NSUTF8StringEncoding) {
print("Output : \(output)")
}
}
else {
print("EOF on stdout")
NSNotificationCenter.defaultCenter().removeObserver(startObserver)
}
}
var endObserver : NSObjectProtocol!
endObserver = NSNotificationCenter.defaultCenter().addObserverForName(NSTaskDidTerminateNotification, object: nil, queue: nil) {
notification -> Void in
print("Task terminated with code \(task.terminationStatus)")
NSNotificationCenter.defaultCenter().removeObserver(endObserver)
}
var errObserver : NSObjectProtocol!
errObserver = NSNotificationCenter.defaultCenter().addObserverForName(NSTaskDidTerminateNotification, object: nil, queue: nil) {
notification -> Void in
let data = errHandle.availableData
if (data.length > 0) {
if let output = String(data: data, encoding: NSUTF8StringEncoding) {
print("Error : \(output)")
NSNotificationCenter.defaultCenter().removeObserver(errObserver)
}
}
}
task.launch()
_ = NSNotificationCenter.defaultCenter().addObserverForName("AppTerminates", object: nil, queue: nil) {
notification -> Void in
task.terminate()
}
task.waitUntilExit()
}
}
When it's launched, everything is OK, but then the whole app freezes. When I stop an app, I always see that let data = handle.availableData line is running. How to fix this issue?

How to get data to return from NSURLSessionDataTask in Swift

I have this same question as was asked and answered here: How to get data to return from NSURLSessionDataTask
The difference: How can I do this in Swift? I do not know Objective C at all so trying to interpret that answer is proving a bit futile for me..
So given the code below I have a FirstViewController which will call my HomeModel class to go and get the data using a NSURLSession call. Once the call is complete I want return the data to the FirstViewController so that I may go and set it in the view.
FirstViewController class looks like:
import UIKit
class FirstViewController: UIViewController
{
let homeModel = HomeModel()
override func viewDidLoad()
{
super.viewDidLoad()
// Go load the home page content
SetHomeContent()
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
/*Call to go get home page content*/
func SetHomeContent()
{
homeModel.GetHomeData();
//TODO find a way to get result here... and set it to the textView
}
}
HomeModel class looks like:
import Foundation
class HomeModel
{
private let siteContent:String = "http://www.imoc.co.nz/MobileApp/HomeContent"
func GetHomeData()
{
var url : NSURL! = NSURL(string:siteContent)
var request: NSURLRequest = NSURLRequest(URL:url)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task : NSURLSessionDataTask = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil
let jsonResult: NSDictionary! = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: error) as? NSDictionary!
// At this stage I want the jsonResult to be returned to the FirstViewController
});
// do whatever you need with the task
task.resume()
}
I guess I want either a way to pass in a completion handler from the original all or something similar to C# 5 using async tasks to await the result..
Any help appreciated..
With the help and suggestion taken from Abizern I finally managed how to write up this block or closure if you want.
So what worked for me in the end:
The GetHomeData function I changed as follows:
private let siteContent:String = "http://www.imoc.co.nz/MobileApp/HomeContent"
// I changed the signiture from my original question
func GetHomeData(completionHandler: ((NSDictionary!) -> Void)?)
{
var url : NSURL! = NSURL(string:siteContent)
var request: NSURLRequest = NSURLRequest(URL:url)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task : NSURLSessionDataTask = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil
let jsonResult: NSDictionary! = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: error) as? NSDictionary!
// then on complete I call the completionHandler...
completionHandler?(jsonResult?);
});
task.resume()
}
Then I call the function like this:
/*Call to go get home page content*/
func SetHomeContent()
{
homeModel.GetHomeData(self.resultHandler)
}
func resultHandler(jsonResult:NSDictionary!)
{
let siteContent = jsonResult.objectForKey("SiteContent") as NSDictionary
let paraOne = siteContent.objectForKey("HomePageParagraphOne") as String
}
Change your GetHomeData() method so that it takes a block. When you call the method pass it the block that does what you want to with the data. In the completion block of the data task, call this passed in block.
You can use this framework for Swift coroutines - https://github.com/belozierov/SwiftCoroutine
DispatchQueue.main.startCoroutine {
let dataFuture = URLSession.shared.dataTaskFuture(for: url)
let data = try dataFuture.await().data
. . . parse data ...
}