Swift CSVImporter framework with remote URLs - swift

Initial use and testing of the framework. The examples provided and most of the searches from across the internet are using "local" or downloaded CSV files to the device, with (path:).
I would like to pass various remote URLs but there are not many examples, using (url: URL).
So far, I am simply in viewDidLoad() following the same code as provided with the sample playground file, and trying to output to the console.
I have tried to run this in a simulator for the iPhone 8 device. Running Xcode 10.1.
From the documentation, there is an ".onFail" handler, which gets invoked on the sourceURL's I have provided, but I do not know what error objects exist to do any further troubleshooting.
let sourceURL = URL(string: "https://files.datapress.com/leeds/dataset/leeds-city-council-dataset-register/Dataset%20register.csv")
guard let sourceURL2 = URL(string: "https://minio.l3.ckan.io/ckan/ni/resources/2477b63a-b1c4-45cc-a5ee-8e33e5b20b5b/supplies-and-services-contracts---2014.2015-yr.csv?AWSAccessKeyId=aspjTDZu90BQVi&Expires=1546982840&Signature=dLDVWMu%2Fp4RiePIRhntCX6WFMpw%3D") else {
fatalError("URL string error")
}
let importer = CSVImporter<[String]>(url: sourceURL)
importer?.startImportingRecords { $0 }.onFail {
print("fail")
}.onFinish({ importedRecords in
print(importedRecords.count)
})

Related

How to remove sandbox without xcode

Earlier I asked a question regarding generateCGImagesAsynchronously. Thankfully it got answered and works great.
The issue is that it only works as a Cocoa app on xcode. I am now trying to move this logic to an executable Swift package but AVFoundation code, such as generateCGImagesAsynchronously, won't work. That is, no error is raised but those functions seem to be mocked. I presume this might have to do with my package being sandboxed? I was able to remove the sandbox from the Cocoa app I previously wrote the code for, but I can't figure out how to do that for this executable.
I am new to Swift and trying to understand it and it's kind of frustrating to think that what I want my code to do is dependent on the IDE I am using.
If anyone can point me in the direction of where to read in the docs, or some other sources, on how to make programs without using xcode, that would be great. Thanks!
Here is my code:
import Darwin
import Foundation
import AppKit
import AVFoundation
import Cocoa
#discardableResult func writeCGImage(
_ image: CGImage,
to destinationURL: URL
) -> Bool {
guard let destination = CGImageDestinationCreateWithURL(
destinationURL as CFURL,
kUTTypePNG,
1,
nil
) else { return false }
CGImageDestinationAddImage(destination, image, nil)
return CGImageDestinationFinalize(destination)
}
func imageGenCompletionHandler(
requestedTime: CMTime,
image: CGImage?,
actualTime: CMTime,
result: AVAssetImageGenerator.Result,
error: Error?
) {
guard let image = image else { return }
let path = saveToPath.appendingPathComponent(
"img\(actualTime).png"
)
writeCGImage(image, to: path)
}
let arguments: [String] = Array(CommandLine.arguments.dropFirst())
// For now, we assume the second arg, which is the
// path that the user wants us to save to, always exists.
let saveToPath = URL(fileURLWithPath: arguments[1], isDirectory: true)
let vidURL = URL(fileURLWithPath: arguments[0])
let vidAsset = AVAsset(url: vidURL)
let vidDuration = vidAsset.duration
let imageGen = AVAssetImageGenerator(asset: vidAsset)
var frameForTimes = [NSValue]()
let sampleCounts = 20
let totalTimeLength = Int(truncatingIfNeeded: vidDuration.value as Int64)
let steps = totalTimeLength / sampleCounts
for sampleCount in 0 ..< sampleCounts {
let cmTime = CMTimeMake(
value: Int64(sampleCount * steps),
timescale: Int32(vidDuration.timescale)
)
frameForTimes.append(NSValue(time: cmTime))
}
imageGen.generateCGImagesAsynchronously(
forTimes: frameForTimes,
completionHandler: imageGenCompletionHandler
)
As I said in a comment on your previous question, this has nothing to do with Xcode per se. Xcode just generates a lot of code and build commands for you.
macOS is a complex operating system and programs that want to use its more advanced features must follow certain patterns. One of this patterns is called the run loop. If you create a Cocoa app, you get most of these things for free.
Since you are trying to perform some asynchronous actions, you need a run loop. Appending this should work:
RunLoop.current.run()
Otherwise, your program will simply terminate when the main thread (your code) finishes. The run loop, however, causes the program to run a loop and wait for asynchronous events (this also includes UI interactions, for example) to occur.
Note that inserting this same line also fixes your issues from the other question.

