//$Id: CompDir.cpp,v 1.44 2007/03/08 19:20:53 markus Rel $

//PROJECT     : DirComp
//SUBSYSTEM   : CompDir
//REFERENCES  :
//TODO        :
//BUGS        :
//REVISION    : $Revision: 1.44 $
//AUTHOR      : Markus Schwab
//CREATED     : 29.7.1999
//COPYRIGHT   : Copyright (C) 1999 - 2007

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


#ifdef _MSC_VER
#pragma warning(disable:4786) // disable warning about truncating debug info
#endif

#include <cstdlib>
#include <limits.h>

#include <vector>
#include <fstream>

#include <dircomp-cfg.h>

#include <YGP/File.h>
#include <YGP/Trace.h>
#include <YGP/ATStamp.h>
#include <YGP/DirSrch.h>
#include <YGP/IDirSrch.h>
#include <YGP/RDirSrch.h>
#include <YGP/XDirSrch.h>
#include <YGP/PathSrch.h>
#include <YGP/FileRExp.h>

#include "CompDir.h"


static const unsigned int LEN_LINE    = 512;


//-----------------------------------------------------------------------------
/// Defaultconstructor
//-----------------------------------------------------------------------------
CompareDirs::CompareDirs ()
    : iOption (NO_OPTION), cOrigLen (0), datBegin (0), datEnd (time_t (INT_MAX))
      , dirOrig (NULL), dirComp (NULL) {
   TRACE9 ("CompareDirs::CompareDirs ()");
}

//-----------------------------------------------------------------------------
/// Copyconstructor
/// \param o: Object to copy
//-----------------------------------------------------------------------------
CompareDirs::CompareDirs (const CompareDirs& o)
    : iOption (o.iOption) , cOrigLen (o.cOrigLen), files (o.files)
      , dirs (o.dirs), datBegin (o.datBegin), datEnd (o.datEnd)
      , dirOrig (o.dirOrig), dirComp (o.dirComp) {
   TRACE9 ("CompareDirs::CompareDirs (const CompareDirs& o)");
}

//-----------------------------------------------------------------------------
/// Constructor
/// \param dirOrig: Original directory
/// \param dirOrig: Comparative directory
/// \param option: Options for compare
/// \pre Both strings are not empty
//-----------------------------------------------------------------------------
CompareDirs::CompareDirs (YGP::IDirectorySearch& dirOrig,
                          YGP::IDirectorySearch& dirComp, const int option)
   : iOption (option), datBegin (0), datEnd (time_t (-1)) {
   TRACE9 ("CompareDirs::CompareDirs (const std::string&, const std::string&, const int))");
   Check1 (dirOrig.isValid ());
   Check1 (dirComp.isValid ());

   addIncludeDirs ("*");

   setOriginalDir (dirOrig);
   setCompareDir (dirComp);
}

//-----------------------------------------------------------------------------
/// Destructor
//-----------------------------------------------------------------------------

CompareDirs::~CompareDirs () {
   TRACE9 ("CompareDirs::~CompareDirs ()");
}


//-----------------------------------------------------------------------------
/// Assignment-operator
/// \param o: Object to copy
/// \returns \c const CompareDirs&: *this
//-----------------------------------------------------------------------------
const CompareDirs& CompareDirs::operator= (const CompareDirs& o) {
   TRACE9 ("CompareDirs::operator= (const CompareDirs&)");

   if (&o != this) {
      iOption = o.iOption;
      dirOrig = o.dirOrig;
      dirComp = o.dirComp;
      cOrigLen = o.cOrigLen;
      files = o.files;
      dirs = o.dirs;
      datBegin = o.datBegin;
      datEnd = o.datEnd;
   } // endif
   return *this;
}

