How to rollback the change in modifyItem in macOS FileProvider Extension? - macos

Let's say user renames a file/folder in FileProvider extension. modifyItem callback is invoked where we issue a rename request to the server. Server responds with an error (ie user doesn't have permission to rename the relevant file/folder). We invoke the completionHandler with error code, but the renamed file/folder will remain in the file system.
What is the best way to revert file/folder name to the original one (before the rename attempt)?
Simplified code would ideally be something like:
func modifyItem(_ item: NSFileProviderItem, baseVersion version: NSFileProviderItemVersion,
changedFields: NSFileProviderItemFields, contents newContents: URL?,
options: NSFileProviderModifyItemOptions = [], request: NSFileProviderRequest,
completionHandler: #escaping (NSFileProviderItem?,
NSFileProviderItemFields, Bool, Error?) -> Void) -> Progress {
let node = localDatabase.getNode(item.id)
let originalFilename = node.filename
node.filename = item.filename
let result = server.performRename(item.filename)
if result == false {
// TODO: revert item to original filename
node.filename = originalFilename
let retItem = node.item()
completionHandler(retItem, [], false, NSError(domain: NSFileProviderErrorDomain, code: NSFileProviderError.Code.cannotSynchronize.rawValue, userInfo: [:]))
}else{
let retItem = node.item()
completionHandler(retItem, [], false, nil)
}
}
But this doesn't revert the file/folder filename value, it just shows file/folder in Finder with cloud-error icon that upsync failed. But I would also like to revert the renamed file/folder to the original value.

I suppose you will have to signal about the change in the working set and then subsequently provide the deleted and added item when requested.

Related

Ask for the Documents permission in a sandboxed app

I am writing a Mac app in SwiftUI and would like to display a live-updated list of documents and folders with the ability to edit the files.
First, users selects any folder with Open File Dialog and then I save the URL into UserDefaults and attempt to read list of files and folders when the app launches.
Assuming I have a valid URL then I do the following:
// Open the folder referenced by URL for monitoring only.
monitoredFolderFileDescriptor = open(url.path, O_EVTONLY)
// Define a dispatch source monitoring the folder for additions, deletions, and renamings.
folderMonitorSource = DispatchSource.makeFileSystemObjectSource(fileDescriptor: monitoredFolderFileDescriptor, eventMask: .write, queue: folderMonitorQueue)
App is crashes when I call DispatchSource.makeFileSystemObjectSource with the EXC_BREAKPOINT exception.
FileManager.default.isReadableFile(atPath: url.path) returns false which tells me I don't have permissions to access this folder.
The URL path is /Users/username/Documents/Folder
I have added NSDocumentsFolderUsageDescription into the info plist.
It's not clear how can I ask for permission programmatically.
Theoretically my URL can point to any folder on File System that the user selects in the Open Dialog. It's unclear what is the best practice to request permission only when necessary. Should I parse the URL for the "Documents" or "Downloads" string?
I also have watched this WWDC video.
Thanks for reading, here's what example what I am trying to show.
Like #vadian said, this needs Secure Scoped Bookmark. If you user picks a folder from NSOpenPanel, permission dialog not necessary. This answer helped me a lot.
I create new NSOpenPanel which gives me URL?, I pass this URL to the saveAccess() function below:
let bookmarksPath = "bookmarksPath"
var bookmarks: [URL: Data] = [:]
func saveAccess(url: URL) {
do {
let data = try url.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
bookmarks[url] = data
NSKeyedArchiver.archiveRootObject(bookmarks, toFile: bookmarksPath)
} catch {
print(error)
}
}
After you save bookmark, you can access when you app launches.
func getAccess() {
guard let bookmarks = NSKeyedUnarchiver.unarchiveObject(withFile: bookmarksPath) as? [URL: Data] else {
print("Nothing here")
return
}
guard let fileURL = bookmarks.first?.key else {
print("No bookmarks found")
return
}
guard let data = bookmarks.first?.value else {
print("No data found")
return
}
var isStale = false
do {
let newURL = try URL(resolvingBookmarkData: data, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)
newURL.startAccessingSecurityScopedResource()
print("Start access")
} catch {
print(error)
return
}
}
The code is very rough, but I hope it can help someone. After acquiring the newURL, you can print content of a folder.
let files = try FileManager.default.contentsOfDirectory(at: newURL, includingPropertiesForKeys: [.localizedNameKey, .creationDateKey], options: .skipsHiddenFiles)
And don't forget to call .stopAccessingSecurityScopedResource() when you done.

In Swiftui, how to check the content from a URLsession?

