Folders physical size with NSFileManager call

Hello,

How do you get – if possible – the physical size of a folder without using the Finder:

if (physical size of theFolder) is less than 400000 then (…)

I tried :

    repeat with theFile in theList
        set fileDic to manager's attributesOfItemAtPath_error_(theFile,missing value)
        if (fileDic's fileType() as string) is "NSFileTypeDirectory" then
            if (fileDic's fileSize() as integer) < 400000 then (...)
        end if
    end repeat

But unfortunately, the file manager returns strictly the size of the folder object, not the size of its contents. Is there a way to do this, or shall I stick to the AS commands ?

Thanks

I’ve tried to do the same thing before, but seems like there isn’t a way to do that from ASOC… can’t really remember the reason why, had to do with accessing methods in C I think. Strangely, NSFileManager doesn’t have a way to report the size of a folder.

One thing you could do is scan the contents of a folder and total the size of all the files in it. There is a method that returns all the paths and subpaths of a folder, then you’d just need to do a loop and get the size of each file with NSFileManager.

Sorry! :slight_smile:

Browser: Safari 531.22.7
Operating System: Mac OS X (10.6)

How does the Finder do ? Maybe using Carbon lib? Can I use Carbon + ASOC ? I read that Carbon’s use is discouraged.

Another question about frameworks : what is this vImage stuff? Do somebody ever experienced these accelerated routines?

Sorry for the delay, vacation…

I read somewhere else in these forums that the Finder does the same thing we have to do: count the total of all files in a folder one by one. This is why it does that “Calculating size…” thing for awhile. There doesn’t seem to be a method (at least not accessible from ASOC) that returns the size of a folder’s content, as far as I know.

Did you try my suggestion?

Browser: Safari 531.22.7
Operating System: Mac OS X (10.6)

I have to admit being discouraged by your previous post – if I have to do what the Finder does, I prefer let the old Finder do it in assembler than to rewrite this in ASOC :confused:

Hope you had a good time on vacation, mine is coming in a few weeks :slight_smile:

Yeah, I understand. I just looked at some old stuff and found this:

You have to provide an NSUrl object to this method to get the size back in bytes:

on returnFolderTotalSizeInBytesWithURL_(URLtoGetSizeFrom)
set filePath to URLtoGetSizeFrom's |path|()
set folderSizeTotal to (do shell script ("/usr/bin/du -k -d 0 " & ((quoted form of (filePath as string)) as string)))
set folderSizeString to current application's NSString's stringWithString_(folderSizeTotal)
set folderSizeStringComponents to folderSizeString's componentsSeparatedByString_("	") --That's a tab character between the "", you're going to have to copy one from somewhere and paste it in there as these get converted to spaces in this forum
set folderSizeStringAsInteger to ((((item 1 of folderSizeStringComponents) as string) as integer) * 1024)
return folderSizeStringAsInteger
end returnFolderTotalSizeInBytesWithURL_

You will have to do some math to convert it to a human-readable string (like 10 Mb), but this should help. I’m going to try the stuff I suggested in my previous posts and try to remember to post the result here. :slight_smile:

Have a nice vacation! :slight_smile:

Browser: Safari 531.22.7
Operating System: Mac OS X (10.6)

I just wanted to post an update for a better and faster way to calculate the size of a folder. I switched from a do shell script to a 100% cocoa method, and it has proven much more stable and easier to use.

Create an Objective-C file, name it for example “FSObjCHelpers” and in the .h file put in this code:

#import <Cocoa/Cocoa.h>

@interface FSObjCHelpers : NSObject 

- (unsigned long long int) returnFolderSize:(NSString *)folderPath;

@end

and in the .m file paste this code:

#import "FSObjCHelpers.h"
#import <Foundation/Foundation.h>

@implementation FSObjCHelpers

- (unsigned long long int) returnFolderSize:(NSString *)folderPath {
    NSArray *filesArray = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:folderPath error:nil];
    NSEnumerator *filesEnumerator = [filesArray objectEnumerator];
    NSString *fileName;
	NSError **errorDetails;
    unsigned long long int fileSize = 0;
	
    while (fileName = [filesEnumerator nextObject]) {
        NSDictionary *fileDictionary = [[NSFileManager defaultManager] attributesOfItemAtPath:[folderPath stringByAppendingPathComponent] error:errorDetails];
        fileSize += [fileDictionary fileSize];
    }
	return fileSize;
}

@end

and after creating a blue cube and assigning it the FSObjCHelpers class in the Identity inspector in IB, linking it to a property with the same name, you simply call it like this from ASOC:

set returnFolderSizeObject to current application's FSObjCHelpers's alloc()'s init()
set folderSizeStringAsInteger to (returnFolderSizeObject's returnFolderSize_(URLtoAdd's |path|()))