//-----------------------------------------------------------------------------
/// Sets the original-directory for the compare; remove trailing
/// directory-chars
/// \param dir: Original directory
//-----------------------------------------------------------------------------
void CompareDirs::setOriginalDir (YGP::IDirectorySearch& dir) {
   TRACE8 ("CompareDirs::setOriginalDir (YGP::IDirectorySearch&) - "
           << dir.getSearchValue () << " (" << dir.isValid () << ')');
   Check1 (dir.isValid ());
   dirOrig = &dir;

   cOrigLen = dirOrig->getSearchValue ().length () + 1; Check3 (cOrigLen);
}

//-----------------------------------------------------------------------------
/// Sets the comparative-directory for the compare; remove trailing
/// directory-chars
/// \param dir: Comparative directory
//-----------------------------------------------------------------------------
void CompareDirs::setCompareDir (YGP::IDirectorySearch& dir) {
   TRACE8 ("CompareDirs::setComparelDir (YGP::IDirectorySearch&) - " << dir.getSearchValue ());
   Check1 (dir.isValid ());
   dirComp = &dir;
}

//-----------------------------------------------------------------------------
/// Converts the time from a const char* into a time_t
/// \param pTime: Pointer to characters forming time
/// \param time: time_t to create
/// \returns \c int: 0 OK
/// \pre pTime is ASCIIZ-string
//-----------------------------------------------------------------------------
int CompareDirs::setTime (const char* pTime, time_t& time) {
   Check1 (pTime);
   struct tm t;
   memset (static_cast<char*> ((void*)&t), 0, sizeof (t));

   TRACE3 ("CompareDirs::setTime (const char*, time_t&-> Set time " << pTime);

   YGP::ATimestamp input (pTime);
   if (!input.isDefined ())
      return -1;

   if (input.getYear () < 100)         // Correct input according to struct tm
      // TODO: FIXME?? Can that be the first year-2100-bug?
      input.add (0, 0, input.getYear () < 80 ? 2000 : 1900);

   time = input.toSysTime ();
   return time == -1;
}

//-----------------------------------------------------------------------------
/// Checks the integrity of this object
/// \returns \c int: Status
///     - 0: OK
///     - 1: A path to compare is no directory
///     - 2: cOrigLen has no length
///     - 3: Invalid options
//-----------------------------------------------------------------------------
int CompareDirs::checkIntegrity () const {
   TRACE1 ("CompareDirs::checkIntegrity () - " << ((dirOrig->isValid () || dirComp->isValid ())
           ? (cOrigLen
              ? (iOption & (OPT_SEARCHEQUALSUBDIRS | OPT_SEARCHSUBDIRS)
                 == (OPT_SEARCHEQUALSUBDIRS | OPT_SEARCHSUBDIRS) ? 3 : 0)
              : 2) : 1));
   return ((dirOrig->isValid () || dirComp->isValid ())
           ? (cOrigLen
              ? ((iOption & (OPT_SEARCHEQUALSUBDIRS | OPT_SEARCHSUBDIRS))
                 == (OPT_SEARCHEQUALSUBDIRS | OPT_SEARCHSUBDIRS) ? 3 : 0)
              : 2) : 1);
}

