Scan a directory structure recursively without recursivity
Posted: Sun Aug 30, 2009 2:33 am
I've just finished converting my delphi directory structure scan function.
Here is what it look like in PB. To be able to do this I add directories in a stack directory structure.
Here is the code:
There is only one thing I've noticed. It seems having a memory leak somewhere. I've tried running the program and just by scanning my complete hard drive memory at the end were 5MB. If anyone can find why please let me know. First thing I'm thinking is that it is a bug with the FileSystem lib or there is some FinishDirectory calls missing. So far I've seen nothing wrong. I'm using PB4.31 on Ubuntu 9.04. As I know I never got this problem with the delphi program I made with this algorithm.
Here is what it look like in PB. To be able to do this I add directories in a stack directory structure.
Here is the code:
Code: Select all
;->Stack.pb
Structure TStackElement
*MptrData
*MptrNext.TStackElement
EndStructure
Structure TStack
*MptrFirst.TStackElement
EndStructure
Declare.l Stack_Init(*stack.TStack)
Declare.l Stack_Push(*stack.TStack, *ptrData)
Declare.l Stack_Pop(*stack.TStack)
Declare.l Stack_Clear(*stack.TStack)
Procedure.l Stack_Init(*stack.TStack)
If (*stack = 0) : ProcedureReturn(#False) : EndIf
*stack\MptrFirst = 0
ProcedureReturn(#True)
EndProcedure ;Procedure.l Stack_Init(*stack.TStack)
Procedure.l Stack_Push(*stack.TStack, *ptrData)
Protected *Lelement.TStackElement = 0
If(*stack = 0) : ProcedureReturn(#False) : EndIf
*Lelement = AllocateMemory(SizeOf(TStackElement))
If (*Lelement = 0) : ProcedureReturn(#False) : EndIf
*Lelement\MptrNext = *stack\MptrFirst
*Lelement\MptrData = *ptrData
*stack\MptrFirst = *Lelement
ProcedureReturn(#True)
EndProcedure ;Procedure.l Stack_Push(*stack.TStack, *ptrData)
Procedure.l Stack_Pop(*stack.TStack)
Protected *LptrFirstTemp.TStackElement = 0
If (*stack = 0) : ProcedureReturn(#False) : EndIf
If (*stack\MptrFirst = 0) : ProcedureReturn(#False) : EndIf
*LptrFirstTemp = *stack\MptrFirst
If (*LptrFirstTemp\MptrData <> 0) : FreeMemory(*LptrFirstTemp\MptrData) : EndIf
*stack\MptrFirst = *stack\MptrFirst\MptrNext
FreeMemory(*LptrFirstTemp)
ProcedureReturn(#True)
EndProcedure ;Procedure.l Stack_Pop(*stack.TStack)
Procedure.l Stack_Clear(*stack.TStack)
Protected LbooFnResult.l = #False
If (*stack = 0) : ProcedureReturn(#False) : EndIf
If (*stack\MptrFirst) : ProcedureReturn(#False) : EndIf
Repeat
LbooFnResult = Stack_Pop(*stack)
Until(*stack\MptrFirst = 0)
ProcedureReturn(#True)
EndProcedure ;Procedure.l Stack_Clear(*stack.TStack)
;->ScanDirStruct.pb
Structure TFileSysEntrie
MintDirectoryNumber.l
MdatCreated.l
MdatAccessed.l
MdatModified.l
MstrName.s
MbooIsDirectory.b
MqSize.q
MintAttributes.l
EndStructure
Structure TStructSearch
MstrPath.s
EndStructure
Prototype ProtoExecSearchHandler(*searchHandler, strPath.s, *entrieFileSys.TFileSysEntrie)
Structure TSearchHandler
Exec.ProtoExecSearchHandler
EndStructure
Enumeration
#CintGetNoEntrie
#CintGetFirstEntrie
#CintGetNextEntrie
EndEnumeration
Global GstrOSDirSep.s = ""
CompilerIf (#PB_Compiler_OS = #PB_OS_Linux) Or (#PB_Compiler_OS = #PB_OS_MacOS)
GstrOSDirSep = "/"
CompilerElse
GstrOSDirSep = "\"
CompilerEndIf
Declare.l __NameIsDotOrTwoDots(strName.s)
Declare.l __FillFileSysEntrie(intDirectoryNumber.l, *entrieFileSys.TFileSysEntrie, booNext.b = #False)
Declare.l __FindFirstFileSysEntrie(strPath.s, strFilter.s, *entrieFileSys.TFileSysEntrie)
Declare.l __FindNextFileSysEntrie(*entrieFileSys.TFileSysEntrie)
Declare.l __FindCloseFileSysEntrie(*entrieFileSys.TFileSysEntrie)
Declare.l ScanDirStruct(strPathDir.s, *searchHandler.TSearchHandler)
Procedure.l __NameIsDotOrTwoDots(strName.s)
Protected LstrDot.s = "."
Protected LstrTwoDots.s = ".."
Protected LbooIsDot.b = #False
Protected LbooIsTwoDots.b = #False
;If the name has no characters then return that the name received
;is not a "." or ".." string.
If Len(strName) = 0 : ProcedureReturn(#False) : EndIf
;If the size of the name received is the number of characters of LstrDot(which is 1) then
If Len(strName) = Len(LstrDot)
;If the name and LstrDot are the same then
;set that the name received is a "." string.
If CompareMemoryString(@strName, @LstrDot, #PB_String_NoCase, Len(LstrDot)) = #PB_String_Equal
LbooIsDot = #True
EndIf
EndIf ;If Len(strName) = Len(LstrDot)
;If the name is not a "." character then
If (LbooIsDot = #False)
;If the length of the name received is sam as the number of characeters of LstrDot(which is 2) then
If Len(strName) = Len(LstrTwoDots)
;If the name and LstrTowDots are the same then
;set that the name received is a ".." string.
If CompareMemoryString(@strName, @LstrTwoDots, #PB_String_NoCase, Len(LstrTwoDots)) = #PB_String_Equal
LbooIsTwoDots = #True
EndIf
EndIf ;If Len(strName) = Len(LTwoDots)
EndIf ;If (LbooIsDot = #False)
;Return if the name received is a "." or a ".." string.
ProcedureReturn((LbooIsDot = #True) Or (LbooIsTwoDots = #True))
EndProcedure ;Procedure.l __NameIsDotOrTwoDots(strName.s)
Procedure.l __FillFileSysEntrie(intDirectoryNumber.l, *entrieFileSys.TFileSysEntrie, booNext.b = #False)
;If the received file system entrie object is not a valid pointer
;then return that filling the system entrie object with the info
;of the current file system entrie examined failed.
If (*entrieFileSys = 0) : ProcedureReturn(#False) : EndIf
;If the received directory identify a invvalid directory then return that
;filling the file system entrie received failed.
If (IsDirectory(intDirectoryNumber) = 0) : ProcedureReturn(#False) : EndIf
;If it is specified to get the next directory entry and it is not possible to get it then
;return that filling file system entrie with the next entrie failed.
If (booNext = #True)
If (NextDirectoryEntry(intDirectoryNumber) = 0) : ProcedureReturn(#False) : EndIf
EndIf
;Assign the received directory number to the received file system entrie.
*entrieFileSys\MintDirectoryNumber = intDirectoryNumber
;Retrieve possible values of the current file system entrie and assign
;them to their respective members.
*entrieFileSys\MdatCreated = DirectoryEntryDate(intDirectoryNumber, #PB_Date_Created)
*entrieFileSys\MdatAccessed = DirectoryEntryDate(intDirectoryNumber, #PB_Date_Accessed)
*entrieFileSys\MdatModified = DirectoryEntryDate(intDirectoryNumber, #PB_Date_Modified)
*entrieFileSys\MstrName = DirectoryEntryName(intDirectoryNumber)
If (DirectoryEntryType(intDirectoryNumber) = #PB_DirectoryEntry_Directory)
*entrieFileSYs\MbooIsDirectory = #True
Else
*entrieFileSYs\MbooIsDirectory = #False
EndIf
*entrieFileSys\MqSize = DirectoryEntrySize(intDirectoryNumber)
*entrieFileSys\MintAttributes = DirectoryEntryAttributes(intDirectoryNumber)
;Return that filling the received file system entrie succeeded.
ProcedureReturn(#True)
EndProcedure ;Procedure.l __FillFileSysEntrie(intDirectoryNumber.l, *entrieFileSys.TFileSysEntrie, booNext.b =#False)
Procedure.l __FindFirstFileSysEntrie(strPath.s, strFilter.s, *entrieFileSys.TFileSysEntrie)
Protected LintDirectoryNumber.l = 0
Protected *LentrieFileSys.TFileSysEntrie = 0
Protected LbooFnResult.l = #False
;If the file system object is not a valid pointer then return
;that first entrie were not found.
If (*entrieFileSys = 0): ProcedureReturn(#False) : EndIf
;Try findinf first entrie in the received path.
LintDirectoryNumber = ExamineDirectory(#PB_Any, strPath, strFilter)
;If no entrie is found then return a invalid pointer
;as the first file system entrie found.
If (LintDirectoryNumber = 0) : ProcedureReturn(#False) : EndIf
;If the fist entrie in the directory were not found then
;close examination of the directory and return that nothing
;were found.
If (NextDirectoryEntry(LintDirectoryNumber) = 0)
FinishDirectory(LintDirectoryNumber)
ProcedureReturn(#False)
EndIf
;Retrieve information about current entrie examines by
;the opened directory list and assign them to the file
;system entrie object passed.
LbooFnResult = __FillFileSysEntrie(LintDirectoryNumber, *entrieFileSys)
;Return pointer to the first directory entrie of the directory
;list opened.
ProcedureReturn(#True)
EndProcedure ;Procedure.l __FindFirstFileSysEntrie(strPath.s, strFilter.s, *entrieFileSys.TFileSysEntrie)
Procedure.l __FindNextFileSysEntrie(*entrieFileSys.TFileSysEntrie)
;If the file system object is not a valid pointer then return
;that next entrie were not found.
If (*entrieFileSys = 0): ProcedureReturn(#False) : EndIf
;Try getting info of the next file system entrie and return
;if that were possible or not. #True is specified for booNext parameter to
;make the function call NextDirectoryEntry to move to the next file system entrie
;and get its info.
ProcedureReturn(__FillFileSysEntrie(*entrieFileSys\MintDirectoryNumber, *entrieFileSys, #True))
EndProcedure ;Procedure.l __FindNextFileSysEntrie(*entrieFileSys.TFileSysEntrie)
Procedure.l __FindCloseFileSysEntrie(*entrieFileSys.TFileSysEntrie)
;If the received file sytem entrie object is not a valid pointer then
;return that closing the directory list it contains failed.
If (*entrieFileSys = 0) : ProcedureReturn(#False) : EndIf
;If the directory number contained in the received file system entrie
;object is not valid directory list number then return that closing the directory list failed.
If (IsDirectory(*entrieFileSys\MintDirectoryNumber) = 0) : ProcedureReturn(#False) : EndIf
;Close opened directory list and clear members of the
;received file system entrie object.
FinishDirectory(*entrieFileSys\MintDirectoryNumber)
*entrieFileSys\MintDirectoryNumber = 0
*entrieFileSys\MdatCreated = 0
*entrieFileSys\MdatAccessed = 0
*entrieFileSys\MdatModified = 0
*entrieFileSys\MstrName = ""
*entrieFileSYs\MbooIsDirectory = #False
*entrieFileSys\MqSize = 0
*entrieFileSys\MintAttributes = 0
;Return that closing directory list of the received file system entrie succeeded.
ProcedureReturn(#True)
EndProcedure ;Procedure.l __FindCloseFileSysEntrie(*entrieFileSys.TFileSysEntrie)
Procedure.l ScanDirStruct(strPathDir.s, *searchHandler.TSearchHandler)
Shared GstrOSDirSep.s
Protected LstackDir.TStack
Protected LstrNewSearchDir.s = ""
Protected LentrieFileSys.TFileSysEntrie
Protected LstrPath.s = strPathDir
Protected LintAction.c = #CintGetNoEntrie
Protected LbooIsDirScanned.b = #False
Protected *LsearchElement.TStructSearch = 0
Protected LbooFnResult.l = #False
;If the received search handler is not a valid pointer then
;return tha scanning directory structure failed.
If (*searchHandler = 0) : ProcedureReturn(#False) : EndIf
;If the exec funtion of the search handler is not set then
;return that scanning directory structure failed.
If (*searchHandler\Exec = 0) : ProcedureReturn(#False) : EndIf
;Init the stack. This set the pointer *MptrFirst to 0.
LbooFnResult = Stack_Init(@LstackDir)
;If the first entrie of the received path can't be found then
;free memory allocated for the file system entrie and return
;that scanning directory structure failed.
If (__FindFirstFileSysEntrie(LstrPath, "*.*", @LentrieFileSys) = #False)
ProcedureReturn(#False)
EndIf
;If the first entrie found is a directory and is not "." or ".." then
If (LentrieFileSys\MbooIsDirectory = #True) And (__NameIsDotOrTwoDots(LentrieFileSys\MstrName) = #False)
;Add the current path to the stack.
*LsearchElement = AllocateMemory(SizeOf(TStructSearch))
*LsearchElement\MstrPath = LstrPath + LentrieFileSys\MstrName + GstrOSDirSep
LbooFnResult = Stack_Push(@LstackDir, *LsearchElement)
;Execute the Exec function of the search handler by passing it
;the path and a pointer to the file system entrie found.
*searchHandler\Exec(*searchHandler, LstrPath, @LentrieFileSys)
Else ;Else ;If (LentrieFileSys\MbooIsDirectory = #True) And (__NameIsDotOrTwoDots(LentrieFileSys\MstrName) = #False)
;If the file system entrie is not "." or ".." then execute the Exec function
;of the search handler by passing it the path and a pointer to the file system
;entrie found.
If (__NameIsDotOrTwoDots(LentrieFileSys\MstrName) = #False)
*searchHandler\Exec(*searchHandler, LstrPath, @LentrieFileSys)
EndIf
EndIf ;Else ;If (LentrieFileSys\MbooIsDirectory = #True) And (__NameIsDotOrTwoDots(LentrieFileSys\MstrName) = #False)
;Set to get the next file system entrie.
LintAction = #CintGetNextEntrie
;Scan until LintAction is equal to #CintGetNoEntrie
Repeat
;If FindFirst call needed then
If (LintAction = #CintGetFirstEntrie)
;If there is no entrie inside the directory then
If (__FindFirstFileSysEntrie(LstrPath, "*.*", @LentrieFileSys) = #False)
;Deallocate previous memory(by FindFirst Or FindNext).
LbooFnResult = __FindCloseFileSysEntrie(@LentrieFileSys)
;Get the first directory of the serarch stack And
;remove it from the stack.
If (LstackDir\MptrFirst <> 0)
;Get the saved path.
*LsearchElement = LstackDir\MptrFirst\MptrData
LstrPath = *LsearchElement\MstrPath
LbooFnResult = Stack_Pop(@LstackDir)
*LsearchElement = 0
;The stack serve only For directories, so
;we need an other call of FindFirst().
LintAction = #CintGetFirstEntrie
Else ;Else ;If (LstackDir\MptrFirst <> 0)
;All files and directory are scanned, so
;it is finished.
LintAction = #CintGetNoEntrie
LbooIsDirScanned = #True
EndIf ;Else ;If (LstackDir\MptrFirst <> 0)
Else ;Else ;If (__FindFirstFileSysEntrie(LstrPath, "*.*", @LentrieFileSys) = #False)
;Check current entrie is a directory but Not '.' or '..'
;because '.' go to the begining of the disk and '..' go
;to parent directory.
If (LentrieFileSys\MbooIsDirectory = #True) And (__NameIsDotOrTwoDots(LentrieFileSys\MstrName) = #False)
;Add the current path to the stack.
*LsearchElement = AllocateMemory(SizeOf(TStructSearch))
*LsearchElement\MstrPath = LstrPath + LentrieFileSys\MstrName + GstrOSDirSep
LbooFnResult = Stack_Push(@LstackDir, *LsearchElement)
;Execute the Exec function of the search handler by passing it
;the path and a pointer to the file system entrie found.
*searchHandler\Exec(*searchHandler, LstrPath, @LentrieFileSys)
Else ;Else ;If (LentrieFileSys\MbooIsDirectory = #True) And (__NameIsDotOrTwoDots(LentrieFileSys\MstrName) = #False)
;If the file system entrie is not "." or ".." then execute the Exec function
;of the search handler by passing it the path and a pointer to the file system
;entrie found.
If (__NameIsDotOrTwoDots(LentrieFileSys\MstrName) = #False)
*searchHandler\Exec(*searchHandler, LstrPath, @LentrieFileSys)
EndIf
EndIf ;Else ;If (LentrieFileSys\MbooIsDirectory = #True) (__NameIsDotOrTwoDots(LentrieFileSys\MstrName) = #False)
;Set to get the next file system entrie.
LintAction = #CintGetNextEntrie
EndIf ;Else ;If (__FindFirstFileSysEntrie(LstrPath, "*.*", @LentrieFileSys) = #False)
Else ;Else ;If (intAction = #CintGetFirstEntrie)
;If a FindNext call is needed then
If (LintAction = #CintGetNextEntrie)
;If we can find a entrie in the directory then
If (__FindNextFileSysEntrie(@LentrieFileSys) = #True)
;Check current entrie is a directory but Not '.' or '..'
;because '.' go to the begining of the disk and '..' go
;to parent directory.
If (LentrieFileSys\MbooIsDirectory = #True) And (__NameIsDotOrTwoDots(LentrieFileSys\MstrName) = #False)
;Add the current path to the stack.
*LsearchElement = AllocateMemory(SizeOf(TStructSearch))
*LsearchElement\MstrPath = LstrPath + LentrieFileSys\MstrName + GstrOSDirSep
LbooFnResult = Stack_Push(@LstackDir, *LsearchElement)
;Execute the Exec function of the search handler by passing it
;the path and a pointer to the file system entrie found.
*searchHandler\Exec(*searchHandler, LstrPath, @LentrieFileSys)
Else ;Else ;If (LentrieFileSys\MbooIsDirectory = #True) And (__NameIsDotOrTwoDots(LentrieFileSys\MstrName) = #False)
;If the file system entrie is not "." or ".." then execute the Exec function
;of the search handler by passing it the path and a pointer to the file system
;entrie found.
If (__NameIsDotOrTwoDots(LentrieFileSys\MstrName) = #False)
*searchHandler\Exec(*searchHandler, LstrPath, @LentrieFileSys)
EndIf
EndIf ;Else ;If (LentrieFileSys\MbooIsDirectory = #True) (__NameIsDotOrTwoDots(LentrieFileSys\MstrName) = #False)
;Set to get the next file system entrie.
LintAction = #CintGetNextEntrie
Else ;Else ;If (__FindFirstFileSysEntrie(LstrPath, "*.*", @LentrieFileSys) = #False)
;Deallocate previous memory(by FindFirst Or FindNext).
LbooFnResult = __FindCloseFileSysEntrie(@LentrieFileSys)
;Get the first directory of the serarch stack And
;remove it from the stack.
If (LstackDir\MptrFirst <> 0)
;Get the saved path.
*LsearchElement = LstackDir\MptrFirst\MptrData
LstrPath = *LsearchElement\MstrPath
LbooFnResult = Stack_Pop(@LstackDir)
*LsearchElement = 0
;The stack serve only For directories, so
;we need an other call of FindFirst().
LintAction = #CintGetFirstEntrie
Else ;Else ;If (LstackDir\MptrFirst <> 0)
;All files and directory are scanned, so
;it is finished.
LintAction = #CintGetNoEntrie
LbooIsDirScanned = #True
EndIf ;Else ;If (LstackDir\MptrFirst <> 0)
EndIf ;Else ;If (__FindNextFileSysEntrie(@LentrieFileSys)
EndIf ;If (LintAction = #CintGetNextEntrie)
EndIf ;Else ;If (intAction = #CintGetFirstEntrie)
Until (LintAction = #CintGetNoEntrie) ;Repeat
;Return if the directory strucutre were scanned or not.
ProcedureReturn(LbooIsDirScanned)
EndProcedure ;Procedure.l ScanDirStruct(strPathDir.s, *searchHandler.TSearchHandler)
Global Gtest.TSearchHandler
Procedure Test(*searchHandler.TSearchHandler, strPath.s, *entrieFileSys.TFileSysEntrie)
Delay(1)
;If (*entrieFileSys\MbooIsDirectory = #False)
; PrintN(strPath + *entrieFileSys\MstrName)
;EndIf
EndProcedure
Gtest\Exec = @Test()
OpenConsole()
PrintN("[PRESS ESC]")
Repeat
Delay(1)
Until Inkey() = Chr(27)
ScanDirStruct(GstrOSDirSep + "usr" + GstrOSDirSep, @Gtest)
PrintN("[SCANNING FINISHED]")
PrintN("[PRESS ESC]")
Repeat
Delay(1)
Until Inkey() = Chr(27)