/************* TabMul C++ Program Source Code File (.CPP) **************/
/* PROGRAM NAME: TABMUL                                                */
/* -------------                                                       */
/*  Version 1.7                                                        */
/*                                                                     */
/* COPYRIGHT:                                                          */
/* ----------                                                          */
/*  (C) Copyright to PlugDB Software Development          2003 - 2012  */
/*  Author: Olivier BERTRAND                                           */
/*                                                                     */
/* WHAT THIS PROGRAM DOES:                                             */
/* -----------------------                                             */
/*  This program are the TDBMUL class DB routines.                     */
/*                                                                     */
/* WHAT YOU NEED TO COMPILE THIS PROGRAM:                              */
/* --------------------------------------                              */
/*                                                                     */
/*  REQUIRED FILES:                                                    */
/*  ---------------                                                    */
/*    TABMUL.CPP     - Source code                                     */
/*    PLGDBSEM.H     - DB application declaration file                 */
/*    TABDOS.H       - TABDOS classes declaration file                 */
/*    TABMUL.H       - TABFIX classes declaration file                 */
/*    GLOBAL.H       - Global declaration file                         */
/*                                                                     */
/*  REQUIRED LIBRARIES:                                                */
/*  -------------------                                                */
/*    Large model C library                                            */
/*                                                                     */
/*  REQUIRED PROGRAMS:                                                 */
/*  ------------------                                                 */
/*    IBM, Borland, GNU or Microsoft C++ Compiler and Linker           */
/*                                                                     */
/***********************************************************************/

/***********************************************************************/
/*  Include relevant section of system dependant header files.         */
/***********************************************************************/
#include "my_global.h"
#if defined(WIN32)
#include <stdlib.h>
#include <stdio.h>
#if defined(__BORLANDC__)
#define __MFC_COMPAT__                   // To define min/max as macro
#endif
//#include <windows.h>
#else
#if defined(UNIX)
#include <fnmatch.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "osutil.h"
#else
//#include <io.h>
#endif
//#include <fcntl.h>
#endif

/***********************************************************************/
/*  Include application header files:                                  */
/***********************************************************************/
#include "global.h"      // global declarations
#include "plgdbsem.h"    // DB application declarations
#include "reldef.h"      // DB definition declares
#include "filamtxt.h"
#include "tabdos.h"      // TDBDOS and DOSCOL class dcls
#include "tabmul.h"      // TDBMUL and MULCOL classes dcls

/* ------------------------- Class TDBMUL ---------------------------- */

/***********************************************************************/
/*  TABMUL constructors.                                               */
/***********************************************************************/
TDBMUL::TDBMUL(PTDBASE tdbp) : TDBASE(tdbp->GetDef())
  {
  Tdbp = tdbp;
  Filenames = NULL;
  Rows = 0;
  Mul = tdbp->GetDef()->GetMultiple();
  NumFiles = 0;
  iFile = 0;
  } // end of TDBMUL standard constructor

TDBMUL::TDBMUL(PTDBMUL tdbp) : TDBASE(tdbp)
  {
  Tdbp = tdbp->Tdbp;
  Filenames = tdbp->Filenames;
  Rows = tdbp->Rows;
  Mul = tdbp->Mul;
  NumFiles = tdbp->NumFiles;
  iFile = tdbp->iFile;
  } // end of TDBMUL copy constructor

// Method
PTDB TDBMUL::CopyOne(PTABS t)
  {
  PTDBMUL tp;
  PGLOBAL g = t->G;        // Is this really useful ???

  tp = new(g) TDBMUL(this);
  tp->Tdbp = (PTDBASE)Tdbp->CopyOne(t);
  tp->Columns = tp->Tdbp->GetColumns();
  return tp;
  } // end of CopyOne

PTDB TDBMUL::Duplicate(PGLOBAL g)
  {
  PTDBMUL tmup = new(g) TDBMUL(this);

  tmup->Tdbp = (PTDBASE)Tdbp->Duplicate(g);
  return tmup;
  } // end of Duplicate

