//
// WILDCARD.CPP
//
//  Source file for ArchiveLib 1.0
//
//  Copyright (c) Greenleaf Software, Inc. 1994
//  All Rights Reserved
//
// CONTENTS
//
//  ALWildCardExpander::operator new()
//  ALWildCardExpander::ALWildCardExpander()
//  ALWildCardExpander::~ALWildCardExpander()
//  ALWildCardExpander::GetNextWildName()
//  ALWildCardExpander::GetNextFile()
//
// DESCRIPTION
//
//  This file contains all the source code for the nifty class
//  ALWildCardExpander.  The wild card expansion code is a state
//  drive routine, which keeps track of its entire state between
//  calls.  So you can call it once to get a new file name, then
//  do some processing.  When you call it again later, you will
//  get the next file name in the sequence.
//
//  There is a little difference between the NT version and the DOS
//  version, because the function calls for get first/get next
//  are different.  A minor difference is created by the fact
//  that under NT you can't specify attributes in a search, so when
//  you want to look for subdirectories, you have to search all files
//  and see if any of your matches turn out to be directories.
//
//  The way the wild card class handles searching through subdirectories
//  is by keeping a link pointer to a subdirectory search.  When it 
//  is time to open up a subdirectory search, we create a new file
//  expander, and assign its pointer to our link pointer.  As long as
//  the link is active, we keep searching there.  When the link runs
//  out of files to return, we continue searching in our own directory.
//
//  A lot of this code is easier to deal with because we use the
//  ALName class.  That makes it easy to strip file names and
//  paths apart, and even easier to put them back together again.
//
// REVISION HISTORY
//
//  May 26, 1994  1.0A  : First release
//
//

#include "arclib.h"
#pragma hdrstop

#include <stdlib.h>
#ifdef __BORLANDC__
#include <dir.h>
#endif

#include "wildcard.h"

//
// void * ALWildCardExpander::operator new( size_t size )
//
// ARGUMENTS:
//
//  size  :  The number of bytes needed to create a new ALWildCardExpander
//           object.
//
// RETURNS
//
//  A pointer to the newly allocated storage area, or 0 if no storage
//  was available.
//
// DESCRIPTION
//
//  When using a DLL, it is easy to get into a dangerous situation when 
//  creating objects whose ctor and dtor are both in the DLL.  The problem
//  arises because when you create an object using new, the memory for
//  the object will be allocated from the EXE.  However, when you destroy
//  the object using delete, the memory is freed inside the DLL.  Since
//  the DLL doesn't really own that memory, bad things can happen.
//
//  But, you say, won't the space just go back to the Windows heap regardless
//  of who tries to free it?  Maybe, but maybe not.  If the DLL is using 
//  a subsegment allocation scheme, it might do some sort of local free
//  before returning the space to the windows heap.  That is the point where
//  you could conceivably cook your heap.
//
//  By providing our own version of operator new inside this class, we
//  ensure that all memory allocation for the class will be done from
//  inside the DLL, not the EXE calling the DLL.
//
// REVISION HISTORY
//
//   May 26, 1994  1.0A  : First release
//

#if defined( AL_BUILDING_DLL )
void AL_DLL_FAR * AL_PROTO ALWildCardExpander::operator new( size_t size )
{
    return ::new char[ size ];
}
#endif