//-----------------------------------------------------------------------------
/// Compares the files in two directories
/// \throw std::exception: Reported errors by search-classes
//-----------------------------------------------------------------------------
void CompareDirs::compare () throw (std::exception) {
   Check1 (!checkIntegrity ());

   TRACE1 ("CompareDirs::compare (statPath) - Handling path: "
           << dirOrig->getSearchValue ());

   std::string strWork (dirOrig->getSearchValue ());
   strWork += YGP::File::DIRSEPARATOR;
   strWork += '*';
   dirOrig->setSearchValue (strWork);
   enterDirectory (dirOrig->getDirectory ());

   std::vector <const YGP::File*> dirList;
   const YGP::File* file;

   unsigned long cOrigFiles (0);
   unsigned long attribs (YGP::IDirectorySearch::FILE_NORMAL
                          | YGP::IDirectorySearch::FILE_READONLY
                          | YGP::IDirectorySearch::FILE_DIRECTORY);
   if (iOption & OPT_CHECKHIDDEN)
      attribs |= YGP::IDirectorySearch::FILE_HIDDEN;

   // Get list of files in original dir and store in dirList
   if ((file = dirOrig->find (attribs)))
      do {
         // Check if file matches in/exclude-criteria
         if (file->isDirectory ()
             ? checkDirectory (file->name ())
             : (YGP::_XDSfileIsValid (files, file->name ())
                && checkDate (file->time ()))) {
            // Yes: Add found entry to filelist
            TRACE3 ("CompareDirs::compare (statPath) - Originalfile: " << file->name ());

            ++cOrigFiles;
            dirList.push_back (file->clone ());
         } // endif
      } while ((file = dirOrig->next ())); // end-do

   // Check all files in strDirComp if they exist in strDirOrig
   strWork = dirComp->getSearchValue ();
   strWork += YGP::File::DIRSEPARATOR;
   strWork += '*';
   dirComp->setSearchValue (strWork);
   enterDirectory (dirComp->getDirectory ());

   file = dirComp->find (attribs);
   while (file) {
      // Check if file matches in/exclude-criteria
      if (file->isDirectory ()
          ? !checkDirectory (file->name ())
          : !(YGP::_XDSfileIsValid (files, file->name ()) && checkDate (file->time ()))) {
         file = dirComp->next ();
         continue;
      } // endif
      TRACE8 ("CompareDirs::compare (statPath) - Comparative file: " << file->name ());

      // Check if found file exists also in original directory
      std::vector<const YGP::File*>::iterator i;
      for (i = dirList.begin (); i != dirList.end (); ++i, *i) {
         if (*i && !file->compare (*i))
            break;
      } // endfor check all files in original dir

      const YGP::File* pOrigEntry = NULL;
      if (i != dirList.end ()) {                                // File found?
         TRACE3 ("CompareDirs::compare (statPath) - Equal comparative file: "
                 << file->name ());
         pOrigEntry = *i;

         if (!((iOption & OPT_NODIRCOMP)        // No directory-compare wanted
               && file->isDirectory ()          // and (two) file(s) found and
               && pOrigEntry->isDirectory ())) {
            long diff (pOrigEntry->time () - file->time ());
            long diffContents ((iOption & OPT_IGNORETIMESTAMP) ? 1 : diff);

            TRACE9 ("CompareDirs::compare (statPath) - Timestamps: "
                    << pOrigEntry->time () << " <-> " << file->time ()
		    << " -> " << diff);

            if (diffContents                           // Timestamp different?
                && (iOption & OPT_CHECKCONTENTS)           // Compare contents
                && (!(file->isDirectory ()                // (only for files)?
                    || pOrigEntry->isDirectory ()))
                && (pOrigEntry->size () == file->size ()))       // Same size?
               if (!compareFiles (*pOrigEntry, *file, diffContents))  // Cont?
                  diffContents = diff;

            if (diffContents) {                    // Timestamp still differnt
               if (!(iOption & OPT_NOCHANGED))
                  showFile (((diff > 0) ? DIR_OLDER : diff ? DIR_YOUNGER : DIR_DIFFERENT),
                            pOrigEntry, file);
            } // endif
            else
               if (iOption & OPT_EQUAL)
                  showFile (DIR_EQUAL, pOrigEntry, file);
         } // endif files to compare

	 // Cleanup: Delete original file from search-list and - if
	 // only equal subdirs are to be compared - add this directory
         --cOrigFiles;
         dirList.erase (i);                   // Adapt list (clear found file)

         if ((iOption & OPT_SEARCHEQUALSUBDIRS)
	     && (pOrigEntry->isDirectory () && file->isDirectory ()))
	    dirList.push_back (pOrigEntry);
	 else
	    delete pOrigEntry;
      } // endif file found
      else
         if (!(iOption & OPT_NONEWDEL))
            showFile (DIR_NEW, NULL, file);      // File not found -> show new

      // If subdirs are compared and dir found: Store file (if orig no dir)
      if (iOption & OPT_SEARCHSUBDIRS) {
         if (file->isDirectory ())
            dirList.push_back (file->clone ());
      }

      file = dirComp->next ();
   } // end-while files available

   std::vector<const YGP::File*>::iterator i (dirList.begin ());
   // Show all files not found in strDirComp as deleted
   while (cOrigFiles--) {
      Check2 (i != dirList.end ());
      file = *i; Check3 (file);

      if (!(iOption & OPT_NONEWDEL))
         showFile (DIR_DELETED, file, NULL);

      if (!(file->isDirectory () && (iOption & OPT_SEARCHSUBDIRS))) {
         *i = NULL;
         delete file;
      } // endif no dir and subdir-compare
      ++i;
   } // end-while handle remaining original files

   if (dirList.size ()) {                // Compare subdirs (if there are one)
      std::string strOrig (dirOrig->getDirectory ());
      std::string strComp (dirComp->getDirectory ());

      for (i = dirList.begin (); i != dirList.end (); ++i)
         if ((*i) != NULL) {
            file = *i;
            TRACE8 ("Check subdir: " << file->name ());
            Check3 (file->isDirectory ());

            checkDirectory (file->name ());

            std::string strTemp (file->name ());
            strTemp += YGP::File::DIRSEPARATOR;

            dirOrig->setSearchValue (strOrig + strTemp);
            dirComp->setSearchValue (strComp + strTemp);
            compare ();

            delete file;
            *i = NULL;
         } // endif entry not NULL

      dirOrig->setSearchValue (strOrig);
      dirComp->setSearchValue (strComp);
   } // endif compare subdirs
}