/***********************************************************************/
/*  Initializes the table filename list.                               */
/*  Note: tables created by concatenating the file components without  */
/*  specifying the LRECL value (that should be restricted to _MAX_PATH)*/
/*  have a LRECL that is the sum of the lengths of all components.     */
/*  This is why we use a big filename array to take care of that.      */
/***********************************************************************/
bool TDBMUL::InitFileNames(PGLOBAL g)
  {
#define PFNZ  8192
  char *pfn[PFNZ], filename[_MAX_DRIVE+_MAX_DIR+_MAX_FNAME+_MAX_EXT];
  int   rc, n = 0;

  // The sub table may need to refer to the Table original block
  Tdbp->SetTable(To_Table);         // Was not set at construction

  PlugSetPath(filename, Tdbp->GetFile(g), Tdbp->GetPath());

  if (Mul == 1) {
    /*******************************************************************/
    /*  To_File is a multiple name with special characters             */
    /*******************************************************************/
#if defined(WIN32)
    char   drive[_MAX_DRIVE], direc[_MAX_DIR];
    WIN32_FIND_DATA FileData;
    HANDLE hSearch;

    _splitpath(filename, drive, direc, NULL, NULL);

    // Start searching files in the target directory.
    hSearch = FindFirstFile(filename, &FileData);

    if (hSearch == INVALID_HANDLE_VALUE) {
      rc = GetLastError();

      if (rc != ERROR_FILE_NOT_FOUND) {
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
                      FORMAT_MESSAGE_IGNORE_INSERTS,
                      NULL, GetLastError(), 0,
                      (LPTSTR)&filename, sizeof(filename), NULL);
        sprintf(g->Message, MSG(BAD_FILE_HANDLE), filename);
        return true;
        } // endif rc

      goto suite;
      } // endif hSearch

    while (n < PFNZ) {
      strcat(strcat(strcpy(filename, drive), direc), FileData.cFileName);
      pfn[n] = (char*)PlugSubAlloc(g, NULL, strlen(filename) + 1);
      strcpy(pfn[n++], filename);

      if (!FindNextFile(hSearch, &FileData)) {
        rc = GetLastError();

        if (rc != ERROR_NO_MORE_FILES) {
          sprintf(g->Message, MSG(NEXT_FILE_ERROR), rc);
          FindClose(hSearch);
          return true;
          } // endif rc

        break;
        } // endif FindNextFile

      } // endwhile n

    // Close the search handle.
    if (!FindClose(hSearch)) {
      strcpy(g->Message, MSG(SRCH_CLOSE_ERR));
      return true;
      } // endif FindClose

#else   // !WIN32
    struct stat fileinfo;
    char   fn[PATH_MAX], direc[PATH_MAX], pattern[256], ftype[8];
    DIR   *dir;
    struct dirent *entry;

    _splitpath(filename, NULL, direc, pattern, ftype);
    strcat(pattern, ftype);

    // Start searching files in the target directory.
    if (!(dir = opendir(direc))) {
      sprintf(g->Message, MSG(BAD_DIRECTORY), direc, strerror(errno));
      return true;
      } // endif dir

    while ((entry = readdir(dir)) && n < PFNZ) {
      strcat(strcpy(fn, direc), entry->d_name);

      if (lstat(fn, &fileinfo) < 0) {
        sprintf(g->Message, "%s: %s", fn, strerror(errno));
        return true;
      } else if (!S_ISREG(fileinfo.st_mode))
        continue;      // Not a regular file (should test for links)

      /*******************************************************************/
      /*  Test whether the file name matches the table name filter.      */
      /*******************************************************************/
      if (fnmatch(pattern, entry->d_name, 0))
        continue;      // Not a match

      strcat(strcpy(filename, direc), entry->d_name);
      pfn[n] = (char*)PlugSubAlloc(g, NULL, strlen(filename) + 1);
      strcpy(pfn[n++], filename);
      } // endwhile readdir

    // Close the dir handle.
    closedir(dir);
#endif  // !WIN32

  } else {
    /*******************************************************************/
    /*  To_File is the name of a file containing the file name list    */
    /*******************************************************************/
    char *p;
    FILE *stream;

    if (!(stream= global_fopen(g, MSGID_OPEN_MODE_STRERROR, filename, "r")))
      return true;

    while (n < PFNZ) {
      if (!fgets(filename, sizeof(filename), stream)) {
        fclose(stream);
        break;
        } // endif fgets

      p = filename + strlen(filename) - 1;

#if defined(UNIX)
      // Data files can be imported from Windows (having CRLF)
      if (*p == '\n' || *p == '\r') {
        // is this enough for Unix ???
        *p--;          // Eliminate ending CR or LF character

        if (p >= filename)
          // is this enough for Unix ???
          if (*p == '\n' || *p == '\r')
            *p--;    // Eliminate ending CR or LF character

        } // endif p

#else
      if (*p == '\n')
        p--;        // Eliminate ending new-line character
#endif
      // Trim rightmost blanks
      for (; p >= filename && *p == ' '; p--) ;

      *(++p) = '\0';

      // Suballocate the file name
      pfn[n] = (char*)PlugSubAlloc(g, NULL, strlen(filename) + 1);
      strcpy(pfn[n++], filename);
    } // endfor n

  } // endif Mul

#if defined(WIN32)
 suite:
#endif

  if (n) {
    Filenames = (char**)PlugSubAlloc(g, NULL, n * sizeof(char*));

    for (int i = 0; i < n; i++)
      Filenames[i] = pfn[i];

  } else {
    Filenames = (char**)PlugSubAlloc(g, NULL, sizeof(char*));
    Filenames[0] = NULL;
  } // endif n

  NumFiles = n;
  return false;
  } // end of InitFileNames

/***********************************************************************/
/*  The table column list is the sub-table column list.                */
/***********************************************************************/
PCOL TDBMUL::ColDB(PGLOBAL g, PSZ name, int num)
  {
  PCOL cp;

  /*********************************************************************/
  /*  Because special columns are directly added to the MUL block,     */
  /*  make sure that the sub-table has the same column list, before    */
  /*  and after the call to the ColDB function.                        */
  /*********************************************************************/
  Tdbp->SetColumns(Columns);
  cp = Tdbp->ColDB(g, name, num);
  Columns = Tdbp->GetColumns();
  return cp;
} // end of ColDB

/***********************************************************************/
/*  MUL GetProgMax: get the max value for progress information.        */
/***********************************************************************/
int TDBMUL::GetProgMax(PGLOBAL g)
  {
  if (!Filenames && InitFileNames(g))
    return -1;

  return NumFiles;                // This is a temporary setting
  } // end of GetProgMax

/***********************************************************************/
/*  MUL GetProgCur: get the current value for progress information.    */
/***********************************************************************/
int TDBMUL::GetProgCur(void)
  {
  return iFile;                   // This is a temporary setting
  } // end of GetProgMax

/***********************************************************************/
/*  MUL Cardinality: returns table cardinality in number of rows.      */
/*  This function can be called with a null argument to test the       */
/*  availability of Cardinality implementation (1 yes, 0 no).          */
/*  Can be used on Multiple FIX table only.                            */
/***********************************************************************/
int TDBMUL::Cardinality(PGLOBAL g)
  {
  if (!g)
    return Tdbp->Cardinality(g);

  if (!Filenames && InitFileNames(g))
    return -1;

  int n, card = 0;

  for (int i = 0; i < NumFiles; i++) {
    Tdbp->SetFile(g, Filenames[i]);
    Tdbp->ResetSize();

    if ((n = Tdbp->Cardinality(g)) < 0) {
//    strcpy(g->Message, MSG(BAD_CARDINALITY));
      return -1;
      } // endif n

    card += n;
    } // endfor i

  return card;
  } // end of Cardinality

