SCANDIR.C


/******************************************************************************\
* This is a part of the Microsoft Source Code Samples.
* Copyright 1993 - 1998 Microsoft Corporation.
* All rights reserved.
* This source code is only intended as a supplement to
* Microsoft Development Tools and/or WinHelp documentation.
* See these sources for detailed information regarding the
* Microsoft samples programs.
\******************************************************************************/

/****************************** Module Header *******************************
* Module Name: SCANDIR.C
*
* Scan a directory tree and build a sorted list of filenames within that
* tree.
*
* Functions:
*
* dir_buildlist()
* dir_delete()
* dir_isfile()
* dir_firstitem()
* dir_nextitem()
* dir_findnextfile()
* dir_getrelname()
* dir_getfullname()
* dir_getroot_list()
* dir_getroot_item()
* dir_freerelname()
* dir_freefullname()
* dir_freeroot_list()
* dir_freerootitem()
* dir_getopenname()
* dir_freeopenname()
* dir_openfile()
* dir_closefile()
* dir_filesize()
* dir_startcopy()
* dir_endcopy()
* dir_copy()
* dir_finalelem()
* dir_cleardirect()
* dir_adddirect()
* dir_addfile()
* dir_scan()
* dir_isvaliddir()
* dir_isvalidfile()
* dir_fileinit()
* dir_dirinit()
* dir_getpathsize()
* dir_findnextfile()
*
* Comments:
*
* The call dir_buildlist takes a pathname and returns a handle. Subsequent
* calls to dir_firstitem and dir_nextitem return handles to
* items within the list, from which you can get the name of the
* file (relative to the original pathname, or complete), and filesize.
*
* The list can be either built entirely during the build call, or
* built one directory at a time as required by dir_nextitem calls. This
* option affects only relative performance, and is taken as a
* recommendation only (ie some of the time we will ignore the flag).
*
* The list is ordered alphabetically (case-insensitive using lstrcmpi).
* within any one directory, we list filenames before going on
* to subdirectory contents.
*
* All memory is allocated from a gmem_* heap hHeap declared
* and initialised elsewhere.
*
* The caller gets handles to two things: a DIRLIST, representing the
* entire list of filenames, and a DIRITEM: one item within the list.
*
* From the DIRITEM he can get the filename (including or excluding the
* tree root passed to dir_build*) - and also he can get to the next
* DIRITEM.
*
* We permit lazy building of the tree (usually so the caller can keep
* the user-interface up-to-date as we go along). In this case,
* we need to store information about how far we have scanned and
* what is next to do. We need to scan an entire directory at a time and then
* sort it so we can return files in the correct order.
*
* We scan an entire directory and store it in a DIRECT struct. This contains
* a list of DIRITEMs for the files in the current directory, and a list of
* DIRECTs for the subdirectories (possible un-scanned).
*
* dir_nextitem will use the list functions to get the next DIRITEM on the list.
* When the end of the list is reached, it will use the backpointer back to the
* DIRECT struct to find the next directory to scan.
*
****************************************************************************/

#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <dos.h>
#include <direct.h>

#include "gutils.h"
#include "list.h"
#include "scandir.h"
#include "windiff.h"
#include "wdiffrc.h"

/*
* Hold name and information about a given file (one ITEM in a DIRectory)
* caller's DIRITEM handle is a pointer to one of these structures
*/
struct diritem {
LPSTR name; /* ptr to filename (final element only) */
long size; /* filesize */
struct direct FAR * direct; /* containing directory */
LPSTR localname; /* name of temp copy of file */
BOOL bLocalIsTemp; /* true if localname is tempfile.
*/
};


/* DIRECT: Hold state about directory and current position in list of filenames.
*/
typedef struct direct {
LPSTR relname; /* name of dir relative to DIRLIST root */
DIRLIST head; /* back ptr (to get fullname) */
struct direct FAR * parent; /* parent directory (NULL if above tree root)*/

BOOL bScanned; /* TRUE if scanned */
LIST diritems; /* list of DIRITEMs for files in cur. dir */
LIST directs; /* list of DIRECTs for child dirs */

int pos; /* where are we begin, files, dirs */
struct direct FAR * curdir; /* subdir being scanned (ptr to list element)*/
} FAR * DIRECT;