Sending CSV file with SFTP in swift

I have a server hosted with webfaction that I would like to be able to send a csv file to from my app with FTP or SFTP. I have found many libraries that should help like ConnectionKit, NMSSH, DLSFPT, and LxFTPRequest. However, all of them are in objective-c and not swift which makes them hard to read, understand, and implement in Swift 4. I have tried to implement LXFTPRequest since I found a swift implementation for the upload and here is my code:
let fileName = "user-data.csv"
guard let path = FileManager.default.urls(for: .documentDirectory, in:.userDomainMask).first else {fatalError(ErrorMessageStrings.couldntAccessDocs.rawValue)}
let fileURL = path.appendingPathComponent(fileName)
let folderLocation = "/home/path/"
let uploadUrl = URL(string: "ftp://server-name.webfaction.com" + folderLocation)
let request = LxFTPRequest.upload()
request?.serverURL = uploadUrl
request?.localFileURL = fileURL
request?.username = "username"
request?.password = "password"
request?.successAction = { (resultClass, result) in
print("File uploaded")
}
request?.failAction = { (domain, error, errorMessage) in
print(error)
print(errorMessage?.description)
fatalError("Connection could not be made. Action was not completed.")
}
request?.progressAction = {(_ totalSize: Int, _ finishedSize: Int, _ finishedPercent: CGFloat) -> Void in
print(finishedPercent)
}
request?.start()`
Using this I almost get it to work but I end up with a 550 error "Requested action not taken. File unavailable (e.g., file not found, no access)." Looking through webfaction documentation I get the feeling that I can only send files through SFTP, which this framework doesnt support.
The doc says "To connect with FTP (for shell users only), substitute the connection type with FTP and the port number with 21." I am assuming since I am sending data from my app it does not count as a shell user and so FTP doesn't grant me access (I may be wrong here). If that is the case how would I go about using the other libraries to send my file over SFTP using Swift and not objective-c?
I ended up using NMSSH and using it in Swift it wasn't as complicated as I thought.
let session = NMSSHSession.init(host: serverHost, port: xx, andUsername: serverUsername)
session.connect()
if session.isConnected{
session.authenticate(byPassword: serverPasswordString)
if session.isAuthorized == true {
let sftpsession = NMSFTP(session: session)
sftpsession.connect()
if sftpsession.isConnected {
sftpsession.writeFile(atPath: csvFileURL.path, toFileAtPath: folderLocation)
}
}
}

How to create plugins in swift

I found article describing how to create plugin using Swift and Cocoa. It uses NSBundle to load plugin, but that, as far as I know, is not available in pure swift (no Cocoa). Is there way how to achieve same result without using Cocoa?
More info:
In case it's relevant, here is what I want to achieve. I create app in swift that runs on linux server. User can connect to it using their browser. I want to be able to have other people write "plugins" that will implement functionality itself (what user can see and do once they connect), from printing out hello world, through chat programs to games without having to worry about low level stuff provided by my app. Some sort of dll, that my server application loads and runs.
Solution to this is not trivial, but it's not impossible to do either. I prefer to use swift package manager to manage dependencies and Xcode as IDE. This combination is not perfect as it needs a lot of tinkering but there is not any other useable free swift IDE as of now.
You will need to set up two projects, let's call them Plugin (3rd party library) and PluginConsumer (app that uses other people plugins). You will also need to decide on API, for now we will use simple
TestPluginFunc()
Create Plugin.swift file with TestPluginFunc implementation in your Plugin project:
public func TestPluginFunc() {
print("Hooray!")
}
Set the project to build framework, not executable and build[1]. You will get Plugin.framework file which contains your plugin.
Now switch to your PluginConsumer project
Copy Plugin.framework from your Plugin project somewhere where you can easily find it. To actually load the framework and use it:
// we need to define how our plugin function looks like
typealias TestPluginFunc = #convention(c) ()->()
// and what is its name
let pluginFuncName = "TestPluginFunc"
func loadPlugin() {
let pluginName = "Plugin"
let openRes = dlopen("./\(pluginName).framework/\(pluginName)", RTLD_NOW|RTLD_LOCAL)
if openRes != nil {
// this is fragile
let symbolName = "_TF\(pluginName.utf8.count)\(pluginName)\(initFuncName.utf8.count)\(initFuncName)FT_T_"
let sym = dlsym(openRes, symbolName)
if sym != nil {
// here we load func from framework based on the name we constructed in "symbolName" variable
let f: TestPluginFunc = unsafeBitCast(sym, to: TestPluginFunc.self)
// and now all we need to do is execute our plugin function
f()
} else {
print("Error loading \(realPath). Symbol \(symbolName) not found.")
dlclose(openRes)
}
} else {
print("error opening lib")
}
}
If done correctly, you should see "Hooray!" being printed to your log.
There is a lot of room for improvement, first thing you should do is replace Plugin.framework string with parameter, preferably using some file library (I am using PerfectLib). Another thing to look at is defining plugin API in your PluginConsumer project as a protocol or base class, creating framework out of that, importing that framework in your plugin project and basing your implementation on that protocol/base class. I am trying to figure out exactly how to do that. I will update this post if I mange to do it properly.
[1]: I usually do this by creating Package.swift file and creating xcode project out of it using swift package generate-xcodeproj. If your project doesn't contain main.swift, xcode will create framework instead of executable
What you will want to do is create a folder your program will look in. Let's say it's called 'plugins'. It should make a list of names from the files in there, and then iterate through using them, passing parameters to the files and getting the output and making use of that in some way.
Activating a program and getting output:
func runCommand(cmd : String, args : String...) -> (output: [String], error: [String], exitCode: Int32) {
var output : [String] = []
var error : [String] = []
let task = Process()
task.launchPath = cmd
task.arguments = args
let outpipe = Pipe()
task.standardOutput = outpipe
let errpipe = Pipe()
task.standardError = errpipe
task.launch()
let outdata = outpipe.fileHandleForReading.readDataToEndOfFile()
if var string = String(data: outdata, encoding: .utf8) {
string = string.trimmingCharacters(in: .newlines)
output = string.components(separatedBy: "\n")
}
let errdata = errpipe.fileHandleForReading.readDataToEndOfFile()
if var string = String(data: errdata, encoding: .utf8) {
string = string.trimmingCharacters(in: .newlines)
error = string.components(separatedBy: "\n")
}
task.waitUntilExit()
let status = task.terminationStatus
return (output, error, status)
}
`
Here is how a swift plugin would accept arguments:
for i in 1..C_ARGC {
let index = Int(i);
let arg = String.fromCString(C_ARGV[index])
switch arg {
case 1:
println("1");
case 2:
println("2")
default:
println("3)
}
}
So once you have the program and plugin communicating you just have to add handling in your program based on the output so the plugins output can do something meaningful. Without cocoa libraries this seems the way to go, though if you use C there are a couple of other options available there as well. Hope this helps.

Swift Vapor SWXMLHash probably SWXMLHash error

I am building a application in Vapor. My website works fine on localhost but on the Heroku doesn't run correctly. I have got a list with elements from xml. Xml is parsing by SWXMLHash. Heroku printing only static header. I cant see any informations about error in server log. Everything need to works fine but not working.
guard let xmlString = response?.body.bytes?.string else {
throw Abort.custom(status: .badRequest, message: "Could not retrieve xml string")
}
let xml = SWXMLHash.parse(xmlString)
var cars:[Car] = []
for item in xml["findItemsByCategoryResponse"]["searchResult"]["item"].all {
cars.append(Car(item:item))
print("1 "+(item["title"].element?.text ?? ""))
}
var table:[Node]=[]
for car in cars {
table.append(try ["title": car.title,"url": car.auctionUrl,"price":car.price,"imgUrl":car.galeryUrl].makeNode())
}
var nodeTables = try table.makeNode()
return try drop.view.make("index", Node(node: ["cars": nodeTables]))
I was unable to get SWXMLHash working properly on Heroku so I opted to use XML from Zewo instead. I admit that Zewo's XML lib is not as nice to use, nor are the docs as good, as compared to SWXMLHash, but it did not take too much work to make the switch for me and works just fine on Heroku.

Open another Mac app

In my app I would like to open another app that is installed on the User's Mac (such as iPhoto). I am not sure what I should be looking for in the documentation. What is this called and how should I do it?
Thank you
Swift 5 or later
import Cocoa
func openPhotos() -> Bool {
if let photosApp = FileManager.default.urls(
for: .applicationDirectory,
in: .systemDomainMask
).first?.appendingPathComponent("Photos.app") {
return NSWorkspace.shared.open(photosApp)
}
return false
}
Usage:
if openPhotos() {
print(true)
}
Or using launchApplication with the app name parameter in the method:
import Cocoa
func openApp(_ named: String) -> Bool {
NSWorkspace.shared.launchApplication(named)
}
Usage:
if openApp("Photos") {
print(true)
}
XCode 11 • MacOS Catalina 10.15 • Swift 5
NSWorkspace.shared.launchApplication is deprecated and starting from the MacOS 10.15 the new function NSWorkspace.shared.openApplication shall be used.
Example - open terminal application by its bundle id
guard let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: "com.apple.Terminal") else { return }
let path = "/bin"
let configuration = NSWorkspace.OpenConfiguration()
configuration.arguments = [path]
NSWorkspace.shared.openApplication(at: url,
configuration: configuration,
completionHandler: nil)
Example - open terminal application by its path
let url = NSURL(fileURLWithPath: "/System/Applications/Utilities/Terminal.app", isDirectory: true) as URL
let path = "/bin"
let configuration = NSWorkspace.OpenConfiguration()
configuration.arguments = [path]
NSWorkspace.shared.openApplication(at: url,
configuration: configuration,
completionHandler: nil)
You can use NSWorkspace class written by Swift/Cocoa.
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSWorkspace_Class/index.html#//apple_ref/occ/instm/NSWorkspace/launchApplication:
let task = NSTask.launchedTaskWithLaunchPath(<#path: String#>, arguments: <#[AnyObject]#>) will probably do what you want
I second the answer by vookimedlo - unfortunately, I cannot yet comment (silly reputation limit) so I post this as an extra answer.
This is just one caveat, which might not affect too many: while launchApplication() accepted a path to an executable (e.g. "MyApp.app/Contents/MacOS/MyApp"), this will result in an error (lacking privileges) with openApplication(::). You have to supply the path to the app bundle ("MyApp.app") instead.
Of particular interest when you try to make a helper ("launcher") to add as a login item. See the following and keep my comment in mind:
https://theswiftdev.com/2017/10/27/how-to-launch-a-macos-app-at-login/
(GREAT article by Tibor Bödecs)
BTW, as for vookimedlo's code: in my experience, you don't need to specify the OpenContext.arguments with [path], you can simply pass a default NSWorkspace.OpenContext()...
There are different ways to do that. The most efficient is to use fvork and execve - see man vfork and man execve.
Less efficient but more flexible is to use the system library call. What that actually does is runs a shell - like bash - then passes the string you provide, to bash. So you can set up pipelines, redirection and such.
Or you can send an Apple Event to the Finder: "Tell Finder Open iPhoto".
In the first two cases you want to launch the executable inside the bundle, that is, /Applications/iPhoto.app/Contents/MacOS/iPhoto.
Try the above from the command line, in the Terminal:
$ /Applications/iPhoto.app/Contents/MacOS/iPhoto
You'll see the iPhoto App launch.