/***********************************************************************/
/*  Sum up the sizes of all sub-tables.                                */
/***********************************************************************/
int TDBMUL::GetMaxSize(PGLOBAL g)
  {
  if (MaxSize < 0) {
    int  i;
    int mxsz;

    if (!Filenames && InitFileNames(g))
      return -1;

    if (Use == USE_OPEN) {
      strcpy(g->Message, MSG(MAXSIZE_ERROR));
      return -1;
    } else
      MaxSize = 0;

    for (i = 0; i < NumFiles; i++) {
      Tdbp->SetFile(g, Filenames[i]);
      Tdbp->ResetSize();

      if ((mxsz = Tdbp->GetMaxSize(g)) < 0) {
        MaxSize = -1;
        return mxsz;
        } // endif mxsz

      MaxSize += mxsz;
      } // endfor i

    } // endif MaxSize

  return MaxSize;
  } // end of GetMaxSize

/***********************************************************************/
/*  Reset read/write position values.                                  */
/***********************************************************************/
void TDBMUL::ResetDB(void)
  {
  for (PCOL colp = Columns; colp; colp = colp->GetNext())
    if (colp->GetAmType() == TYPE_AM_FILID)
      colp->COLBLK::Reset();

  Tdbp->ResetDB();
  } // end of ResetDB

/***********************************************************************/
/*  Returns RowId if b is false or Rownum if b is true.                */
/***********************************************************************/
int TDBMUL::RowNumber(PGLOBAL g, bool b)
  {
  return ((b) ? 0 : Rows)
       + ((iFile < NumFiles) ? Tdbp->RowNumber(g, b) : 1);
  } // end of RowNumber

/***********************************************************************/
/*  MUL Access Method opening routine.                                 */
/*  Open first file, other will be opened sequencially when reading.   */
/***********************************************************************/
bool TDBMUL::OpenDB(PGLOBAL g)
  {
#ifdef DEBTRACE
 htrc("MUL OpenDB: tdbp=%p tdb=R%d use=%d key=%p mode=%d\n",
  this, Tdb_No, Use, To_Key_Col, Mode);
#endif

  if (Use == USE_OPEN) {
    /*******************************************************************/
    /*  Table already open, replace it at its beginning.               */
    /*******************************************************************/
    if (Filenames[iFile = 0]) {
      Tdbp->CloseDB(g);
      Tdbp->SetUse(USE_READY);
      Tdbp->SetFile(g, Filenames[iFile = 0]);
      Tdbp->ResetSize();
      Rows = 0;
      ResetDB();
      return Tdbp->OpenDB(g);  // Re-open with new file name
    } else
      return false;

    } // endif use

  /*********************************************************************/
  /*  We need to calculate MaxSize before opening the query.           */
  /*********************************************************************/
  if (GetMaxSize(g) < 0)
    return true;

  /*********************************************************************/
  /*  Open the first table file of the list.                           */
  /*********************************************************************/
//if (!Filenames && InitFileNames(g))     done in GetMaxSize
//  return true;

  if (Filenames[iFile = 0]) {
    Tdbp->SetFile(g, Filenames[0]);
    Tdbp->SetMode(Mode);
    Tdbp->ResetDB();
    Tdbp->ResetSize();

    if (Tdbp->OpenDB(g))
      return true;

    } // endif *Filenames

  Use = USE_OPEN;
  return false;
  } // end of OpenDB

/***********************************************************************/
/*  ReadDB: Data Base read routine for MUL access method.              */
/***********************************************************************/
int TDBMUL::ReadDB(PGLOBAL g)
  {
  int rc;

  if (NumFiles == 0)
    return RC_EF;
  else if (To_Kindex) {
    /*******************************************************************/
    /*  Reading is by an index table.                                  */
    /*******************************************************************/
    strcpy(g->Message, MSG(NO_INDEX_READ));
    rc = RC_FX;
  } else {
    /*******************************************************************/
    /*  Now start the reading process.                                 */
    /*******************************************************************/
   retry:
    rc = Tdbp->ReadDB(g);

    if (rc == RC_EF) {
      if (Tdbp->GetDef()->GetPseudo() & 1)
        // Total number of rows met so far
        Rows += Tdbp->RowNumber(g) - 1;

      if (++iFile < NumFiles) {
        /***************************************************************/
        /*  Continue reading from next table file.                     */
        /***************************************************************/
        Tdbp->CloseDB(g);
        Tdbp->SetUse(USE_READY);
        Tdbp->SetFile(g, Filenames[iFile]);
        Tdbp->ResetSize();
        ResetDB();

        if (Tdbp->OpenDB(g))     // Re-open with new file name
          return RC_FX;

        goto retry;
        } // endif iFile

    } else if (rc == RC_FX)
      strcat(strcat(strcat(g->Message, " ("), Tdbp->GetFile(g)), ")");

  } // endif To_Kindex

  return rc;
  } // end of ReadDB

/***********************************************************************/
/*  Data Base write routine for MUL access method.                     */
/***********************************************************************/
int TDBMUL::WriteDB(PGLOBAL g)
  {
  return Tdbp->WriteDB(g);
//  strcpy(g->Message, MSG(TABMUL_READONLY));
//  return RC_FX;                    // NIY
  } // end of WriteDB

/***********************************************************************/
/*  Data Base delete line routine for MUL access method.               */
/***********************************************************************/
int TDBMUL::DeleteDB(PGLOBAL g, int irc)
  {
  // When implementing DELETE_MODE InitFileNames must be updated to
  // eliminate CRLF under Windows if the file is read in binary.
  strcpy(g->Message, MSG(TABMUL_READONLY));
  return RC_FX;                                      // NIY
  } // end of DeleteDB

/***********************************************************************/
/*  Data Base close routine for MUL access method.                     */
/***********************************************************************/
void TDBMUL::CloseDB(PGLOBAL g)
  {
  if (NumFiles > 0) {
    Tdbp->CloseDB(g);
    iFile = NumFiles;
    } // endif NumFiles

  } // end of CloseDB

/* --------------------------- Class DIRDEF -------------------------- */

