How to distinguish a folder from a File package?

I’m using a recursive code to explore down the file system. I’m using FolderItem.isFolder to decide whether to recurse. It works, but it is not just recursing into real folders, but also into file packages (“files” like Keynote .key and .rtfd files). Is there a way that I can get my code to tell which is which and to treat a package like a file rather than a folder?

Thanks,

Ian.

Any valid package will contain a Contents folder and inside that an Info.plist file. You could create an isPackage method to detect that. Something along the lines of:

// Put the method in a Module
Function isPackage( Extends oFolder as FolderItem ) as Boolean
   Var Contents as FolderItem = oFolder.Child( "Contents" )
   If Contents = Nil Then Return False
   If Not Contents.Exists Then Return False
   If not Contents.isFolder Then Return False
   Var Infoplist as FolderItem = Contents.Child( "Info.plist" )
   If Infoplist = Nil Then Return False
   If Not Infoplist.Exists Then Return False
   If Infoplist.isFolder Then Return False
   If not Infoplist.Exists Then Return False
   Return True
End Function

// Use it like:
Var oFolderItem As New FolderItem( "/Applications/Pages.app", FolderItem.PathModes.Shell )
MessageBox CStr( oFolderItem.isPackage )
1 Like

Thanks. That makes sense. I will give it a try.
It seems odd that for something so pervasive on a Mac as packages there is not a built-in feature (say, a FolderItem property) to do this.

There is for macOS but not for Xojo. You can use declares or MBS to find out for real if the target is a bundle.

2 Likes

Maybe our Folderitem.isBundleMBS function tells you.

You may find bundles, that don’t have a contents folder.

2 Likes

Interesting, I presume the Info.plist would be at the top level?

I see iOS has ones without Contents.

Sorry, there is no need for info.plist.

If you want to have the same behavior as Finder, you need to ask the OS via API. We have a couple of functions in the plugin which use various APIs to do this.

And technically you could mark any folder as package with some file attributes.

1 Like

I’m thinking of Bundles. Which need a Bundle ID etc in an Info.plist.

There’s a system API to create bundles. Perhaps you ask an AI assistant for guidance instead of hacking something?

There was not request to create a bundle. Just to identify when you have found one on disk. So as to not iterate inside it.

Here’s the Declare you need on the Mac:

This is implemented in MacOSLib, if anybody other than me still uses that. :slight_smile:

And this is the difference from a package, which this thread is about :wink:.

1 Like

I asked long time ago the same question and someone gave me the answer.
MyFlag = MyFolderItem.IsPackage_f

Public Function IsPackage_f(Extends CetElt as FolderItem) As Boolean
  
  Dim DrapPackage as Boolean ' Retourne Vrai si  CetElt  est un Package (.app .rtfd . pkg .mpkg etc.) et Faux sinon. Note : J'utilise  Extends  pour pouvoir écrire  MonElt.IsPackage_f
  
  #If DebugBuild Then ' Si  CetElt  n'existe pas ça plantera, doit être testé avant
    If not CetElt.IsFolder Then MessageBox "Problème Tom, IsPackage_f , tu ne devrais (dois ?) y tester que des dossiers !"
  #EndIf
  
  #If TargetMacOS Then
    ' #If RBVersion < 2013 Then ' RealStudio donc 10.5 (et +) ' If TargetCarbon Then
    ' Declare Function LSCopyItemInfoForRef lib "ApplicationServices" (ref as Ptr, whichInfo as Integer, info as Ptr) as Integer
    ' Dim err as Integer
    ' Dim rec as New MemoryBlock(24)
    ' 
    ' err = LSCopyItemInfoForRef(CetElt.FSRef_f, &h4, rec)
    ' DrapPackage = not(Bitwise.BitAnd(rec.Long(0), 2) = 0)
    ' 
    ' #Else ' Xojo donc 10.6 et + ' #If TargetCocoa Then
    Declare Function boolValue Lib "Foundation" selector "boolValue" ( obj as ptr ) as Boolean
    Dim isPackage as ptr
    
    If GetFileValue(CetElt, "NSURLIsPackageKey", isPackage) Then
      DrapPackage = boolValue(isPackage)
    Else
      ' At this point, isPackage isa NSError. Do something with it or just assume that it's not a package.
      DrapPackage = False
    End If
    
    ' #EndIf ' Xojo ou RealStudio
    
  #Else ' Windows , Linux
    DrapPackage = False
    
  #EndIf
  
  ' Méthose Commande Shell
  '#If TargetMacOS Then
  'Dim CdeShell as New Shell
  '' NE PAS utiliser  "'" + MonFolderItem.NativePath = MonFolderItem.ShellPath_fAS + "'"
  '' NON : CdeShell.Execute "mdls -name kMDItemContentTypeTree '" + CetElt.ShellPath + "'"
  '' On n'utilise pas le Quotedform avec ' ' car s'il y a un ' dans le nom ça merde
  '' CdeShell.Execute "mdls " + CetElt.ShellPath
  '' MessageBox CdeShell.Result
  '' Avant : CdeShell.Execute "mdls -name kMDItemContentType -raw " + CetElt.ShellPath
  'CdeShell.Execute "mdls -name kMDItemContentTypeTree " + CetElt.ShellPath
  'If CdeShell.ErrorCode = 0 Then
  '' MessageBox CdeShell.Result
  'DrapPackage = not(CdeShell.Result.IndexOf("com.apple.package", ComparisonOptions.CaseInsensitive, AlocaleProg) = -1)
  '' Avant : DrapPackage = not((CdeShell.Result = "public.folder") or (CdeShell.Result = "public.volume"))
  'Else ' Dossier sur lequel on n'a pas les droits ou etc.
  'DrapPackage = False
  '' MessageBox "Error code : " + Str(CdeShell.ErrorCode)
  'End If
  'CdeShell.Close
  '
  '' Avant avec AppleScript mais c'est plus lent :
  '' DrapPackage = (IsPackage_s(CetElt.NativePath = CetElt.ShellPath_fAS) = "True") ' Méthode AppleScript plus lente que méthode avec Carbon
  '
  '#Else
  'DrapPackage = False
  '
  '#EndIf
  
  Return DrapPackage
  
End Function

I didn’t erase all the comment I wrote. You must test before if you FolderItem is a folder, and if yes test if it is a package.

Looks better to me.

Yes, thanks, FolderItem.isBundleMBS does the trick. BTW I like the overridden “S” method (with different behaviours for different parameters) in the File information example; I hadn’t thought about doing that before.

1 Like

Yep. I’m happily using it.