Swift Vapor: How to read a JSON file? - swift

I'm using Vapor, the server-side Swift 4 framework for creating web servers. I have a migration that I'd like to apply that reads in from a JSON file; however, most if not all Swift tutorials I see on this indicate using Bundle, which to my knowledge, isn't available when developing server-side swift applications, which means attempts like this don't work:
if let path = Bundle.main.url(forResource: "myFile", withExtension: "json") {
...
This begs the question, given a relative file path, how can I read in a file using server-side Swift?

Silly me. Vapor exposes a DirectoryConfig object from which you can retrieve the working directory of the application. To parse a file, it thus becomes:
let directory = DirectoryConfig.detect()
let configDir = "Sources/App/Configuration"
do {
let data = try Data(contentsOf: URL(fileURLWithPath: directory.workDir)
.appendingPathComponent(configDir, isDirectory: true)
.appendingPathComponent("myFile.json", isDirectory: false))
// continue processing
} catch {
print(error)
}

Related

Vapor uploads files to DerivedData instead of the Public folder in the project structure

When I try to upload files via my Vapor server, it always uploads files into the DerivedData folder instead of the Public folder inside the project structure.
I can verify that the file is created in the path, but the path is somewhere in DerivedData directory .. why? How am I going to server such file when it's not in the project's Public folder?
My upload code:
func create(request: Request) async throws -> HTTPStatus {
try CreateDogRequest.validate(content: request)
let createDogRequest = try request.content.decode(CreateDogRequest.self)
guard let userId = try request.auth.require(User.self).id else {
throw Abort(.notFound)
}
let dogId = UUID()
let directory = DirectoryConfiguration.detect()
let publicFolder = "Public"
let folderPath = URL(fileURLWithPath: directory.workingDirectory)
.appendingPathComponent(publicFolder, isDirectory: true)
.appendingPathComponent("dogProfiles", isDirectory: true)
.appendingPathComponent(userId.uuidString, isDirectory: true)
.appendingPathComponent(dogId.uuidString, isDirectory: true)
print("Starting writing to path: \(folderPath)")
let filePath = folderPath.appendingPathComponent("hello" + ".jpg", isDirectory: false)
try FileManager.default.createDirectory(at: folderPath, withIntermediateDirectories: true)
let data = Data(buffer: createDogRequest.dogImage.data)
try data.write(to: filePath, options: .atomic)
print("file uploaded at: \(filePath.relativePath)")
return .ok
}
Now this shows that the file is uploaded here:
/Users/<USERNAME>/Library/Developer/Xcode/DerivedData/<PROJECT_HANDLE>/Build/Products/Debug/Public/dogProfiles/62C340CE-262B-4DE4-9E2A-99B3B3126BB6/hello.jpg
Why? How can I serve such file then?
I debugged the DirectoryConfiguration class and the detect() method checks if the server is running via Xcode and it looks like this:
#if Xcode
if workingDirectory.contains("DerivedData") {
Logger(label: "codes.vapor.directory-config")
.warning("No custom working directory set for this scheme, using \(workingDirectory)")
}
#endif
But the funny thing is, if I put a file inside the Resource folder to read data from, and use the DirectoryConfiguration's detect() method and create a path to that file, it finds the file no problem ??!!! Why? How? :D That is a mystery to me
This is the code that I wrote for reading from the file:
let directory = DirectoryConfiguration.detect()
let configDir = "Resources"
let path = URL(fileURLWithPath: directory.workingDirectory)
.appendingPathComponent(configDir, isDirectory: true)
.appendingPathComponent("file.txt", isDirectory: false)
.relativePath
What am I missing here? How come the file put inside Resources folder gets read, but when I want to put something inside the Public folder it gets put inside DerivedData even tho I am using the same patter when creating the path ??
You need to set a custom working directory in Xcode so Vapor knows where to looks. See https://docs.vapor.codes/getting-started/xcode/#custom-working-directory
Ooookay, once again, I tried to think differently and I googled a bit and found this post:
How to get path of root directory of your project folder in Perfect 2.0?
and the answer from worthbak pointed to the conclusion that I should rather run the server from terminal using swift run and try if the file gets created, and guess what? It does!
This also explains how the file I put in the Resources folder was read, because it is inside a migration that you run from guess where? .. the Terminal :)

How to get the url path of Apple ODR resource?