It returns an integer of the total bytes, same as reported in the Finder on OS X Snow Leopard. For some reason, the count is different in Lion, no explanation yet. Seems like Apple changed something in Lion…

Hope that helps someone!

Model: MBPro8,2
Browser: Safari 534.48.3
Operating System: Mac OS X (10.7)

If that’s still a bit sluggish, have a look at -enumeratorAtURL:includingPropertiesForKeys:options:errorHandler:. The docs give you sample code you should be able to rework easily enough.

My main app relies on fast directory size calculations and I tried many ways to get folder size and eventually made a separate OBJ-c class using Daniel Jalkuts carbon method which is discussed here:

http://www.edenwaith.com/development/tutorials/foldersize/

You can download the source and pop it into your project and call the method from your ASOC code. It is the fastest and most accurate. Previously I just used

do shell script ("du -sk " & inputFolderPath)

which works too and is way simpler, but is slower.

Cheers, Rob

I’m curious, if the Carbon libs are going away, what becomes of Dan’s project (or any Carbon stuff)? Are you relegated to living with lower performance code? Or does anyone know if Carbon libs are somehow being “Cocoa-ized” or ported to straight C or something?

Fred,

I tried the method you just posted, and I get very different numbers for some folders, like the Users folder – your method gives me 8 GB (and only took 3 seconds) while “get Info” from Finder gives me 113 GB.

Another point. If you are going to use the line “set returnFolderSizeObject to current application’s FSObjCHelpers’s alloc()'s init()” you don’t need to create a blue cube and hook it up. That line creates an instance of your class, as does the blue cube. If you are using the blue cube and a property you don’t need an alloc init, and it would be better to use a different name than your class name so that it can start with a lower case letter (as is the convention, and ASOC won’t let you create a name with the same spelling but different capitalization).

I haven’t tried Shane’s method yet, but your code could be faster (and simpler) if you used the “fast enumeration” rather than an enumerator object:

- (unsigned long long int) returnFolderSize:(NSString *)folderPath {
    NSArray *filesArray = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:folderPath error:nil];
    NSError **errorDetails;
    unsigned long long int fileSize = 0;
    
    for (id aFile in filesArray){
        NSDictionary *fileDictionary = [[NSFileManager defaultManager] attributesOfItemAtPath:[folderPath stringByAppendingPathComponent:aFile] error:errorDetails];
       fileSize += [fileDictionary fileSize];
    }
    return fileSize;
}

This is faster, but still gives the same wrong answer for my Users folder

Ric

Thank you everyone for your comments!

Ric, I don’t know if you are on Lion, but for me it does not give the right size only in Lion, I get the correct bytes count in Snow leopard every time. But for some strange reason, Lion always ends up with less, and it always vary, i.e. it’s not always 75% of the real count. No idea why.

I’ll try your suggestions, and Rob’s too, just to see if it helps.

I don’t believe Apple would have changed again the way bytes are counted, like they did from Leopard to Snow Leopard, it would be way too confusing for people. And besides people like the new way, they see 750 Gb capacity in the mac when they bought a 750 Gb HD…

Fred

Model: MBPro8,2
Browser: Safari 534.48.3
Operating System: Mac OS X (10.7)

This looks interesting, but the carbon method requires an FSRef be provided, but FSRef are not allowed in ASOC, right? Or is an FSRef just another fancy name for an HFS path?

Model: MBPro8,2
Browser: Safari 534.48.3
Operating System: Mac OS X (10.7)

Tried to find out why the method would not report the correct folder size in Lion, and I might have found something.

I decided to add a file count to the method and log it, and for example if I try to get the size of the App Support folder in my User Library, the Finder reports 7157 elements for a total of 510,459,148 bytes, as the method returns 6998 elements and 507,851,044 bytes. Clearly some files are skipped, but which ones?

