Mac OS: how to determine path is file or directory - objective-c

I have a path and I want to know, is this directory or a file. Pretty simple, but I have a problem. Here's my path:
NSString *file = #"file://localhost/Users/myUser/myFolder/TestFolder/";
or
NSString *file = #"file://localhost/Users/myUser/myFolder/my_test_file.png";
Here is my code:
BOOL isDir;
// the original code from apple documentation:
// if ([fileManager fileExistsAtPath:file isDirectory:&isDir] && isDir)
// but even my if returns me "not ok" in log
if([[NSFileManager defaultManager] fileExistsAtPath:file isDirectory:&isDir])
{
NSLog(#"directory");
}
else
{
NSLog(#"not ok");
}
files and dirs on this pathes are exists and they are ok. But I thinks problem could be in it. But I don't know why. Help me with this please.
By the way, I get path from another method:
NSArray *contentOfMyFolder = [[NSFileManager defaultManager]
contentsOfDirectoryAtURL:urlFromBookmark
includingPropertiesForKeys:#[NSURLContentModificationDateKey, NSURLLocalizedNameKey]
options:NSDirectoryEnumerationSkipsHiddenFiles
error:nil];
after this in for loop, I get items that stored in array contentOfMyFolder
and get path like this:
for (id item in contentOfMyFolder) {
NSString *path = [item absoluteString];
}
I thinks this is perfectly valid path for method fileExistsAtPath:(NSString *)path isDirectory:(BOOL)isDir
Where the problem could hide?!

The problem is here:
NSString *path = [item absoluteString];
because that creates a string representation of the URL, such as
file://localhost/Users/myUser/myFolder/TestFolder/
and that is not what fileExistsAtPath: expects.
What you need is the path method to convert the URL to a path:
for (NSURL *item in contentOfMyFolder) {
NSString *path = [item path];
BOOL isDir;
if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) {
if (isDir) {
NSLog(#"%# is a directory", path);
} else {
NSLog(#"%# is a file", path);
}
} else {
NSLog(#"Oops, %# does not exist?", path);
}
}
Alternatively, you can ask the URL for its "directory property":
for (NSURL *item in contentOfMyFolder) {
NSNumber *isDir;
NSError *error;
if ([item getResourceValue:&isDir forKey:NSURLIsDirectoryKey error:&error]) {
if ([isDir boolValue]) {
NSLog(#"%# is a directory", item);
} else {
NSLog(#"%# is a file", item);
}
} else {
NSLog(#"error: %#", error);
}
}

Related

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

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.

Prevent app from backing up documents folder?

I'm trying to prevent my app backing up files to iCloud but have become completely confused and a little lost.
-EDIT-
I've updated this to reflect the changes I've made thanks to the posters below.
I want to prevent back up of files which are downloaded to the app's documents directory.
So far I have a class called PreventBackup with the following method:
+ (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
NSError *error = nil;
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
forKey: NSURLIsExcludedFromBackupKey error: &error];
if(!success){
NSLog(#"Error excluding %# from backup %#", [URL lastPathComponent], error);
}
NSLog(#"prevent backup method called without error");
return success;
}
I'm then calling it with this code when the app starts:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSURL *pathURL= [NSURL fileURLWithPath:documentsDirectory];
[PreventBackup addSkipBackupAttributeToItemAtURL:pathURL];
The cosole prints prevent backup method called without error but the app is still showing as having the same amount of data for back up with it did before.
Any idea where this is going wrong?
-EDIT 2-
OK, I think this is solved. The files are downloading to a sub folder called "downloads". changing the code above so that it reads as follows appears to have done the trick:
NSString *downloadsFolder = [documentsDirectory stringByAppendingPathComponent:(#"/downloads")];
NSURL *pathURL= [NSURL fileURLWithPath:downloadsFolder];
[PreventBackup addSkipBackupAttributeToItemAtURL:pathURL];
Thanks to all for your help.
- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
NSError *error = nil;
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
forKey: NSURLIsExcludedFromBackupKey error: &error];
if(!success){
NSLog(#"Error excluding %# from backup %#", [URL lastPathComponent], error);
}
return success;
}
NSURL *documentURLWithExtension = [documentURL URLByAppendingPathExtension:extensionType];
pass this "documentURLWithExtension" to this function
[self addSkipBackupAttributeToItemAtURL:documentURLWithExtension];
In Swift:
//Path of document directory
var docPathAry : NSArray! = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
var docDirPathStr: AnyObject? = docPathAry.count > 0 ? docPathAry[0] : nil
self.addSkipBackupAttributeToItemAtURL(NSURL.fileURLWithPath(docDirPathStr as NSString))
and:
func addSkipBackupAttributeToItemAtURL(URL: NSURL!) -> Bool {
assert(NSFileManager.defaultManager().fileExistsAtPath(URL.path))
var err : NSError? = nil
var success : Bool! = URL.setResourceValue(NSNumber.numberWithBool(true), forKey: NSURLIsExcludedFromBackupKey, error: &err)
if(!success) {
println("Error excluding \(URL.lastPathComponent) from backup\(err) ")
}
return success
}

NSFileManager fileExistsAtPath:isDirectory issue

Can someone help me understand what I'm doing wrong with this method?
I'm trying to recursively detect the contents of directories and create an xml file in each one. Non-recursive works perfectly and outputs proper xml files. Recursive chokes on dir detection and add's all files + dir's under the "directories" element.
_dirArray = [[NSMutableArray alloc] init];
_fileArray = [[NSMutableArray alloc] init];
NSError *error;
NSFileManager *filemgr = [NSFileManager defaultManager];
NSArray *filelist = [filemgr contentsOfDirectoryAtPath:dirPath error:&error];
for (int i = 0; i < filelist.count; i++)
{
BOOL isDir;
NSString *file = [NSString stringWithFormat:#"%#", [filelist objectAtIndex:i]];
[_pathToDirectoryTextField stringValue], [filelist objectAtIndex:i]];
if ([filemgr fileExistsAtPath:dirPath isDirectory:&isDir] && isDir) // I think this is what is crapping out.
{
[_dirArray addObject:file];
}
else
{
if ([file hasPrefix:#"."])
{
// Ignore file.
}
else
{
[_fileArray addObject:file];
}
}
}
Thanks for any tips guys.
i can see "if ([fileManager fileExistsAtPath:fontPath isDirectory:&isDir] && isDir)" coming from Apple's examples in the documentation but to copy it piecemeal and use it with else is a very bad idea unless you only want to get directories or deleted files because what it means is:
if (itexists and itsadirectory){
//its a existing directory
matches directories
}else{
//it is not a directory or it does not exist
matches files that were deleted since you got the listing
}
here is how i would do it:
NSString *dirPath = #"/Volumes/Storage/";
NSError *error;
NSFileManager *filemgr = [NSFileManager defaultManager];
NSArray *filelist = [filemgr contentsOfDirectoryAtPath:dirPath error:&error];
for (NSString *lastPathComponent in filelist) {
if ([lastPathComponent hasPrefix:#"."]) continue; // Ignore file.
NSString *fullPath = [dirPath stringByAppendingPathComponent:lastPathComponent];
BOOL isDir;
BOOL exists = [filemgr fileExistsAtPath:fullPath isDirectory:&isDir];
if (exists) {
if (isDir) {
[_dirArray addObject:lastPathComponent];
}else{
[_fileArray addObject:lastPathComponent];
}
}
}

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.