//
// ALWildCardExpander::ALWildCardExpander( const char *file_list,
//                                         int traverse_flag = 0,
//                                         ALCase name_case = AL_LOWER ) 
//
// ARGUMENTS:
//
//  file_list       : A list of wild card file specifications, separated
//                    by commas, semicolons, or spaces, maybe looking 
//                    something like this: "*.CPP, BOB.DAT, *.*"
//
//  traverse_flag   : A flag that indicates whether you want to traverse
//                    all subdirectories under the current path.
//
//  name_case       : An indicator of whether you want all the returned
//                    file names forced to a certain case.
//
// RETURNS
//
//  No returns.
//
// DESCRIPTION
//
//  The constructor for the expander has to set up a bunch of data members
//  that will all be used during the expansion process.  The mCase
//  member is easy to understand.  All of the objname objects that
//  we create are going to be force to a certain case by this
//  using this data member.  miTraverseFlag is just our copy of the
//  input parameter.  And the mState variable keeps track of what we
//  are doing in between function calls.  We set it to GET_NEXT_WILD_NAME,
//  which means we will be doing that the first time we get called.
// 
//  mInputLine is where we keep a copy of the list of wild card file
//  specifications passed by the calling program.  Each time we take
//  a new file name out of mInputLine, we remove it from the ALName
//  object, making mInputLine just a little shorter.
//
//  The mResultFileName member is the storage area where we keep a copy
//  of the file name created by the expander.  This is our local copy,
//  when it gets returned to the calling program they need to make
//  their own copy of it and leave ours alone.
//
//  Every time we get asked to get a new file, the very first thing
//  we do is check to see if the mpNextExpander member is pointing
//  to a new expander object.  If it is, we ask him to provide
//  the next file name, instead of giving it ourselves.  When he
//  doesn't have any file names left to give, we destroy him and
//  set that pointer back to 0.  Here in the constructor, the smart
//  thing to do is set him to 0 for starters.
//
//  The final data member differs between NT and DOS.  The structure
//  NT uses to expand directories is store in mFindFileHandle.  The
//  DOS version is stored in mpFfblk.  Both of these are presently
//  in an invalid state, but will get initialized when the user
//  calls the member function.
//
// REVISION HISTORY
//
//   May 26, 1994  1.0A  : First release
//

AL_PROTO ALWildCardExpander::ALWildCardExpander(
          const char AL_DLL_FAR *file_list,
          int traverse_flag /* = 0 */,
          ALCase name_case /* = AL_LOWER */ ) 
    : mCase( name_case ), 
      mResultFileName( "", name_case )
{
    mInputLine = file_list;
    mState = GET_NEXT_WILD_NAME;
    mpNextExpander = 0;
    miTraverseFlag = traverse_flag;
#if defined( AL_WIN32S ) 
    mFindFileHandle = INVALID_HANDLE_VALUE;
#else
    mpFfblk = new find_t;
#endif
}

//
// ALWildCardExpander::~ALWildCardExpander()
//
// ARGUMENTS:
//
//  None, destructors don't get any.
//
// RETURNS
//
//  None, destructor.
//
// DESCRIPTION
//
//  There are a couple of big deals we need to worry about in the
//  destructor an ALWildCardExpander.  First, we have to worry about
//  any additional handlers we created to search subdirectories.  If
//  this destructor is being called before our search is done, we
//  may have some of those expander objects just hanging around out
//  there.  We take care of the by checking the mpNextExpander member.
//  If it isn't set to 0, we delete the dynamically created expander.
//
//  Under NT we also have to worry about our mpFindFileHandle.  Under
//  NT, the file expansion algorithm isn't just a get first/get next
//  deal.  Instead, it is get first/get next/terminate.  The termination
//  is done using the FindClose() call.  If we still had a search in progress
//  we call that function.
//
//  Under DOS, we just have to delete the dynamically created
//  mpFfblk structure.  I wanted to make that a data member of this
//  class, instead of a pointer, but one of our compilers wasn't happy
//  about putting this C struct in a class, it complained about something.
//  So, to expedite, we made it a pointer.
//
//
// REVISION HISTORY
//
//   May 26, 1994  1.0A  : First release
//

AL_PROTO ALWildCardExpander::~ALWildCardExpander()
{
    if ( mpNextExpander )
        delete mpNextExpander;
#if defined( AL_WIN32S )
    if ( mFindFileHandle != INVALID_HANDLE_VALUE )
        FindClose( mFindFileHandle );
#else
    if ( mpFfblk )
        delete mpFfblk;
#endif
}

// PROTECTED MEMBER FUNCTION
//
// int ALWildCardExpander::GetNextWildName()
//
// ARGUMENTS:
//
//  None.
//
// RETURNS
//
//  1 if it got a new file spec, 0 if it didn't.
//
// DESCRIPTION
//
//  This function is called internally to get the next file spec out of
//  the input line.  This is simply a matter of parsing past all the 
//  delimiter characters.  The resulting file spec is stored in
//  data member mFullWildName.  That member will be the one used to
//  kick off the next wild card search.
//
// REVISION HISTORY
//
//   May 26, 1994  1.0A  : First release
//