/***********************************************************************/
/*  DefineAM: define specific AM block values from XDB file.           */
/***********************************************************************/
bool DIRDEF::DefineAM(PGLOBAL g, LPCSTR am, int poff)
  {
  Desc = Fn = Cat->GetStringCatInfo(g, "Filename", NULL);
  Incl = (Cat->GetIntCatInfo("Subdir", 0) != 0);
  Huge = (Cat->GetIntCatInfo("Huge", 0) != 0);
  return false;
  } // end of DefineAM

/***********************************************************************/
/*  GetTable: makes a new Table Description Block.                     */
/***********************************************************************/
PTDB DIRDEF::GetTable(PGLOBAL g, MODE m)
  {
#if 0
  if (Huge)
    return new(g) TDBDHR(this);        // Not implemented yet
  else
#endif
  if (Incl)
    return new(g) TDBSDR(this);        // Including sub-directory files
  else
    return new(g) TDBDIR(this);    // Not Including sub-directory files

  } // end of GetTable

/* ------------------------- Class TDBDIR ---------------------------- */

/***********************************************************************/
/*  TABDIR constructors.                                               */
/***********************************************************************/
TDBDIR::TDBDIR(PDIRDEF tdp) : TDBASE(tdp)
  {
  To_File = tdp->Fn;
  iFile = 0;
#if defined(WIN32)
  memset(&FileData, 0, sizeof(_finddata_t));
  Hsearch = -1;
  *Drive = '\0';
#else   // !WIN32
  memset(&Fileinfo, 0, sizeof(struct stat));
  Entry = NULL;
  Dir = NULL;
  Done = false;
  *Pattern = '\0';
#endif  // !WIN32
  *Fpath = '\0';
  *Direc = '\0';
  *Fname = '\0';
  *Ftype = '\0';
  } // end of TDBDIR standard constructor

TDBDIR::TDBDIR(PTDBDIR tdbp) : TDBASE(tdbp)
  {
  To_File = tdbp->To_File;
  iFile = tdbp->iFile;
#if defined(WIN32)
  FileData = tdbp->FileData;
  Hsearch = tdbp->Hsearch;
  strcpy(Drive, tdbp->Drive);
#else   // !WIN32
  Fileinfo = tdbp->Fileinfo;
  Entry = tdbp->Entry;
  Dir = tdbp->Dir;
  Done = tdbp->Done;
  strcpy(Pattern, tdbp->Pattern);
#endif  // !WIN32
  strcpy(Direc, tdbp->Direc);
  strcpy(Fname, tdbp->Fname);
  strcpy(Ftype, tdbp->Ftype);
  } // end of TDBDIR copy constructor

// Method
PTDB TDBDIR::CopyOne(PTABS t)
  {
  PTDB    tp;
  PGLOBAL g = t->G;        // Is this really useful ???

  tp = new(g) TDBDIR(this);
  tp->SetColumns(Columns);
  return tp;
  } // end of CopyOne

/***********************************************************************/
/*  Initialize/get the components of the search file pattern.          */
/***********************************************************************/
char* TDBDIR::Path(PGLOBAL g)
  {
  PCATLG cat = PlgGetCatalog(g);

#if defined(WIN32)
  if (!*Drive) {
    PlugSetPath(Fpath, To_File, cat->GetDataPath());
    _splitpath(Fpath, Drive, Direc, Fname, Ftype);
  } else
    _makepath(Fpath, Drive, Direc, Fname, Ftype);   // Usefull ???

  return Fpath;
#else   // !WIN32
  if (!Done) {
    PlugSetPath(Fpath, To_File, cat->GetDataPath());
    _splitpath(Fpath, NULL, Direc, Fname, Ftype);
    strcat(strcpy(Pattern, Fname), Ftype);
    Done = true;
    } // endif Done

  return Pattern;
#endif  // !WIN32
  } // end of Path

/***********************************************************************/
/*  Allocate DIR column description block.                             */
/***********************************************************************/
PCOL TDBDIR::MakeCol(PGLOBAL g, PCOLDEF cdp, PCOL cprec, int n)
  {
  return new(g) DIRCOL(cdp, this, cprec, n);
  } // end of MakeCol

/***********************************************************************/
/*  DIR GetMaxSize: returns the number of retrieved files.             */
/***********************************************************************/
int TDBDIR::GetMaxSize(PGLOBAL g)
  {
  if (MaxSize < 0) {
    int    n = -1;
#if defined(WIN32)
    int    h;

    // Start searching files in the target directory.
    h = _findfirst(Path(g), &FileData);

    if (h != -1) {
      for (n = 1;; n++)
        if (_findnext(h, &FileData))
          break;

      // Close the search handle.
      _findclose(h);
    } else
      n = 0;

#else   // !WIN32
    Path(g);

    // Start searching files in the target directory.
    if (!(Dir = opendir(Direc))) {
      sprintf(g->Message, MSG(BAD_DIRECTORY), Direc, strerror(errno));
      return -1;
      } // endif dir

    while ((Entry = readdir(Dir))) {
      strcat(strcpy(Fpath, Direc), Entry->d_name);

      if (lstat(Fpath, &Fileinfo) < 0) {
        sprintf(g->Message, "%s: %s", Fpath, strerror(errno));
        return -1;
      } else if (S_ISREG(Fileinfo.st_mode))
        // Test whether the file name matches the table name filter
        if (!fnmatch(Pattern, Entry->d_name, 0))
          n++;      // We have a match

      } // endwhile Entry

    // Close the DIR handle.
    closedir(Dir);
#endif  // !WIN32
    MaxSize = n;
    } // endif MaxSize

  return MaxSize;
  } // end of GetMaxSize

/***********************************************************************/
/*  DIR Access Method opening routine.                                 */
/*  Open first file, other will be opened sequencially when reading.   */
/***********************************************************************/
bool TDBDIR::OpenDB(PGLOBAL g)
  {
#ifdef DEBTRACE
 htrc("DIR OpenDB: tdbp=%p tdb=R%d use=%d mode=%d\n",
  this, Tdb_No, Use, Mode);
#endif

  if (Use == USE_OPEN) {
    /*******************************************************************/
    /*  Table already open, reopen it.                                 */
    /*******************************************************************/
    CloseDB(g);
    SetUse(USE_READY);
    } // endif use

  Use = USE_OPEN;
#if !defined(WIN32)
  Path(g);                          // Be sure it is done
  Dir = NULL;                       // For ReadDB
#endif   // !WIN32
  return false;
  } // end of OpenDB