//-----------------------------------------------------------------------------
/// Compares the contents of two files
/// \param fileOrig: Name of original file
/// \param fileComp: Name of file to compare
/// \param cmp: Defines how fileOrig and fileComp differs (analogue to
/// \param strcmp); only valid if true is returned
/// \returns \c bool: Flag if compare was successful
/// \pre Both files must have the same length
//-----------------------------------------------------------------------------
bool CompareDirs::compareFiles (const YGP::File& fileOrig,
				const YGP::File& fileComp, long& cmp) const {
   char szLineOrig[LEN_LINE], szLineComp[LEN_LINE];

   try {
      cmp = 0;

      // Open the files to compare
      void* fOrig = fileOrig.open ("rb");
      void* fComp = fileComp.open ("rb");

      unsigned int len;
      while (!fileOrig.isEOF (fOrig)) {
         len = fileOrig.read (fOrig, szLineOrig, LEN_LINE);
         fileComp.read (fComp, szLineComp, LEN_LINE);
         cmp = memcmp (szLineOrig, szLineComp, len);
         if (cmp)
            break;
      } // end-while not EOF

      fileOrig.close (fOrig);
      fileComp.close (fComp);

      return true;
   }
   catch (YGP::FileError& e) {
      TRACE1 ("CompareDirs::compareFiles (const YGP::File&, const YGP::File&, long&) -"
              " Error during comparison: " << e.what ());
      return false;
   }
}

//-----------------------------------------------------------------------------
/// Checks if the passed pFile matches the passed dir-arguments
/// \param pFile: Directoryname
/// \returns \c bool: true: Directory matches; false else
/// \pre pFile valid ASCIIZ-string
//-----------------------------------------------------------------------------
bool CompareDirs::checkDirectory (const char* pFile) const {
   Check1 (pFile); Check1 (*pFile);

   // Ignore  special-dirs '.' and '..'
   if (!YGP::IDirectorySearch::isSpecial (pFile)) {
      YGP::PathSearch l (dirs);
      std::string node;
      bool included (false), include (false);
      TRACE7 ("CompareDirs::checkDirectory (const char*) - Checking " << pFile);

      while (!(node = l.getNextNode ()).empty ()) {
         Check2 ((node[0] == 'I') || (node[0] == 'X'));

         include = (node[0] == 'I');
         included |= include;
         node.replace (0, 1, 0, '\0');

         TRACE8 ("CompareDirs::checkDirectory (const char*) - Checking " << pFile
                 << " against " << node);
         if (checkDirname (pFile, node))
            return include;
      } // end-while nodes available

      return included ? false : true;
   } // endif

   return false;
}