/* Values for direct.pos */
#define DL_FILES 1 /* reading files from the diritems */
#define DL_DIRS 2 /* in the dirs: List_Next on curdir */


/*
* The DIRLIST handle returned from a build function is in fact
* a pointer to one of these
*/
struct dirlist {

char rootname[256]; /* name of root of tree */
BOOL bFile; /* TRUE if root of tree is file, not dir */
DIRECT dot; /* dir for '.' - for tree root dir */
};

extern BOOL bAbort; /* from windiff.c (read only here). */


/* ------ memory allocation ---------------------------------------------*/

/* All memory is allocated from a heap created by the application */
extern HANDLE hHeap;

/*-- forward declaration of internal functions ---------------------------*/

LPSTR dir_finalelem(LPSTR path);
void dir_cleardirect(DIRECT dir);
void dir_adddirect(DIRECT dir, LPSTR path);
void dir_addfile(DIRECT dir, LPSTR path, DWORD size);
void dir_scan(DIRECT dir, BOOL bRecurse);
BOOL dir_isvaliddir(LPSTR path);
BOOL dir_isvalidfile(LPSTR path);
void dir_fileinit(DIRITEM pfile, DIRECT dir, LPSTR path, long size);
void dir_dirinit(DIRECT dir, DIRLIST head, DIRECT parent, LPSTR name);
long dir_getpathsize(LPSTR path);
DIRITEM dir_findnextfile(DIRLIST dl, DIRECT curdir);



/***************************************************************************
* Function: dir_buildlist
*
* Purpose:
*
* Build a list of filenames
*
* Optionally build the list on demand, in which case we scan the
* entire directory but don't recurse into subdirs until needed
*
*/

DIRLIST
dir_buildlist(LPSTR path, BOOL bOnDemand)
{
DIRLIST dl;
BOOL bFile;

/* first check if the path is valid */
if (dir_isvaliddir(path)) {
bFile = FALSE;
} else if (dir_isvalidfile(path)) {
bFile = TRUE;
} else {
/* not valid */
return(NULL);
}


/* alloc and init the DIRLIST head */

dl = (DIRLIST) gmem_get(hHeap, sizeof(struct dirlist));
memset(dl, 0, sizeof(struct dirlist));

/* convert the pathname to an absolute path */

_fullpath(dl->rootname, path, sizeof(dl->rootname));

dl->bFile = bFile;
/* make a '.' directory for the current directory -
* all files and subdirs will be listed from here
*/
dl->dot = (DIRECT) gmem_get(hHeap, sizeof(struct direct));
dir_dirinit(dl->dot, dl, NULL, ".");

/* were we given a file or a directory ? */
if (bFile) {
/* its a file. create a single file entry
* and set the state accordingly
*/
dl->dot->bScanned = TRUE;

dir_addfile(dl->dot, dir_finalelem(path),
dir_getpathsize(path));

return(dl);
}

/* scan the root directory and return. if we are asked
* to scan the whole thing, this will cause a recursive
* scan all the way down the tree
*/
dir_scan(dl->dot, (!bOnDemand) );

return(dl);
} /* dir_buildlist */

/***************************************************************************
* Function: dir_delete
*
* Purpose:
*
* Free up the DIRLIST and all associated memory
*/
void
dir_delete(DIRLIST dl)
{
if (dl == NULL) {
return;
}
dir_cleardirect(dl->dot);
gmem_free(hHeap, (LPSTR) dl->dot, sizeof(struct direct));


gmem_free(hHeap, (LPSTR) dl, sizeof(struct dirlist));
}



/***************************************************************************
* Function: dir_isfile
*
* Purpose:
*
* Was the original build request a file or a directory ?
*/
BOOL
dir_isfile(DIRLIST dl)
{
if (dl == NULL) {
return(FALSE);
}

return(dl->bFile);
}