/***********************************************************************/
/*  Data Base read routine for DIR access method.                      */
/***********************************************************************/
int TDBDIR::ReadDB(PGLOBAL g)
  {
  int rc = RC_OK;

#if defined(WIN32)
  if (Hsearch == -1) {
    /*******************************************************************/
    /*  Start searching files in the target directory. The use of the  */
    /*  Path function is required when called from TDBSDR.             */
    /*******************************************************************/
    Hsearch = _findfirst(Path(g), &FileData);

    if (Hsearch == -1)
      rc = RC_EF;
    else
      iFile++;

  } else {
    if (_findnext(Hsearch, &FileData)) {
      // Restore file name and type pattern
      _splitpath(To_File, NULL, NULL, Fname, Ftype);
      rc = RC_EF;
    } else
      iFile++;

  } // endif Hsearch

  if (rc == RC_OK)
    _splitpath(FileData.name, NULL, NULL, Fname, Ftype);

#else   // !Win32
  rc = RC_NF;

  if (!Dir)
    // Start searching files in the target directory.
    if (!(Dir = opendir(Direc))) {
      sprintf(g->Message, MSG(BAD_DIRECTORY), Direc, strerror(errno));
      rc = RC_FX;
      } // endif dir

  while (rc == RC_NF)
    if ((Entry = readdir(Dir))) {
      // We need the Fileinfo structure to get info about the file
      strcat(strcpy(Fpath, Direc), Entry->d_name);

      if (lstat(Fpath, &Fileinfo) < 0) {
        sprintf(g->Message, "%s: %s", Fpath, strerror(errno));
        rc = RC_FX;
      } else if (S_ISREG(Fileinfo.st_mode))
        // Test whether the file name matches the table name filter
        if (!fnmatch(Pattern, Entry->d_name, 0)) {
          iFile++;      // We have a match
          _splitpath(Entry->d_name, NULL, NULL, Fname, Ftype);
          rc = RC_OK;
          } // endif fnmatch

    } else {
      // Restore file name and type pattern
      _splitpath(To_File, NULL, NULL, Fname, Ftype);
      rc = RC_EF;
    } // endif Entry

#endif  // !WIN32

  return rc;
  } // end of ReadDB

/***********************************************************************/
/*  Data Base write routine for DIR access method.                     */
/***********************************************************************/
int TDBDIR::WriteDB(PGLOBAL g)
  {
  strcpy(g->Message, MSG(TABDIR_READONLY));
  return RC_FX;                    // NIY
  } // end of WriteDB

/***********************************************************************/
/*  Data Base delete line routine for DIR access method.               */
/***********************************************************************/
int TDBDIR::DeleteDB(PGLOBAL g, int irc)
  {
  strcpy(g->Message, MSG(TABDIR_READONLY));
  return RC_FX;                                      // NIY
  } // end of DeleteDB

/***********************************************************************/
/*  Data Base close routine for MUL access method.                     */
/***********************************************************************/
void TDBDIR::CloseDB(PGLOBAL g)
  {
#if defined(WIN32)
  // Close the search handle.
  _findclose(Hsearch);
  Hsearch = -1;
#else   // !WIN32
  // Close the DIR handle.
  closedir(Dir);
  Dir = NULL;
#endif  // !WIN32
  iFile = 0;
  } // end of CloseDB

// ------------------------ DIRCOL functions ----------------------------

/***********************************************************************/
/*  DIRCOL public constructor.                                         */
/***********************************************************************/
DIRCOL::DIRCOL(PCOLDEF cdp, PTDB tdbp, PCOL cprec, int i, PSZ am)
  : COLBLK(cdp, tdbp, i)
  {
  if (cprec) {
    Next = cprec->GetNext();
    cprec->SetNext(this);
  } else {
    Next = tdbp->GetColumns();
    tdbp->SetColumns(this);
  } // endif cprec

  // Set additional DIR access method information for column.
  N = cdp->GetOffset();
  } // end of DIRCOL constructor

/***********************************************************************/
/*  DIRCOL constructor used for copying columns.                       */
/*  tdbp is the pointer to the new table descriptor.                   */
/***********************************************************************/
DIRCOL::DIRCOL(DIRCOL *col1, PTDB tdbp) : COLBLK(col1, tdbp)
  {
  N = col1->N;
  } // end of DIRCOL copy constructor

/***********************************************************************/
/*  ReadColumn: what this routine does is to access the information    */
/*  corresponding to this column and convert it to buffer type.        */
/***********************************************************************/
void DIRCOL::ReadColumn(PGLOBAL g)
  {
  PTDBDIR tdbp = (PTDBDIR)To_Tdb;

#ifdef DEBTRACE
 fprintf(debug,
   "DIR ReadColumn: col %s R%d use=%.4X status=%.4X type=%d N=%d\n",
  Name, tdbp->GetTdb_No(), ColUse, Status, Buf_Type, N);
#endif

  /*********************************************************************/
  /*  Retrieve the information corresponding to the column number.     */
  /*********************************************************************/
  switch (N) {
#if defined(WIN32)
    case  0: Value->SetValue_psz(tdbp->Drive);                  break;
#endif   // WIN32
    case  1: Value->SetValue_psz(tdbp->Direc);                  break;
    case  2: Value->SetValue_psz(tdbp->Fname);                  break;
    case  3: Value->SetValue_psz(tdbp->Ftype);                  break;
#if defined(WIN32)
    case  4: Value->SetValue((int)tdbp->FileData.attrib);      break;
    case  5: Value->SetValue((int)tdbp->FileData.size);        break;
    case  6: Value->SetValue((int)tdbp->FileData.time_write);  break;
    case  7: Value->SetValue((int)tdbp->FileData.time_create); break;
    case  8: Value->SetValue((int)tdbp->FileData.time_access); break;
#else   // !WIN32
    case  4: Value->SetValue((int)tdbp->Fileinfo.st_mode);     break;
    case  5: Value->SetValue((int)tdbp->Fileinfo.st_size);     break;
    case  6: Value->SetValue((int)tdbp->Fileinfo.st_mtime);    break;
    case  7: Value->SetValue((int)tdbp->Fileinfo.st_ctime);    break;
    case  8: Value->SetValue((int)tdbp->Fileinfo.st_atime);    break;
    case  9: Value->SetValue((int)tdbp->Fileinfo.st_uid);      break;
    case 10: Value->SetValue((int)tdbp->Fileinfo.st_gid);      break;
#endif  // !WIN32
    default:
      sprintf(g->Message, MSG(INV_DIRCOL_OFST), N);
      longjmp(g->jumper[g->jump_level], GetAmType());
    } // endswitch N

  } // end of ReadColumn

