Unable to delete the contents of a Directory in my Home Directory using NSDirectoryEnumerator - objective-c

I have been unable to delete the contents of a directory using NSDirectoryEnumerator.
Here is my method:
- (BOOL) deleteDirectoryContents:(NSString *)directoryPath
{
BOOL success = FALSE;
BOOL isDir;
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:directoryPath isDirectory:&isDir] && isDir)
{
NSDirectoryEnumerator *dirEnum = [fileManager enumeratorAtPath:directoryPath];
NSLog(#"dirEnum in deleteDirectoryContents is %#", dirEnum);
NSString *documentsName;
// NSLog(#"[dirEnum nextObject] is: %#", [dirEnum nextObject]);
while (documentsName = [dirEnum nextObject])
{
NSString *filePath = [directoryPath stringByAppendingString:documentsName];
NSLog(#"filePath is: %#", filePath);
BOOL isFileDeleted = [fileManager removeItemAtPath:filePath error:nil];
if(isFileDeleted == NO)
{
NSLog(#"All Contents not removed");
success = FALSE;
break;
}
success = TRUE;
}
if (success) NSLog(#"All Contents Removed");
}
return success;
}
And this is my code in my main program:
NSString *testDir = #"/Users/grinch/MyTestTemp";
//testDir = [NSHomeDirectory() stringByAppendingPathComponent: #"MyTestTemp"];
NSLog(#"testDir is: %#", testDir);
BOOL result = [self deleteDirectoryContents:testDir];
NSLog(#"result is: %d", result);
Here is my console output:
testDir is: /Users/grinch/MyTestTemp
dirEnum in deleteDirectoryContents is <NSAllDescendantPathsEnumerator: 0x60000008c300>
result is: 0
I also checked the value of [dirEnum nextObject] (by uncommenting the NSLog statement in my code). It returns null. And I never see the "filePath is" NSLog statement. So the inner while loop is NEVER executed.
And yes the directory (with files) does exist in my home directory. I created these files. I have permissions. I can easily delete the files in this directory using Finder
What am I missing?
P.S. I did some more testing. It looks like my simple program in Xcode does not have permissions to access files and folders in my home directory. Why? I have no idea.
Here is my additional test code:
NSError *errorMsg;
[[NSFileManager defaultManager] removeItemAtPath:#"/Users/grinch/myTestTemp/Hello.txt" error:&errorMsg];
if (errorMsg) NSLog(#"ERROR - File delete errorMsg is: %#", errorMsg);
success = [[NSFileManager defaultManager] removeItemAtPath:testDir error:&errorMsg];
if (errorMsg) NSLog(#"ERROR - Folder delete errorMsg is: %#", errorMsg);}
And here is my console output:
2022-09-10 09:56:48.958352-0400 NSAlert[97106:7511815] ERROR - File delete errorMsg is: Error Domain=NSCocoaErrorDomain Code=513 "“Hello.txt” couldn’t be removed because you don’t have permission to access it." UserInfo={NSFilePath=/Users/grinch/myTestTemp/Hello.txt, NSUserStringVariant=(
), NSUnderlyingError=0x6040006421f0 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}
2022-09-10 09:56:48.960560-0400 NSAlert[97106:7511815] ERROR - Folder delete errorMsg is: Error Domain=NSCocoaErrorDomain Code=513 "“MyTestTemp” couldn’t be removed because you don’t have permission to access it." UserInfo={NSFilePath=/Users/grinch/MyTestTemp, NSUserStringVariant=(
), NSUnderlyingError=0x60400045ff80 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}
So my questions are:
Why didn't the directory enumerator work?
Why doesn't Xcode have the permissions to delete items in my home folder?
How can I give Xcode the the ability (or permissions) to delete items in my home directory?

I have found a bug in my deleteDirectoryContents: method above. And as a result, have answered most of my questions!
First, the statement NSString *filePath = [directoryPath stringByAppendingString:documentsName]; in the while loop will not work. One must insert a / before documentsname. So the line should be NSString *filePath = [directoryPath stringByAppendingFormat:#"/%#",documentsName]; OR even better use NSString *filePath = [directoryPath stringByAppendingPathComponent:documentsName];.
Now the method deleteDirectoryContents: will work as expected.
Here is the code to the fixed method:
- (BOOL) deleteDirectoryContents:(NSString *)directoryPath
{
BOOL success = FALSE;
BOOL isDir;
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:directoryPath isDirectory:&isDir] && isDir)
{
NSDirectoryEnumerator *dirEnum = [fileManager enumeratorAtPath:directoryPath];
NSLog(#"dirEnum in deleteDirectoryContents is %#", dirEnum);
NSString *documentsName;
// NSLog(#"[dirEnum nextObject] is: %#", [dirEnum nextObject]);
while (documentsName = [dirEnum nextObject])
{
//NSString *filePath = [directoryPath stringByAppendingFormat:#"/%#",documentsName];
NSString *filePath = [directoryPath stringByAppendingPathComponent:documentsName];
NSLog(#"filePath is: %#", filePath);
NSError *errorMsg = nil;
BOOL isFileDeleted = [fileManager removeItemAtPath:filePath error:&errorMsg];
if (errorMsg) NSLog(#"ERROR - Failed to delete file or folder at: %#. Error message is: %#", filePath, errorMsg);
if(isFileDeleted == NO)
{
NSLog(#"All Contents not removed");
success = FALSE;
break;
}
success = TRUE;
}
if (success) NSLog(#"All Contents Removed");
}
return success;
}
And I can confirm that if your program creates the directory and the files within it, the program can also delete these files using deleteDirectoryContents: while running in the Xcode sandbox DEPENDING on where the folder was originally created.
If the program creates the folder and files in a temporary directory (e.g. caches directory) NOT the users home folder, it works.
But due to Xcode sandboxing,
deleteDirectoryContents: does not appear to work on folder and files created by the program in the users Home directory when running within Xcode.
P.S. I used the following to obtain a temporary directory name:
- (NSString *) get_temporary_dir
{
NSString *path = nil;
NSString *bundleName = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleIdentifier"];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
if ([paths count]) {
path = [[paths objectAtIndex:0] stringByAppendingPathComponent:bundleName];
} else {
path = NSTemporaryDirectory();
path = [path stringByAppendingPathComponent:bundleName];
}
return [[[NSString alloc] initWithUTF8String:[path UTF8String]] autorelease];
}
Then the program created the temporary directory using that name. The program created some files in that temporary directory and then was able to delete them using the deleteDirectoryContents: method above.

Related

NSFileManager moveItemAtPath does not work

I am using https://www.tutorialspoint.com/objective_c/objective_c_file_handling.htm as a reference to move a file to another directory but it is not working. There is no error. I added the code to see if it could find the file and it can, but when I am using moveItemAtPath it does not seem to work.
NSFileManager *fileManager = [NSFileManager defaultManager];
//Get documents directory
NSArray *directoryPaths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectoryPath = [directoryPaths objectAtIndex:0];
if ([fileManager fileExistsAtPath:#"/Users/SAMPLE/folder1/file.txt"] == YES) {
NSLog(#"File exists");
}else{
NSLog(#"DNE");
}
NSError* error = nil;
if([fileManager moveItemAtPath:#"/Users/SAMPLE/folder1/file.txt"
toPath:#"/Users/SAMPLE/folder2/" error:&error]) {
NSLog(#"Moved successfully");
}
else if (error != nil) {
NSLog(#"%#", [error localizedDescription]);
}
I checked to see what the error is with code from https://stackoverflow.com/a/19244978/12486838 and the error message I get is
“file.txt” couldn’t be moved to “SAMPLE” because an item with the
same name already exists.
Why is it just stopping at Users/? Why is it comparing the second folder in the file path?
I have also tried to change toPath:#"/Users/SAMPLE/folder2/" to toPath:#"/Users/SAMPLE/folder2/file.txt" and the error message is
“file.txt” couldn’t be moved because you don’t have permission to access “folder2”.
What kind of permission would I need?

writeToFile does not work after removeItemAtPath

I have an iOS app that at times needs to store UIImage objects locally.
I am using [UIImagePNGRepresentation(image) writeToFile:full_path options:NSAtomicWrite error:nil]; to save the image and [file_manager removeItemAtPath:full_path error:NULL]; to delete the file.
This all works great, however, whenever I delete a file, should I decide to save a new file (which just so happens to have the same name as the old file), the save code doesn't work and returns the following error:
: ImageIO: CGImageReadCreateDataWithMappedFile 'open' failed
error = 2 (No such file or directory)
: ImageIO: CGImageReadCreateDataWithMappedFile 'open' failed
error = 2 (No such file or directory)
: ImageIO: PNG zlib error
So heres what I don't get, why can't I save a file with the same name as the old file, after I have deleted the old file?
The reason I ask this, is that, my app will save certain image files and then when they are no longer needed, my app will delete them. However, there are times when my app needs the image files again (could be a few hours after deletion or a few weeks). When this happens, my app will load the appropriate image data and then try to save it. And thats when the error occurs.
Whats going wrong here?
Thanks for your time, Dan.
UPDATE - Here are the methods I have setup to save/access and delete my image files
-(void)save_local_image:(UIImage *)image :(NSString *)file_name {
// Get the app documents directory link.
NSString *documents = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
// Add the new file name to the link.
NSString *database_link = [[NSString alloc] initWithString:[documents stringByAppendingPathComponent:file_name]];
// Save the image data locally.
[UIImagePNGRepresentation(image) writeToFile:database_link options:NSAtomicWrite error:nil];
}
-(UIImage *)get_local_image:(NSString *)file_name {
// Create the return data.
UIImage *image_data = nil;
// Get the app documents directory.
NSArray *directory = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] error:NULL];
// Only check for data if at least
// one file has been saved locally.
if ([directory count] > 0) {
// Loop through the different local files.
for (NSString *path in directory) {
// Get the full local file URL.
NSString *full_path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:path];
// Get the range of the file name.
NSRange range = [full_path rangeOfString:file_name];
// Get the image data if it exists.
if ((range.location != NSNotFound) || (range.length == [file_name length])) {
// Load the image file in.
image_data = [UIImage imageWithContentsOfFile:full_path];
break;
}
}
}
return image_data;
}
-(void)delete_local_image:(NSString *)file_name {
// Get the app documents directory.
NSArray *directory = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] error:NULL];
// Only check for data if at least
// one file has been saved locally.
if ([directory count] > 0) {
// Loop through the different local files.
for (NSString *path in directory) {
// Get the full local file URL.
NSString *full_path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:path];
// Get the range of the file name.
NSRange range = [full_path rangeOfString:file_name];
// Delete the local image data if it exists.
if ((range.location != NSNotFound) || (range.length == [file_name length])) {
NSError *testError = nil;
// Delete the image file.
NSFileManager *file_manager = [[NSFileManager alloc] init];
BOOL success = [file_manager removeItemAtPath:full_path error:&testError];
NSLog(#"%d", success);
if (testError != nil) {
NSLog(#"%#", testError.localizedDescription);
}
break;
}
}
}
}
I am using this to save and read:
- (void) saveImageToFile:(NSString *) urlImg withNameNumber:(int)numberName andQuestionId:(int) questionID
{
NSString* documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString* foofile = [documentsPath stringByAppendingPathComponent:[NSString stringWithFormat:#"gallery%d%d.jpg",numberName,questionID]];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:foofile];
if (!fileExists) {
NSData *tempImgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#%#",#"https://testurl/",urlImg]]];
NSString *filename = [NSString stringWithFormat:#"gallery%d%d.jpg",numberName,questionID];
NSString *imagePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:filename];
[tempImgData writeToFile:imagePath atomically:YES];
}
}
-(UIImage*) readImageFromFile:(int)numberName andQuestionId:(int) questionID
{
NSString * documentsDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
return [UIImage imageWithContentsOfFile:[NSString stringWithFormat:#"%#/gallery%d%d.jpg",documentsDirectoryPath,numberName,questionID]];
}

What happens to the Users/username/Library directory. Why can't I change the current directory?

I was following a code sample from "Programming in Objective-C Fourth Edition" by Stephen Kochan.
The program looks for a file, and performs a few things on it:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
#autoreleasepool {
NSString *fName = #"testfile";
NSFileManager *fm;
NSDictionary *attr;
//Need to create an instance of the file manager
fm = [NSFileManager defaultManager];
//Let's make sure our test file exists first
NSLog([fm currentDirectoryPath]);
if ([fm fileExistsAtPath: fName] == NO) {
NSLog(#"File doesn't exist!");
return 1;
}
//now lets make a copy
if ([fm copyItemAtPath: fName toPath: #"newfile" error: NULL]) {
NSLog(#"File Copy failed!");
return 2;
}
//Now let's see test to see if the two files are equal
if ([fm contentsEqualAtPath: fName andPath: #"newfile"] == NO) {
NSLog(#"Files are Not Equal!");
return 3;
}
//Now lets rename the copy
if ([fm moveItemAtPath: #"newfile" toPath: #"newfile2" error: NULL] == NO) {
NSLog(#"File rename Failed");
return 4;
}
//get the size of the newfile2
if((attr = [fm attributesOfItemAtPath: #"newfile2" error: NULL]) == nil)
{
NSLog(#"Couldn't get file attributes");
return 5;
}
NSLog(#"File size is %llu bytes", [[attr objectForKey: NSFileSize] unsignedLongLongValue]);
//And finally, let's delete the original file
if([fm removeItemAtPath: fName error: NULL])
{
NSLog(#"file removal failed");
return 6;
}
NSLog(#"All operations were successful");
//Display the contents of the newly-createed file
NSLog(#" %#", [NSString stringWithContentsOfFile: #"newfile2" encoding:NSUTF8StringEncoding error: NULL]);
}
return 0;
}
I created a file named "testfile" and placed it in project directory. When I ran the program, It couldn't find the file. I added [NSFileManager currentDirectoryPath] to check the current path which was apparently something like this:
/Users/myusername/Library/Developer/Xcode/DerivedData/Prog_16.1-eekphhgfzdjviqauolqexkowfqfg/Build/Products/Debug
I went looking for the Library directory, but it doesn't exist. Is this some temporary directory that's created when the program is running, then deleted after it exits?
Edit: I've tried changing the path of the current directory using [NSFileManager changeCurrentDirectoryPath: newPath] and it fails to change the path. I tried setting to newPath to #"Users/username/Desktop" and that fails too!
The Library directory does exist but it is hidden as standard. Open up the terminal and type the command: chflags nohidden ~/Library/.
Then look again and it will be magically there!
Edit: for programming with NSFileManager there's a very useful function: NSSearchPathForDirectoriesInDomains(). E.g. To get the desktop do directory:
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:#"test.txt"];
NSString *test = #"hello";
[test writeToFile:path atomicallly:YES];
This will write a little txt file to your desktop. (As long as you app isn't sandboxed).
Hope this helps.
When you place a file in the project directory, it gets shipped as part of the app's bundle. To get at the bundle directory tree, use NSBundle...
// use the file extension (if any) for the ofType: param
NSString *path = [[NSBundle mainBundle] pathForResource:#"testfile" ofType:#""];
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
NSLog(#"there it is! %#", path);
}

Strange error when moving a folder to temp with moveItemAtPath. Cocoa error 516

I try to move a folder with a file to a temp folder, but I always receive the same error:
The operation couldn’t be completed. (Cocoa error 516.)
This is the code, do you see anything strange? Thanks in advance.
// create new folder
NSString* newPath=[[self getDocumentsDirectory] stringByAppendingPathComponent:#"algo_bueno"];
NSLog(#"newPath %#", newPath);
if ([[NSFileManager defaultManager] fileExistsAtPath:newPath]) {
NSLog(#"newPath already exists.");
} else {
NSError *error;
if ([[NSFileManager defaultManager] createDirectoryAtPath:newPath withIntermediateDirectories:YES attributes:nil error:&error]) {
NSLog(#"newPath created.");
} else {
NSLog(#"Unable to create directory: %#", error.localizedDescription);
return;
}
}
// create a file in that folder
NSError *error;
NSString* myString=[NSString stringWithFormat:#"Probando..."];
NSString* filePath=[newPath stringByAppendingPathComponent:#"myfile.txt"];
if ([myString writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error]) {
NSLog(#"File created.");
} else {
NSLog(#"Failed creating file. %#", error.localizedDescription);
return;
}
// move this folder and its folder
NSString *tmpDir = NSTemporaryDirectory();
NSLog(#"temporary directory, %#", tmpDir);
if ([[NSFileManager defaultManager] moveItemAtPath:newPath toPath:tmpDir error:&error]) {
NSLog(#"Movido a temp correctamente");
} else {
NSLog(#"Failed moving to temp. %#", error.localizedDescription);
}
Error 516 is NSFileWriteFileExistsError
You can't move a file to a place where a file already exists :)
(See docs here - https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Constants/Reference/reference.html and search for '516')
More usefully, your destination path should be a file name, not a folder. Try this :
// move this folder and its folder
NSString *tmpDir = NSTemporaryDirectory();
NSString *tmpFileName = [tmpDir stringByAppendingPathComponent:#"my-temp-file-name"];
NSLog(#"temporary directory, %#", tmpDir);
if ([[NSFileManager defaultManager] moveItemAtPath:newPath toPath:tmpFileName error:&error]) {
NSLog(#"Movido a temp correctamente");
} else {
NSLog(#"Failed moving to temp. %#", error.localizedDescription);
}
Obtain a unique temp folder name by using the following method:
NSFileManager unique file names
CFUUIDRef uuid = CFUUIDCreate(NULL);
CFStringRef uuidString = CFUUIDCreateString(NULL, uuid);
NSString *tmpDir = [[NSTemporaryDirectory() stringByAppendingPathComponent:(NSString *)uuidString];
CFRelease(uuid);
CFRelease(uuidString);
// MOVE IT
[[NSFileManager defaultManager] moveItemAtPath:myDirPath toPath:tmpDir error:&error]
The target path for moveItemAtPath:toPath:error: has to be the complete new path of the file or directory you're moving. As you do it now, you're basically trying to overwrite the temp directory itself with your file.
Simple fix:
NSString *targetPath = [tmpDir stringByAppendingPathComponent:[newPath lastPathComponent]];
if ([[NSFileManager defaultManager] moveItemAtPath:targetPath toPath: error:&error]) {
NSLog(#"Moved sucessfully");
}

Appending NSString while using NSApplicationSupportDirectory to create a new directory

I have been trying to create a new file inside of my application support folder while using NSApplicationSupportDirectory; I can write a file to it, but I have been unable to create a folder inside of Application Support.
NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString *applicationDirectory = [paths objectAtIndex:0];
//make a file name to write the data to using the application support: (attempting to create the blasted directory inside of application support directory
NSString *fileName = [NSString stringWithFormat:#"%#/managersemail.txt",
applicationDirectory];
//create content - formats with the managersemail.txt location
NSString* content = [NSString stringWithFormat:#"%#",[nameField stringValue]];
//save content to the documents directory
[content writeToFile:fileName
atomically:NO
encoding:NSStringEncodingConversionAllowLossy
error:nil];
NSDictionary* errorDict;
The code that I have listed above works great, except for the part about creating the folder in which I want to place the managersemail.txt. I tried to mimic the stringWithFormat that is listed in the NSString* content and then varying several ways however to no avail! Any thoughts?
NSAppleEventDescriptor* returnDescriptor = NULL;
Perhaps the solution provided on Cocoa with Love might be useful?
Excerpt:
- (NSString *)findOrCreateDirectory:(NSSearchPathDirectory)searchPathDirectory
inDomain:(NSSearchPathDomainMask)domainMask
appendPathComponent:(NSString *)appendComponent
error:(NSError **)errorOut
{
// Search for the path
NSArray* paths = NSSearchPathForDirectoriesInDomains(
searchPathDirectory,
domainMask,
YES);
if ([paths count] == 0)
{
// *** creation and return of error object omitted for space
return nil;
}
// Normally only need the first path
NSString *resolvedPath = [paths objectAtIndex:0];
if (appendComponent)
{
resolvedPath = [resolvedPath
stringByAppendingPathComponent:appendComponent];
}
// Check if the path exists
BOOL exists;
BOOL isDirectory;
exists = [self
fileExistsAtPath:resolvedPath
isDirectory:&isDirectory];
if (!exists || !isDirectory)
{
if (exists)
{
// *** creation and return of error object omitted for space
return nil;
}
// Create the path if it doesn't exist
NSError *error;
BOOL success = [self
createDirectoryAtPath:resolvedPath
withIntermediateDirectories:YES
attributes:nil
error:&error];
if (!success)
{
if (errorOut)
{
*errorOut = error;
}
return nil;
}
}
if (errorOut)
{
*errorOut = nil;
}
return resolvedPath;
}
Maybe you can try using the NSFileManager to create the folder, then write the file into the folder.
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *applicationSupport = [[NSString stringWithString:#"~/Library/Application Support/'YOUR APP'] stringByExpandingTildeInPath];
if ([fileManager fileExistsAtPath:applicationSupport] == NO)
[fileManager createDirectoryAtPath:applicationSupport withIntermediateDirectories:YES attributes:nil error:nil];
NSString *fileName = [NSString stringWithFormat:#"%#/managersemail.txt", applicationSupport];
NSString* content = [NSString stringWithFormat:#"%#",[nameField stringValue]];
//save content to the documents directory
[content writeToFile:fileName
atomically:NO
encoding:NSStringEncodingConversionAllowLossy
error:nil];
So something like that should work. Feel free to leave comments to ask questions.