/***************************************************************************
* Function: dir_firstitem
*
* Purpose:
*
* Return the first file in the list, or NULL if no files found.
* Returns a DIRITEM. This can be used to get filename, size and chcksum.
* If there are no files in the root, we recurse down until we find a file.
*/
DIRITEM
dir_firstitem(DIRLIST dl)
{
if (dl == NULL) {
return(NULL);
}
/*
* reset the state to indicate that no files have been read yet
*/
dl->dot->pos = DL_FILES;
dl->dot->curdir = NULL;

/* now get the next filename */
return(dir_findnextfile(dl, dl->dot));
} /* dir_firstitem */


/***************************************************************************
* Function:dir_nextitem
*
* Purpose:
*
* Get the next filename after the one given.
*
* The List_Next function can give us the next element on the list of files.
* If this is null, we need to go back to the DIRECT and find the
* next list of files to traverse (in the next subdir).
*
* After scanning all the subdirs, return to the parent to scan further
* dirs that are peers of this, if there are any. If we have reached the end of
* the tree (no more dirs in dl->dot to scan), return NULL.
*
* Don't recurse to lower levels unless fDeep is TRUE
*/
DIRITEM
dir_nextitem(DIRLIST dl, DIRITEM cur, BOOL fDeep)
{
DIRITEM next;

if ((dl == NULL) || (cur == NULL)) {
return(NULL);
}
if (bAbort) return NULL; /* user requested abort */

if ( (next = List_Next(cur)) != NULL) {
/* there was another file on this list */
return(next);
}
if (!fDeep) return NULL;

/* get the head of the next list of filenames from the directory */
cur->direct->pos = DL_DIRS;
cur->direct->curdir = NULL;
return(dir_findnextfile(dl, cur->direct));
} /* dir_nextitem */

/***************************************************************************
* Function: dir_findnextfile
*
* Purpose:
*
* Gets the next file in the directory
*/
DIRITEM
dir_findnextfile(DIRLIST dl, DIRECT curdir)
{
DIRITEM curfile;

if ((dl == NULL) || (curdir == NULL)) {
return(NULL);
}

/* scan the subdir if necessary */
if (!curdir->bScanned) {
dir_scan(curdir, FALSE);
}

/* have we already read the files in this directory ? */
if (curdir->pos == DL_FILES) {
/* no - return head of file list */
curfile = (DIRITEM) List_First(curdir->diritems);
if (curfile != NULL) {
return(curfile);
}

/* no more files - try the subdirs */
curdir->pos = DL_DIRS;
}

/* try the next subdir on the list, if any */
/* is this the first or the next */
if (curdir->curdir == NULL) {
curdir->curdir = (DIRECT) List_First(curdir->directs);
} else {
curdir->curdir = (DIRECT) List_Next(curdir->curdir);
}
/* did we find a subdir ? */
if (curdir->curdir == NULL) {

/* no more dirs - go back to parent if there is one */
if (curdir->parent == NULL) {
/* no parent - we have exhausted the tree */
return(NULL);
}

/* reset parent state to indicate this is the current
* directory - so that next gets the next after this.
* this ensures that multiple callers of dir_nextitem()
* to the same tree work.
*/
curdir->parent->pos = DL_DIRS;
curdir->parent->curdir = curdir;

return(dir_findnextfile(dl, curdir->parent));
}

/* there is a next directory - set it to the
* beginning and get the first file from it
*/
curdir->curdir->pos = DL_FILES;
curdir->curdir->curdir = NULL;
return(dir_findnextfile(dl, curdir->curdir));

} /* dir_findnextfile */


