
/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples. 
*       Copyright (C) 1993-1995 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 (head[lstrlen(head) -1] == '\\') {
                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 (head[lstrlen(head) -1] == '\\') {
                        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 (newpath[lstrlen(newpath) -1] != '\\') {
                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 (dir->relname[lstrlen(dir->relname) -1] != '\\') {
                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 (path[strlen(path)-1] == '\\') {
                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 (path[size] == '\\') {
                /* find the slash before this */
                for (size-- ; size > 0; size--) {
                        if (path[size] == '\\') {
                                /* skip the slash itself */
                                size++;
                                break;
                        }
                }
                return(&path[size]);
        }
        /* 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 */