int AL_PROTO ALWildCardExpander::GetNextWildName()
{
    char wild_spec[ _MAX_PATH ];
    int i = 0;
    char *p = mInputLine;

    for ( ; ; p++ ) {
        int c = *p;
        if ( c != ' ' && c != ',' && c != '\t' )
            break;
    }
    for ( ; ; p++ ) {
        int c = *p;
        if ( c == ' ' || c == ',' || c == '\t' || c == '\0' )
            break;
        wild_spec[ i++ ] = (char) c;
        if ( i >= ( _MAX_PATH - 2 ) )
            return 0;
    }
    wild_spec[ i++ ] = '\0';
    if ( i <= 1 )
         return 0;
    mFullWildName = wild_spec;
    mInputLine = p;
    return 1;
}

//
// char * ALWildCardExpander::GetNextFile()
//
// ARGUMENTS:
//
//  None.
//
// RETURNS
//
//  In the event that this routine is able to come up with a new
//  file name, it returns a character pointer to the name, which
//  is kept in member variable mResultFileName.  If no new file
//  name could be cooked up, we return a 0, which means you are
//  done.
//
// DESCRIPTION
//
//  There are two wild card expander routines.  One for NT, and one
//  for DOS.  They are both very similar in structure, but they weren't
//  quite close enough to combine into a single routine.  However, the
//  both share a common structure, which is being described here.
//
//  The ALWildCardExpander has what amounts to six different internal
//  states.  They are:
//
//              Searching subdirectories, using another object
//
//              Extracting the next wild spec from the input line
//
//              Expanding the wild card to get the first matching file
//
//              Expanding the wild card to get the next matching file
//
//              Looking for the first subdirectory
//
//              Looking for the next subdirectory
//
//  For the most part, we keep track of the state using the mState
//  variable.  However, we keep track of whether we are searching
//  subdirectories by examining the pointer to the next expander.  If
//  it is non-null, it means we are in that state.
//                      
// REVISION HISTORY
//
//   May 26, 1994  1.0A  : First release
//