/*-- pathnames ----
*
* This module supports two types of pathnames, called relative and full.
* Relative names are relative to the root passed in the initial call
* to dir_build*, and full names include the tree root.
*
* Note that this is a different distinction to relative vs absolute
* pathnames, since the tree root may still be either relative or absolute.
*
* Examples:
*
* - if you called dir_buildlist("c:\")
* getrelname gives: ".\config.sys"
* getfullname gives: "c:\config.sys"
*
* - if you called dir_buildlist(".\geraintd")
* getrelname gives: ".\source\scandir.h"
* getfullname gives either
* ".\geraintd\source\scandir.h"
* or "c:\geraintd\source\scandir.h"
* (depending on the implementation).
*
* To support this, we maintain the tree root name in the DIRLIST head, and
* in each directory, the name of that directory relative to tree root.
* Files just have the filename, so we need to prepend the directory name,
* and (for getfullname) the tree root name as well
*
* We store the directory name with a trailing
* slash to make concatenation easier
*
* -----
*/

/***************************************************************************
* Function: dir_getrelname
*
* Purpose:
*
* Return the name of the current file relative to tree root
*/
LPSTR
dir_getrelname(DIRITEM cur)
{
LPSTR name;
int size;

/* check this is a valid item */
if (cur == NULL) {
return(NULL);
}
/* remember to include the NULL when sizing */
size = lstrlen(cur->direct->relname) + lstrlen(cur->name) + 1;
name = gmem_get(hHeap, size);
lstrcpy(name, cur->direct->relname);
lstrcat(name, cur->name);

return(name);
} /* dir_getrelname */

/***************************************************************************
* Function: dir_getfullname
*
* Purpose:
*
* Return the fullname of the file (including the tree root passed in)
*/
LPSTR
dir_getfullname(DIRITEM cur)
{
LPSTR name;
int size;
LPSTR head;

/* check this is a valid item */
if (cur == NULL) {
return(NULL);
}

if (cur->direct->head->bFile) {
return(cur->direct->head->rootname);
}

/* remember to include the NULL when sizing */
size = lstrlen(cur->name) + 1;

size += lstrlen(cur->direct->relname);

/* add on root name */
head = cur->direct->head->rootname;
size += lstrlen(head);

/* root names may not end in a slash. we need to
* insert one in this case. Also, relnames always begin .\, so
* we skip the . always, and the .\ if we don't need to
* append a slash
*
*/
size--; /* omit the '.' */
if (*CharPrev(head, head+lstrlen(head)) == '\\') {
size--; /* omit the .\ */
}

name = gmem_get(hHeap, size);

lstrcpy(name, cur->direct->head->rootname);

/* add relname and then name, omiting the .\ */

/* skip . or .\ before relname */
if (*CharPrev(head, head+lstrlen(head)) == '\\') {
lstrcat(name, &cur->direct->relname[2]);
} else {
lstrcat(name, &cur->direct->relname[1]);
}
lstrcat(name, cur->name);
return(name);
} /* dir_getfullname */


/***************************************************************************
* Function: dir_getroot_list
*
* Purpose:
*
* Return the name of the tree root given a handle to the DIRLIST.
*/
LPSTR
dir_getroot_list(DIRLIST dl)
{
if (dl == NULL)
return(NULL);
return(dl->rootname);
} /* dir_getroot_list */

/***************************************************************************
* Function: dir_getroot_item
*
* Purpose:
*
* Return the root name of this tree given a handle to a DIRITEM in the
* list.
*/
LPSTR dir_getroot_item(DIRITEM item)
{
if (item == NULL)
return(NULL);

return(dir_getroot_list(item->direct->head));
}


/***************************************************************************
* Function: dir_freerelname
*
* Purpose:
*
* Free up a relname that we allocated. This interface allows us
* some flexibility in how we store relative and complete names
*
*/
void
dir_freerelname(DIRITEM cur, LPSTR name)
{
if((cur != NULL) && (name != NULL))
gmem_free(hHeap, name, lstrlen(name) +1);
} /* dir_freerelname */

/***************************************************************************
* Function: dir_freefullname
*
* Purpose:
*
*/
void
dir_freefullname(DIRITEM cur, LPSTR name)
{
if (cur->direct->head->bFile)
return;

if (name != NULL)
gmem_free(hHeap, name, lstrlen(name) + 1);
} /* dir_freefullname */