I am new in Xcode and Swift. Currently working on a script dealing Apple's ODR:
https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/On_Demand_Resources_Guide/Managing.html#//apple_ref/doc/uid/TP40015083-CH4-SW1
I am using NSBundleResourceRequest as
assetsPack = NSBundleResourceRequest(tags: [tag])
and than download a specific resource from my Assets.xcassets by a given tag with:
conditionallyBeginAccessingResources instance method.
The main code snippet I have is:
var assetsPack: NSBundleResourceRequest?
if let req = assetsPack{
req.assetsPack()
}
assetsPack = NSBundleResourceRequest(tags: [tag])
guard let req = assetsPack else {
return
}
req.conditionallyBeginAccessingResources{available in
if available{
print("available")
print(available)
self.anotherFunction(tag)
} else {
req.beginAccessingResources{error in
guard error == nil else{
return
}
self.anotherFunction(tag)
}
}
What I need is to return the path of the ODR resource here or pass it to another function. I need to be able and use this path to copy my file to another place, or access to it once its downloaded with another external plugin.
I've been trying some method like:
let path = req.bundle.url(forResource: "myFile", withExtension: "data")
print(path)
Considering that i have a myFile of type data in my Assets.xcassets.
But it returns nil.
I've tried also:
let stringPath = Bundle.main.path(forResource: "myFile", ofType: "data")
let urlPath = Bundle.main.url(forResource: "myFile", withExtension: "data")
It's is possible that you called endAccessingResources before you accessed the resource, or your request is released/nilified too early, which caused the system to deallocate the files.
An irrelevant side note: ODR tags don't seem to support whitespaces in them. If one adds a tag with whitespace, the request will execute without actually downloading the files. The behavior would be conditionallyBeginAccessingResources constantly giving isResourceAvailable = false.

Can you perform file I/O in a REPL on Repl.it when using Swift?

There's an online site here called Repl.it that gives you an in-browser REPL environment for a ton of languages. It's great to prove out code that you post here on SO. (I think you can even include it here actually but I wasn't successful in embedding mine.)
Anyway, when using Swift, I'm wondering if it's possible to perform file read/write persistence up there. I haven't found any articles that say yes, but I have found some that show them talking about how much storage you have, and it is supposed to be the full Swift runtime with all features, so I'm not sure.
This code fails however, saying it can't be performed.
import Foundation
let file = "file.txt" //this is the file. we will write to and read from it
let text = "some text" //just a text
if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let fileURL = dir.appendingPathComponent(file)
//writing
do {
try text.write(to: fileURL, atomically: false, encoding: .utf8)
}
catch {
print(error)
}
//reading
do {
let text2 = try String(contentsOf: fileURL, encoding: .utf8)
print("Read back in '\(text2)'")
}
catch {/* error handling here */}
}
else{
print("Couldn't get document directory")
}
You can open it here... Swift File Persistence REPL
I admit I'm 90% sure this isn't the right place for this, but since Repl.it does let you play with and execute Swift and this is a question about what Swift is needed to accomplish this, I figured I'd try!

Swift CSVImporter framework with remote URLs

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)
})

Copy TPK file from AppGroup Container to Documents

I have a file that exists within the AppGroup Shared Container and I was wondering if it was possible to copy the file from the Shared Container into the application bundle.
I am getting the file path as follows :
let filePath = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier("group.com.sharedBasemap")!.URLByAppendingPathComponent("localLayer.tpk")!.path
The reason I am trying to do this is it seems that the ArcGIS SDK will not recognize the TPK file from within the App Group so I am wondering if it will recognize it if I copy it into the app bundle.
EDIT: Based on Leo's comment it appears that you can not copy to the bundle, so I am trying to copy to the App Support folder.Here is my code now, I see the "file exists" message but then it is displaying the Oops message indicating it can not move the file :
let filePath = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier("group.com.sharedBasemap")!.URLByAppendingPathComponent("localLayer.tpk")!.path!
let appSupportFolder = String(NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask)[0]) + "localLayer.tpk"
let fileManager = NSFileManager.defaultManager()
if NSFileManager.defaultManager().fileExistsAtPath(filePath){
print("File exists at \(filePath)")
do {
try fileManager.copyItemAtPath(filePath, toPath: appSupportFolder)
}
catch let error as NSError {
print("Ooops! Something went wrong: \(error)")
}
} else {
print("File does not exist")
}
EDIT 2: I have modified the code again to just move the TPK file into the documents directory.I believe that piece is working but I receive an error message when trying to load the TPK file into ArcGIS.At this point in time, I am thinking that the issue is related to the ArcGIS SDK and that it does not support loading a TPK file from anywhere except the application bundle.
let destPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first!
let fullDestPath = NSURL(fileURLWithPath: destPath).URLByAppendingPathComponent("localLayer.tpk")
let fullDestPathString = fullDestPath!.path!
im pretty sure the appSupportFolder doesn't exist by default -- nobody creates it unless needed -- try to verify that first and create it if needed
pseudocode if(!fileExists(supportFolder)) { createDirectory(supportFolder) }