Security-Scoped Bookmarks for a directory - objective-c

I need to give full read/write permission for a directory where the application write some file to this directory. I read that using sandboxed application it required to Enable Security-Scoped Bookmark and URL Access to access a folder after relaunch the app.
So I am trying to implement it based on the code here with some minor modification What is the correct way to handle stale NSURL bookmarks?
NSOpenPanel* openDlg = [NSOpenPanel openPanel];
[openDlg setCanChooseDirectories:YES];
[openDlg setCanCreateDirectories:YES];
[openDlg setAllowsMultipleSelection:FALSE];
if ( [openDlg runModal] == NSOKButton )
{
NSArray *files = [openDlg URLs];
NSString* dirPath =[[files objectAtIndex:0] path];// absoluteString];
BOOL isDirectory;
NSFileManager* manager = [NSFileManager defaultManager];
NSString *Dir = [dirPath stringByAppendingPathComponent:#"ScreenCaptures"];
if (![manager fileExistsAtPath:Dir isDirectory:&isDirectory] || !isDirectory)
{
NSError *error = nil;
[manager createDirectoryAtPath:Dir
withIntermediateDirectories:NO
attributes:nil
error:&error];
if (error)
NSLog(#"Error creating directory snap path: %#", [error localizedDescription]);
}
NSURL *url = [NSURL URLWithString:[Dir stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSData *bookmark = nil;
NSError *error = nil;
bookmark = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
includingResourceValuesForKeys:nil
relativeToURL:nil // Make it app-scoped
error:&error];
if (error) {
NSLog(#"Error creating bookmark for URL (%#): %#", url, error);
[NSApp presentError:error];
}
NSLog(#"bookmark: %#", bookmark);
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:bookmark forKey:#"bookmark"];
}
But the above code giving me the error
016-08-20 02:19:53.390 FileAccess[635:85753] modalSession has been exited prematurely - check for a reentrant call to endModalSession:
2016-08-20 02:19:59.979 FileAccess[635:85753] Error creating bookmark for URL (/Users/development/Documents/c/ScreenCaptures): Error Domain=NSCocoaErrorDomain Code=262 "Scoped bookmarks can only be made with file URLs" UserInfo={NSURL=/Users/development/Documents/c/ScreenCaptures, NSDebugDescription=Scoped bookmarks can only be made with file URLs}
2016-08-20 02:20:00.021 FileAccess[635:85753] CFURLCopyResourcePropertyForKey failed because it was passed an URL which has no scheme
2016-08-20 02:20:04.967 FileAccess[635:85753] bookmark: (null)
What could be the problem?, anything wrong on above code.

Your second error message tells you what is wrong - you haven't used a file:// URL.
This can be fixed by creating the URL properly from your path variable, however you will probably be better of sticking with URLs throughout and not doing the URL -> path -> URL transformation. All the operations you've used the path for can be done directly with URLs, just check the documentation for NSFileManager and NSURL. The only one which may be non-obvious is using NSURL's checkResourceIsReachableAndReturnError: rather than NSFileManager's fileExistsAtPath:, however read the documentation for checkResourceIsReachableAndReturnError: carefully and take its advice.
Making these changes should address at least three of the errors you have reported.
HTH

Related

How do I handle file names with spaces?

I am using this code below to copy a file selected in the file browser and copying it to the temp directory with a different name. But when I select a file with spaces in it, the program throws an error saying it cannot find the specified fine path. I have tried using escape methods but they do not work either. Are there any other ways to handle file names with spaces?
Code starts here:
[openPanel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result) {
[openPanel close];
if (result == NSFileHandlingPanelOKButton) {
myString = [self randomStringWithLength:7];
NSString *filePath = [[[openPanel URLs] objectAtIndex:0] absoluteString];
NSLog(#"%#", filePath);
NSString *strTemp = [self extractString:filePath toLookFor:#"//" skipForwardX:2 toStopBefore:#".png"];
NSLog(#"%#",strTemp);
NSString *realThing = [strTemp stringByReplacingOccurrencesOfString:#"%20" withString:#"\\ "];
//strTemp = [strTemp stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSLog(#"%#", realThing);
NSString* fullPath = [NSString stringWithFormat:#"/tmp/%#.png", myString];
NSLog(fullPath);
NSError *error = nil;
[[NSFileManager defaultManager] copyItemAtPath:realThing toPath:fullPath error:&error];
if(error) {
NSLog(#"Error!!!");
NSLog(#" error => %# ",error);
}
else {
NSLog(#"Saved to temp directory");
}
Anyone have experience with this? Thanks
Your conversion of the URL to a path is much too complicated and error-prone.
Just use the path method:
NSString *filePath = [[[openPanel URLs] objectAtIndex:0] path];
Alternatively, use copyItemAtURL:... instead of copyItemAtPath:....
You also should check the return value of copyItemAtPath:... as the indicator
of a failure:
if (![[NSFileManager defaultManager] copyItemAtPath:filePath toPath:fullPath error:&error]) {
NSLog(#" error => %# ",error);
}
Compare Handling Error Objects Returned From Methods:
Important: Success or failure is indicated by the return value of the
method. Although Cocoa methods that indirectly return error objects in
the Cocoa error domain are guaranteed to return such objects if the
method indicates failure by directly returning nil or NO, you should
always check that the return value is nil or NO before attempting to
do anything with the NSError object.
You seem to be trying to convert URLs to file paths by hand. Use fileSystemRepresentation instead.

NSURLSessionDownloadTask - downloads but ends with error

I am trying to download a pdf file. earlier when i used the completion handler block i was able to see the file in the tmp location. I then wanted to show download progress so i implemented the delegate methods. But i can now see the progress bar working and the file being downloaded. but once the download is complete (bytes written/total bytes = 1) the error delegate is called and there is no file in the tmp location. what am i missing ? below is my code. I have uploaded the project at https://www.dropbox.com/s/vn5zwfwx9izq60a/trydownload.zip
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:[NSURL URLWithString:#"http://aayudham.com/URLLoadingSystem.pdf"]];
[downloadTask resume];
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
NSLog(#"%#",[error localizedDescription]);
}
-(void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
dispatch_async(dispatch_get_main_queue(), ^{
_progressBar.progress = (double)totalBytesWritten/(double)totalBytesExpectedToWrite;
double value =(double)totalBytesWritten/(double)totalBytesExpectedToWrite;
NSLog(#"%f",value);
});
}
-(void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{
}
-(void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
NSError *error;
//getting docs dir path
NSArray * tempArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsDir = [tempArray objectAtIndex:0];
//adding folder path
NSString *appDir = [docsDir stringByAppendingPathComponent:#"/Reader/"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if(![fileManager fileExistsAtPath:appDir])
{
[fileManager createDirectoryAtPath:appDir withIntermediateDirectories:NO attributes:nil error:&error];
}
BOOL fileCopied = [fileManager moveItemAtPath:[location path] toPath:[appDir stringByAppendingString:#"/demo.pdf"] error:&error];
NSLog(fileCopied ? #"Yes" : #"No");
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
#Rob thank you for your prompt replies and that helped me a lot. Here is my code that worked. Hope it helps someone. I am able to get the actual file name and move the file to my documents directory using the original name.
-(void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
//getting application's document directory path
NSArray * tempArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsDir = [tempArray objectAtIndex:0];
//adding a new folder to the documents directory path
NSString *appDir = [docsDir stringByAppendingPathComponent:#"/Reader/"];
//Checking for directory existence and creating if not already exists
if(![fileManager fileExistsAtPath:appDir])
{
[fileManager createDirectoryAtPath:appDir withIntermediateDirectories:NO attributes:nil error:&error];
}
//retrieving the filename from the response and appending it again to the path
//this path "appDir" will be used as the target path
appDir = [appDir stringByAppendingFormat:#"/%#",[[downloadTask response] suggestedFilename]];
//checking for file existence and deleting if already present.
if([fileManager fileExistsAtPath:appDir])
{
NSLog([fileManager removeItemAtPath:appDir error:&error]?#"deleted":#"not deleted");
}
//moving the file from temp location to app's own directory
BOOL fileCopied = [fileManager moveItemAtPath:[location path] toPath:appDir error:&error];
NSLog(fileCopied ? #"Yes" : #"No");
}
In didFinishDownloadingToURL you should move the file from location to some place more permanent (e.g. your Documents folder). If you're looking for that file in the temporary location later, I'm not surprised it's no longer there.
As the documentation says, the location is defined as such:
A file URL for the temporary file. Because the file is temporary, you must either open the file for reading or move it to a permanent location in your app’s sandbox container directory before returning from this delegate method.
You must move the file to its new location before returning from didFinishDownloadingToURL.
Just in case someone had the same issue that I did I thought I would post my solution here.
My problem was that the predicate methods are firing on a background thread so I was dispatching to my "file io" thread which handles any Writing to files, deleting etc within the app.
The problem with this is that the temporary file gets deleted as soon as the delegate method ends which was occurring at the exact moment I switched threads. So when I tried to access the file in my file io thread it had already been deleted.
My solution was to parse the file into NSData in the delegate method, then use the NSData to write to the filesystem in my file io thread.

NSString writeToFile, NSSavePanel and write permissions

I've only been leaning Cocoa/Objective C for a few days so apologies that this is probably simple/obvious but it's got me stumped.
I've written this handler for saving 3 floats to a text file. However when I'm running it the files are not being saved. Could anyone suggest if there's an error in my code or if you think there's something else (like file write permissions) preventing the file from being written.
Research has lead me to look into Sandboxing, but that gets confusing very quickly and I'm hoping just running the app from xcode in debug would let me write to my user directory.
Heres the code:
- (IBAction)saveResultsAction:(id)sender {
//Sets up the data to save
NSString *saveLens = [NSString stringWithFormat:#"Screen width is %.02f \n Screen Height is %.02f \n Lens is %.02f:1",
self.myLens.screenWidth,
self.myLens.screenHeight,
self.myLens.lensRatio];
NSSavePanel *save = [NSSavePanel savePanel];
long int result = [save runModal];
if (result == NSOKButton) {
NSURL *selectedFile = [save URL];
NSLog(#"Save URL is %#", selectedFile);
NSString *fileName = [[NSString alloc] initWithFormat:#"%#.txt", selectedFile];
NSLog(#"Appended URL is %#", fileName);
[saveLens writeToFile:fileName
atomically:YES
encoding:NSUTF8StringEncoding
error:nil];
}
}
a NSURL object is no POSIX path..
its a URL and getting its description doesnt make it a path
NSString *fileName = [selectedFile.path stringByAppendingPathExtension:#"txt"];
BUT as said, you shouldnt have to append the .txt at all. just use what the panel returns. Else, there would be sandboxd errors because you dont have access rights to the modified filename :)
NSString *fileName = selectedFile.path;
The problem is that you don't need to append the file extension to the URL.The extension is already there.You could directly do this:
if (result == NSOKButton)
{
[saveLens writeToURL: [save URL]
atomically:YES
encoding:NSUTF8StringEncoding
error:nil];
}
I see you've already accepted an answer, but it may also be helpful to know how to debug this type of issue using NSError pointers.
Cocoa uses NSError with method calls which generate error conditions, which richly encapsulate errors. (Objective-C also has exceptions, but they're reserved for cases of programmer error, like an array index out of bounds, or a nil parameter that should never be.)
When you have a method which accepts an error pointer, usually it also return a BOOL indicating overall success or failure. Here's how to get more information:
NSError *error = nil;
if (![saveLens writeToFile:fileName
atomically:YES
encoding:NSUTF8StringEncoding
error:&error]) {
NSLog(#"error: %#", error);
}
Or even:
NSError *error = nil;
if (![saveLens writeToFile:fileName
atomically:YES
encoding:NSUTF8StringEncoding
error:&error]) {
[NSApp presentError:error];
}

move/copy a file to iCloud

I am a beginner using Objective-C. I used the following code to move a file to iCloud but it gives an error that The operation could not be completed. The file exists.
//store the file locally in document folder
NSArray *docPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *filePath = [docPaths objectAtIndex:0];
filePath = [filePath stringByAppendingString:#"/"];
filePath = [filePath stringByAppendingString:fileName];
NSString *writeError = nil;
NSData * fileData = [NSPropertyListSerialization dataFromPropertyList:dataDic format:NSPropertyListXMLFormat_v1_0 errorDescription:&writeError];
if ([fileData writeToFile:filePath atomically:YES]) {
NSLog(#"Server file is stored locally");
}else {
NSLog(#"%#", writeError);
}
// store the file in iCloud folder
NSURL *ubiquitousURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
NSString *tmpubiquitousURL = ubiquitousURL.absoluteString;
tmpubiquitousURL = [tmpubiquitousURL stringByAppendingString:fileName];
NSURL *ubi2 = [[NSURL alloc] initWithString:tmpubiquitousURL];
[[NSFileManager defaultManager] setUbiquitous:YES itemAtURL:filePathURL destinationURL:ubi2 error:&error];
I used the following to remove the file from iCloud but it gives an error that Cannot disable syncing on an un-synced file.
[[NSFileManager defaultManager] setUbiquitous:NO itemAtURL:filePathURL destinationURL:ubi2 error:&error];
I checked the availability of iCloud in my app delegate and it's available. The file is an XML file (.plist) and I have a local copy stored in NSDocumentDirectory.
Overall, I want to sync that file in iCloud so it will be accessible on all devices using my app. I have been struggling with this for 2 days, so if you could help me to resolve the problem I would appreciate it.
Note: I would rather not to use UIDocument, however, if that is the only option please let me know.
I also have the same problem while using the code
[[NSFileManager defaultManager] setUbiquitous:NO itemAtURL:filePathURL destinationURL:ubi2 error:&error];
you have to change the code like below for this to work correctly
[[[NSFileManager alloc]init]setUbiquitous:YES itemAtURL:filePathURL destinationURL:ubi2 error:nil];
this code is for moving a file to icloud, also you should change the name of the file you are moving. It should not be same.

Problem getting files from folder, error recognizing folder. (Objective c)

I have the user select a folder from an NSOpenPanel. This returns a filepath like: file://localhost/Folder. Here is my code where it all goes wrong:
NSURL *filePath = [openDlg URL]; //OpenDlg is my NSOpenPanel
NSString *s = [filePath absoluteString];
NSLog(#"%#",s);
NSError *error;
NSArray *b = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:s error:&error];
if (error) {
NSLog(#"%#",error);
}
NSLog(#"%lu",b.count);
Here, no matter what folder I select, this error message is sent: The folder “Folder” doesn’t exist." UserInfo=0x10518b320 {NSFilePath=file://localhost/Folder, NSUserStringVariant=(
Folder
), NSUnderlyingError=0x10515d5e0 "The operation couldn’t be completed. (OSStatus error -43.)"}
What is going on?!? If this isn't the best way to do it how can I access all the files inside of a folder?
Try using this method instead:
- (NSArray *)contentsOfDirectoryAtURL:(NSURL *)url includingPropertiesForKeys:(NSArray *)keys options:(NSDirectoryEnumerationOptions)mask error:(NSError **)error
You can just pass in the NSURL without having to convert it into a NSString. To give you an example of how you would use it, see below:
[[NSFileManager defaultManager] contentsOfDirectoryAtURL:filePathURL
includingPropertiesForKeys:[NSArray arrayWithObjects:NSURLNameKey, nil]
options:NSDirectoryEnumerationSkipsHiddenFiles
error:&error];
I can't see how you setup your NSOpenPanel so I will also include an example of how to set that up below:
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
[openPanel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result){
if (result == NSFileHandlingPanelOKButton) {
NSArray* urls = [openPanel URLs];
NSURL *url = [urls objectAtIndex:0];
if (url != nil) {
// If you want to convert the path to a NSString
self.filePathString = [url path];
// If you want to keep the path as a NSURL
self.filePathURL = url;
}
}
}];
The above method will get the path to the file or folder after the user has pressed the OK button. Give that a try and see if it works. To further elaborate on why I suggested you use NSURL, here is the explanation that the Apple Documentation gives:
The preferred way to specify the location of a file or directory is to use the NSURL class. Although the NSString class has many methods related to path creation, URLs offer a more robust way to locate files and directories. For applications that also work with network resources, URLs also mean that you can use one type of object to manage items located on a local file system or on a network server.