/***************************************************************************
* Function: dir_freeroot_list
*
* Purpose:
*
* Free up rootname allocated by dir_getroot_list.
* We just gave a pointer to the rootname, so do nothing.
*/
void
dir_freeroot_list(DIRLIST dl, LPSTR name)
{
if ((dl == NULL) || (name == NULL)) {
return;
}
return;
} /* dir_freeroot_list */

/***************************************************************************
* Function: dir_freeroot_item
*
* Purpose:
*
* Free up memory alloc-ed by a call to dir_getroot_item.
*/
void
dir_freeroot_item(DIRITEM item, LPSTR name)
{
if ((item == NULL) || (name == NULL))
return;
dir_freeroot_list(item->direct->head, name);
}

/***************************************************************************
* Function: dir_getopenname
*
* Purpose:
*
* Get an open-able name for the file. This will be the same as the fullname.
*/
LPSTR
dir_getopenname(DIRITEM item)
{
LPSTR fname;

if (item == NULL)
return(NULL);

fname = dir_getfullname(item);

return(fname);
} /* dir_getopenname */


/***************************************************************************
* Function: dir_freeopenname
*
* Purpose:
*
* Free up memory created by a call to dir_getopenname(). This *may*
* cause the file to be deleted if it was a temporary copy.
*/
void
dir_freeopenname(DIRITEM item, LPSTR openname)
{
if ((item == NULL) || (openname == NULL))
return;

dir_freefullname(item, openname);
} /* dir_freeopenname */

/***************************************************************************
* Function: dir_openfile
*
* Purpose:
*
* Return an open file handle to the file.
*/
int
dir_openfile(DIRITEM item)
{
LPSTR fname;
int fh;
OFSTRUCT os;

fname = dir_getfullname(item);
fh = OpenFile(fname, &os, OF_READ|OF_SHARE_DENY_NONE);
dir_freefullname(item, fname);
return(fh);
} /* dir_openfile */

/***************************************************************************
* Function: dir_closefile
*
* Purpose:
*
* Close a file opened with dir_openfile.
*/
void
dir_closefile(DIRITEM item, int fh)
{
_lclose(fh);

} /* dir_closefile */


/***************************************************************************
* Function: dir_getfilesize
*
* Purpose:
*
* Return the file size (set during scanning)
*/
long
dir_getfilesize(DIRITEM cur)
{
/* check this is a valid item */
if (cur == NULL)
return(0);

return(cur->size);
} /* dir_getfilesize */



/* ss_endcopy returns a number indicating the number of files copied,
but we may have some local copies too. We need to count these
ourselves and add them in
*/

int nLocalCopies; /* cleared in startcopy, ++d in copy
** inspected in endcopy
*/

/***************************************************************************
* Function: dir_startcopy
*
* Purpose:
*
* Start a bulk copy
*/
BOOL dir_startcopy(DIRLIST dl)
{
nLocalCopies = 0;
return(TRUE);

} /* dir_startcopy */
/***************************************************************************
* Function: dir_endcopy
*
*/

int dir_endcopy(DIRLIST dl)
{
return(nLocalCopies);

} /* dir_endcopy */