/* ------------------------- Class TDBSDR ---------------------------- */

/***********************************************************************/
/*  TABSDR copy constructors.                                          */
/***********************************************************************/
TDBSDR::TDBSDR(PTDBSDR tdbp) : TDBDIR(tdbp)
  {
  Sub = tdbp->Sub;
  } // end of TDBSDR copy constructor

// Method
PTDB TDBSDR::CopyOne(PTABS t)
  {
  PTDB    tp;
  PGLOBAL g = t->G;        // Is this really useful ???

  tp = new(g) TDBSDR(this);
  tp->SetColumns(Columns);
  return tp;
  } // end of CopyOne

/***********************************************************************/
/*  SDR GetMaxSize: returns the number of retrieved files.             */
/***********************************************************************/
int TDBSDR::GetMaxSize(PGLOBAL g)
  {
  if (MaxSize < 0) {
    Path(g);
    MaxSize = FindInDir(g);
    } // endif MaxSize

  return MaxSize;
  } // end of GetMaxSize

/***********************************************************************/
/*  SDR GetMaxSize: returns the number of retrieved files.             */
/***********************************************************************/
int TDBSDR::FindInDir(PGLOBAL g)
  {
  int   n = 0;
  size_t m = strlen(Direc);

  // Start searching files in the target directory.
#if defined(WIN32)
  int h = _findfirst(Path(g), &FileData);

  if (h != -1) {
    for (n = 1;; n++)
      if (_findnext(h, &FileData))
        break;

    // Close the search handle.
    _findclose(h);
    } // endif h

  // Now search files in sub-directories.
  _makepath(Fpath, Drive, Direc, "*", "");
  h = _findfirst(Fpath, &FileData);

  if (h != -1) {
    while (true) {
      if (FileData.attrib & _A_SUBDIR && *FileData.name != '.') {
        // Look in the name sub-directory
        strcat(strcat(Direc, FileData.name), "\\");
        n += FindInDir(g);
        Direc[m] = '\0';         // Restore path
        } // endif SUBDIR

      if (_findnext(h, &FileData))
        break;

      } // endwhile

    // Close the search handle.
    _findclose(h);
    } // endif h
#else   // !WIN32
  int k;
  DIR *dir = opendir(Direc);

  if (!dir) {
    sprintf(g->Message, MSG(BAD_DIRECTORY), Direc, strerror(errno));
    return -1;
    } // endif dir

  while ((Entry = readdir(dir))) {
    strcat(strcpy(Fpath, Direc), Entry->d_name);

    if (lstat(Fpath, &Fileinfo) < 0) {
      sprintf(g->Message, "%s: %s", Fpath, strerror(errno));
      return -1;
    } else if (S_ISDIR(Fileinfo.st_mode) && *Entry->d_name != '.') {
      // Look in the name sub-directory
      strcat(strcat(Direc, Entry->d_name), "/");

      if ((k = FindInDir(g)) < 0)
        return k;
      else
        n += k;

      Direc[m] = '\0';         // Restore path
    } else if (S_ISREG(Fileinfo.st_mode))
      // Test whether the file name matches the table name filter
      if (!fnmatch(Pattern, Entry->d_name, 0))
        n++;      // We have a match

    } // endwhile readdir

  // Close the DIR handle.
  closedir(dir);
#endif  // !WIN32

  return n;
  } // end of FindInDir

/***********************************************************************/
/*  DIR Access Method opening routine.                                 */
/*  Open first file, other will be opened sequencially when reading.   */
/***********************************************************************/
bool TDBSDR::OpenDB(PGLOBAL g)
  {
  if (!Sub) {
    Path(g);
    Sub = (PSUBDIR)PlugSubAlloc(g, NULL, sizeof(SUBDIR));
    Sub->Next = NULL;
    Sub->Prev = NULL;
#if defined(WIN32)
    Sub->H = -1;
    Sub->Len = strlen(Direc);
#else   // !WIN32
    Sub->D = NULL;
    Sub->Len = 0;
#endif  // !WIN32
    } // endif To_Sub

  return TDBDIR::OpenDB(g);
  } // end of OpenDB

