How to get the url path of Apple ODR resource? - swift

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.

Related

Swift Vapor: How to read a JSON file?

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

Security Scoped Bookmark - bookmark resolves but still can't access the file

EDIT: Additional information added at the bottom
I have a sandboxed, document based application that loads a user selected quicktime movie into an AVPlayer, and everything was working perfectly.
Now I am upgrading the code so that it will use Security Scoped bookmarks to get the URL rather than just storing a URL string so that the persistent store will allow the movie to be loaded upon relaunch of the application. When the bookmark is created it is stored in a Data variable of a managed object.
For some reason, this has broken the AVPlayer. While I have created a bookmark from the user selected URL, and can resolving the URL from the bookmark when the application is relaunched, the movie is not getting loaded into the AVPlayer correctly and I can't figure out why... I have confirmed that the URL being resolved from the bookmark does point to the movie file.
I have also added the appropriate entitlements to the project.
Here is my code:
Function Where User Selects a Movie To Load and Bookmark is Created
#IBAction func loadMovie(_ sender: Any) {
let openPanel = NSOpenPanel()
openPanel.title = "Select Video File To Import"
openPanel.allowedFileTypes = ["mov", "avi", "mp4"]
openPanel.begin { (result: NSApplication.ModalResponse) -> Void in
if result == NSApplication.ModalResponse.OK {
self.movieURL = openPanel.url
self.player = AVPlayer.init(url: self.movieURL!)
self.setupMovie()
if self.loadedMovieDatabase.count > 0 {
print("Movie Object Exists. Adding URL String")
self.loadedMovieDatabase[0].urlString = String(describing: self.movieURL!)
} else {
print("No Movie Object Exists Yet. Creating one and adding URL String")
let document = NSDocumentController.shared.currentDocument as! NSPersistentDocument
let myManagedObjectContext = document.managedObjectContext!
let newMovie = NSEntityDescription.insertNewObject(forEntityName: "Movie", into: myManagedObjectContext) as! MovieMO
self.loadedMovieDatabase.append(newMovie)
self.loadedMovieDatabase[0].urlString = String(describing: self.movieURL!)
}
// create Security-Scoped bookmark - Added 2/1/18
do {
try self.loadedMovieDatabase[0].bookmark = (self.movieURL?.bookmarkData(options: NSURL.BookmarkCreationOptions.withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil))!
} catch {
print("Can't create security bookmark!")
}
}
}
}
Function where Bookmark is Resolved into URL and Movie is Loaded
// initialize AVPlayer with URL stored in coreData movie object if it exists and is a valid path
if loadedMovieDatabase.count > 0 {
// initialize with saved movie path if it is valid (from security bookmark data)
// let myURL = URL(string: loadedMovieDatabase[0].urlString!) <- replaced with new code below
print("Loading URL from Bookmark")
var urlResult = false
var myURL : URL
do {
try myURL = URL.init(resolvingBookmarkData: loadedMovieDatabase[0].bookmark, bookmarkDataIsStale: &urlResult)!
print("URL Loaded from Bookmark")
print("URL is", myURL)
let isSecuredURL = myURL.startAccessingSecurityScopedResource()
print("IsSecured = ", isSecuredURL)
player = AVPlayer.init(url: myURL)
print("Setting Up Movie")
setupMovie()
} catch {
// No Data in bookmark so load default ColorBars movie instead
print("No Security Bookmark Available. Reverting to Default Color Bars")
let myURL = URL(string: initialMoviePath)
player = AVPlayer.init(url: myURL!)
setupMovie()
}
} else {
// load default ColorBars movie instead
print("Nothing was loaded so just set up a new document.")
let myURL = URL(string: initialMoviePath)
player = AVPlayer.init(url: myURL!)
setupMovie()
}
I am new to Security-Scoped Bookmarks, so I'm hoping that this may be obvious to anyone who has worked with them before.
I'm wondering if it's a problem with:
let isSecuredURL = myURL.startAccessingSecurityScopedResource()
Perhaps I'm calling this incorrectly? Sometimes I find Apple's documentation to be vague and confusing... Any insight would be appreciated!
EDIT:
I believe I know why this is happening, but I'm not sure how to fix it...
myURL.startAccessingSecurityScopedResource()
always returns FALSE... per the documentation that would mean that it's not working. Additionally, while the movie file is located on my Desktop, the Resolved URL comes up as the following (this may be normal, I don't know.):
file:///Users/me/Library/Containers/myapp/Data/Desktop/sample_on_desktop.mov
The apple docs make reference to the fact that a Document Scope can not use files in the system (aka "/Library"), but my entitlements are setup to use application-scope bookmarks, and my bookmark was created using the nil flag for relativeURL: so this shouldn't be an issue.
I just stumbled upon the answer accidentally...
For starters, when I was resolving the URL, I was not using the method which allows you to include OPTIONS, so my URL was resolved WITHOUT the security-scope. My original code to resolve was:
try myURL = URL.init(resolvingBookmarkData: loadedMovieDatabase[0].bookmark, bookmarkDataIsStable: &urlResult)!
When I should have been using the version with options here:
try myURL = URL.init(resolvingBookmarkData: loadedMovieDatabase[0].bookmark, Options: URL.bookmarkResolutionOptions.withSecurityScope, relativeTo: nil, bookmarkDataIsStable: &urlResult)!
Basically, I used the first init option Xcode presented in the predictive list with the words "resolvingBookmarkData:" when I should have looked further down the list. (This is how I found my error.)
NOTE also that it's important to use...
URL.bookmarkResolutionOptions.withSecurityScope
and not
URL.bookmarkCreationOptions.withSecurityScope
...when you're resolving your URL or it doesn't appear to work correctly.
Thus ends my frustration with this problem :) I hope this explanation might help others facing this problem!