/***************************************************************************
* Function: dir_copy
*
* Purpose:
*
* Create a copy of the file, in the new root directory. Creates sub-dirs as
* necessary.
*
* Returns TRUE for success and FALSE for failure.
*/
BOOL dir_copy(DIRITEM item, LPSTR newroot)
{
static char newpath[256];
LPSTR relname, fullname;
LPSTR pstart, pdest, pel;
BOOL bOK;

BY_HANDLE_FILE_INFORMATION bhfi;
HANDLE hfile;

/*
* check that the newroot directory itself exists
*/
if ((item == NULL) || !dir_isvaliddir(newroot)) {
return(FALSE);
}

/*
* name of file relative to the tree root
*/
relname = dir_getrelname(item);

/*
* build the new pathname by concatenating the new root and
* the old relative name. add one path element at a time and
* ensure that the directory exists, creating it if necessary.
*/
lstrcpy(newpath, newroot);

/* add separating slash if not already there */
if (*CharPrev(newpath, newpath+lstrlen(newpath)) != '\\') {
lstrcat(newpath, "\\");
}

pstart = relname;
while ( (pel = strchr(pstart, '\\')) != NULL) {

/* found another element ending in slash. incr past the \\ */
pel++;

/*
* ignore .
*/
if (strncmp(pstart, ".\\", 2) != 0) {

pdest = &newpath[lstrlen(newpath)];
strncpy(pdest, pstart, pel - pstart);
pdest[pel - pstart] = '\0';

/* create subdir if necessary */
if (!dir_isvaliddir(newpath)) {
if (_mkdir(newpath) != 0) {
return(FALSE);
}
}
}

pstart = pel;
}

/*
* there are no more slashes, so pstart points at the final
* element
*/
lstrcat(newpath, pstart);

fullname = dir_getfullname(item);

bOK = CopyFile(fullname, newpath, FALSE);

/* having copied the file, now copy the times, attributes */
hfile = CreateFile(fullname, GENERIC_READ, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
bhfi.dwFileAttributes = GetFileAttributes(fullname);
GetFileTime(hfile, &bhfi.ftCreationTime,
&bhfi.ftLastAccessTime, &bhfi.ftLastWriteTime);
CloseHandle(hfile);

hfile = CreateFile(newpath, GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
SetFileTime(hfile, &bhfi.ftCreationTime,
&bhfi.ftLastAccessTime,
&bhfi.ftLastWriteTime);
CloseHandle(hfile);
SetFileAttributes(newpath, bhfi.dwFileAttributes);


if (bOK) ++nLocalCopies;

dir_freerelname(item, relname);
dir_freefullname(item, fullname);

return(bOK);
} /* dir_copy */


/***************************************************************************
* Function: dir_dirinit
*
* Purpose:
*
* Fill out a new DIRECT for a subdirectory (pre-allocated).
* Init files and dirs lists to empty (List_Create). Set the relname
* of the directory by pre-pending the parent relname if there
* is a parent, and appending a trailing slash (if there isn't one).
*/
void
dir_dirinit(DIRECT dir, DIRLIST head, DIRECT parent, LPSTR name)
{
int size;

dir->head = head;
dir->parent = parent;

/* add on one for the null and one for the trailing slash */
size = lstrlen(name) + 2;
if (parent != NULL) {
size += lstrlen(parent->relname);
}

/* build the relname from the parent and the current name
* with a terminating slash
*/
dir->relname = gmem_get(hHeap, size);
if (parent != NULL) {
lstrcpy(dir->relname, parent->relname);
} else{
dir->relname[0] = '\0';
}

lstrcat(dir->relname, name);

if (*CharPrev(dir->relname,
dir->relname+lstrlen(dir->relname)) != '\\') {
lstrcat(dir->relname, "\\");
}

/* force name to lowercase */
AnsiLowerBuff(dir->relname, lstrlen(dir->relname));

dir->diritems = List_Create();
dir->directs = List_Create();
dir->bScanned = FALSE;
dir->pos = DL_FILES;

} /* dir_dirinit */


/***************************************************************************
* Function: dir_fileinit
*
* Purpose:
*
* Initialise the contents of an (allocated) DIRITEM struct.
*/
void
dir_fileinit(DIRITEM pfile, DIRECT dir, LPSTR path, long size)
{

pfile->name = gmem_get(hHeap, lstrlen(path) + 1);
lstrcpy(pfile->name, path);

/* force name to lower case */
AnsiLowerBuff(pfile->name, lstrlen(path));

pfile->direct = dir;
pfile->size = size;

pfile->localname = NULL;

} /* dir_fileinit */

/***************************************************************************
* Function: dir_isfilevalid
*
* Purpose:
*
* Is this a valid file or not
*/
BOOL
dir_isvalidfile(LPSTR path)
{
DWORD dwAttrib;

dwAttrib = GetFileAttributes(path);
if (dwAttrib == -1) {
return(FALSE);
}
if (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) {
return(FALSE);
}
return(TRUE);
} /* dir_isvalidfile */

/***************************************************************************
* Function: dir_isvaliddir
*
* Purpose:
*
* Is this a valid directory ?
*/
BOOL
dir_isvaliddir(LPSTR path)
{
DWORD dwAttrib;

dwAttrib = GetFileAttributes(path);
if (dwAttrib == -1) {
return(FALSE);
}
if (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) {
return(TRUE);
}
return(FALSE);
} /* dir_isvaliddir */

/*************************************************************************** 
* Function: dir_scan
*
* Purpose:
*
* Scan the directory given. Add all files to the list
* in alphabetic order, and add all directories in alphabetic
* order to the list of child DIRITEMs. If bRecurse is true, go on to
* recursive call dir_scan for each of the child DIRITEMs
*/
void
dir_scan(DIRECT dir, BOOL bRecurse)
{
PSTR path;
int size;
DIRECT child;
BOOL bMore;
long filesize;
BOOL bIsDir;
LPSTR name;

HANDLE hFind;
WIN32_FIND_DATA finddata;

char debugmsg[200];
wsprintf(debugmsg, "scandir: %s %s\n",
dir->relname, bRecurse?"recursive":"non-recursive"
);

/* make the complete search string including *.* */
size = lstrlen(dir->head->rootname);
size += lstrlen(dir->relname);

/* add on one null and *.* */
size += 4;

path = LocalLock(LocalAlloc(LHND, size));

lstrcpy(path, dir->head->rootname);

/* omit the . at the beginning of the relname, and the
* .\ if there is a trailing \ on the rootname
*/
if (*CharPrev(path, path+lstrlen(path)) == '\\') {
lstrcat(path, &dir->relname[2]);
} else {
lstrcat(path, &dir->relname[1]);
}
lstrcat(path, "*.*");

/* read all entries in the directory */
hFind = FindFirstFile(path, &finddata);
bMore = (hFind != (HANDLE) -1);
LocalUnlock(LocalHandle ( (PSTR) path));
LocalFree(LocalHandle ( (PSTR) path));

while (bMore) {

bIsDir = (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
name = (LPSTR) &finddata.cFileName;
filesize = finddata.nFileSizeLow;
if (!bIsDir) {

dir_addfile(dir, name, filesize);

} else if ( (lstrcmp(name, ".") != 0) &&
( lstrcmp(name, "..") != 0) ) {

dir_adddirect(dir, name);
}

bMore = FindNextFile(hFind, &finddata);
}

FindClose(hFind);

dir->bScanned = TRUE;
dir->pos = DL_FILES;

if (bRecurse) {
List_TRAVERSE(dir->directs, child) {
dir_scan(child, TRUE);
}
}

} /* dir_scan */


/***************************************************************************
* Function: dir_addfile
*
* Purpose:
*
* Add the file 'path' to the list of files in dir, in order.
*/
void
dir_addfile(DIRECT dir, LPSTR path, DWORD size)
{
DIRITEM pfile;

AnsiLowerBuff(path, lstrlen(path)); // needless?

List_TRAVERSE(dir->diritems, pfile) {
/////if (lstrcmpi(pfile->name, path) > 0) {
if (utils_CompPath(pfile->name, path) > 0) {

/* goes before this one */
pfile = List_NewBefore(dir->diritems, pfile, sizeof(struct diritem));
dir_fileinit(pfile, dir, path, size);
return;
}
}
/* goes at end */
pfile = List_NewLast(dir->diritems, sizeof(struct diritem));
dir_fileinit(pfile, dir, path, size);
} /* dir_addfile */


/***************************************************************************
* Function: dir_addirect
*
* Purpose:
*
* Add a new directory in alphabetic order on
* the list dir->directs
*
*/
void
dir_adddirect(DIRECT dir, LPSTR path)
{
DIRECT child;
LPSTR finalel;
char achTempName[256];

AnsiLowerBuff(path, lstrlen(path));
List_TRAVERSE(dir->directs, child) {

int cmpval;

/* we need to compare the child name with the new name.
* the child name is a relname with a trailing
* slash - so compare only the name up to but
* not including the final slash.
*/
finalel = dir_finalelem(child->relname);

/*
* we cannot use strnicmp since this uses a different
* collating sequence to lstrcmpi. So copy the portion
* we are interested in to a null-term. buffer.
*/
strncpy(achTempName, finalel, lstrlen(finalel)-1);
achTempName[lstrlen(finalel)-1] = '\0';

cmpval = utils_CompPath(achTempName, path);

if (cmpval > 0) {

/* goes before this one */
child = List_NewBefore(dir->directs, child, sizeof(struct direct));
dir_dirinit(child, dir->head, dir, path);
return;
}
}
/* goes at end */
child = List_NewLast(dir->directs, sizeof(struct direct));
dir_dirinit(child, dir->head, dir, path);
} /* dir_adddirect */


/***************************************************************************
* Function: dir_cleardirect
*
* Purpose:
*
* Free all memory associated with a DIRECT (including freeing
* child lists). Don't de-alloc the direct itself (allocated on a list)
*/
void
dir_cleardirect(DIRECT dir)
{
DIRITEM pfile;
DIRECT child;

/* clear contents of files list */
List_TRAVERSE(dir->diritems, pfile) {
gmem_free(hHeap, pfile->name, lstrlen(pfile->name));
if ((pfile->localname) && (pfile->bLocalIsTemp)) {

/*
* the copy will have copied the attributes,
* including read-only. We should unset this bit
* so we can delete the temp file.
*/
SetFileAttributes(pfile->localname,
GetFileAttributes(pfile->localname)
& ~FILE_ATTRIBUTE_READONLY);
DeleteFile(pfile->localname);
gmem_free(hHeap, pfile->localname, 256);
pfile->localname = NULL;
}

}
List_Destroy(&dir->diritems);

/* clear contents of dirs list (recursively) */
List_TRAVERSE(dir->directs, child) {
dir_cleardirect(child);
}
List_Destroy(&dir->directs);

gmem_free(hHeap, dir->relname, lstrlen(dir->relname) + 1);

} /* dir_cleardirect */

/***************************************************************************
* Function: dir_finalelem
*
* Purpose:
*
* Return a pointer to the final element in a path. Note that
* we may be passed relnames with a trailing final slash - ignore this
* and return the element before that final slash.
*/
LPSTR
dir_finalelem(LPSTR path)
{
LPSTR chp;
int size;

/* is the final character a slash ? */
size = lstrlen(path) - 1;
if (*(chp = CharPrev(path, path+lstrlen(path))) == '\\') {
/* find the slash before this */
while (chp > path) {
if (*(chp = CharPrev(path, chp)) == '\\') {
/* skip the slash itself */
chp++;
break;
}
}
return(chp);
}
/* look for final slash */
chp = strrchr(path, '\\');
if (chp != NULL) {
return(chp+1);
}

/* no slash - is there a drive letter ? */
chp = strrchr(path, ':');
if (chp != NULL) {
return(chp+1);
}

/* this is a final-element anyway */
return(path);

} /* dir_finalelem */

/***************************************************************************
* Function: dir_getpathsize
*
* Purpose:
*
* Find the size of a file given a pathname to it
*/
long
dir_getpathsize(LPSTR path)
{
int fh;
OFSTRUCT os;
long size;
fh = OpenFile(path, &os, OF_READ|OF_SHARE_DENY_NONE);
if (fh == -1) {
return(0);
}

size = GetFileSize( (HANDLE) fh, NULL);
_lclose(fh);
return(size);
} /* dir_getpathsize */