NSString writeToFile, NSSavePanel and write permissions - objective-c

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];
}

Related

NSData writeToFile make synchronous or find out when it completes

I know there are a few questions asked on the behavior of NSData writeToFile atomically: and how that is supposed to be a SYNC method.
But in my experience it is not behaving like that. I've included pseudo code of my method that will accept and array of file names and will save them from Camera Roll to Documents folder of the app.
The For loop runs through each item in array and calls 'writeToFile'. My NSLogs show that the loop runs through and the NSLog message after the loop completion is displayed somewhere in the middle of the files being saved. for eg: I pass 4 images to this array to save, the NSLogs show that first file was saved and then it displays log that says it reached end of For loop and then the rest of the files being saved is displayed.
My question is - how do I make it synchronous or setup a completion handler of some sort that will only get called when the file write has completely finished. I need to eventually upload the files once they've been saved in the apps documents folder. Rest of the logic is in place. I just am having trouble finding the moment when saving of all files has completed.
I do not want to do a hack and set a delay because my file sizes and number of files can vary widely. Any guidance on this is much appreciated. Thanks
//'each item in 'arrayOfSelectedMedia' holds a dictionary of keys 'imageData' ,absolutePathToFileData' and 'filename'
-(void) saveArrayOfFiles: (NSMutableArray*) arrayOfSelectedMedia
toLocation: (NSString*) subFolder
whenCompletePostNotificationTo:(NSString*)backToCaller{
NSError *error;
if (![[NSFileManager defaultManager] fileExistsAtPath:globalChatsMediaFolder]){
[[NSFileManager defaultManager] createDirectoryAtPath:mediaFolder withIntermediateDirectories:NO attributes:nil error:&error]; //Create folder
}
else{
NSLog(#"folder %# exists already",mediaFolder);
}
NSString *completePath = [globalChatsMediaFolder stringByAppendingPathComponent:subFolder];
if (![[NSFileManager defaultManager] completePath]){
[[NSFileManager defaultManager] completePath withIntermediateDirectories:NO attributes:nil error:&error]; //Create folder
NSLog(#"creating folder %# ", completePath);
}
else{
NSLog(#"folder %# exists already", completePath);
}
/******* go through all files in array and save it to their specific folder ***********/
for(int i=0; i < [arrayOfSelectedMedia count]; i++){
NSString *saveFileAs = [NSString stringWithFormat:#"%#",[arrayOfSelectedMedia[i] objectForKey:#"filename"]];
NSURL *absolutePathToSelectedFile = [arrayOfSelectedMedia[i] objectForKey:#"absolutePathToFileInCameraRoll"];
// Create assets library
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init] ;
// Try to load asset at mediaURL
[library assetForURL:absolutePathToSelectedFile resultBlock:^(ALAsset *asset) {
// If asset exists
if (asset) {
// Type your code here for successful
ALAssetRepresentation *rep = [asset defaultRepresentation];
Byte *buffer = (Byte*)malloc((NSUInteger)rep.size);
NSUInteger buffered = [rep getBytes:buffer fromOffset:0.0 length:(NSUInteger)rep.size error:nil];
NSData *dataToUpload = [NSData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES];
NSLog(#"%s , A direct BYTE size of file %d bytes", __PRETTY_FUNCTION__, [dataToUpload length]);
NSLog(#"absolute path to asset is %# ", asset.defaultRepresentation.url);
NSString *absolutePathToFile = [completePath stringByAppendingPathComponent:saveFileAs];
BOOL writeStatus = [dataToUpload writeToFile:absolutePathToFile options:NSDataWritingAtomic error:&error];
if(writeStatus){
NSLog(#"file %# with %lu bytes was successfully saved locally", saveFileAs, (unsigned long)[dataToUpload length]);
}
else{
NSLog(#"write to local file failed: %#",error);
}
}
else {
// Type your code here for not existing asset
NSLog(#"%s, File at path '%#' could not be found.",__PRETTY_FUNCTION__,absolutePathToSelectedFile);
}
} failureBlock:^(NSError *error) {
// Type your code here for failure (when user doesn't allow location in your app)
NSLog(#"%s, failure to read file at path: %#. Check if user has allowed access to file location.",__PRETTY_FUNCTION__,absolutePathToSelectedFile);
}];
} // end of For loop
NSLog(#"Finished saving array of Files to local Disk. back to caller provided as '%#'",backToCaller);
if(backToCaller.length > 0){ // i.e. it was not set to nil and I need to post notification
NSLog(#"posting notification to go back to Caller %#", backToCaller);
[[NSNotificationCenter defaultCenter] postNotificationName:backToCaller object:nil userInfo:nil];
}
}

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.

Writing to /tmp folder iPad

I´m writing certain values to a file. See Write Operations below.
This works fine when using iPad 6.1 Simulator.
When trying the same thing on my iPad it fails. I think it´s something with sandboxing. I haven´t found out yet which path is best on iOS Devices to write stuff for internal use.
Any ideas?
#pragma mark Write Operations to Tmp Folder
- (BOOL) psWriteFileWithName: (NSString*) fileName
withString:(NSString*) string {
NSString *fileName = #"artistNumber";
NSError * error = NULL;
NSString *filePath = [NSString stringWithFormat:#"/tmp/%#.txt",fileName];
[string writeToFile:filePath
atomically:YES
encoding: NSUTF8StringEncoding
error:&error];
return YES;
}
You cannot write to /tmp since this is outside of your app sandbox.
However your app also has a temp directory, which can be referenced with the NSTemporaryDirectory() function:
Which works like:
NSString *tempfilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:filename];
Here is you method with the correct NSTemporaryDirectory() implementation, also edit some error handling:
#pragma mark Write Operations to Tmp Folder
- (BOOL) psWriteFileWithName: (NSString*) fileName
withString:(NSString*) string {
NSString *fileName = #"artistNumber";
NSError *error = nil;
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:filename];
if (![string writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error] ) {
NSLog(#"Error writing file: %#", error);
return NO;
}
return YES;
}

Parsing of m3u files in Objective-C for iPhone from file system or URL

The example below should take a link from m3u playlist and add it to anArray. (So I will get the NSArray(NSMutableArray) with certain links in it)
NSString *fileContents = [NSString stringWithContentsOfFile:#"myfile.m3u" encoding:NSUTF8StringEncoding error:NULL];
NSArray *lines = [fileContents componentsSeparatedByString:#"\n"];
NSLog (#"%#",lines);
All the time I had (null) in NSLog Message.
All the time when I try NSLog or if/else statement to check is there is link in array it gives me the null object in it.
After that I thought the problem was in m3u type and I've tried to change type in txt and read. (For those who don't know, M3U is just the text in UTF-8 encoding and the changing type should give the result)
Then I've tried the .txt files but it doesn't work too. So there is the code of it.
//Check if there is my file
NSString *addPath = [[NSBundle mainBundle] pathForResource:#"somefile" ofType:#"m3u" ];
if ([fileMgr fileExistsAtPath:addPath] ) {
NSLog(#"Yes.We see the file");
}
else {
NSLog(#"Nope there is no file");
}
//Rename
NSString *path1 = addPath;
NSString *theNewFilename = [path1 stringByReplacingOccurrencesOfString:#"m3u" withString:#"txt"];
NSLog(#"Renamed file adress is %#", theNewFilename);
//Check if there is our renamed file(file manager was allocated before)
NSString *addPath1 = [[NSBundle mainBundle] pathForResource:#"somefile" ofType:#"txt" ];
if ([fileMgr fileExistsAtPath:addPath1] ) {
NSLog(#"Yes we had the renamed file");
}
else {
NSLog(#"No we don't");
}
Checking is there is m3u file worked fine. I had Addres to Renamed file too. But when it was checking is there is renamed file, there was no file (null in NSLog).
After all that stuff, and without any hope to reach my destination I've tried to read txt file line by line separated by /n with 5 links in it.
NSString *fileContents1 = [NSString stringWithContentsOfFile:#"myfile.txt" encoding:NSUTF8StringEncoding error:NULL];
NSArray *lines1 = [fileContents1 componentsSeparatedByString:#"\n"];
NSLog (#"%#",fileContents1);
NSLog (#"%#",lines1);
Both Messages were NULL
One more thing all this stuff I tried to make in -(IBAction)fileRead { } linked to button
(Yes I've presed button every time to check my NSLog)Program was checked in iPhone Simulator. Will be glad if someone say what is the trouble. Also if there is easier way to make this with url. (Tried Couple times with NSUrl and had Nothing but null )
Just because you've changed the path doesn't mean that you've renamed/moved/copied an item, path is just a string. Use NSFileManager methods like
– moveItemAtURL:toURL:error: or
– moveItemAtPath:toPath:error:.
Also, NSString doesn't care about extension, so it's completely safe to read your m3u file to NSString, no need to rename it.
NSString *addPath = [[NSBundle mainBundle] pathForResource:#"somefile" ofType:#"m3u" ];
if ([fileMgr fileExistsAtPath:addPath] ) {
NSLog(#"Yes.We see the file");
NSString *fileContents1 = [NSString stringWithContentsOfFile:addPath encoding:NSUTF8StringEncoding error:NULL];
NSArray *lines1 = [fileContents1 componentsSeparatedByString:#"\n"];
NSLog (#"%#",fileContents1);
NSLog (#"%#",lines1);
}
else {
NSLog(#"Nope there is no file");
}

NSRegularExpression:enumerateMatchesInString hangs when called more than once

In the context of an iPhone app I am developing, I am parsing some html to extract data to map, using NSRegularExpression. This information is updated whenever the user "pans" the map to a new location.
This works fine the first time or two through, but on the second or third time the function is called, the application hangs. I have used XCode's profiler to confirm I am not leaking memory, and no error is generated (the application does not terminate, it just sits in execution at the point shown below).
When I examine the HTML being parsed, I do not see that it is incomplete or otherwise garbled when the application hangs.
Furthermore, if I replace the regex code with a collection of explicitly address strings, everything works as expected.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// receivedData contains the returned HTML
NSString *result = [[NSString alloc] initWithData:receivedData encoding:NSASCIIStringEncoding];
NSError *error = nil;
NSString *pattern = #"description.*?h4>(.*?)<\\/h4>.*?\"address>[ \\s]*(.*?)<.*?zip>.*?(\\d{5,5}), US<";
NSRegularExpression *regex = [NSRegularExpression
regularExpressionWithPattern:pattern
options:NSRegularExpressionDotMatchesLineSeparators
error:&error];
__block NSUInteger counter = 0;
// the application hangs on the next line after 1-2 times through
[regex enumerateMatchesInString:result options:0 range:NSMakeRange(0, [result length]) usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop){
NSRange range = [match rangeAtIndex:2];
NSString *streetAddress =[result substringWithRange:range];
range = [match rangeAtIndex:3];
NSString *cityStateZip = [result substringWithRange:range];
NSString *address = [NSString stringWithFormat:#"%# %#", streetAddress, cityStateZip];
EKItemInfo *party = [self addItem:address]; // geocode address and then map it
if (++counter > 4) *stop = true;
}];
[receivedData release];
[result release];
[connection release]; //alloc'd previously, so release here.
}
I realize this is going to be difficult/impossible to duplicate, but I was wondering if anyone has run into a similar issue with NSRegularExpression or if there is something obviously wrong here.
I also have encountered the regular expression exception, too. In my case, the problem was Character Encoding. So that I wrote a code to go well with several character encoding. Maybe this code help you.
+ (NSString *)encodedStringWithContentsOfURL:(NSURL *)url
{
// Get the web page HTML
NSData *data = [NSData dataWithContentsOfURL:url];
// response
int enc_arr[] = {
NSUTF8StringEncoding, // UTF-8
NSShiftJISStringEncoding, // Shift_JIS
NSJapaneseEUCStringEncoding, // EUC-JP
NSISO2022JPStringEncoding, // JIS
NSUnicodeStringEncoding, // Unicode
NSASCIIStringEncoding // ASCII
};
NSString *data_str = nil;
int max = sizeof(enc_arr) / sizeof(enc_arr[0]);
for (int i=0; i<max; i++) {
data_str = [
[NSString alloc]
initWithData : data
encoding : enc_arr[i]
];
if (data_str!=nil) {
break;
}
}
return data_str;
}
You can download the whole category library from GitHub and just run it. I wish this helps you.
https://github.com/weed/p120801_CharacterEncodingLibrary
Maybe the answer to this question can be found at: NSRegularExpression enumerateMatchesInString: [...] usingBlock does never stop .
I had this issue solved by passing NSMatchingReportCompletion as option and by setting stop to YES when the match is nil.