//$Id: DirComp.cpp,v 1.53 2007/03/22 19:40:35 markus Rel $
//PROJECT : DirComp
//SUBSYSTEM : DirComp
//REFERENCES :
//TODO :
//BUGS :
//REVISION : $Revision: 1.53 $
//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 <dircomp-cfg.h>
#include <ygp-cfg.h>
#include <cstdio>
#include <clocale>
#if SYSTEM == UNIX
# include <unistd.h>
#endif
#include <iomanip>
#include <iostream>
#include <string>
#include <stdexcept>
#ifdef HAVE_CGP
# include <CORBA.h>
# include "../CGP/CICompDir.h"
#endif
#include <YGP/Check.h>
#include <YGP/Trace.h>
#include <YGP/Log.h>
#include <YGP/File.h>
#include <YGP/ATStamp.h>
#include <YGP/DirSrch.h>
#include <YGP/RDirSrch.h>
#include <YGP/IVIOAppl.h>
#include <YGP/FileRExp.h>
#include <YGP/SmartPtr.h>
#include <YGP/RDirSrchSrv.h>
#include "CompDir.h"
static const int DEFAULTPORT = 31336U;
// Class to print result of CompareDirs
class DirCompare2Cout : public CompareDirs {
public:
DirCompare2Cout ();
virtual ~DirCompare2Cout () { }
typedef enum Options { OPT_NONE = 0, OPT_NO_OUTPUT = 1, OPT_VERBOSE = 2 } Options;
void setAction (unsigned int iDiffType, const char* pszAktion)
{ if (iDiffType < sizeof (pszActions) / sizeof (pszActions[0]))
pszActions[iDiffType] = pszAktion; }
int getDiffFiles () const { return iNumFiles; }
int getNewFiles () const { return iNumNewFiles; }
int getChangedFiles () const { return iNumChangedFiles; }
void setOption (Options opt) { option = opt; }
static unsigned int calcMaxStatiLen ();
protected:
virtual void showFile (const Status diff, const YGP::File* pFileOrig,
const YGP::File* pFileComp = NULL);
private:
int iNumFiles;
int iNumChangedFiles;
int iNumNewFiles;
Options option;
const char* pszActions[DIR_NEW + 1]; // Array for actions
void execProgram (const char* pszAction, const char* pszFile,
const CompareDirs::Status diff) const;
static char* apFileStati[];
static unsigned int maxStatiLen;
};
// Class to run DirComp-Application
class Application : public YGP::IVIOApplication {
public:
Application (const int argc, const char* argv[])
: YGP::IVIOApplication (argc, argv, lo), option (0) { }
~Application () { }
protected:
virtual bool handleOption (const char opt);
// Program-handling
virtual int perform (int argc, const char* argv[]);
virtual const char* description () const {
if (option & SHOW_VERSION) {
static const std::string strLongVer =
std::string (VERSION) + _(" - Compiled on ")
+ std::string (__DATE__ " - " __TIME__)
+ _("\nCopyright (C) 1999 - 2007 Markus Schwab; e-Mail: g17m0@lycos.com"
"\nDistributed under the terms of the GNU General Public License");
return strLongVer.c_str ();
}
else
return PRG_RELEASE; }
// Help-handling
virtual bool shallShowInfo () const { return !(option & NO_OUTPUT); }
virtual void showHelp () const;
private:
// Prohobited manager functions
Application ();
Application (const Application&);
const Application& operator= (const Application&);
static const longOptions lo[];
DirCompare2Cout dc;
enum { NO_OUTPUT = 1, SHOW_VERSION = 2, RET_CHANGED = 4, RET_NEWDEL = 8,
SHOW_VERBOSE = 16 };
int option;
};
#ifdef HAVE_GETTEXT
// Initialize I18n *before* the first use of gettext!
static int _x_ = (Application::initI18n (PACKAGE, LOCALEDIR), 0);
#endif
char* DirCompare2Cout::apFileStati[] = {
_("Younger"), _("Older"), _("Diff"), _("Equal"), _("Deleted"), _("New") };
unsigned int DirCompare2Cout::maxStatiLen = DirCompare2Cout::calcMaxStatiLen ();
const YGP::IVIOApplication::longOptions Application::lo[] = {
{ IVIOAPPL_HELP_OPTION },
#ifdef HAVE_CGP
{ "CORBA", '|' },
#endif
#ifdef ENABLE_SERVER
{ "server", 'S' },
{ "daemon", 'D' },
#endif
{ "ret-new", 'R' },
{ "ret-changed", 'r' },
{ "action", 'a' },
{ "silent", 'Q' },
{ "quiet", 'q' },
{ "version", 'V' },
{ "end-time", 'T' },
{ "begin-time", 't' },
{ "exclude-dirs", 'X' },
{ "include-dirs", 'I' },
{ "exclude", 'x' },
{ "include", 'i' },
{ "force-compare", 'C' },
{ "compare", 'c' },
{ "hidden", 'H' },
{ "verbose", 'v' },
{ "show-dirs", 'd' },
{ "equal", 'e' },
{ "existing", 'N' },
{ "no-changed", 'n' },
{ "rec-equal", 'E' },
{ "recursive", 's' },
{ NULL, '\0' } };
//-----------------------------------------------------------------------------
/// Defaultconstructor
//-----------------------------------------------------------------------------
DirCompare2Cout::DirCompare2Cout () : CompareDirs (), iNumFiles (0)
, iNumChangedFiles (0), iNumNewFiles (0), option (OPT_NONE)
{
addOption (CompareDirs::OPT_NODIRCOMP);
Check2 ((sizeof (pszActions) / sizeof (pszActions[0])) == (DIR_NEW + 1));
for (unsigned int i (0); i < sizeof (pszActions) / sizeof (pszActions[0]); ++i)
pszActions[i] = NULL;
}
//-----------------------------------------------------------------------------
/// Displays the different file
/// \param diff: Flag how the files differ
/// \param pFileOrig: Original file (NULL if new)
/// \param pFileComp: Compared file (NULL if deleted)
//-----------------------------------------------------------------------------
void DirCompare2Cout::showFile (const Status diff, const YGP::File* pFileOrig,
const YGP::File* pFileComp) {
Check1 (diff <= DIR_NEW);
Check1 (((diff == DIR_NEW) ? !pFileOrig : pFileOrig != NULL));
Check1 (((diff == DIR_DELETED) ? !pFileComp : pFileComp != NULL));
// Adapt number of differnt files
++iNumFiles;
if (diff < DIR_EQUAL)
++iNumChangedFiles;
else if (diff > DIR_EQUAL)
++iNumNewFiles;
if (!(option & OPT_NO_OUTPUT)) {
if (diff == DIR_DELETED) // For easier handling adapt file-
pFileComp = pFileOrig; // pointers the file was deleted
Check3 (pFileComp);
std::cout.setf (std::ios::left, std::ios::left|std::ios::right|std::ios::internal);
std::cout << std::setw (maxStatiLen) << apFileStati[diff] << ": "
<< getDirStartComp () << pFileComp->name () << ' ';
if (option & OPT_VERBOSE) {
YGP::ATimestamp filetime (pFileComp->time (), false);
Check3 (filetime.isDefined ());
std::string strTime (filetime.toString ());
std::cout.setf (std::ios::right,
std::ios::left | std::ios::right | std::ios::internal);
if ((maxStatiLen + getDirStartComp ().length ()
+ strlen (pFileComp->name ()))
<= (76 - ((diff < DIR_DELETED) ? (strTime.length () + 1) : 0)))
std::cout << std::setw (76 - maxStatiLen
- getDirStartComp ().length ()
- strlen (pFileComp->name ())
- ((diff < DIR_DELETED)
? (strTime.length () + 1) : 0));
std::cout << strTime.c_str ();
if (diff < DIR_DELETED) {
filetime.setGMT (pFileOrig->time ()); Check3 (filetime.isDefined ());
std::cout << ' ' << filetime.toString ().c_str ();
} // endif file not deleted
} // endif verbose mode
std::cout << '\n';
} // endif output wanted
// Perform the specified action
if (pszActions[diff] && *pszActions[diff])
execProgram (pszActions[diff], pFileComp->name (), diff);
}
//-----------------------------------------------------------------------------
/// Calculates the max. length of the change-stati-strings They can (and
/// propably do) differ in every language
/// \returns \c unsigned int: Length of longest stati-string
//-----------------------------------------------------------------------------
unsigned int DirCompare2Cout::calcMaxStatiLen () {
for (unsigned int i (0); i < sizeof (apFileStati) / sizeof (apFileStati[0]); ++i) {
Check2 (apFileStati[i]);
unsigned int act (strlen (apFileStati[i]));
if (act > maxStatiLen)
maxStatiLen = act;
}
return maxStatiLen;
}
//-----------------------------------------------------------------------------
/// Performs the passed action with the passed file Following subsitution is
/// performed with the action {OP} ... replaced with the path of the original
/// file {OF} ... replaced with path and name of the original file {CP} ...
/// replaced with the path of the compared file {CF} ... replaced with path
/// and name of the compared file
/// \param pAction: Action to perform
/// \param pFile: Paramter for substitution
/// \param diff: Status of compare (new, deleted, ...)
/// \pre pszAction, pszFile valid ASCIIZ-strings
//-----------------------------------------------------------------------------
void DirCompare2Cout::execProgram (const char* pAction, const char* pFile,
const CompareDirs::Status diff) const {
Check1 (pAction); Check1 (pFile); Check1 (diff <= DIR_NEW);
TRACE8 ("DirCompare2Cout::execProgram -> Passed action: " << pAction);
std::string strAction (pAction); Check3 (!strAction.empty ());
int iPos;
static char* aPlaceholders[] = { "{OP}", "{OF}", "{CP}", "{CF}" };
// Substitute actual names in action
for (unsigned int i (0);
i < (sizeof (aPlaceholders) / sizeof (aPlaceholders[0])); ++i) {
iPos = 0;
while ((iPos = strAction.find (aPlaceholders[i], iPos)) != -1) {
strAction.replace (iPos, 4, (i & 1) ? pFile : "");
strAction.replace (iPos, 0, (i & 2) ? getDirComp () : getDirOrig ());
}
} // endfor;
TRACE3 ("DirCompare2Cout::execProgram -> Final action: " << strAction);
if (system (strAction.c_str ())) {
std::string error (PACKAGE);
error += _("-error: Can't execute action");
perror (error.c_str ());
}
}
//-----------------------------------------------------------------------------
/// Handles the passed options
/// \param option: Actual option
/// \pre option not '\0'
//-----------------------------------------------------------------------------
bool Application::handleOption (const char opt) {
Check1 (opt != '\0');
const char* pOptArg;
switch (opt) {
case 'I':
case 'X':
if ((pOptArg = getOptionValue ()) != NULL) {
YGP::FileRegularExpr regexp (pOptArg);
try {
regexp.checkIntegrity ();;
(opt == 'X') ? dc.addExcludeDirs (pOptArg) : dc.addIncludeDirs (pOptArg);
}
catch (std::invalid_argument& e) {
std::string error (_("-Warning: Invalid name for a "
"directory (%1).\nIgnoring option %2\n"));
error.replace (error.find ("%1"), 2, e.what ());
error.replace (error.find ("%2"), 2, 1, opt);
std::cerr << name () << error;
}
}
else {
std::string error (_("-Warning: Option %1 needs an argument!\n"
"Ignoring option %1\n"));
error.replace (error.find ("%1"), 2, 1, opt);
error.replace (error.find ("%1"), 2, 1, opt);
std::cerr << name () << error;
}
case 's':
dc.addOption (CompareDirs::OPT_SEARCHSUBDIRS);
if (dc.hasOption (CompareDirs::OPT_SEARCHEQUALSUBDIRS)) {
std::cerr << name () << _("-Warning: Option 's' overwrites "
"previous option 'E'\n");
dc.removeOption (CompareDirs::OPT_SEARCHEQUALSUBDIRS);
}
break;
case 'E':
dc.addOption (CompareDirs::OPT_SEARCHEQUALSUBDIRS);
if (dc.hasOption (CompareDirs::OPT_SEARCHSUBDIRS)) {
std::cerr << name () << _("-Warning: Option 'E' overwrites "
"previous option 's'\n");
dc.removeOption (CompareDirs::OPT_SEARCHSUBDIRS);
}
break;
case 'N':
dc.addOption (CompareDirs::OPT_NONEWDEL);
break;
case 'n':
dc.addOption (CompareDirs::OPT_NOCHANGED);
break;
case 'e':
dc.addOption (CompareDirs::OPT_EQUAL);
break;
case 'C':
dc.addOption (CompareDirs::OPT_IGNORETIMESTAMP);
case 'c':
dc.addOption (CompareDirs::OPT_CHECKCONTENTS);
break;
case 'd':
dc.removeOption (CompareDirs::OPT_NODIRCOMP);
break;
case 'v':
option |= SHOW_VERBOSE;
dc.setOption (DirCompare2Cout::OPT_VERBOSE);
break;
case 'i':
case 'x':
if ((pOptArg = getOptionValue ()) != NULL) {
YGP::FileRegularExpr regexp (pOptArg);
try {
regexp.checkIntegrity ();;
(opt == 'x') ? dc.addExcludeFiles (pOptArg) : dc.addIncludeFiles (pOptArg);
}
catch (std::invalid_argument& e) {
std::string error (_("-Warning: Invalid filename (%1).\n"
"Ignoring option %2\n"));
error.replace (error.find ("%1"), 2, e.what ());
error.replace (error.find ("%2"), 2, 1, opt);
std::cerr << name () << error;
}
}
else {
std::string error (_("-Warning: Option %1 needs an argument!\n"
"Ignoring option %1\n"));
error.replace (error.find ("%1"), 2, 1, opt);
error.replace (error.find ("%1"), 2, 1, opt);
std::cerr << name () << error;
}
break;
case 't':
case 'T':
if ((pOptArg = getOptionValue ()) != NULL) {
if ((opt == 't') ? dc.setBeginTime (pOptArg) : dc.setEndTime (pOptArg)) {
std::string err (_("-Warning: Date `%1' is not valid!\nIgnoring"
" option %2\n"));
err.replace (err.find ("%1"), 2, pOptArg);
err.replace (err.find ("%2"), 2, 1, opt);
std::cerr << name () << err;
}
}
else {
std::string error (_("-Warning: Option %1 needs an argument!\n"
"Ignoring option %1\n"));
error.replace (error.find ("%1"), 2, 1, opt);
error.replace (error.find ("%1"), 2, 1, opt);
std::cerr << name () << error;
}
break;
case 'Q':
dc.setOption (DirCompare2Cout::OPT_NO_OUTPUT);
case 'q':
option |= NO_OUTPUT;
break;
case 'V':
option |= SHOW_VERSION;
break;
case 'H':
dc.addOption (CompareDirs::OPT_CHECKHIDDEN);
break;
case 'r':
case 'R':
option |= (opt == 'r' ? RET_CHANGED : RET_NEWDEL);
break;
case 'a':
case 'A':
if ((pOptArg = getOptionValue ()) != NULL) {
int iValue (0);
if (opt == 'a')
iValue = -1;
else {
while (isdigit (*pOptArg)) {
iValue *= 10;
iValue += (*pOptArg++) - '0';
} // end-while
}
if (iValue) {
// Set action for all specified types
for (unsigned int i (0); i < (sizeof (int) << 4); ++i)
if (iValue & (1 << i))
dc.setAction (i, pOptArg);
}
else
std::cerr << name () << _("-Warning: Option A needs a (numeric) "
"change-value\n");
} // endif
else {
std::string error (_("-Warning: Option %1 needs an argument!\n"
"Ignoring option %1\n"));
error.replace (error.find ("%1"), 2, 1, opt);
error.replace (error.find ("%1"), 2, 1, opt);
std::cerr << name () << error;
}
break;
#ifdef ENABLE_SERVER
case 'S':
case 'D':
try {
unsigned int port (DEFAULTPORT);
if ((pOptArg = checkOptionValue ()) != NULL) { // Use another port
getOptionValue ();
port = YGP::Socket::getPortOfService (pOptArg);
}
TRACE1 ("DirComp: Listening on port " << port);
YGP::Socket orig (port);
if (opt == 'D') {
pid_t pid (fork ());
switch (pid) {
case 0: // Child-part: Perform compare in background
close (0);
close (1);
close (2);
break;
case -1:
throw std::runtime_error (_("Can't create background-process"));
default: // Parent-part: Just exit
exit (0);
} // end-switch
} // endif daemon-mode
while (true) {
TRACE5 ("DirComp: Waiting for input");
YGP::Socket sock (orig.waitForInput ());
YGP::RemoteDirSearchSrv rds;
rds.performCommands (sock);
} // end-while
}
catch (std::exception& e) {
if (opt == 'D') {
YGP::Syslog log (name ());
std::string error (_("Initialisation-error: "));
error += e.what ();
LOGERROR (error.c_str ());
}
std::cerr << name () << _("-Error during initialisation: ")
<< e.what () << '\n';
exit (1);
}
break;
#endif
#ifdef HAVE_CGP
case '|': {
int argc (1);
char* argv[1] = { const_cast<char*> (filename ()) };
CORBA::ORB_var orb = CORBA::ORB_init (argc, argv, "mico-local-orb" );
CICompareDirs* compDirs = new CICompareDirs ();
CORBA::String_var id = orb->object_to_string (compDirs);
std::cout << id << '\n';
std::cout.flush ();
orb->run ();
CORBA::release (compDirs);
exit (0);
}
break;
#endif
default: {
std::string error (_("-Warning: Ignoring invalid option %1\n"));
error.replace (error.find ("%1"), 2, 1, opt);
std::cerr << name () << error;
}
} // end-case
return true;
}
//-----------------------------------------------------------------------------
/// Shows the help for the programm
//-----------------------------------------------------------------------------
void Application::showHelp () const {
std::cout << _("Compares two directories\n\n"
"Usage") << ":\n " << name ()
<< _(" [<OPTION> [...]] <Originaldir> <Comparedir>")
#ifdef ENABLE_SERVER
<< "\n " << name () << " --daemon[[=]<port>]"
"\n " << name () << " --server[[=]<port>]"
#endif
#ifdef HAVE_CGP
<< "\n " << name () << " --CORBA"
#endif
<< "\n\n" << _(" Originaldir ........... Original directory\n")
<< _(" Comparedir ............ Directory to compare (new)\n")
<< " -s, --recursive ....... " << _("Compares subdirectories recursively\n")
<< " -E, --rec-equal ....... " << _("Recurse into subdirectories found in both dirs\n")
<< " -e, --no-changed ...... " << _("Suppress younger and older files\n")
<< " -N, --existing ........ " << _("Suppress new and deleted files\n")
<< " -e, --equal ........... " << _("Show equal files\n")
<< " -d, --show-dirs ....... " << _("Displays directories with differnt timestamps\n")
<< " -v, --verbose ......... " << _("Show files with timestamps\n")
<< " -H, --hidden .......... " << _("Compare also hidden files\n")
<< " -c, --compare ......... " << _("Compare contents of (differing) files (if readable)\n")
<< " -C, --force-compare ... " << _("Compare contents of (all) files (if readable)\n")
<< " -i, --include ......... " << _("<Filelist> Restricts the compare to passed files\n")
<< " -x, --exclude ......... " << _("<Filelist>: Excludes the passed files\n")
<< " -I, --include-dirs .... " << _("<Filelist>: Restricts the compare to passed dirs\n")
<< " -X, --exclude-dirs .... " << _("<Filelist>: Excludes the passed directories\n")
<< " -t, --begin-time ...... " << _("<Date>: Lower border of the time-range for compare\n")
<< " -T, --end-time ........ " << _("<Date>: Upper border of the time-range for compare\n")
<< " -V, --version ......... " << _("Output version information\n")
<< " -q, --quiet ........... " << _("Quiet mode: Suppress any unneccessary output\n")
<< " -Q, --silent .......... " << _("Silent mode: No output at all (for batch-processing)\n")
<< " -a, --action .......... " << _("<Action>: Perform action on displayed files\n")
<< " -A .................... " << _("<Change-ID>[<Action>]: Action for specified files\n")
#ifdef ENABLE_SERVER
<< " -S, --server .......... " << _("[<port>]: Start as server (run in foreground)\n")
<< " -D, --deamon .......... " << _("[<port>]: Start as server (run as deamon)\n")
#endif
<< " -r, --ret-changed ..... " << _("Return number of changed files\n")
<< " -R, --ret-new ......... " << _("Return number of new or deleted files\n")
#ifdef HAVE_CGP
<< " --CORBA ................" << _("Start as CORBA-server; Write ID to stdout\n")
#endif
<< " -h, -?, --help ........ " << _("Display help and exit\n\n"
"Exit with the number of different files (unless specified differently)\n");
if (option & SHOW_VERBOSE) {
std::cout << _("\n<Date> is in the format D.M.YYYY-H:m:S (with any split-char; WITHOUT "
"leading\n zeros)"
"\n\n<File> Filename, where the wildcards asterisk (*) for any"
" number of any\n characters and the questionmark (?) for"
" any one character are supported.\n\n"
" Furthermore it is possible to specify valid characters"
" in brackets ([)\n and (]), either by listening all of"
" them or by specifying the borders,\n seperated with a"
" minus (-). To invert this selection a leading caret (^)\n "
" can be used.\n\n"
" Files are considered hidden, if either the appropiate "
"flag of the\n operating system ist set (OS/2, DOS/WINDOWS)"
" or if they are starting\n with a dot (.) (UNIX).\n\n"
"<Filelist> List of <File>-nodes; seperated with the split-char"
" of the \n\t operating-system (the colon (:) in UNIX; the "
"semicolon (;)\n\t in OS/2 and DOS/Windows."
"\n\n\t These nodes are checked from right to left; if no "
"include-option\n\t (-i) is specified, a leading -i* is assumed.\n\n"
"The options 'a' and 'A' specify, which actions shall be performed "
"for the\ndiffering files (the option 'A' needs additionally a flag "
"how the files\nshould differ (1: Younger, 2: Older, 4: Different, "
"8: Equal, 16: Deleted,\n32: New; this values can be ORed together).\n\n"
"Following placeholders are supported with the <Action>:\n\n"
" {OF}, {CF}: Name of original or compared directory (incl. relative path)\n"
" {OP}, {CP}: Relative path of original or compared directory\n\n"
"Example.: \"xcopy {OF} {CP}\" -A40\n");
} // endif
}
//-----------------------------------------------------------------------------
/// Performs the job of the application
/// \param argc: Number of arguments (without options)
/// \param argv: Array with arguments
/// \returns \c Number of different files
//-----------------------------------------------------------------------------
int Application::perform (int argc, const char* argv[]) {
if ((argc) != 2) {
showHelp ();
return -1;
}
try {
YGP::SmartPtr<YGP::IDirectorySearch> dirs[2];
for (unsigned int i (0); i < (sizeof (dirs) / sizeof(dirs[0])); ++i) {
try {
dirs[i] = CompareDirs::makeSearchobject (argv[i], DEFAULTPORT);
}
catch (std::exception& e) {
std::string error (_("-error accessing directory `%1'\nReason: %2"));
error.replace (error.find ("%1"), 2, argv[i]);
error.replace (error.find ("%2"), 2, e.what ());
std::cerr << filename () << error << '\n';
return -2;
}
}
dc.setOriginalDir (*dirs[0]);
dc.setCompareDir (*dirs[1]);
dc.compare ();
}
catch (std::exception& e) {
std::cerr << filename () << _("-error: ") << e.what () << '\n';
return -3;
}
return (option & RET_CHANGED) ? dc.getChangedFiles () :
(option & RET_NEWDEL) ? dc.getNewFiles () : dc.getDiffFiles ();
}
//-----------------------------------------------------------------------------
/// Entrypoint of application
/// \param argc: Number of parameters
/// \param argv: Array with pointer to parameter
/// \returns \c int: Status
//-----------------------------------------------------------------------------
int main (int argc, const char* argv[]) {
Application appl (argc, argv);
return appl.run ();
}
syntax highlighted by Code2HTML, v. 0.9.1