NSFileManager fileExistsAtPath:isDirectory issue - objective-c

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

Related

Unable to rename the file while moving from temporary directorary

I am developing a zip extractor app i followed the algorithm that CRD explained #Here but i stuck at third step i am unable to rename the unzipped file which is at temporary directorary.
here is my code
NSURL *tempDir = [NSURL fileURLWithPath:destinationPath];
NSError *error;
NSURL *tmpDirectory = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory inDomain:NSUserDomainMask appropriateForURL:tempDir create:YES error:&error];
if (error) {
return ;
}
tmpDirectory = [tmpDirectory URLByAppendingPathComponent:#"extracts"];
NSLog(#"temp dir %#",tmpDirectory);
NSLog(#"temp path %#",tmpDirectory.path);
[SSZipArchive unzipFileAtPath:zipFilePath toDestination:tmpDirectory.path];
NSArray *dirFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:tmpDirectory.path error:nil];
NSLog(#"dir file %#",dirFiles);
for (NSString *string in dirFiles) {
NSArray *dirDestinationFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:destinationPath error:nil];
NSLog(#"dir destination file %#",dirDestinationFiles);
[dirDestinationFiles enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSFileManager *fm = [NSFileManager defaultManager];
NSError *error;
if ([string isEqualToString:obj]) {
NSLog(#"Already present");
BOOL isMoved = [fm moveItemAtPath:tmpDirectory.path toPath:[destinationPath stringByAppendingString:[NSString stringWithFormat:#"/%#-1",string]] error:&error];
if (isMoved) {
NSLog(#"Moved");
}else{
NSLog(#"errorL %#", error);
NSLog(#"Not moved");
}
[fm removeItemAtPath:tmpDirectory.path error:&error];
[self moveFileToTrash:zipFilePath];
[self openExtractedFolderWithZipPath:zipFilePath toDestinationPath:destinationPath];
}
}];
}
Any Suggestions..
Thanks in Advance !
Let's just review your code to hopefully help you on your way.
It may seem minor, but pick good variable names:
NSURL *tempDir = [NSURL fileURLWithPath:destinationPath];
NSURL *tmpDirectory = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory inDomain:NSUserDomainMask appropriateForURL:tempDir create:YES error:&error];
Two names which are semantically similar for different things, that is just confusing. How about, say, destinationURL instead of tempDir?
Next, when constructing/pulling apart/etc. pathnames or URLs you will be better off being consistent. Both NSURL and NSString provide similar methods for these operations, in one place you use them:
tmpDirectory = [tmpDirectory URLByAppendingPathComponent:#"extracts"];
but then restort to direct string manipulation using a path separator which may, or may not, be correct:
[destinationPath stringByAppendingString:[NSString stringWithFormat:#"/%#-1",string]]
The routines provided by NSURL and NSString abstract away from the details of path separators and how to, say, find the extension on the last path component (which you might find useful when renaming to avoid clashes).
Going back to:
tmpDirectory = [tmpDirectory URLByAppendingPathComponent:#"extracts"];
There is no reason for you to do this. The temporary directory is created for you and you should delete it after using it. So there is no need to create a subdirectory extracts within it, and by reassigning to the same variable you've lost the URL you need to delete the temporary directory.
Now something less obvious, in my comment above I wrote:
To move each item you must handle name clashes, to do this try the move and if you get an error indicating a name clash modify the destination name however you like and re-try the move, repeating until you succeed or you until reach some limit of tries (determined by you).
I didn't explain why you should do it this way and you have tackled the problem a different way: for each item you are going to move you check for names clashes before attempting the move by iterating over the names in the destination directory.
If you read Apple's documentation on the file system you will find they often recommend you try an operation and then examine any error returned instead of trying to predict whether an error will occur and avoid it. The reason for this is the file system is dynamic, other processes can be modifying it, so if you try to avoid an error you may still get one. In pseudocode you are better of doing something like:
moveDone = false
attemptCount = 0
while not moveDone and attemptCount < MAX_ATTEMPTS
move object
if object exists error
modify destination URL
increment attemptCount
else
moveDone = true
end
end
if not moveDone then handle error
Following this outline and using a simple count and the NSString/NSURL path routines will produce you a much simpler and more reliable solution than the one you have now posted as an answer.
HTH
Here is the code working for me.
NSURL *tempDir = [NSURL fileURLWithPath:destinationPath];
NSError *error;
NSURL *tmpDirectory = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory inDomain:NSUserDomainMask appropriateForURL:tempDir create:YES error:&error];
if (error) {
return ;
}
tmpDirectory = [tmpDirectory URLByAppendingPathComponent:#"extracts"];
NSLog(#"temp dir %#",tmpDirectory);
NSLog(#"temp path %#",tmpDirectory.path);
[SSZipArchive unzipFileAtPath:zipFilePath toDestination:tmpDirectory.path];
NSArray *dirFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:tmpDirectory.path error:nil];
NSLog(#"dir file %#",dirFiles);
for (NSString *string in dirFiles) {
NSArray *dirDestinationFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:destinationPath error:nil];
NSLog(#"dir destination file %#",dirDestinationFiles);
NSMutableArray *folderCount = [[NSMutableArray alloc] init];
NSMutableArray *folderNumCount = [[NSMutableArray alloc] init];
[dirDestinationFiles enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj containsString:string]){
[folderNumCount addObject:obj];
}
if ([string isEqualToString:obj]) {
[folderCount addObject:string];
}
}];
NSFileManager *fm = [NSFileManager defaultManager];
NSError *error;
if (folderCount.count == 0) {
NSLog(#"First time extract");
BOOL isMoved = [fm moveItemAtPath:tmpDirectory.path toPath:[destinationPath stringByAppendingString:[NSString stringWithFormat:#"/%#",string]] error:&error];
if (isMoved) {
NSLog(#"Moved");
}else{
NSLog(#"errorL %#", error);
NSLog(#"Not moved");
}
[fm removeItemAtPath:tmpDirectory.path error:&error];
// [self moveFileToTrash:zipFilePath];
// [self openExtractedFolderWithZipPath:zipFilePath toDestinationPath:destinationPath];
}else if (folderCount.count > 0){
NSLog(#"Already present");
BOOL isMoved = [fm moveItemAtPath:tmpDirectory.path toPath:[destinationPath stringByAppendingString:[NSString stringWithFormat:#"/%#-%lu",string,folderNumCount.count-1]] error:&error];
if (isMoved) {
NSLog(#"Moved");
}else{
NSLog(#"errorL %#", error);
NSLog(#"Not moved");
}
[fm removeItemAtPath:tmpDirectory.path error:&error];
// [self moveFileToTrash:zipFilePath];
// [self openExtractedFolderWithZipPath:zipFilePath toDestinationPath:destinationPath];
}
}

contentsOfDirectoryAtPath with Contents of Subfolders objective-c

How can I get the contents of a directory and all of its subfolders? I would like to have tree stored in a NSDictionary.
I want the dictionary to print something like this:
{
MyFolder = (
"Water.png",
{
MySubfolder = (
"Note.txt",
{
Sub-Subfolder = (
"3D.pdf",
"MyFile.txt"
);
}
);
}
);
}
Ive tried:
NSFileManager *manager = [NSFileManager defaultManager];
NSArray *array = [manager contentsOfDirectoryAtPath:path error:nil];
NSMutableDictionary *files = [[NSMutableDictionary alloc]init];
NSString *newPath = #"";
for (int i=0; array.count>i; i++) {
newPath = [NSString stringWithFormat:#"%#/%#", path, [array objectAtIndex:i]];
//echo(newPath);
if ([[[manager attributesOfItemAtPath:newPath error:nil] objectForKey:NSFileType] isEqualToString:NSFileTypeRegular])
{
NSLog(#"Setting: %#||%#", [array objectAtIndex:i], [newPath lastPathComponent]);
[files setObject:[array objectAtIndex:i] forKey:[newPath lastPathComponent]];
}
else
{echo([NSString stringWithFormat:#"newPath=%#", newPath]);
dict = [self reachedDirectory:newPath dict:dict oldPath:path];
}
}
NSMutableDictionary *transferred = [dict objectForKey:[newPath lastPathComponent]];
if (!transferred) {
transferred = [[NSMutableDictionary alloc]init];
}
for (int n=0; files.count>n; n++) {
[transferred setObject:[[files allValues] objectAtIndex:n] forKey:[[files allKeys] objectAtIndex:n]];
}
echo([newPath lastPathComponent]);
[dict setObject:transferred forKey:[path lastPathComponent]];
return dict;
But All of the folders are not aligned and it doesnt go past the second dimension of subfolders. I would like it to be able to have as many subfolders that is possible.
Thanks for your help!
You can use NSDirectoryEnumerator class and it provides enumerateAtPath method so that you need to just pass your main folder path and inside that just loop your condtion. So that whatever your subfolder exist it will print the path accordingly.

iOS list existing directories

I already get path for documents directory and create some directories inside. I already know how to check if directory exist, delete it or its files but, how could I list directories? Thank you.
for file listing I use:
int Count;
NSString *path;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
path = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"SomeDirectoryName"];
NSArray *directoryContent = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL];
for (Count = 0; Count < (int)[directoryContent count]; Count++)
{
NSLog(#"File %d: %#", (Count + 1), [directoryContent objectAtIndex:Count]);
}
For example, this method removes all files from temporary directory of application:
- (void)cleatTmpDirectory
{
// Create a local file manager instance
NSFileManager *localFileManager = [[NSFileManager alloc] init];
NSURL *directoryToScan = [NSURL fileURLWithPath:[self applicationTmpDirectory]];
NSDirectoryEnumerator *dirEnumerator =
[[localFileManager enumeratorAtURL:directoryToScan
includingPropertiesForKeys:[NSArray arrayWithObjects:NSURLIsDirectoryKey,nil]
options: NSDirectoryEnumerationSkipsHiddenFiles |
NSDirectoryEnumerationSkipsSubdirectoryDescendants |
NSDirectoryEnumerationSkipsPackageDescendants
errorHandler:nil] retain];
NSError *error;
// Enumerate the dirEnumerator results, each value is stored in allURLs
for (NSURL *theURL in dirEnumerator)
{
// Retrieve whether a directory.
NSNumber *isDirectory;
[theURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL];
if ([isDirectory boolValue] == NO)
{
[localFileManager removeItemAtURL:theURL error:&error];
}
}
// Release the localFileManager.
[localFileManager release];
}
As you can find you should use NSDirectoryEnumerator *dirEnumerator and pass to its initialization method appropriate keys that you will then use.
Use the NSDirectoryEnumerator returned by NSFileManager's -enumeratorAtPath: method.

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.

Check the attribute of items in a directory in Objective-C

I have made this little code to check how many subdirectories are in a given directory. It checks only the first level, is there anyway I can make it simplier? I have added comments, maybe easier to understand my intention. Thank you!
#import < Foundation/Foundation.h >
int main (int argc, const char * argv[]){
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// insert code here...
NSFileManager *filemgr;
NSMutableArray *listOfFiles;
NSDictionary *listOfFolders;
NSDictionary *controllDir;
int i, count;
NSString *myPath;
filemgr = [NSFileManager defaultManager];
myPath = #"/";
// list the files in the given directory (myPath)
listOfFiles = [filemgr directoryContentsAtPath: myPath];
// count the number of elements in the array
count = [listOfFiles count];
// check them one by one
for (i = 0; i < count; i++)
{
// I need the full path
NSString *filePath =[NSString stringWithFormat:#"%#/%#", myPath, [listOfFiles objectAtIndex: i]];
// add every item with its attributes
listOfFolders = [filemgr attributesOfItemAtPath:filePath error:NULL];
// to avoid typo get the attribute and create a string
controllDir = [filemgr attributesOfItemAtPath:#"/" error:NULL];
NSString *toCheck = [NSString stringWithFormat:#"%#", [controllDir objectForKey:NSFileType]];
// the folder elements one by one
NSString *fileType = [NSString stringWithFormat:#"%#", [listOfFolders objectForKey:NSFileType]];
if([toCheck isEqualToString:fileType])
{
NSLog(#"NAME: %# TYPE: %#" ,[listOfFiles objectAtIndex:i],[listOfFolders objectForKey:NSFileType]);
}
}
[pool drain];
return 0;
}
NSURL *url = [NSURL fileURLWithPath:#"/Users"];
NSError *error;
NSArray *items = [[NSFileManager defaultManager]
contentsOfDirectoryAtURL:url
includingPropertiesForKeys:[NSArray array]
options:0
error:&error];
NSMutableArray *dirs = [NSMutableArray array];
for (NSURL *url in items) {
if (CFURLHasDirectoryPath((CFURLRef)url)) {
[dirs addObject:url];
}
}
}
You can get fancy with blocks this way:
NSPredicate *predicate = [NSPredicate predicateWithBlock:
^BOOL (id evaluatedObject, NSDictionary *bindings){
return CFURLHasDirectoryPath((CFURLRef)evaluatedObject); }];
NSArray *dirs = [items filteredArrayUsingPredicate:predicate]);
Of note here is that it only hits the disk the one time, and it doesn't spend any time fetching unneeded attributes. Once you construct the NSURL, you can always tell if it's a directory because it ends in a / (this is specified behavior). That's all CFURLHasDirectoryPath() is doing. It doesn't actually hit the disk.
Brief thoughts (posting from a cell phone):
use an NSDirectoryEnumerator.
it has a method called fileAttributes that will return an NSDictionary with the item's attributes.
NSDictionary has a fileType method that will return a constant to indicate the kind of the item.
there's a nice constant called NSFileTypeDirectory you can use for comparison.
How's this?
NSFileManager *fm = [NSFileManager defaultManager];
NSError *error;
NSArray *subpaths = [fm contentsOfDirectoryAtPath:myPath error:&error];
if (!subpaths) ...handle error.
NSMutableArray *subdirs = [NSMutableArray array];
for (NSString *name in subpaths)
{
NSString *subpath = [myPath stringByAppendingPathComponent:name];
BOOL isDir;
if ([fm fileExistsAtPath:subpath isDirectory:&isDir] &&
isDir)
{
[subdirs addObject:subpath];
}
}
Now the subdirs array contains all of the immediate subdirectories.