//-----------------------------------------------------------------------------
/// Checks if the passed pDir matches any of the nodes in the list
/// \param pDir: Directoryname
/// \param regexp: RegExp to check
/// \returns \c bool: true: Directory matches; false else
/// \pre pDir valid ASCIIZ-string
//-----------------------------------------------------------------------------
bool CompareDirs::checkDirname (const char* pDir,
                                const std::string& regexp) const {
   Check1 (pDir); Check1 (*pDir); Check1 (!regexp.empty ());

   std::string path (getDirStartComp ());
   path += pDir;

   YGP::FileRegularExpr rex (regexp.c_str ());
   size_t pos;

   do {
      TRACE8 ("CompareDirs::checkDirname (const char*, std::string&) const - "
               "Matching " << path << " against " << regexp);

      if (rex.matches (path.c_str ()))                     // Test if dir matches
         return true;

      if ((pos = path.find (YGP::File::DIRSEPARATOR)) == std::string::npos)
         break;

      path.replace (0, pos + 1, 0, '\0');
   } while (true);

   return false;
}

//-----------------------------------------------------------------------------
/// Appends a node to a list. Every subnode in the node is prefixed with the
/// prefix-character
/// \param list: List to change
/// \param prefix: Leading character
/// \param node: Node to check
/// \param Notes     : The node is added to the beginning of the list
//-----------------------------------------------------------------------------
void CompareDirs::addNode (std::string& list, char prefix, const std::string& node) {
   YGP::PathSearch  l (node);
   std::string temp;

   while (!(temp = l.getNextNode ()).empty ())
      list = prefix + temp + (std::string)(YGP::PathSearch::PATHSEPARATOR + list);
}

//-----------------------------------------------------------------------------
/// Creates an object for comparing directories; depending on the passed
/// parameter local or over the net
/// \param search: Directory to search; if this parameter contains an
/// \param double-point (:) the part before is considered to be a host-name
/// \param (in Windoze of course only if it's longer than 1 char; fuck
/// \param those drive-letters)
/// \returns YGP::IDirectorySearch*: Pointer to search-object
/// \throw std::invalid_argument: In case of an error
//-----------------------------------------------------------------------------
YGP::IDirectorySearch* CompareDirs::makeSearchobject (const char* search,
                                                      unsigned int port) throw (std::invalid_argument) {
   TRACE9 ("CompareDirs::makeSearchObject (const char*, unsigned int) - "
           << search << ':' << port);
   Check1 (search); Check1 (*search);

   std::string tmp (search);
   size_t pos;

   YGP::IDirectorySearch* ret (NULL);

#if SYSTEM == UNIX
   if ((pos = tmp.find (YGP::RemoteDirSearch::SEPARATOR)) != std::string::npos)
#else
   // Search after drive-letter-seperator in Windoze, ...
   if ((pos = tmp.find (YGP::RemoteDirSearch::SEPARATOR, 2)) != std::string::npos)
#endif
   {
      unsigned int posPort (tmp.rfind (':'));

      if (posPort > pos) {
         TRACE8 ("CompareDirs::makeSearchObject (const char*) - Port = "
                 << (search + posPort + 1));

	 port = YGP::Socket::getPortOfService (search + posPort + 1);
         tmp.replace (posPort, tmp.length (), 0, '\0');
      }

      TRACE8 ("CompareDirs::makeSearchObject (const char*) - Server = "
              << tmp.c_str ());

      ret = new YGP::RemoteDirSearch (tmp, port);
      TRACE8 ("CompareDirs::makeSearchObject (const char*) - object created");
      if (ret->isValid ())
	 errno = 0;
      else {
	 errno = ENOENT;
	 delete ret;
      }
   }
   else {
      errno = 0;
      if (YGP::DirectorySearch::isValid (tmp))
	 ret = new YGP::DirectorySearch (tmp);
   }


   if (errno)
      throw (std::invalid_argument (strerror (errno)));

   Check3 (ret);
   return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1