//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "FileFindThread.h"
#pragma package(smart_init)
//---------------------------------------------------------------------------

//   Important: Methods and properties of objects in VCL can only be
//   used in a method called using Synchronize, for example:
//
//      Synchronize(&UpdateCaption);
//
//   where UpdateCaption could look like:
//
//      void __fastcall TFileFindThread::UpdateCaption()
//      {
//        Form1->Caption = "Updated in a thread";
//      }
//---------------------------------------------------------------------------

/*
  Default constructor. Call the base class with a boolean to indicate
  if the thread should be created in suspended state. This will allow
  the client to set the attributes of the thread before it starts to
  run.
*/
 __fastcall TFileFindThread::TFileFindThread(void) : TThread(true)
{
  FFileSpecs = 0;
  FFileList = 0;
  FCallbackProc = 0;
  FCompletedProc = 0;
  FID = -1;
  FIncludeSubdirectories = false;

  OnTerminate = OnThreadTerminate; // this is called when the thread finishes
  FreeOnTerminate = true;          // let the thread "free" itself when done
}
//---------------------------------------------------------------------------

 __fastcall TFileFindThread::~TFileFindThread(void)
{
  delete FFileSpecs;
}

/*
  Function that is called when the thread has finished (or been terminated)
*/
void __fastcall TFileFindThread::OnThreadTerminate(TObject *Sender)
{
  if (FCompletedProc)
    FCompletedProc(FID);
}

/*
  This starts the thread.
*/
void __fastcall TFileFindThread::Execute()
{
  FindFiles(FDirectory, *FFileSpecs, *FFileList, FIncludeSubdirectories);
}
//---------------------------------------------------------------------------

/*
  Proxy for the callback. It must be Synchronized, and therefore, has
  a specific signature.
*/
void __fastcall TFileFindThread::CallbackProxy(void)
{
  if (FCallbackProc)
    FCallbackProc(FFInfo, FID);
}

/*
  Proxy for the callback. It must be Synchronized, and therefore, has
  a specific signature. (Actually, if called from the OnTerminate handler,
  it doesn't need to be Synchronized.)
*/
void __fastcall TFileFindThread::CompletedProxy(void)
{
  if (FCompletedProc)
    FCompletedProc(FID);
}

/*
  Retrieves the names of all of the files in 'Directory' that match the
  file specification 'FileSpec' and adds them to 'List'.
*/
int TFileFindThread::CatalogFiles(const AnsiString& Directory, const AnsiString& FileSpec, TStringList& List)
{
  TSearchRec finfo;

  int count = 0;
  AnsiString dir = IncludeTrailingPathDelimiter(Directory);

    // Prime the loop (it's like an iterator paradigm)
  int done = FindFirst(dir + FileSpec, faAnyFile, finfo);
  while ((done == 0) && (!Terminated))
  {
    if ((finfo.Name != ".") && (finfo.Name != ".."))
    {
        // Convert file date format to a "better" format
      FFInfo.DateModified = FileDateToDateTime(finfo.Time);

      FFInfo.Name = finfo.Name;
      FFInfo.Extension = ExtractFileExt(finfo.Name);
      FFInfo.Path = Directory;
      FFInfo.Size = finfo.Size;

        // If it's a directory (folder)
      if ((finfo.Attr & faDirectory) != 0)
      {
        FFInfo.IsFolder = true;
        FFInfo.Extension = "Folder";
        List.Add(dir + finfo.Name);
      }
      else
      {
        FFInfo.IsFolder = false;
        List.Add(dir + finfo.Name);
      }

      count++;

        // Callback client with information
      if (FCallbackProc)
        Synchronize(&CallbackProxy);
    }
    done = FindNext(finfo);
    Application->ProcessMessages();
  }
  FindClose(finfo);
  return count;
}

/*
  This function recursively traverses through a file system looking for
  specific files.
  Directory - The directory to start in (e.g. D:\Data)
  FileSpecs - A list of file specifications (e.g. *.txt, *.ini, abc???.*)
  List - Found filenames will be added to this container.
  IncludeSudirectories - Determines whether or not to recurse into subdirectories.
*/
int TFileFindThread::FindFiles(const AnsiString& Directory, TStringList& FileSpecs, TStringList& List, bool IncludeSubdirectories)
{
  TSearchRec finfo;
  AnsiString filespec;

  AnsiString dir = IncludeTrailingPathDelimiter(Directory);
  AnsiString dirspec = dir + "*.*";


    // Get all files for each spec
  int count = 0;
  for (int i = 0; i < FileSpecs.Count; i++)
  {
      // Get the next filespec
    filespec = FileSpecs[i];

      // Find matching files in this directory
    count = count + CatalogFiles(dir, filespec, List);
  }

    // Always search for *.* when looking for directories
  if (IncludeSubdirectories)
  {
    int done = FindFirst(dirspec, faAnyFile, finfo);
    while ((done == 0) && (!Terminated))
    {
        // Is this a directory? If so, recurse into it
      if (((finfo.Attr & faDirectory) != 0) && (finfo.Name != ".") && (finfo.Name != ".."))
      {
        AnsiString subdir = Format("%s%s", ARRAYOFCONST((dir, finfo.Name)));
        count += FindFiles(subdir, FileSpecs, List, IncludeSubdirectories);
      }
      done = FindNext(finfo);
      Application->ProcessMessages();
    }
    FindClose(finfo);
  }
  return count;
}

void TFileFindThread::SetID(long ID)
{
  FID = ID;
}

void TFileFindThread::SetFileList(TStringList* list)
{
  FFileList = list;
}

void TFileFindThread::SetFileSpecs(TStringList* filespecs)
{
  FFileSpecs = new TStringList();
  FFileSpecs->Assign(filespecs);
}

void TFileFindThread::SetDirectory(const AnsiString& directory)
{
  FDirectory = directory;
}

void TFileFindThread::SetCallbackProc(CALLBACKPROC callback)
{
  FCallbackProc = callback;
}

void TFileFindThread::SetCompletedProc(COMPLETEDPROC callback)
{
  FCompletedProc = callback;
}

void TFileFindThread::SetIncludeSubdirectories(bool Include)
{
  FIncludeSubdirectories = Include;
}