I am using SwiftUI in Xcode 11, trying to check the content of a .txt file from the internet.
The problem is that the URLSession.shared.downloadTask takes time to finish. The code to check the content is always performed before the download is finished. Can anyone help me please? Thanks very much.
Sorry, forgot to add some codes.
let url = URL(string: "https://www.myweb.com/myfile.txt”)!
var myweb = “test”
URLSession.shared.downloadTask(with: url) { localURL, response, error in
if let localURL = localURL {
do { try myweb = String(contentsOf: localURL)}
catch { print (“test”) }
}
}.resume()
if myweb != “test” { Call some function here}
I assume that you need to create ViewModel with Published property and change it flag on true statement if downloadTask has finished. Use this property inside View

You don't have permission to save file in Mac Mojave

In my macOS app I'm trying to create a directory using the below extension
extension URL {
static func createFolder(folderName: String, folderPath:URL) -> URL? {
let fileManager = FileManager.default
let folderURL = folderPath.appendingPathComponent(folderName)
// If folder URL does not exist, create it
if !fileManager.fileExists(atPath: folderURL.path) {
do {
// Attempt to create folder
// try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil)
// try fileManager.createDirectory(atPath: folderURL.path, withIntermediateDirectories: true, attributes: nil)
try fileManager.createDirectory(atPath: folderURL.relativePath, withIntermediateDirectories: true, attributes: nil)
} catch {
print(error.localizedDescription + ":\(folderURL.path)")
return nil
}
}
return folderURL
}
}
When I invoke this call its giving me error
You don’t have permission to save the file “FolderName” in the folder
“SelectedFolder”.:/Users/USERNAME/Workspace/SelectedFolder/FolderName
I have taken look at a similar post and have tried all methods but its still giving me the error, am I missing something here? Any help is appreciated
I am Assuming that your app is sandboxed. So you don't have permission to write folder for location where you are trying to.
If it not intended for Sandboxed you can disable the App Sandbox, it can be turned off by clicking on your project file > target name, selecting the capabilities tab and switching the App Sandbox off.
File System Programming Guide: https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html
App Sandbox documentation here: https://developer.apple.com/library/archive/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html
You can also look security scoped bookmark for persistent resource access.
Documentation here:
https://developer.apple.com/library/archive/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW16
https://developer.apple.com/library/archive/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html#//apple_ref/doc/uid/TP40011195-CH4-SW18

QLPreviewController works with URLs provided by UIDocumentBrowserViewController, but not with URLs generated manually

I'm using UIDocumentBrowserViewController combined with QLPreviewController to preview documents selected by users in UIDocumentBrowserViewController, which works perfectly fine. The pickedDocumentURL variable used by QLPreviewController is populated as follows:
func documentBrowser(_ controller: UIDocumentBrowserViewController, didPickDocumentURLs documentURLs: [URL]) {
// (...)
pickedDocumentURL = documentURLs.first as NSURL?
// Present QLPreviewController instance ...
}
However, when I populate the pickedDocumentURL variable using:
pickedDocumentURL = NSURL(string: documentURLs.first!.absoluteString)
or:
pickedDocumentURL = URL(string: documentURLs.first!.absoluteString) as NSURL?
... then the QLPreviewController does not work (it is presented, but the preview is empty) and I get the following error on the console:
[default] QLUbiquitousItemFetcher: could not create sandbox wrapper. Error: Error
Domain=NSPOSIXErrorDomain Code=1 "couldn't issue sandbox extension
com.apple.quicklook.readonly for
'/private/var/mobile/Containers/Shared/AppGroup/07524B34-D877-449F-A5C3-89A0431560E5/File
Provider
Storage/22207162/1qrbGgy6-u0f69mPqOjjpzlYiUYPR8OG_/Sample.pdf':
Operation not permitted" UserInfo={NSDescription=couldn't issue
sandbox extension com.apple.quicklook.readonly for
'/private/var/mobile/Containers/Shared/AppGroup/07524B34-D877-449F-A5C3-89A0431560E5/File
Provider
Storage/22207162/1qrbGgy6-u0f69mPqOjjpzlYiUYPR8OG_/Sample.pdf':
Operation not permitted} #PreviewItem
Moreover, the URL absolute strings in each of those cases are exactly the same.
you are using .absoluteString, use .path instead, I had same issue and this solved it:
pickedDocumentURL = NSURL(string: documentURLs.first!.path)
Here's the code that works for me:
func getPreviewItem(withName name: String ) -> NSURL
{
//let file = name.components(separatedBy: ".")
let pdfFile = getDocumentsDirectory().appendingPathComponent(name)
let url = pdfFile as NSURL (this line was the key)
return url
}

I got issues writing a string in a txt file using swift XCode 7 [duplicate]

I am trying a simple dictionary retrieve, update key value and write back to file. For some reason the writeToFile does not update the file in the main bundle.
the code reads:
let filename = "testFile"
if let path = NSBundle.mainBundle().pathForResource(filename, ofType: "json") {
var error: NSError?
let InputData: NSData? = NSData(contentsOfFile: path, options: NSDataReadingOptions(), error: &error)
var jsonDictionary: NSMutableDictionary = NSJSONSerialization.JSONObjectWithData(InputData!, options: NSJSONReadingOptions.MutableContainers, error: &error) as NSMutableDictionary
jsonDictionary.setValue(1, forKey: "levelRow")
let options = NSJSONWritingOptions.PrettyPrinted
var outputData : NSData? = NSJSONSerialization.dataWithJSONObject(jsonDictionary, options: options, error: &error)
outputData?.writeToFile(path, atomically: true)
}
the file looks like this:
{
"levelColumn" : 0,
"levelRow" : 0,
}
the read and update work fine... but the file doe not update levelRow to 1?
thanks in advance.
You cannot write to the main bundle. All files in the bundle are read-only. Copy your file into the application documents directory before modifying it.
If you need a different file in the bundle to include in your application, you can update it in the documents directory during development and then manually copy it to the bundle before shipping your app.

Resources