Here’s my new code, and btw, i’d like to return an NSDictionary with both the total size and total count for the folder, but i’m having problems, can anyone clarify this for me please?

#define TOTAL_FOLDER_SIZE_KEY @"totalFolderSize"
#define TOTAL_COUNT_KEY @"totalCountOfFiles"

- (unsigned long long int) returnFolderSize:(NSString *)folderPath {
	
	NSArray *filesArray = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:folderPath error:nil];
	NSError **errorDetails;
	unsigned long long int fileSize = 0;
	NSInteger fileCount = 0;
	NSMutableDictionary *infoToReturn = [NSMutableDictionary dictionary];
	
	for (id aFile in filesArray){
		NSDictionary *fileDictionary = [[NSFileManager defaultManager] attributesOfItemAtPath:[folderPath stringByAppendingPathComponent:aFile] error:errorDetails];
		fileSize += [fileDictionary fileSize];
		if ([fileDictionary fileType] != NSFileTypeDirectory) {
			fileCount += 1;
		}
	}
	[infoToReturn setObject:fileSize forKey:TOTAL_FOLDER_SIZE_KEY]; // I get an "incompatible integer to pointer conversion sending 'unsigned long long' to parameter of type 'id'"
	[infoToReturn setObject:fileCount forKey:TOTAL_COUNT_KEY]; // same error here
	NSLog(@"Total count of files: %i", fileCount);
	return infoToReturn; //this is what I would like to get
	//Tried this too, but crashes the app:
	//return [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedLongLong], @"totalFolderSize", [NSNumber numberWithInt:fileCount], @"totalCountOfFiles";, nil;

}

Is NSFileTypeDirectory skipping packages? and i just saw this in the dev notes:

So perhaps the method is not getting the correct size because of resource forks? I thought resource forks were long dead and gone…

Model: MBPro8,2
Browser: Safari 534.48.3
Operating System: Mac OS X (10.7)

I tried Shane’s method, and it gives a much more accurate answer, although not quite the same as the Finder method.

Here is the Objective_C file:

- (unsigned long long int) returnFolderSize:(NSURL *)folderURL {
    NSDirectoryEnumerator *dirEnumerator =  [[NSFileManager defaultManager]enumeratorAtURL:folderURL includingPropertiesForKeys:[NSArray arrayWithObject:NSURLFileSizeKey] options:0 errorHandler:nil];
    NSNumber *fileSize;
    unsigned long long int cumulativeSize;
    for (NSURL *theURL in dirEnumerator) {
        [theURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:NULL];
        cumulativeSize += [fileSize longLongValue];
    }
    return cumulativeSize;
}

I call this in ASOC with:

on applicationWillFinishLaunching_(aNotification)
		set sizeGetter to current application's FSObjCHelpers's alloc()'s init()
		log sizeGetter's returnFolderSize_(current application's NSURL's fileURLWithPath_isDirectory_("/Users", 1))
	end applicationWillFinishLaunching_

This gives me 113.19 GB vs 113.23 for the Finder

I don’t know if the NSURLFieSizeKey argument in the enumeratorAtURL:includingPropertiesForKeys:options:errorHandler: method is doing anything since it is not a Common FIle System Resource Key. It is an ok argument for the NSURL method getResourceValue:forKey:error:

Ric

I’ve modified my existing code with yours, Ric, and I had to change this line:

unsigned long long int cumulativeSize;

to:

unsigned long long int cumulativeSize = 0;

Because I was getting an integer that was outside AS’s limits it seems and it returned formatted like 1.7799292E+01.

I’m getting a better result, now getting 508,260,907 bytes for 7768 (?!?) elements from the method, but still the Finder reports 7171 elements for a total of 511,161,930 bytes. Now having 597 elements more than what the Finder has, which can’t really account for the slight difference in size.

Here’s the updated code, still having problems with returning the total file count:

