Getting file and directory information

This page explains how to find the size (in bytes) of a certain file, the long and short filenames, the date and time a file was last modified. It also shows a way to get a directory listing.
This is based on a rather large procedure library, file-api.p and file-api.i, which are available in everything.zip in page windows.i and hpApi

Using the library

To use any of the functions in library file-api.p simply include {file-api.i} in the definitions section of your program. This will define some constants you might want to use and it runs file-api.p persistent in handle hpFileApi.

Organisation of the library

The library file-api.p defines a couple of API functions but you will probably not run these directly. Instead, you will probably run one of the 4GL internal procedures in there.
The procedures work with a memptr to a WIN32_FIND_DATA structure. So the procedures will either have an output parameter or an input parameter of this type. You don't have to allocate or fill this structure or fetch information from it; this is all done by the procedures themselves.

File Information

Here's an example of getting information about file "c:\autoexec.bat" :

{file-api.i}
 
DEFINE VARIABLE lpFindData AS MEMPTR.
 
DEFINE VARIABLE longname AS CHARACTER.
DEFINE VARIABLE shortname AS CHARACTER.
DEFINE VARIABLE SIZE AS INTEGER.
DEFINE VARIABLE chDate AS DATE.
DEFINE VARIABLE chTime AS INTEGER.
 
/* get a lpFindData structure */
RUN FileFind IN hpFileApi ("c:\autoexec.bat", OUTPUT lpFindData).
 
/* read information from the lpFileInfo structure */
RUN FileInfo_LongName  IN hpFileApi(lpFindData, OUTPUT longname).
RUN FileInfo_ShortName IN hpFileApi(lpFindData, OUTPUT shortname).
RUN FileInfo_Size      IN hpFileApi(lpFindData, OUTPUT SIZE).
RUN FileInfo_LastWrite IN hpFileApi(lpFindData, OUTPUT chDate, OUTPUT chTime).
 
MESSAGE 
 "name="       longname SKIP
 "short name=" shortname SKIP
 "modified="   chDate STRING(chTime,"hh:mm:ss") SKIP
 "size="       SIZE " bytes"
 VIEW-AS ALERT-BOX.

Directory listing

The general idea is: find the first file in the directory and repeat to find the next file until no more files are found. This loop is performed by the procedure FileFindLoop in file-api.p. Whenever it finds a file, it runs an internal procedure you specified with an lpFindData pointer as input parameter. Example:

{file-api.i}
 
RUN FileFindLoop IN hpFileApi ("d:\progress\*.p",      /* mask, must contain wildcards */
                               "ProcessOneFile",       /* name of callback procedure   */
                               THIS-PROCEDURE:HANDLE). /* location of callback proc    */
 
PROCEDURE ProcessOneFile :
  DEFINE INPUT PARAMETER lpFindData AS MEMPTR.
 
  /* do whatever you like here, for example show the file name
     if modified within last 3 days */
  DEFINE VARIABLE longname AS CHARACTER NO-UNDO.
  DEFINE VARIABLE chDate AS DATE NO-UNDO.
  DEFINE VARIABLE chTime AS INTEGER NO-UNDO.
  RUN FileInfo_LongName  IN hpFileApi(lpFindData, OUTPUT longname).
  RUN FileInfo_LastWrite IN hpFileApi(lpFindData, OUTPUT chDate, OUTPUT chTime).
  IF chDate> TODAY - 3 THEN 
     MESSAGE longname VIEW-AS ALERT-BOX.
 
END PROCEDURE.

Testing file attributes

The callback procedure in the above example will be called for every file that meets the mask, these can include files or (sub)directories. Often you will want to show only directories, or only files, or only files that have the Archive-bit set, or skip all hidden and system files. Whatever. In those cases you will need to test the file attributes during the callback function.
File attributes are one DWORD where each bit represents one certain attribute. The attributes are stored in the first element of the lpFindData structure, the meaning of the different bits is listed in file-api.i for your convenience. To test for the presence of a certain bit you must use binary logic (the AND), covered on page "Bitwise operators using ProExtra.DLL".
So if you want to make sure if a file is actually a directory, you would include a test like this:

{file-api.i}
 
PROCEDURE ProcessOneFile :
  DEFINE INPUT PARAMETER lpFindData AS MEMPTR.
 
  DEFINE VARIABLE attribs AS INTEGER NO-UNDO.
  DEFINE VARIABLE RESULT  AS INTEGER NO-UNDO.
  attribs = GET-LONG(lpFindData,1).
  RUN Bit_And IN hpExtra(attribs, {&DDL_DIRECTORY}, OUTPUT RESULT).
  IF result NE 0 THEN DO:
     /* whatever you want to do with a directory */
  END.
  ELSE DO:
     /* whatever you want to do with a file */
  END.
 
END PROCEDURE.

Note that flag DDL_READWRITE has value 0. This is a brain teaser: it can't be set, so you can't test for its presence! The definition says: a file is READWRITE unless any other bit is set. In other words: you don't use Bit_And() to test if attribs contains DDL_READWRITE, but you simply test if attribs=DDL_READWRITE.