FileHandle not accepting my URLs for write access

I'd like to open a uniquely named output file for writing either plist or data, but not having any luck in getting a handle using either URL routine of init(fileURLWithPath:) or init(string:)
func NewFileHandleForWritingFile(path: String, name: String, type: String, outFile: inout String?) -> FileHandle? {
let fm = FileManager.default
var file: String? = nil
var uniqueNum = 0
while true {
let tag = (uniqueNum > 0 ? String(format: "-%d", uniqueNum) : "")
let unique = String(format: "%#%#.%#", name, tag, type)
file = String(format: "%#/%#", path, unique)
if false == fm.fileExists(atPath: file!) { break }
// Try another tag.
uniqueNum += 1;
}
outFile = file!
do {
let fileURL = URL.init(fileURLWithPath: file!)
let fileHandle = try FileHandle.init(forWritingTo: fileURL)
print("\(file!) was opened for writing")
//set the file extension hidden attribute to YES
try fm.setAttributes([FileAttributeKey.extensionHidden: true], ofItemAtPath: file!)
return fileHandle
} catch let error {
NSApp.presentError(error)
return nil;
}
}
debugger shows
which for this URL init routine adds the scheme (file://) but otherwise the same as the other, and I'd like to prefer the newer methods which throw reutrning (-1) when just using paths. The error thrown (2) is an ENOENT (no such entity!?) as I need a handle to write to I'm confused how else to get one? The sample path is a new folder created at desktop to triage.
Unlike the previous answer, I recommend using Data's write(to:options:) API instead of FileManager's createFile(atPath:contents:attributes:), because it is a URL-based API, which is generally to be preferred over path-based ones. The Data method also throws an error instead of just returning false if it fails, so if something goes wrong, you can tell the user why.
try Data().write(to: fileURL, options: [])
I would also suggesting replacing the path-based FileManager.fileExists(atPath:) with the URL-based checkResourceIsReachable():
if false == ((try? fileURL.checkResourceIsReachable()) ?? false)
You can't create a file handle to a non-existent file. That is what is causing the ENOENT error.
Use FileManager createFile(atPath:contents:attributes:) to create the file just before creating the file handle.
do {
fm.createFile(atPath: file!, contents: nil, attributes: [FileAttributeKey.extensionHidden: true])
let fileURL = URL(fileURLWithPath: file!)
let fileHandle = try FileHandle(forWritingTo: fileURL)
print("\(file!) was opened for writing")
return fileHandle
} catch let error {
NSApp.presentError(error)
return nil;
}

Get path of a file in a data set located in Assets.xcassets

I have a data set of audio files in my Assets.xcassets:
I'm trying to get the path of one of those audio files like this:
let path: String = Bundle.main.path(forResource: "acoustic_grand_piano/A4", ofType: "f32")!
But I get a EXC_BAD_INSTRUCTION. I tried to look on the internet but I don't find anything on Data Sets.
How can I get the content of one of these files?
Thanks!
Try this:
Manually put your files into a folder, named anything you want.
Append ".bundle" to the folder to create a bundle. You'll get a warning, accept it. Congrats, you've just created your first bundle! :-)
Manually drag that folder into your app.
Get at your files by using the following code....
public func returnFile(_ named:String) -> String {
let path: String = Bundle.main.path(forResource: "myAudioFiles", ofType: "bundle")! + "/" + name + ".f32"
do {
return try String(contentsOfFile: path)
}
catch let error as NSError {
return error.description
}
}
Now, my files are text files of CIKernel code. Since your's are audio files you may need to change the String return to something else.
EDIT:
In my case I'm using a framework, as I wish to share these files/images with extensions and other apps. If you are working in such a set up, here's the unaltered code:
public func returnFile(_ resource:String, _ fileName:String, _ fileType:String) -> String {
let identifier = "com.companyname.appname" // replace with framework bundle identifier
let fileBundle = Bundle.init(identifier: identifier)
let filePath = (fileBundle?.path(forResource: resource, ofType: "bundle"))! + "/" + fileName + "." + fileType
do {
return try String(contentsOfFile: filePath)
}
catch let error as NSError {
return error.description
}
}