- (unsigned long long int) returnFolderSizeFromURL:(NSURL *)folderURL {
	NSDirectoryEnumerator *dirEnumerator = [[NSFileManager defaultManager]enumeratorAtURL:folderURL includingPropertiesForKeys:[NSArray arrayWithObject:NSURLFileSizeKey] options:nil errorHandler:nil];
	NSNumber *fileSize;
	NSNumber *isdirectory;
	NSNumber *ispackage;
	unsigned long long int cumulativeSize = 0;
	NSInteger fileCount = 0;
	
	for (NSURL *theURL in dirEnumerator) {
		[theURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:NULL];
		[theURL getResourceValue:&isdirectory forKey:NSURLIsDirectoryKey error:NULL];
		[theURL getResourceValue:&ispackage forKey:NSURLIsPackageKey error:NULL];
		cumulativeSize += [fileSize integerValue];
		if (isdirectory != 0) {
			fileCount += 1;
		}
	}
	NSLog(@"Total count of files: %i", fileCount);
	NSLog(@"Total size of folder: %llu", cumulativeSize);
	NSNumber *cumulativeSizeAsNumber = [NSNumber numberWithUnsignedLongLong:cumulativeSize]; //had to do this to shut up Xcode about non-compatible pointers
	NSArray *infoToReturnAsList = [NSArray arrayWithObjects: cumulativeSizeAsNumber, fileCount, nil];
	return infoToReturnAsList;
}

Switched from an NSMutableArray to an NSArray, no need for extras I thought. No longer getting the error “incompatible integer to pointer conversion sending ‘unsigned long long’ to parameter of type ‘id’”, but… the data returned to applescript is not an array, but a different integer. Very confusing…

Model: MBPro8,2
Browser: Safari 534.48.3
Operating System: Mac OS X (10.7)

The items of an NSArray must be objects.
cumulativeSize is not an object, wrap it with a NSNumber object.

And the method’s return type doesn’t match the real tpye, you return an NSArray instead of an unsigned long long

Note: long long int is not nedded, long long is sufficient,

PS: To avoid math inaccuracy you should get the proper value from NSNumber for the file size

cumulativeSize += [fileSize unsignedlonglongValue];

Yes, that was it… two beginner’s mistakes!

Here’s the method that works now:

- (NSArray *) returnFolderSizeFromURL:(NSURL *)folderURL {
	NSDirectoryEnumerator *dirEnumerator = [[NSFileManager defaultManager]enumeratorAtURL:folderURL includingPropertiesForKeys:[NSArray arrayWithObject:NSURLFileSizeKey] options:nil errorHandler:nil];
	NSNumber *fileSize;
	NSNumber *isdirectory;
	NSNumber *ispackage;
	unsigned long long int cumulativeSize = 0;
	NSInteger fileCount = 0;
	
	for (NSURL *theURL in dirEnumerator) {
		[theURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:NULL];
		[theURL getResourceValue:&isdirectory forKey:NSURLIsDirectoryKey error:NULL];
		[theURL getResourceValue:&ispackage forKey:NSURLIsPackageKey error:NULL];
		cumulativeSize += [fileSize unsignedLongLongValue];
		if (isdirectory != 0) {
			fileCount += 1;
		}
	}
	NSLog(@"Total count of files: %i", fileCount);
	NSLog(@"Total size of folder: %llu", cumulativeSize);
	NSNumber *cumulativeSizeAsNumber = [NSNumber numberWithUnsignedLongLong:cumulativeSize];
	NSNumber *fileCountAsNumber = [NSNumber numberWithInteger:fileCount];
	NSArray *infoToReturnAsList = [NSArray arrayWithObjects: cumulativeSizeAsNumber, fileCountAsNumber, nil];
	return infoToReturnAsList;
}

And here’s how I call it from ASOC:

set folderSizeStringAsInteger to (FSObjCHelpers's returnFolderSizeFromURL_(URLtoAdd)) as list
-> result: (508267730, 7768)

You need to pass it an NSUrl object for this to work.

Still not the exact same size reported, but close enough for now I guess.

Model: MBPro8,2
Browser: Safari 534.48.3
Operating System: Mac OS X (10.7)

And just need a refresh: a package is automatically a directory, correct?

So in my code, i’m skipping the file count if the item is a directory, and i’m assuming that the NSDirectoryEnumerator will report files inside packages, thus be added to the cumulative size. Maybe this is where the difference in count and size comes from…

Model: MBPro8,2
Browser: Safari 534.48.3
Operating System: Mac OS X (10.7)