/***********************************************************************/
/*  Data Base read routine for SDR access method.                      */
/***********************************************************************/
int TDBSDR::ReadDB(PGLOBAL g)
  {
  int rc;

#if defined(WIN32)
 again:
  rc = TDBDIR::ReadDB(g);

  if (rc == RC_EF) {
    // Are there more files in sub-directories
   retry:
    do {
      if (Sub->H == -1) {
        _makepath(Fpath, Drive, Direc, "*", "");
        Sub->H = _findfirst(Fpath, &FileData);
      } else if (_findnext(Sub->H, &FileData)) {
        _findclose(Sub->H);
        Sub->H = -1;
        *FileData.name = '\0';
        } // endif findnext

      } while(*FileData.name == '.');

    if (Sub->H == -1) {
      // No more sub-directories. Are we in a sub-directory?
      if (!Sub->Prev)
        return rc;               // No, all is finished

      // here we must continue in the parent directory
      Sub = Sub->Prev;
      goto retry;
    } else {
      // Search next sub-directory
      Direc[Sub->Len] = '\0';

      if (!Sub->Next) {
        PSUBDIR sup;

        sup = (PSUBDIR)PlugSubAlloc(g, NULL, sizeof(SUBDIR));
        sup->Next = NULL;
        sup->Prev = Sub;
        sup->H = -1;
        Sub->Next = sup;
        } // endif Next

      Sub = Sub->Next;
      strcat(strcat(Direc, FileData.name), "\\");
      Sub->Len = strlen(Direc);

      // Reset Hsearch used by TDBDIR::ReadDB
      _findclose(Hsearch);
      Hsearch = -1;
      goto again;
    } // endif H

    } // endif rc
#else   // !WIN32
  rc = RC_NF;

 again:
  if (!Sub->D)
    // Start searching files in the target directory.
    if (!(Sub->D = opendir(Direc))) {
      sprintf(g->Message, MSG(BAD_DIRECTORY), Direc, strerror(errno));
      rc = RC_FX;
      } // endif dir

  while (rc == RC_NF)
    if ((Entry = readdir(Sub->D))) {
      // We need the Fileinfo structure to get info about the file
      strcat(strcpy(Fpath, Direc), Entry->d_name);

      if (lstat(Fpath, &Fileinfo) < 0) {
        sprintf(g->Message, "%s: %s", Fpath, strerror(errno));
        rc = RC_FX;
      } else if (S_ISDIR(Fileinfo.st_mode) && *Entry->d_name != '.') {
        // Look in the name sub-directory
        if (!Sub->Next) {
          PSUBDIR sup;

          sup = (PSUBDIR)PlugSubAlloc(g, NULL, sizeof(SUBDIR));
          sup->Next = NULL;
          sup->Prev = Sub;
          Sub->Next = sup;
          } // endif Next

        Sub = Sub->Next;
        Sub->D = NULL;
        Sub->Len = strlen(Direc);
        strcat(strcat(Direc, Entry->d_name), "/");
        goto again;
      } else if (S_ISREG(Fileinfo.st_mode))
        // Test whether the file name matches the table name filter
        if (!fnmatch(Pattern, Entry->d_name, 0)) {
          iFile++;      // We have a match
          _splitpath(Entry->d_name, NULL, NULL, Fname, Ftype);
          rc = RC_OK;
          } // endif fnmatch

    } else {
      // No more files. Close the DIR handle.
      closedir(Sub->D);

      // Are we in a sub-directory?
      if (Sub->Prev) {
        // Yes, we must continue in the parent directory
        Direc[Sub->Len] = '\0';
        Sub = Sub->Prev;
      } else
        rc = RC_EF;              // No, all is finished

    } // endif Entry

#endif  // !WIN32

  return rc;
  } // end of ReadDB

#if 0
/* ------------------------- Class TDBDHR ---------------------------- */

/***********************************************************************/
/*  TABDHR constructors.                                               */
/***********************************************************************/
TDBDHR::TDBDHR(PDHRDEF tdp) : TDBASE(tdp)
  {
  memset(&FileData, 0, sizeof(WIN32_FIND_DATA));
  Hsearch = INVALID_HANDLE_VALUE;
  iFile = 0;
  *Drive = '\0';
  *Direc = '\0';
  *Fname = '\0';
  *Ftype = '\0';
  } // end of TDBDHR standard constructor

TDBDHR::TDBDHR(PTDBDHR tdbp) : TDBASE(tdbp)
  {
  FileData = tdbp->FileData;
  Hsearch = tdbp->Hsearch;
  iFile = tdbp->iFile;
  strcpy(Drive, tdbp->Drive);
  strcpy(Direc, tdbp->Direc);
  strcpy(Fname, tdbp->Fname);
  strcpy(Ftype, tdbp->Ftype);
  } // end of TDBDHR copy constructor

// Method
PTDB TDBDHR::CopyOne(PTABS t)
  {
  PTDB    tp;
  PGLOBAL g = t->G;        // Is this really useful ???

  tp = new(g) TDBDHR(this);
  tp->Columns = Columns;
  return tp;
  } // end of CopyOne

/***********************************************************************/
/*  Allocate DHR column description block.                             */
/***********************************************************************/
PCOL TDBDHR::MakeCol(PGLOBAL g, PCOLDEF cdp, PCOL cprec, int n)
  {
  return new(g) DHRCOL(cdp, this, cprec, n);
  } // end of MakeCol

/***********************************************************************/
/*  DHR GetMaxSize: returns the number of retrieved files.             */
/***********************************************************************/
int TDBDHR::GetMaxSize(PGLOBAL g)
  {
  if (MaxSize < 0) {
    char    filename[_MAX_PATH];
    int     i, rc;
    int    n = -1;
    HANDLE  h;
    PDBUSER dup = PlgGetUser(g);

    PlugSetPath(filename, To_File, dup->Path);

    // Start searching files in the target directory.
    h = FindFirstFile(filename, &FileData);

    if (h == INVALID_HANDLE_VALUE) {
      switch (rc = GetLastError()) {
        case ERROR_NO_MORE_FILES:
        case ERROR_FILE_NOT_FOUND:
          n = 0;
          break;
        default:
          FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
                        FORMAT_MESSAGE_IGNORE_INSERTS,
                        NULL, rc, 0,
                        (LPTSTR)&filename, sizeof(filename), NULL);
          sprintf(g->Message, MSG(BAD_FILE_HANDLE), filename);
        } // endswitch rc

    } else {
      for (n = 1;; n++)
        if (!FindNextFile(h, &FileData)) {
          rc = GetLastError();

          if (rc != ERROR_NO_MORE_FILES) {
            sprintf(g->Message, MSG(NEXT_FILE_ERROR), rc);
            n = -1;
            } // endif rc

          break;
          } // endif FindNextFile

      // Close the search handle.
      if (!FindClose(h) && n != -1)
        strcpy(g->Message, MSG(SRCH_CLOSE_ERR));

    } // endif Hsearch

    MaxSize = n;
    } // endif MaxSize

  return MaxSize;
  } // end of GetMaxSize

