tags:

views:

316

answers:

5

How can I determine whether a certain path points to a hidden file/folder?

NSString *file = @"/my/file/some.where";
BOOL fileIsHidden = // <-- what do I do here?

I know that hidden files are prefixed by a period. This is not the only criteria for a file to be hidden. I've read somewhere that there's a .hidden file that also configures what files are hidden.

Is there a Cocoa/Carbon way to find this out easily without rewriting all this logic and gathering information from various sources?

EDIT: the kLSItemInfoIsInvisible check seems to work for some files. It doesn't seem to hide:

/dev
/etc
/tmp
/var

All these are hidden by Finder by default.

+3  A: 

From http://forums.macosxhints.com/archive/index.php/t-22641.html:

BOOL isInvisibleCFURL(CFURLRef inURL)
{
  LSItemInfoRecord itemInfo;
  LSCopyItemInfoForURL(inURL, kLSRequestAllFlags, &itemInfo);

  BOOL isInvisible = itemInfo.flags & kLSItemInfoIsInvisible;
  return isInvisible;
}

Update

Aha! /etc, /tmp, and /var are all invisible because they're actually symlinks to /private/etc, /private/tmp, and /private/var. If you tell Finder to directly visit /private (using the Go To Folder menu item), you'll see that they show up just fine. (Thanks to @IlDan for the tip)

I'm not sure what the best way to deal with this is; it only matters if you have a visible symlink to a file inside a hidden folder. You could probably get away with just manually excluding symlinks that go into /private, but if now, you might have to check the hidden status of every folder on the way up the path.

BJ Homer
Re: /dev, /etc, et al., I'm looking into it, but haven't found anything yet.
BJ Homer
I've been able to find nothing about those root-level files; I'm beginning to wonder if they're hard-coded. Not very elegant, but I can't find anything.
BJ Homer
Found it! See my update
BJ Homer
I added the BOOL isFile parameter, and it seems to work great now!Check my comment.
micmoo
+2  A: 

As far as I know, hidden files on OS X are determined by either the filename being prefixed with a period or by a special "invisible" bit that is tracked by the Finder.

A few years back, I had to write something that toggled the visibility of a given file, and I found it was actually a lot more complicated than I expected. The crux of it was obtaining a Finder info (FInfo) record for the file and checking if the kIsInvisible bit was set. Here's the method I wrote for toggling file visibility—I think a lot of it is relevant to your task at hand, although you'll obviously have to tweak it a bit.

- (BOOL)toggleVisibilityForFile:(NSString *)filename isDirectory:(BOOL)isDirectory
{
    // Convert the pathname to HFS+
    FSRef fsRef;
    CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (CFStringRef)filename, kCFURLPOSIXPathStyle, isDirectory);

    if (!url)
    {
     NSLog(@"Error creating CFURL for %@.", filename);
     return NO;
    }

    if (!CFURLGetFSRef(url, &fsRef))
    {
     NSLog(@"Error creating FSRef for %@.", filename);
     CFRelease(url);
     return NO;
    }

    CFRelease(url);

    // Get the file's catalog info
    FSCatalogInfo *catalogInfo = (FSCatalogInfo *)malloc(sizeof(FSCatalogInfo));
    OSErr err = FSGetCatalogInfo(&fsRef, kFSCatInfoFinderInfo, catalogInfo, NULL, NULL, NULL);

    if (err != noErr)
    {
     NSLog(@"Error getting catalog info for %@. The error returned was: %d", filename, err);
     free(catalogInfo);
     return NO;
    }

    // Extract the Finder info from the FSRef's catalog info
    FInfo *info = (FInfo *)(&catalogInfo->finderInfo[0]);

    // Toggle the invisibility flag
    if (info->fdFlags & kIsInvisible)
     info->fdFlags &= ~kIsInvisible;
    else
     info->fdFlags |= kIsInvisible;

    // Update the file's visibility
    err = FSSetCatalogInfo(&fsRef, kFSCatInfoFinderInfo, catalogInfo);

    if (err != noErr)
    {
     NSLog(@"Error setting visibility bit for %@. The error returned was: %d", filename, err);
     free(catalogInfo);
     return NO;
    }

    free(catalogInfo);
    return YES;
}

Here's Apple's documentation on the Finder Interface, if you want more information. Hope this helps.

htw
`FInfo` is obsoleted by `FileInfo`. Likewise, `FXInfo` by `ExtendedFileInfo`, `DInfo` by `FolderInfo`, and `DXInfo` by `ExtendedFolderInfo`.
Peter Hosey
Yeah, this is pretty old code. I believe I wrote it at least three or four years ago. Nevertheless, thanks for the info.
htw
+1  A: 

Philosophical bit first:

No file is actually hidden. The Finder maintains its own internal data to determine if a file should be displayed in a directory listing or not; and this information may be shared with the other applications on the system.

Still, unless you are implementing a file system browser, the relevant determinations are usually transparently taken care of (no pun intended) by the internal workings of NSOpenPanel and friends.

If you are accessing a file programatically, and you maintain some semblance of ownership over the same file, or you are not displaying the file (or not) in the UI, it really doesn't matter if Finder considers it hidden or not.

As for the technical bit; since any application is able (through the aforementioned and venerable NSOpenPanel) to access this information, it's probably available somewhere; but as has been pointed out, this requires a rather circuitous little delve into CoreFoundation and LaunchServices.

The real issue is probably wether you need to know.

Williham Totland
I am creating something similar to a file browser (directory diff to be precise). I would like to not show the files that are generally hidden by Finder or NSOpenPanel.
rein
+2  A: 

As the poster pointed out, it doesn't seem to work on /etc and /var and what not, so I modified the method.

It now takes a "isFile" boolean, YES means its a file, NO means a directory

BOOL isInvisible(NSString *str, BOOL isFile){
        CFURLRef inURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (CFStringRef)str, kCFURLPOSIXPathStyle, isFile);
        LSItemInfoRecord itemInfo;
        LSCopyItemInfoForURL(inURL, kLSRequestAllFlags, &itemInfo);

        BOOL isInvisible = itemInfo.flags & kLSItemInfoIsInvisible;
        return (isInvisible != 0);
    }

    int main(){
           NSLog(@"%d",isInvisible(@"/etc",NO)); // => 1
           NSLog(@"%d",isInvisible(@"/Users",NO)); // => 0
           NSLog(@"%d",isInvisible(@"/mach_kernel",YES)); // => 1

    }

It seems to work on everything now!

micmoo
Hmm.. this works great but doesn't seem to hide /etc, /tmp, /var -- all of which are hidden by Finder.
rein
I added the BOOL isFile parameter, and it seems to work great now!
micmoo
Thanks. Soooo close! I still see /home, /net and /dev in my list but not in Finder.
rein
micmoo
+2  A: 

I think the point is that the Finder is the frontend towards the user of the filesystem tree(s). You want to ask the Finder if he thinks that a file is hidden or not, so you need an API to do that.

It seems that LSCopyItemInfoForURL does the work, as shown in the other answers. This post is very useful:

There are a few ways for things to be considered invisible (under Mac OS X):

  • kLSItemInfoIsInvisible Finder flag is set
  • filename begins with period
  • listing in the /.hidden file
  • invisible due to parentage
  • invisible due to package

I'm not copying it all, it's long but well written.

IlDan