#if defined( AL_WIN32S )
//
// This is the NT version.  It has to use FindFirstFile() and
// FindNextFile() to get file names.  Note that this is implemented
// as a giant loop.  This means we may go through several states
// inside this routine until we finally come up with a filename.
//
char AL_DLL_FAR * AL_PROTO ALWildCardExpander::GetNextFile()
{
    for ( ; ; ) {
//
// If the pointer to the next expander is set, it means we are working
// on a subdirectory, so I have to let him do the work.  If the subdirectory
// search fails, I continue right back where I was when interrupted.
//
        if ( mpNextExpander ) {
            char *p = mpNextExpander->GetNextFile();
            if ( p )
                return p;          // Return the name if he found one
            delete mpNextExpander; // If not, he is toast
            mpNextExpander = 0;
        }
        switch ( mState ) {
//
// This is where I get the next wild spec from the input line.  If
// there aren't any more, I return 0, because we are done.  If there
// is one, I set up the member variable that will be used in the
// rest of the search, and set up the state so that next I will get
// get the first file name.
//
            case GET_NEXT_WILD_NAME :
                if ( GetNextWildName() == 0 )
                    return 0;
                mWildPathOnly = mFullWildName;
                mWildPathOnly.StripFileName();
                mWildNameOnly = mFullWildName;
                mWildNameOnly.StripPath();
                mState = GET_FIRST_FILE_NAME;
                break;
//
// Once I have a wild spec, time to start getting file names.  
// FindFirstFile() does it for me.  if there aren't any files, I
// either go on to search directories, or go the the next wild
// name in the input line.  If there is a name, I return it to
// the calling procedure.
//
            case GET_FIRST_FILE_NAME :
                mFindFileHandle = FindFirstFile( mFullWildName, &mFindFileData );
                if ( mFindFileHandle == INVALID_HANDLE_VALUE ) {
                    if ( miTraverseFlag )
                        mState = GET_FIRST_DIRECTORY;
                    else
                        mState = GET_NEXT_WILD_NAME;
                    break;
                }
                mState = GET_NEXT_FILE_NAME;
                if ( mFindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
                    break;
                mResultFileName = ALName( mWildPathOnly + mFindFileData.cFileName );
                return mResultFileName;
//
// Time to get another file name with FindNextFile().  If there aren't
// any more, I clean up, and either get the next name for the input
// line or start searching subdirectories. If there was a name, I return
// it to the calling procedure.
//
            case GET_NEXT_FILE_NAME :
                if ( !FindNextFile( mFindFileHandle, &mFindFileData ) ) {
                    FindClose( mFindFileHandle );
                    mFindFileHandle = INVALID_HANDLE_VALUE;
                    if ( miTraverseFlag )
                        mState = GET_FIRST_DIRECTORY;
                    else
                        mState = GET_NEXT_WILD_NAME;
                    break;
                }
                if ( mFindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
                    break;
                mResultFileName = ALName( mWildPathOnly + mFindFileData.cFileName );
                return mResultFileName;
//
// The procedure to get the first subdirectory is an awful lot like that
// we use to get the first file.  If we find a valid subdirectory, we create
// a new expander to deal with its wildcards.  If we find a file, but
// it isn't a subdirectory, we keep on searching.  If we don't find
// anything, we are going to go back and check out the next file spec
// from the input line.
// 
            case GET_FIRST_DIRECTORY :
                mFindFileHandle = FindFirstFile( mWildPathOnly + "*.*", &mFindFileData );
                if ( mFindFileHandle == INVALID_HANDLE_VALUE ) {
                    mState = GET_NEXT_WILD_NAME;
                    break;
                }
                mState = GET_NEXT_DIRECTORY;
                if ( mFindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) {
                    if ( strcmp( mFindFileData.cFileName, ".." ) == 0 )
                        break;
                    if ( strcmp( mFindFileData.cFileName, "." ) == 0 )
                        break;
                    mpNextExpander = new ALWildCardExpander( mWildPathOnly + mFindFileData.cFileName + "\\" + (char *) mWildNameOnly, 1, mCase );
                }
                break;
//
// This works the same as the state where I get the first directory.
// The only difference here is that if I run out of file names in the
// directory, I have to call FindClose() to clean up after myself.
//
            case GET_NEXT_DIRECTORY :
                if ( !FindNextFile( mFindFileHandle, &mFindFileData ) ) {
                    FindClose( mFindFileHandle );
                    mFindFileHandle = INVALID_HANDLE_VALUE;
                    mState = GET_NEXT_WILD_NAME;
                    break;
                }
                if ( mFindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) {
                    if ( strcmp( mFindFileData.cFileName, ".." ) == 0 )
                        break;
                    if ( strcmp( mFindFileData.cFileName, "." ) == 0 )
                        break;
                    mpNextExpander = new ALWildCardExpander( mWildPathOnly + mFindFileData.cFileName + "\\" + (char *) mWildNameOnly, 1 );
                }
                break;
            default :
                return 0;
        }
    }
}

#else


//
// This is the MS-DOS version of the file expander.  In structure,
// it is almost identical to the NT version.
//
char AL_DLL_FAR * AL_PROTO ALWildCardExpander::GetNextFile()
{
//
// mpFfblk is the pointer to my structure used by _dos_findfirst()
// and _dos_findnext().  If for some reason this is a null pointer,
// I need to quit.  The only reason this should be null is a memory
// allocation failure.
//
    if ( mpFfblk == 0 )
        return 0;
    for ( ; ; ) {
//
// If the pointer to the next expander is non-zero, it means I am in
// the middle of a subdirectory search.  If that is the case, I call
// the next expander to see if it can come up with a file name.  if
// it does, we return it.  If it doesn't, it means it is done, and
// I can delete it and try my luck with the next subdirectory.
//
        if ( mpNextExpander ) {
            char *p = mpNextExpander->GetNextFile();
            if ( p )
                return p;
            delete mpNextExpander;
            mpNextExpander = 0;
        }
        switch ( mState ) {
//
// This is where I start, and this is where I end up after completely
// processing one of the input wild specs.  I get the next name from
// the input line here.  If there aren't any more names, I can return
// 0, meaning the whole thing is done.
//
            case GET_NEXT_WILD_NAME :
                if ( GetNextWildName() == 0 )
                    return 0;
                mWildPathOnly = mFullWildName;
                mWildPathOnly.StripFileName();
                mWildNameOnly = mFullWildName;
                mWildNameOnly.StripPath();
                mState = GET_FIRST_FILE_NAME;
                break;
//
// Once I have a file name, I start parsing using _dos_findfirst().
// If that doesn't return a name, I have struck out on my first swing.
// if that is the case, I either move on to start searching subdirectories,
// or go back and look for another name from the input line.  On the
// other hand, if I get a name, I return it to the caller.
//
            case GET_FIRST_FILE_NAME :
                if ( _dos_findfirst( mFullWildName, 0, mpFfblk ) ) {
                    if ( miTraverseFlag )
                        mState = GET_FIRST_DIRECTORY;
                    else
                        mState = GET_NEXT_WILD_NAME;
                    break;
                }
                mState = GET_NEXT_FILE_NAME;
                mResultFileName = ALName( mWildPathOnly + mpFfblk->name );
                return mResultFileName;
//
// This state is identical to GET_FIRST_FILE_NAME, except it has to
// use _dos_findnext() instead of _dos_findfirst()
//
            case GET_NEXT_FILE_NAME :
                if ( _dos_findnext( mpFfblk ) ) {
                    if ( miTraverseFlag )
                        mState = GET_FIRST_DIRECTORY;
                    else
                        mState = GET_NEXT_WILD_NAME;
                    break;
                }
                mResultFileName = mWildPathOnly + mpFfblk->name;
                return mResultFileName;
//
// After getting all of the file names that a wildspec expands into,
// we can start searching subdirectories, if needed.  Unlike with NT,
// we can set our search up to look for directories only.  that means
// we don't have to check the status of the file returned from _dos_findxxxx().
// However, we always do have to check to make sure it isn't one of the
// two bogus directory entries, "." or "..".
//
// If we score here, we create a new ALWildCardExpander, and put him to
// work.  If we strike out, time to go back and get our next input
// file name.
//
            case GET_FIRST_DIRECTORY :
                if ( _dos_findfirst( mWildPathOnly + "*.*", _A_SUBDIR, mpFfblk ) ) {
                    mState = GET_NEXT_WILD_NAME;
                    break;
                }
                mState = GET_NEXT_DIRECTORY;
                if ( mpFfblk->attrib & _A_SUBDIR ) {
                    if ( strcmp( mpFfblk->name, ".." ) == 0 )
                        break;
                    if ( strcmp( mpFfblk->name, "." ) == 0 )
                        break;
                    mpNextExpander = new ALWildCardExpander( mWildPathOnly + mpFfblk->name + "\\" + (char *) mWildNameOnly, 1, mCase );
                }
                break;
//
// This is just like GET_FIRST_DIRECTORY, except it gets to call
// _dos_findnext() instead of _dos_findfirst().
//
            case GET_NEXT_DIRECTORY :
                if ( _dos_findnext( mpFfblk ) ) {
                    mState = GET_NEXT_WILD_NAME;
                    break;
                }
                if ( mpFfblk->attrib & _A_SUBDIR ) {
                    if ( strcmp( mpFfblk->name, ".." ) == 0 )
                        break;
                    if ( strcmp( mpFfblk->name, "." ) == 0 )
                        break;
                    mpNextExpander = new ALWildCardExpander( mWildPathOnly + mpFfblk->name + "\\" + (char *) mWildNameOnly, 1 );
                }
                break;
            default :
                return 0;
        }
    }
#if defined( AL_MICROSOFT ) && ( AL_MICROSOFT < 800 )
    return 0; //MSC 7.0 thinks I need this return path.  No way to get here!
#endif
}
#endif // #if defined( AL_WIN32S )... #else