/***********************************************************************/
/*  DHR Access Method opening routine.                                 */
/*  Open first file, other will be opened sequencially when reading.   */
/***********************************************************************/
bool TDBDHR::OpenDB(PGLOBAL g)
  {
#ifdef DEBTRACE
 htrc("DHR OpenDB: tdbp=%p tdb=R%d use=%d mode=%d\n",
  this, Tdb_No, Use, Mode);
#endif

  if (Use == USE_OPEN) {
    /*******************************************************************/
    /*  Table already open, reopen it.                                 */
    /*******************************************************************/
    CloseDB(g);
    SetUse(USE_READY);
    } // endif use

  /*********************************************************************/
  /*  Direct access needed for join or sorting.                        */
  /*********************************************************************/
  if (NeedIndexing(g)) {
    // Direct access of DHR tables is not implemented yet
    sprintf(g->Message, MSG(NO_DIR_INDX_RD), "DHR");
    return true;
    } // endif NeedIndexing

  Use = USE_OPEN;
  return false;
  } // end of OpenDB

/***********************************************************************/
/*  Data Base read routine for DHR access method.                      */
/***********************************************************************/
int TDBDHR::ReadDB(PGLOBAL g)
  {
  int   rc = RC_OK;
  DWORD erc;

  if (Hsearch == INVALID_HANDLE_VALUE) {
    char   *filename[_MAX_PATH];
    PDBUSER dup = PlgGetUser(g);

    PlugSetPath(filename, To_File, dup->Path);
    _splitpath(filename, Drive, Direc, NULL, NULL);

    /*******************************************************************/
    /*  Start searching files in the target directory.                 */
    /*******************************************************************/
    Hsearch = FindFirstFile(filename, &FileData);

    if (Hsearch != INVALID_HANDLE_VALUE)
      iFile = 1;
    else switch (erc = GetLastError()) {
      case ERROR_NO_MORE_FILES:
      case ERROR_FILE_NOT_FOUND:
//    case ERROR_PATH_NOT_FOUND:               ???????
        rc = RC_EF;
        break;
      default:
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
                      FORMAT_MESSAGE_IGNORE_INSERTS,
                      NULL, erc,  0,
                      (LPTSTR)&filename, sizeof(filename), NULL);
        sprintf(g->Message, MSG(BAD_FILE_HANDLE), filename);
        rc = RC_FX;
      } // endswitch erc

  } else {
    if (!FindNextFile(Hsearch, &FileData)) {
      DWORD erc = GetLastError();

      if (erc != ERROR_NO_MORE_FILES) {
        sprintf(g->Message, MSG(NEXT_FILE_ERROR), erc);
        FindClose(Hsearch);
        rc = RC_FX;
      } else
        rc = RC_EF;

    } else
      iFile++;

  } // endif Hsearch

  if (rc == RC_OK)
    _splitpath(FileData.cFileName, NULL, NULL, Fname, Ftype);

  return rc;
  } // end of ReadDB

/***********************************************************************/
/*  Data Base close routine for MUL access method.                     */
/***********************************************************************/
void TDBDHR::CloseDB(PGLOBAL g)
  {
  // Close the search handle.
  if (!FindClose(Hsearch)) {
    strcpy(g->Message, MSG(SRCH_CLOSE_ERR));
    longjmp(g->jumper[g->jump_level], GetAmType());
    } // endif FindClose

  iFile = 0;
  Hsearch = INVALID_HANDLE_VALUE;
  } // end of CloseDB

// ------------------------ DHRCOL functions ----------------------------

/***********************************************************************/
/*  DHRCOL public constructor.                                         */
/***********************************************************************/
DHRCOL::DHRCOL(PCOLDEF cdp, PTDB tdbp, PCOL cprec, int i, PSZ am)
  : COLBLK(cdp, tdbp, i)
  {
  if (cprec) {
    Next = cprec->GetNext();
    cprec->SetNext(this);
  } else {
    Next = tdbp->GetColumns();
    tdbp->SetColumns(this);
  } // endif cprec

  // Set additional DHR access method information for column.
  N = cdp->GetOffset();
  } // end of DOSCOL constructor

/***********************************************************************/
/*  DHRCOL constructor used for copying columns.                       */
/*  tdbp is the pointer to the new table descriptor.                   */
/***********************************************************************/
DHRCOL::DHRCOL(DHRCOL *col1, PTDB tdbp) : COLBLK(col1, tdbp)
  {
  N = col1->N;
  } // end of DHRCOL copy constructor

/***********************************************************************/
/*  ReadColumn: what this routine does is to access the information    */
/*  corresponding to this column and convert it to buffer type.        */
/***********************************************************************/
void DHRCOL::ReadColumn(PGLOBAL g)
  {
  int     rc;
  PTDBDHR tdbp = (PTDBDHR)To_Tdb;

#ifdef DEBTRACE
 fprintf(debug,
   "DHR ReadColumn: col %s R%d use=%.4X status=%.4X type=%d N=%d\n",
  Name, tdbp->GetTdb_No(), ColUse, Status, Buf_Type, N);
#endif

  /*********************************************************************/
  /*  Retrieve the information corresponding to the column number.     */
  /*********************************************************************/
  switch (N) {
    case 0:                                // Drive
      Value->SetValue(Drive, _MAX_DRIVE);
      break;
    case 1:                                // Path
      Value->SetValue(Direc, _MAX_DHR);
      break;
    case 2:                                // Name
      Value->SetValue(Fname, _MAX_FNAME);
      break;
    case 3:                                // Extention
      Value->SetValue(Ftype, _MAX_EXT);
      break;
    case 4:                                // Extention
      Value->SetValue(tdbp->FileData.cAlternateFileName, 14);
      break;
    case 5:
      Value->SetValue(tdbp->FileData.dwFileAttributes);
      break;
    case 6:
      Value->SetValue(..................
  } // end of ReadColumn
#endif // 0