FileManager.contentsEqual returns false when comparing copied files

I need to preload SQLite files from my bundle's resources into the application support directory. I want to make sure the correct files are there vs. the empty files that Core Data puts there by default. To do this, I'm using FileManager.default.contentsEqual; however, this always returns false.
I tried testing with a playground, but the copy there is creating alias files, still resulting in a false comparison.
In the app, the files do copy over with the same name and size. The dates are different: the copies have the current date/time rather than the original's timestamps. Using contentsEqual, though, I wouldn't think that matters.
Update: diff at the command line shows the files are the same...
What am I missing?
Here's the code from the playground, which is virtually the same as my app code:
// get the URL for the application support directory
let appSupportDir: URL = try!
FileManager.default.url(for: FileManager.SearchPathDirectory.applicationSupportDirectory,
in: FileManager.SearchPathDomainMask.userDomainMask,
appropriateFor: nil, create: true)
// get the source URLs for the preload files
let sqliteFileBundleURL: URL = Bundle.main.url(forResource: "My_DB", withExtension: "sqlite")!
let sqliteShmFileBundleURL: URL = Bundle.main.url(forResource: "My_DB", withExtension: "sqlite-shm")!
let sqliteWalFileBundleURL: URL = Bundle.main.url(forResource: "My_DB", withExtension: "sqlite-wal")!
// create target URLs for copy to application support directory
let sqliteFileAppSptURL: URL = appSupportDir.appendingPathComponent("My_DB.sqlite")
let sqliteShmFileAppSptURL: URL = appSupportDir.appendingPathComponent("My_DB.sqlite-shm")
let sqliteWalFileAppSptURL: URL = appSupportDir.appendingPathComponent("My_DB.sqlite-wal")
// remove the files if they already exist at the target (for test - app doesn't do this)
do {
let filesFound: [URL] = try FileManager.default.contentsOfDirectory(at: appSupportDir,
includingPropertiesForKeys: nil,
options: .skipsHiddenFiles)
if !filesFound.isEmpty {
for fileURL in filesFound {
try FileManager.default.removeItem(at: fileURL)
}
print("Removed \(filesFound.count) files without error.")
}
}
catch {
print("Error:\n\(error)")
}
// copy the files to the application support directory
do {
try FileManager.default.copyItem(at: sqliteFileBundleURL, to: sqliteFileAppSptURL)
try FileManager.default.copyItem(at: sqliteShmFileBundleURL, to: sqliteShmFileAppSptURL)
try FileManager.default.copyItem(at: sqliteWalFileBundleURL, to: sqliteWalFileAppSptURL)
}
catch {
print("Error: \(error)")
}
// compare the copied target files to their source using contentsEqual
let sqliteFileCopied: Bool =
FileManager.default.contentsEqual(atPath: sqliteFileBundleURL.absoluteString, andPath: sqliteFileAppSptURL.absoluteString)
let sqliteShmFileCopied: Bool =
FileManager.default.contentsEqual(atPath: sqliteShmFileBundleURL.absoluteString, andPath: sqliteShmFileAppSptURL.absoluteString)
let sqliteWalFileCopied: Bool =
FileManager.default.contentsEqual(atPath: sqliteWalFileBundleURL.absoluteString, andPath: sqliteWalFileAppSptURL.absoluteString)
Aha! When using FileManager, one should be using path rather than absoluteString to convert a URL to a String:
// compare the copied target files to their source using contentsEqual
let sqliteFileCopied: Bool =
FileManager.default.contentsEqual(atPath: sqliteFileBundleURL.path, andPath: sqliteFileAppSptURL.path)
let sqliteShmFileCopied: Bool =
FileManager.default.contentsEqual(atPath: sqliteShmFileBundleURL.path, andPath: sqliteShmFileAppSptURL.path)
let sqliteWalFileCopied: Bool =
FileManager.default.contentsEqual(atPath: sqliteWalFileBundleURL.path, andPath: sqliteWalFileAppSptURL.path)
The difference between the two is that path generates a file system-type path:
/var/folders/kb/y2d_vrl133d1b04_5kc3kw880000gn/T/com.apple.dt.Xcode.pg/resources/238FF955-236A-42FC-B6EA-9A74FC52F235/My_DB.sqlite
whereas absoluteString generates a browser-friendly path:
file:///var/folders/kb/y2d_vrl133d1b04_5kc3kw880000gn/T/com.apple.dt.Xcode.pg/resources/238FF955-236A-42FC-B6EA-9A74FC52F235/My_DB.sqlite
Note: path also works in the playground with the alias files.