Thursday, 23 January 2014

Scan / Search directory and sub directories using Mapbasic and Windows API

In this post I will demonstrate how to scan a directory and sub-directories and return a list of files.

I have coded the function GetFileList that you can copy into your project (make sure you also copy the required delarations, type, defines ect...)

Function GetFileList(ByVal strFilePath as String,
                     ByVal strFileFilter as String,
                     strFileList() as String
                     ) as Logical

All you have to do is call the funtion and parse a starting directory strFilePath (Note: make sure you have a trailing slash), a filter strFileFilter (eg "*.tab", "*.*") and a string array to accept the string file directories.

You can download a copy of the source code here DirectoryScan.mb

Include "MAPBASIC.def"

Type FILETIME
   dwLowDateTime As Integer
   dwHighDateTime As Integer
End Type

Type
WIN32_FIND_DATA
   dwFileAttributes As Integer
   ftCreationTime As FILETIME   ftLastAccessTime As FILETIME
   ftLastWriteTime As FILETIME
   nFileSizeHigh As Integer
   nFileSizeLow As Integer
   dwReserved0 As Integer
   dwReserved1 As Integer
   cFileName As String * 260   cAlternate As String * 14
End Type

Define
FILE_ATTRIBUTE_DIRECTORY 16

Declare Sub Main()

Declare Function GetFileList(ByVal strFilePath as String, ByVal strFileFilter as String, strFileList() as String) as Logical

Declare Function FindFirstFile Lib "kernel32" Alias "FindFirstFileA" (ByVal lpFileName As String, lpFindFileData As WIN32_FIND_DATA) As Integer

Declare Function FindNextFile Lib "kernel32" Alias "FindNextFileA" (ByVal hFindFile As Integer, lpFindFileData As WIN32_FIND_DATA) As Integer

Declare Function FindClose Lib "kernel32" (ByVal hFindFile As Integer) As Integer
'------------------------------------------------------------------------------------------------
Sub Main()
OnError Goto CatchEx

   Dim bResult As Logical
   Dim strDir As String
   Dim saFileNames() as String
   Dim i as integer
  
   Print chr$(12)
  
   strDir = "C:\Temp\"

   bResult = GetFileList(strDir, ".tab" , saFileNames())
  
   If bResult = True Then
     
      For i = 1 to UBound(saFileNames())
         Print i & ": " & saFileNames(i)
      Next

      Note "Total files found: " & UBound(saFileNames())
  
   Else
      Note strDir & " Doesn't Exist"
   End If

Done:
   Exit Sub

CatchEx:
   Note Error$()
   Resume Done
End Sub
'------------------------------------------------------------------------------------------------
Function GetFileList(ByVal strFilePath as String, ByVal strFileFilter as String, strFileList() as String) as Logical
OnError Goto CatchEx

   Dim hFind As Integer
   Dim wfd As WIN32_FIND_DATA Dim strFileName as String

   Dim iReturn as Integer
   iReturn = 1
  
   Dim i as Integer
   i = 1

   Dim strSubDirFileList() as String

   Dim j as Integer

   hFind = FindFirstFile(strFilePath & "*.*", wfd)

   strFileName = LTrim$(RTrim$(wfd.cFileName))
  
   If Len(strFileName) > 0 Then

      Do While iReturn <> 0

         If strFileName = "." or strFileName = ".." then
            iReturn = FindNextFile(hFind, wfd)strFileName = LTrim$(RTrim$(wfd.cFileName))
         Else
         strFileName = LTrim$(RTrim$(wfd.cFileName))
            If wfd.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY then
               iReturn = GetFileList(strFilePath & strFileName & "\", strFileFilter, strSubDirFileList())
               For j = 1 to Ubound(strSubDirFileList)
                  ReDim strFileList(i)
                  strFileList(i) = strSubDirFileList(j)
                  i = i + 1
               Next
              iReturn = FindNextFile(hFind, wfd)
            Else
               If Right$(strFileName, Len(strFileFilter)) = strFileFilter then
                  ReDim strFileList(i)strFileList(i)= strFilePath & strFileName
                  i = i + 1
               End If
               iReturn = FindNextFile(hFind, wfd)
            End If
         End If
      Loop
   End If

   iReturn = FindClose(hFind)
   GetFileList = true

Done:
   Exit Sub
CatchEx:
   Note Error$()
   Resume Done
End Function
'------------------------------------------------------------------------------------------------

5 comments:

  1. Hi James
    Nice blog with some interesting topics.
    In the example above I notice that you aren't applying the variable strFileFilter to this statement:
    hFind = FindFirstFile(strFilePath & "*.*", wfd)

    But you are using it further down to compare the name of the found file to the strFileFilter:
    If Right$(strFileName, Len(strFileFilter)) = strFileFilter then

    Wouldn't it be more efficient to let Windows filter the list of files:
    hFind = FindFirstFile(strFilePath & strFileFilter, wfd)
    and then skip the If statement further down?

    Thanks
    Peter

    ReplyDelete
  2. Hi Peter,

    Yes that is true! I am assuming that if I filter first, I should only have to iterate through the filtered list? When I get a chance I will have a play and refactor the function and modify the post.

    Cheers
    James

    ReplyDelete
  3. I've used a batch file, running in DOS prompt, to create a txt file.
    this code in the batch file (LC.bat - saved in same location as MB app):
    c: && CD C:\ && CD C:\My Workflow Data\GISdata\DataIn\ && DIR *.TAB /a /b /oE >tablist.txt
    this code in the MapBasic App:
    Run Program "U:\shared\GIS_Data\MapBasics\Jon working\FastMap\LC.BAT"

    more efficient for just one folder.

    ReplyDelete
  4. Hi Jonathan

    Yes that may be the case, but it is un-managed code and it is harder to detect when something has gone wrong! I would prefer to have a list inside my mbx, as a variable, than have a separate text file that I have to open and iterate through.

    There are some efficiency that you can gain by using Peters suggestion above.

    Cheers

    James

    ReplyDelete
  5. Great help on my project but I did make Peter's suggested modifications...

    ReplyDelete