mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1392 lines
38 KiB
1392 lines
38 KiB
/*++
|
|
|
|
Copyright (c) 1990-2000 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
FC
|
|
|
|
Abstract:
|
|
|
|
FC is a DOS-5 compatible file comparison utility
|
|
|
|
Author:
|
|
|
|
Ramon Juan San Andres (ramonsa) 01-May-1991
|
|
|
|
Notes:
|
|
|
|
This FC is a port of the DOS5 FC code. It has been slightly modified
|
|
to use some of the ULIB functionality (e.g. argument parsing), however
|
|
it does not make full use of the ULIB functionality (e.g. it uses
|
|
stdio.h functions for file handling).
|
|
|
|
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
|
|
/****************************************************************************
|
|
File Compare
|
|
|
|
Fcom compares two files in either a line-by-line mode or in a strict
|
|
BYTE-by-BYTE mode.
|
|
|
|
The BYTE-by-BYTE mode is simple; merely read both files and print the
|
|
offsets where they differ and the contents.
|
|
|
|
The line compare mode attempts to isolate differences in ranges of lines.
|
|
Two buffers of lines are read and compared. No hashing of lines needs
|
|
to be done; hashing only speedily tells you when things are different,
|
|
not the same. Most files run through this are expected to be largely
|
|
the same. Thus, hashing buys nothing.
|
|
|
|
|
|
***********************************************************************
|
|
The algorithm that immediately follows does not work. There is an error
|
|
somewhere in the range of lines 11 on. An alternative explanation follows.
|
|
KGS
|
|
************************************************************************
|
|
|
|
[0] Fill buffers
|
|
[1] If both buffers are empty then
|
|
[1.1] Done
|
|
[2] Adjust buffers so 1st differing lines are at top.
|
|
[3] If buffers are empty then
|
|
[3.1] Goto [0]
|
|
|
|
This is the difficult part. We assume that there is a sequence of inserts,
|
|
deletes and replacements that will bring the buffers back into alignment.
|
|
|
|
[4] xd = yd = FALSE
|
|
[5] xc = yc = 1
|
|
[6] xp = yp = 1
|
|
[7] If buffer1[xc] and buffer2[yp] begin a "sync" range then
|
|
[7.1] Output lines 1 through xc-1 in buffer 1
|
|
[7.2] Output lines 1 through yp-1 in buffer 2
|
|
[7.3] Adjust buffer 1 so line xc is at beginning
|
|
[7.4] Adjust buffer 2 so line yp is at beginning
|
|
[7.5] Goto [0]
|
|
[8] If buffer1[xp] and buffer2[yc] begin a "sync" range then
|
|
[8.1] Output lines 1 through xp-1 in buffer 1
|
|
[8.2] Output lines 1 through yc-1 in buffer 2
|
|
[8.3] Adjust buffer 1 so line xp is at beginning
|
|
[8.4] Adjust buffer 2 so line yc is at beginning
|
|
[8.5] Goto [0]
|
|
[9] xp = xp + 1
|
|
[10] if xp > xc then
|
|
[10.1] xp = 1
|
|
[10.2] xc = xc + 1
|
|
[10.3] if xc > number of lines in buffer 1 then
|
|
[10.4] xc = number of lines
|
|
[10.5] xd = TRUE
|
|
[11] if yp > yc then
|
|
[11.1] yp = 1
|
|
[11.2] yc = yc + 1
|
|
[11.3] if yc > number of lines in buffer 2 then
|
|
[11.4] yc = number of lines
|
|
[11.5] yd = TRUE
|
|
[12] if not xd or not yd then
|
|
[12.1] goto [6]
|
|
|
|
At this point there is no possible match between the buffers. For
|
|
simplicity, we punt.
|
|
|
|
[13] Display error message.
|
|
|
|
EXPLANATION 2
|
|
|
|
This is a variation of the Largest Common Subsequence problem. A
|
|
detailed explanation of this can be found on p 189 of Data Structures
|
|
and Algorithms by Aho Hopcroft and Ulman.
|
|
|
|
FC maintains two buffers within which it tries to find the Largest Common
|
|
Subsequence (The largest common subsequence is simply the pattern in
|
|
buffer1 that yields the most matches with the pattern in buffer2, or the
|
|
pattern in buffer2 that yields the most matches with the pattern in buffer1)
|
|
|
|
FC makes a simplifying assumption that the contents of one buffer can be
|
|
converted to the contents of the other buffer by deleting the lines that are
|
|
different between the two buffers.
|
|
|
|
Two indices into each buffer are maintained:
|
|
|
|
xc, yc == point to the last line that has been scanned up to now
|
|
|
|
xp, yp == point to the first line that has not been exhaustively
|
|
compared to lines 0 - #c in the other buffer.
|
|
|
|
FC now makes a second simplifying assumption:
|
|
It is unnecessary to do any calculations on lines that are equal.
|
|
|
|
Hence FC scans File1 and File two line by line until a difference is
|
|
encountered.
|
|
|
|
When a difference is encountered the two buffers are filled such that
|
|
the line containing the first difference heads the buffer. The following
|
|
exhaustive search algorithm is applied to find the first "sync" occurance.
|
|
(The below is simplified to use == for comparison. In practice more than
|
|
one line needs to match for a "sync" to be established).
|
|
|
|
FOR xc,yc = 1; xc,yx <= sizeof( BUFFERS ); xc++, yc++
|
|
|
|
FOR xp,yp = 1; xp,yp <= xc,yc; xp++, yp++
|
|
|
|
IF ( BUFFER1[xp] == BUFFER2[yc] )
|
|
|
|
Then the range of lines BUFFER1[ 1 ... xp ] and
|
|
BUFFER2[ 1 ... yc ] need to be deleted for the
|
|
two files to be equal. Therefore DISPLAY these
|
|
ranges, and begin scanning both files starting at
|
|
the matching lines.
|
|
FI
|
|
|
|
IF ( BUFFER1[yp] == BUFFER2[xc] )
|
|
|
|
Then the range of lines BUFFER2[ 1 ... yp ] and
|
|
BUFFER1[ 1 ... xc ] need to be deleted for the
|
|
two files to be equal. Therefore DISPLAY these
|
|
ranges, and begin scanning both files starting at
|
|
the matching lines.
|
|
FI
|
|
FOREND
|
|
FOREND
|
|
|
|
If a match is not found within the buffers, the message "RESYNC FAILED"
|
|
is issued and further comparison is aborted since there is no valid way
|
|
to find further matching lines.
|
|
|
|
END EXPLANATION 2
|
|
|
|
Certain flags may be set to modify the behavior of the comparison:
|
|
|
|
-a abbreviated output. Rather than displaying all of the modified
|
|
ranges, just display the beginning, ... and the ending difference
|
|
-b compare the files in binary (or BYTE-by-BYTE) mode. This mode is
|
|
default on .EXE, .OBJ, .LIB, .COM, .BIN, and .SYS files
|
|
-c ignore case on compare (cmp = strcmpi instead of strcmp)
|
|
-l compare files in line-by-line mode
|
|
-lb n set the size of the internal line buffer to n lines from default
|
|
of 100
|
|
-u Files to be compared are UNICODE text files
|
|
-w ignore blank lines and white space (ignore len 0, use strcmps)
|
|
-t do not untabify (use fgets instead of fgetl)
|
|
-n output the line number also
|
|
-NNNN set the number of lines to resynchronize to n which defaults
|
|
to 2. Failure to have this value set correctly can result in
|
|
odd output:
|
|
file1: file2:
|
|
abcdefg abcdefg
|
|
aaaaaaa aaaaaab
|
|
aaaaaaa aaaaaaa
|
|
aaaaaaa aaaaaaa
|
|
abcdefg abcdefg
|
|
|
|
with default sync of 2 yields: with sync => 3 yields:
|
|
|
|
*****f1 *****f1
|
|
abcdefg abcdefg
|
|
aaaaaaa aaaaaaa
|
|
*****f2 aaaaaaa
|
|
abcdefg *****f2
|
|
aaaaaab abcdefg
|
|
aaaaaaa aaaaaab
|
|
aaaaaaa
|
|
*****f1
|
|
aaaaaaa
|
|
aaaaaaa
|
|
abcdefg
|
|
*****f2
|
|
aaaaaaa
|
|
abcdefg
|
|
|
|
WARNING:
|
|
This program makes use of GOTO's and hence is not as straightforward
|
|
as it could be! CAVEAT PROGRAMMER.
|
|
****************************************************************************/
|
|
|
|
|
|
#include "ulib.hxx"
|
|
#include "fc.hxx"
|
|
#include "arg.hxx"
|
|
#include "array.hxx"
|
|
#include "arrayit.hxx"
|
|
#include "bytestrm.hxx"
|
|
#include "dir.hxx"
|
|
#include "file.hxx"
|
|
#include "filestrm.hxx"
|
|
#include "filter.hxx"
|
|
#include "mbstr.hxx"
|
|
#include "system.hxx"
|
|
#include "wstring.hxx"
|
|
|
|
#include <malloc.h>
|
|
#include <process.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
#include <locale.h>
|
|
|
|
/**************************************************************************/
|
|
/* main */
|
|
/**************************************************************************/
|
|
|
|
INT __cdecl
|
|
main (
|
|
)
|
|
{
|
|
DEFINE_CLASS_DESCRIPTOR( FC );
|
|
|
|
{
|
|
FC Fc;
|
|
|
|
if ( Fc.Initialize() ) {
|
|
|
|
return Fc.Fcmain();
|
|
}
|
|
}
|
|
|
|
return FAILURE;
|
|
}
|
|
|
|
|
|
|
|
|
|
CHAR *ExtBin[] = { "EXE", "OBJ", "LIB",
|
|
"COM", "BIN", "SYS", NULL };
|
|
|
|
|
|
|
|
|
|
DEFINE_CONSTRUCTOR( FC, PROGRAM );
|
|
|
|
|
|
FC::~FC () {
|
|
}
|
|
|
|
|
|
|
|
BOOLEAN FC::Initialize() {
|
|
|
|
|
|
if ( PROGRAM::Initialize() ) {
|
|
|
|
ValidateVersion();
|
|
|
|
ctSync = -1; // number of lines required to sync
|
|
cLine = -1; // number of lines in internal buffs
|
|
|
|
fAbbrev = FALSE; // abbreviated output
|
|
fBinary = FALSE; // binary comparison
|
|
fLine = FALSE; // line comparison
|
|
fNumb = FALSE; // display line numbers
|
|
fCase = TRUE; // case is significant
|
|
fIgnore = FALSE; // ignore spaces and blank lines
|
|
fSkipOffline = TRUE; // skip offline files
|
|
|
|
fOfflineSkipped = FALSE; // no files are skipped
|
|
|
|
#ifdef DEBUG
|
|
fDebug = FALSE;
|
|
#endif
|
|
|
|
fExpandTabs = TRUE;
|
|
// funcRead = (int (*)(char *,int,FILE *))fgetl;
|
|
|
|
extBin = (CHAR **)ExtBin;
|
|
|
|
return ParseArguments();
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
BOOLEAN
|
|
FC::ParseArguments(
|
|
)
|
|
{
|
|
|
|
ARGUMENT_LEXEMIZER ArgLex;
|
|
ARRAY LexArray;
|
|
ARRAY ArrayOfArg;
|
|
|
|
PATH_ARGUMENT ProgramName;
|
|
FLAG_ARGUMENT FlagAbbreviate;
|
|
FLAG_ARGUMENT FlagAsciiCompare;
|
|
FLAG_ARGUMENT FlagBinaryCompare;
|
|
FLAG_ARGUMENT FlagCaseInsensitive;
|
|
FLAG_ARGUMENT FlagCompression;
|
|
FLAG_ARGUMENT FlagExpansion;
|
|
FLAG_ARGUMENT FlagLineNumber;
|
|
FLAG_ARGUMENT FlagRequestHelp;
|
|
FLAG_ARGUMENT FlagUnicode;
|
|
FLAG_ARGUMENT FlagIncludeOffline;
|
|
FLAG_ARGUMENT FlagIncludeOffline2;
|
|
LONG_ARGUMENT LongBufferSize;
|
|
#ifdef DEBUG
|
|
FLAG_ARGUMENT FlagDebug;
|
|
#endif
|
|
STRING_ARGUMENT LongMatch;
|
|
PATH_ARGUMENT InFile1;
|
|
PATH_ARGUMENT InFile2;
|
|
|
|
LONG Long;
|
|
INT i;
|
|
|
|
|
|
if( !LexArray.Initialize() ) {
|
|
DebugAbort( "LexArray.Initialize() Failed!\n" );
|
|
return( FALSE );
|
|
}
|
|
if( !ArgLex.Initialize(&LexArray) ) {
|
|
DebugAbort( "ArgLex.Initialize() Failed!\n" );
|
|
return( FALSE );
|
|
}
|
|
|
|
// Allow only the '/' as a valid switch
|
|
ArgLex.PutSwitches("/");
|
|
ArgLex.SetCaseSensitive( FALSE );
|
|
|
|
ArgLex.PutStartQuotes("\"");
|
|
ArgLex.PutEndQuotes("\"");
|
|
ArgLex.PutSeparators(" \t");
|
|
|
|
if( !ArgLex.PrepareToParse() ) {
|
|
DebugAbort( "ArgLex.PrepareToParse() Failed!\n" );
|
|
return( FALSE );
|
|
}
|
|
|
|
if( !ProgramName.Initialize("*") ||
|
|
!FlagAbbreviate.Initialize("/A") ||
|
|
!FlagAsciiCompare.Initialize("/L") ||
|
|
!FlagBinaryCompare.Initialize("/B") ||
|
|
!FlagCaseInsensitive.Initialize("/C") ||
|
|
!FlagCompression.Initialize("/W") ||
|
|
!FlagExpansion.Initialize("/T") ||
|
|
!FlagLineNumber.Initialize("/N") ||
|
|
!FlagRequestHelp.Initialize("/?") ||
|
|
!FlagUnicode.Initialize("/U") ||
|
|
!FlagIncludeOffline.Initialize("/OFFLINE") ||
|
|
!FlagIncludeOffline2.Initialize("/OFF") ||
|
|
#ifdef DEBUG
|
|
!FlagDebug.Initialize("/D") ||
|
|
#endif
|
|
!LongBufferSize.Initialize("/LB#") ||
|
|
!LongMatch.Initialize("/*") ||
|
|
!InFile1.Initialize("*") ||
|
|
!InFile2.Initialize("*") ) {
|
|
|
|
DebugAbort( "Unable to Initialize some or all of the Arguments!\n" );
|
|
return( FALSE );
|
|
}
|
|
|
|
|
|
if( !ArrayOfArg.Initialize() ) {
|
|
DebugAbort( "ArrayOfArg.Initialize() Failed\n" );
|
|
return( FALSE );
|
|
}
|
|
|
|
if( !ArrayOfArg.Put(&ProgramName) ||
|
|
!ArrayOfArg.Put(&FlagAbbreviate) ||
|
|
!ArrayOfArg.Put(&FlagAsciiCompare) ||
|
|
!ArrayOfArg.Put(&FlagBinaryCompare) ||
|
|
!ArrayOfArg.Put(&FlagCaseInsensitive) ||
|
|
!ArrayOfArg.Put(&FlagCompression) ||
|
|
!ArrayOfArg.Put(&FlagExpansion) ||
|
|
!ArrayOfArg.Put(&FlagLineNumber) ||
|
|
!ArrayOfArg.Put(&FlagRequestHelp) ||
|
|
!ArrayOfArg.Put(&FlagUnicode) ||
|
|
!ArrayOfArg.Put(&FlagIncludeOffline) ||
|
|
!ArrayOfArg.Put(&FlagIncludeOffline2) ||
|
|
#ifdef DEBUG
|
|
!ArrayOfArg.Put(&FlagDebug) ||
|
|
#endif
|
|
!ArrayOfArg.Put(&LongBufferSize) ||
|
|
!ArrayOfArg.Put(&LongMatch) ||
|
|
!ArrayOfArg.Put(&InFile1) ||
|
|
!ArrayOfArg.Put(&InFile2) ) {
|
|
|
|
DebugAbort( "ArrayOfArg.Put() Failed!\n" );
|
|
return( FALSE );
|
|
}
|
|
|
|
|
|
if( !( ArgLex.DoParsing( &ArrayOfArg ) ) ) {
|
|
// For each incorrect command line parameter, FC displays the
|
|
// following message:
|
|
//
|
|
// FC: Invalid Switch
|
|
//
|
|
// It does *not* die if a parameter is unrecognized...(Dos does...)
|
|
//
|
|
DisplayMessage( MSG_FC_INVALID_SWITCH, ERROR_MESSAGE, "" );
|
|
// return( FALSE );
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// It should now be safe to test the arguments for their values...
|
|
//
|
|
if( FlagRequestHelp.QueryFlag() ) {
|
|
DisplayMessage( MSG_FC_HELP_MESSAGE, NORMAL_MESSAGE, "" );
|
|
return( FALSE );
|
|
}
|
|
|
|
if( FlagBinaryCompare.QueryFlag() &&
|
|
( FlagAsciiCompare.QueryFlag() || FlagLineNumber.QueryFlag() ) ) {
|
|
|
|
DisplayMessage( MSG_FC_INCOMPATIBLE_SWITCHES, ERROR_MESSAGE, "" );
|
|
return( FALSE );
|
|
}
|
|
|
|
if( !InFile1.IsValueSet() ||
|
|
!InFile2.IsValueSet() ) {
|
|
DisplayMessage( MSG_FC_INSUFFICIENT_FILES, ERROR_MESSAGE, "" );
|
|
return( FALSE );
|
|
}
|
|
|
|
|
|
//
|
|
// Convert filenames to upper case
|
|
//
|
|
_File1.Initialize( InFile1.GetPath() );
|
|
_File2.Initialize( InFile2.GetPath() );
|
|
|
|
((PWSTRING)_File1.GetPathString())->Strupr();
|
|
((PWSTRING)_File2.GetPathString())->Strupr();
|
|
|
|
|
|
fUnicode = FlagUnicode.QueryFlag();
|
|
fAbbrev = FlagAbbreviate.QueryFlag();
|
|
fCase = !FlagCaseInsensitive.QueryFlag();
|
|
fIgnore = FlagCompression.QueryFlag();
|
|
fNumb = FlagLineNumber.QueryFlag();
|
|
fBinary = FlagBinaryCompare.QueryFlag();
|
|
fSkipOffline = ( !FlagIncludeOffline.QueryFlag() ) &&
|
|
( !FlagIncludeOffline2.QueryFlag() );
|
|
|
|
if ( FlagExpansion.QueryFlag() ) {
|
|
fExpandTabs = FALSE;
|
|
//funcRead = (int (*)(char *,int,FILE *))fgets;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
fDebug = FlagDebug.QueryFlag();
|
|
#endif
|
|
|
|
|
|
if ( LongBufferSize.IsValueSet() ) {
|
|
cLine = (INT)LongBufferSize.QueryLong();
|
|
fLine = TRUE;
|
|
} else {
|
|
cLine = 100;
|
|
}
|
|
|
|
if ( LongMatch.IsValueSet() ) {
|
|
if ( LongMatch.GetString()->QueryNumber( &Long ) ) {
|
|
ctSync = (INT)Long;
|
|
fLine = TRUE;
|
|
} else {
|
|
|
|
DisplayMessage( MSG_FC_INVALID_SWITCH, ERROR_MESSAGE, "" );
|
|
ctSync = 2;
|
|
}
|
|
} else {
|
|
ctSync = 2;
|
|
}
|
|
|
|
if (!fBinary && !fLine) {
|
|
|
|
DSTRING ExtBin;
|
|
PWSTRING Ext = _File1.QueryExt();
|
|
|
|
if ( Ext ) {
|
|
for (i=0; extBin[i]; i++) {
|
|
ExtBin.Initialize( extBin[i] );
|
|
if ( !ExtBin.Stricmp( Ext ) ) {
|
|
fBinary = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
DELETE( Ext );
|
|
}
|
|
|
|
if (!fBinary) {
|
|
fLine = TRUE;
|
|
}
|
|
}
|
|
|
|
if (!fUnicode) {
|
|
if (fIgnore) {
|
|
if (fCase) {
|
|
fCmp = MBSTR::Strcmps;
|
|
} else {
|
|
fCmp = MBSTR::Strcmpis;
|
|
}
|
|
} else {
|
|
|
|
if (fCase) {
|
|
fCmp = MBSTR::Strcmp;
|
|
} else {
|
|
fCmp = MBSTR::Stricmp;
|
|
}
|
|
}
|
|
} else {
|
|
if (fIgnore) {
|
|
if (fCase) {
|
|
fCmp_U = WSTRING::Strcmps;
|
|
} else {
|
|
fCmp_U = WSTRING::Strcmpis;
|
|
}
|
|
} else {
|
|
|
|
if (fCase) {
|
|
fCmp_U = WSTRING::Strcmp;
|
|
} else {
|
|
fCmp_U = WSTRING::Stricmp;
|
|
}
|
|
}
|
|
}
|
|
|
|
return( TRUE );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
INT
|
|
FC::Fcmain
|
|
(
|
|
)
|
|
{
|
|
return ParseFileNames();
|
|
}
|
|
|
|
|
|
|
|
|
|
/**************************************************************************/
|
|
/* BinaryCompare */
|
|
/**************************************************************************/
|
|
|
|
int
|
|
FC::BinaryCompare (
|
|
PCWSTRING f1,
|
|
PCWSTRING f2
|
|
)
|
|
{
|
|
|
|
PATH FileName;
|
|
PFSN_FILE File1 = NULL;
|
|
PFSN_FILE File2 = NULL;
|
|
PFILE_STREAM Stream1 = NULL;
|
|
PFILE_STREAM Stream2 = NULL;;
|
|
BYTE_STREAM Bs1;
|
|
BYTE_STREAM Bs2;
|
|
BYTE c1, c2;
|
|
ULONG64 pos;
|
|
BOOLEAN fSame;
|
|
char buffer[128];
|
|
BOOLEAN fFileSkipped;
|
|
|
|
|
|
if ( !FileName.Initialize( f1 ) ||
|
|
!(File1 = SYSTEM::QueryFile( &FileName, fSkipOffline, &fFileSkipped )) ||
|
|
!(Stream1 = File1->QueryStream( READ_ACCESS, FILE_FLAG_OPEN_NO_RECALL )) ||
|
|
!Bs1.Initialize( Stream1 )
|
|
) {
|
|
|
|
DELETE( Stream2 );
|
|
DELETE( File2 );
|
|
DELETE( Stream1 );
|
|
DELETE( File1 );
|
|
if (fFileSkipped) {
|
|
// Skipping offline files is not an error, just track this happened
|
|
fOfflineSkipped = TRUE;
|
|
return FILES_OFFLINE;
|
|
|
|
} else {
|
|
DisplayMessage( MSG_FC_UNABLE_TO_OPEN, ERROR_MESSAGE, "%W", f1 );
|
|
return FILES_NOT_FOUND;
|
|
}
|
|
}
|
|
|
|
if ( !FileName.Initialize( f2 ) ||
|
|
!(File2 = SYSTEM::QueryFile( &FileName, fSkipOffline, &fFileSkipped )) ||
|
|
!(Stream2 = File2->QueryStream( READ_ACCESS, FILE_FLAG_OPEN_NO_RECALL )) ||
|
|
!Bs2.Initialize( Stream2 )
|
|
) {
|
|
|
|
DELETE( Stream2 );
|
|
DELETE( File2 );
|
|
DELETE( Stream1 );
|
|
DELETE( File1 );
|
|
if (fFileSkipped) {
|
|
// Skipping offline files is not an error, just track this happened
|
|
fOfflineSkipped = TRUE;
|
|
return FILES_OFFLINE;
|
|
|
|
} else {
|
|
DisplayMessage( MSG_FC_UNABLE_TO_OPEN, ERROR_MESSAGE, "%W", f2 );
|
|
return FILES_NOT_FOUND;
|
|
}
|
|
}
|
|
|
|
fSame = TRUE;
|
|
pos = 0;
|
|
|
|
while ( TRUE ) {
|
|
|
|
if ( Bs1.ReadByte( &c1 ) ) {
|
|
|
|
if ( Bs2.ReadByte( &c2 ) ) {
|
|
|
|
if (c1 != c2) {
|
|
|
|
if (pos > MAXULONG) {
|
|
sprintf( buffer, "%016I64X: %02X %02X", pos, c1, c2 );
|
|
} else {
|
|
sprintf( buffer, "%08I64X: %02X %02X", pos, c1, c2 );
|
|
}
|
|
DisplayMessage( MSG_FC_DATA, NORMAL_MESSAGE, "%s", buffer );
|
|
fSame = FALSE;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
DisplayMessage( MSG_FC_FILES_DIFFERENT_LENGTH, NORMAL_MESSAGE, "%W%W", f1, f2 );
|
|
fSame = FALSE;
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if ( Bs2.ReadByte( &c2 ) ) {
|
|
|
|
DisplayMessage( MSG_FC_FILES_DIFFERENT_LENGTH, NORMAL_MESSAGE, "%W%W", f2, f1 );
|
|
fSame = FALSE;
|
|
break;
|
|
|
|
} else {
|
|
|
|
if (fSame) {
|
|
DisplayMessage( MSG_FC_NO_DIFFERENCES, NORMAL_MESSAGE );
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
pos++;
|
|
}
|
|
|
|
DELETE( Stream2 );
|
|
DELETE( File2 );
|
|
DELETE( Stream1 );
|
|
DELETE( File1 );
|
|
|
|
return fSame ? SUCCESS : FILES_ARE_DIFFERENT;
|
|
}
|
|
|
|
|
|
/**************************************************************************/
|
|
/* Compare a range of lines. */
|
|
/**************************************************************************/
|
|
|
|
BOOLEAN FC::compare (int l1, register int s1, int l2, register int s2, int ct)
|
|
{
|
|
#ifdef DEBUG
|
|
if (fDebug)
|
|
DebugPrintTrace(("compare (%d, %d, %d, %d, %d)\n", l1, s1, l2, s2, ct));
|
|
#endif
|
|
|
|
if (ct <= 0 || s1+ct > l1 || s2+ct > l2)
|
|
return (FALSE);
|
|
|
|
while (ct--)
|
|
{
|
|
|
|
#ifdef DEBUG
|
|
if (fDebug)
|
|
DebugPrintTrace(("'%s' == '%s'? ", buffer1[s1].text, buffer2[s2].text));
|
|
#endif
|
|
if(!fUnicode) {
|
|
if ((*fCmp)(buffer1[s1++].text, buffer2[s2++].text)) {
|
|
|
|
#ifdef DEBUG
|
|
if (fDebug)
|
|
DebugPrintTrace(("No\n"));
|
|
#endif
|
|
return (FALSE);
|
|
}
|
|
} else {
|
|
if ((*fCmp_U)(buffer1[s1++].wtext, buffer2[s2++].wtext)) {
|
|
|
|
#ifdef DEBUG
|
|
if (fDebug)
|
|
DebugPrintTrace(("No\n"));
|
|
#endif
|
|
return (FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if (fDebug)
|
|
DebugPrintTrace(("Yes\n"));
|
|
#endif
|
|
|
|
return (TRUE);
|
|
}
|
|
|
|
|
|
/**************************************************************************/
|
|
/* LineCompare */
|
|
/**************************************************************************/
|
|
|
|
|
|
INT
|
|
FC::LineCompare(
|
|
PCWSTRING f1,
|
|
PCWSTRING f2
|
|
)
|
|
{
|
|
|
|
PATH FileName;
|
|
PFSN_FILE File1 = NULL;
|
|
PFSN_FILE File2 = NULL;
|
|
PFILE_STREAM Stream1 = NULL;
|
|
PFILE_STREAM Stream2 = NULL;
|
|
int result;
|
|
BOOLEAN fFileSkipped;
|
|
|
|
|
|
buffer1 = buffer2 = NULL;
|
|
|
|
if ( !FileName.Initialize( f1 ) ||
|
|
!(File1 = SYSTEM::QueryFile( &FileName, fSkipOffline, &fFileSkipped )) ||
|
|
!(Stream1 = File1->QueryStream( READ_ACCESS, FILE_FLAG_OPEN_NO_RECALL ))
|
|
) {
|
|
|
|
FREE(buffer1);
|
|
FREE(buffer2);
|
|
DELETE(Stream2);
|
|
DELETE(File2);
|
|
DELETE(Stream1);
|
|
DELETE(File1);
|
|
if (fFileSkipped) {
|
|
// Skipping offline files is not an error, just track this happened
|
|
fOfflineSkipped = TRUE;
|
|
return FILES_OFFLINE;
|
|
|
|
} else {
|
|
DisplayMessage( MSG_FC_UNABLE_TO_OPEN, ERROR_MESSAGE, "%W", f1 );
|
|
return FILES_NOT_FOUND;
|
|
}
|
|
}
|
|
|
|
if ( !FileName.Initialize( f2 ) ||
|
|
!(File2 = SYSTEM::QueryFile( &FileName, fSkipOffline, &fFileSkipped )) ||
|
|
!(Stream2 = File2->QueryStream( READ_ACCESS, FILE_FLAG_OPEN_NO_RECALL ))
|
|
) {
|
|
|
|
FREE(buffer1);
|
|
FREE(buffer2);
|
|
DELETE(Stream2);
|
|
DELETE(File2);
|
|
DELETE(Stream1);
|
|
DELETE(File1);
|
|
if (fFileSkipped) {
|
|
// Skipping offline files is not an error, just track this happened
|
|
fOfflineSkipped = TRUE;
|
|
return FILES_OFFLINE;
|
|
|
|
} else {
|
|
DisplayMessage( MSG_FC_UNABLE_TO_OPEN, ERROR_MESSAGE, "%W", f2 );
|
|
return FILES_NOT_FOUND;
|
|
}
|
|
}
|
|
|
|
|
|
if ( (buffer1 = (struct lineType *)MALLOC(cLine * (sizeof *buffer1))) == NULL ||
|
|
(buffer2 = (struct lineType *)MALLOC(cLine * (sizeof *buffer1))) == NULL) {
|
|
|
|
DisplayMessage( MSG_FC_OUT_OF_MEMORY, ERROR_MESSAGE );
|
|
FREE(buffer1);
|
|
FREE(buffer2);
|
|
DELETE(Stream2);
|
|
DELETE(File2);
|
|
DELETE(Stream1);
|
|
DELETE(File1);
|
|
return FAILURE;
|
|
}
|
|
|
|
result = RealLineCompare( f1, f2, Stream1, Stream2 );
|
|
|
|
FREE(buffer1);
|
|
FREE(buffer2);
|
|
DELETE(Stream2);
|
|
DELETE(File2);
|
|
DELETE(Stream1);
|
|
DELETE(File1);
|
|
|
|
return result;
|
|
}
|
|
|
|
int
|
|
FC::RealLineCompare (
|
|
PCWSTRING f1,
|
|
PCWSTRING f2,
|
|
PSTREAM Stream1,
|
|
PSTREAM Stream2
|
|
)
|
|
{
|
|
|
|
int l1, l2, i, xp, yp, xc, yc;
|
|
BOOLEAN xd, yd, fSame;
|
|
int line1, line2;
|
|
|
|
|
|
fSame = TRUE;
|
|
l1 = l2 = 0;
|
|
line1 = line2 = 0;
|
|
|
|
l0:
|
|
|
|
#ifdef DEBUG
|
|
if (fDebug) {
|
|
DebugPrintTrace(("At scan beginning\n"));
|
|
}
|
|
#endif
|
|
|
|
l1 += xfill (buffer1+l1, Stream1, cLine-l1, &line1);
|
|
l2 += xfill (buffer2+l2, Stream2, cLine-l2, &line2);
|
|
|
|
if (l1 == 0 && l2 == 0) {
|
|
|
|
if (fSame) {
|
|
DisplayMessage( MSG_FC_NO_DIFFERENCES, NORMAL_MESSAGE );
|
|
}
|
|
return fSame ? SUCCESS : FILES_ARE_DIFFERENT;
|
|
}
|
|
|
|
xc = min (l1, l2);
|
|
|
|
for (i=0; i < xc; i++) {
|
|
|
|
if (!compare (l1, i, l2, i, 1)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i != xc) {
|
|
i = ( i-1 > 0 )? ( i-1 ) : 0;
|
|
}
|
|
|
|
l1 = adjust (buffer1, l1, i);
|
|
l2 = adjust (buffer2, l2, i);
|
|
|
|
if (l1 == 0 && l2 == 0) {
|
|
goto l0;
|
|
}
|
|
|
|
l1 += xfill (buffer1+l1, Stream1, cLine-l1, &line1);
|
|
l2 += xfill (buffer2+l2, Stream2, cLine-l2, &line2);
|
|
|
|
#ifdef DEBUG
|
|
if (fDebug) {
|
|
DebugPrintTrace(("buffers are adjusted, %d, %d remain\n", l1, l2));
|
|
}
|
|
#endif
|
|
|
|
xd = yd = FALSE;
|
|
xc = yc = 1;
|
|
xp = yp = 1;
|
|
|
|
l6:
|
|
|
|
#ifdef DEBUG
|
|
if (fDebug) {
|
|
DebugPrintTrace(("Trying resync %d,%d %d,%d\n", xc, xp, yc, yp));
|
|
}
|
|
#endif
|
|
|
|
i = min (l1-xc,l2-yp);
|
|
i = min (i, ctSync);
|
|
|
|
if (compare (l1, xc, l2, yp, i)) {
|
|
|
|
fSame = FALSE;
|
|
DisplayMessage( MSG_FC_OUTPUT_FILENAME, NORMAL_MESSAGE, "%W", f1 );
|
|
dump (buffer1, 0, xc);
|
|
DisplayMessage( MSG_FC_OUTPUT_FILENAME, NORMAL_MESSAGE, "%W", f2 );
|
|
dump (buffer2, 0, yp);
|
|
DisplayMessage( MSG_FC_DUMP_END, NORMAL_MESSAGE );
|
|
|
|
l1 = adjust (buffer1, l1, xc);
|
|
l2 = adjust (buffer2, l2, yp);
|
|
|
|
goto l0;
|
|
}
|
|
|
|
i = min (l1-xp, l2-yc);
|
|
i = min (i, ctSync);
|
|
|
|
if (compare (l1, xp, l2, yc, i)) {
|
|
|
|
fSame = FALSE;
|
|
DisplayMessage( MSG_FC_OUTPUT_FILENAME, NORMAL_MESSAGE, "%W", f1 );
|
|
dump (buffer1, 0, xp);
|
|
DisplayMessage( MSG_FC_OUTPUT_FILENAME, NORMAL_MESSAGE, "%W", f2 );
|
|
dump (buffer2, 0, yc);
|
|
DisplayMessage( MSG_FC_DUMP_END, NORMAL_MESSAGE );
|
|
|
|
l1 = adjust (buffer1, l1, xp);
|
|
l2 = adjust (buffer2, l2, yc);
|
|
|
|
goto l0;
|
|
}
|
|
|
|
if (++xp > xc) {
|
|
|
|
xp = 1;
|
|
if (++xc >= l1) {
|
|
|
|
xc = l1;
|
|
xd = TRUE;
|
|
}
|
|
}
|
|
|
|
if (++yp > yc) {
|
|
|
|
yp = 1;
|
|
if (++yc >= l2) {
|
|
|
|
yc = l2;
|
|
yd = TRUE;
|
|
}
|
|
}
|
|
|
|
if (!xd || !yd) {
|
|
goto l6;
|
|
}
|
|
|
|
fSame = FALSE;
|
|
|
|
if (l1 >= cLine || l2 >= cLine) {
|
|
DisplayMessage( MSG_FC_RESYNC_FAILED, NORMAL_MESSAGE );
|
|
}
|
|
|
|
DisplayMessage( MSG_FC_OUTPUT_FILENAME, NORMAL_MESSAGE, "%W", f1 );
|
|
dump (buffer1, 0, l1-1);
|
|
DisplayMessage( MSG_FC_OUTPUT_FILENAME, NORMAL_MESSAGE, "%W", f2 );
|
|
dump (buffer2, 0, l2-1);
|
|
DisplayMessage( MSG_FC_DUMP_END, NORMAL_MESSAGE );
|
|
|
|
return fSame ? SUCCESS : FILES_ARE_DIFFERENT;
|
|
}
|
|
|
|
|
|
/**************************************************************************/
|
|
/* Return number of lines read in. */
|
|
/**************************************************************************/
|
|
|
|
FC::xfill (struct lineType *pl, PSTREAM Stream, int ct, int *plnum)
|
|
{
|
|
int i;
|
|
DWORD StrSize;
|
|
|
|
#ifdef DEBUG
|
|
if (fDebug)
|
|
DebugPrintTrace(("xfill (%04x, %04x)\n", pl, fh));
|
|
#endif
|
|
|
|
i = 0;
|
|
|
|
if (!fUnicode) {
|
|
while ( ct-- && !Stream->IsAtEnd() && Stream->ReadMbLine( pl->text, MAXLINESIZE, &StrSize, fExpandTabs, 8 ) ) {
|
|
|
|
if (fIgnore && !MBSTR::Strcmps(pl->text, "")) {
|
|
|
|
pl->text[0] = 0;
|
|
++*plnum;
|
|
}
|
|
|
|
if (strlen (pl->text) != 0 || !fIgnore)
|
|
{
|
|
pl->line = ++*plnum;
|
|
pl++;
|
|
i++;
|
|
}
|
|
}
|
|
} else {
|
|
while( ct-- && !Stream->IsAtEnd() && Stream->ReadWLine( pl->wtext,MAXLINESIZE, &StrSize, fExpandTabs, 8 ) ) {
|
|
//while( ct-- && !Stream->IsAtEnd() && Stream->ReadLine( &_String , TRUE )) {
|
|
|
|
//_String.QueryWSTR(0,TO_END,pl->wtext,MAXLINESIZE,TRUE);
|
|
|
|
if (fIgnore && !WSTRING::Strcmps((PWSTR)pl->wtext, (PWSTR)L"")) {
|
|
|
|
pl->wtext[0] = 0;
|
|
++*plnum;
|
|
}
|
|
|
|
if (wcslen (pl->wtext) != 0 || !fIgnore)
|
|
{
|
|
pl->line = ++*plnum;
|
|
pl++;
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if (fDebug)
|
|
DebugPrintTrace(("xfill returns %d\n", i));
|
|
#endif
|
|
|
|
return (i);
|
|
|
|
#if 0
|
|
|
|
while (ct-- && (*funcRead) (pl->text, MAXLINESIZE, fh) != NULL)
|
|
{
|
|
if (funcRead == ( int (*) (char *,int, FILE *))fgets)
|
|
pl->text[strlen(pl->text)-1] = 0;
|
|
if (fIgnore && !MBSTR::Strcmps(pl->text, ""))
|
|
{
|
|
pl->text[0] = 0;
|
|
++*plnum;
|
|
}
|
|
if (strlen (pl->text) != 0 || !fIgnore)
|
|
{
|
|
pl->line = ++*plnum;
|
|
pl++;
|
|
i++;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if (fDebug)
|
|
DebugPrintTrace(("xfill returns %d\n", i));
|
|
#endif
|
|
|
|
return (i);
|
|
|
|
# endif
|
|
|
|
}
|
|
|
|
|
|
/**************************************************************************/
|
|
/* Adjust returns number of lines in buffer. */
|
|
/**************************************************************************/
|
|
|
|
FC::adjust (struct lineType *pl, int ml, int lt)
|
|
{
|
|
#ifdef DEBUG
|
|
if (fDebug)
|
|
DebugPrintTrace(("adjust (%04x, %d, %d) = ", pl, ml, lt));
|
|
if (fDebug)
|
|
DebugPrintTrace(("%d\n", ml-lt));
|
|
#endif
|
|
|
|
if (ml <= lt)
|
|
return (0);
|
|
|
|
#ifdef DEBUG
|
|
if (fDebug)
|
|
DebugPrintTrace(("move (%04x, %04x, %04x)\n", &pl[lt], &pl[0], sizeof (*pl)*(ml-lt)));
|
|
#endif
|
|
|
|
// Move((char *)&pl[lt], (char *)&pl[0], sizeof (*pl)*(ml-lt));
|
|
memmove( (char *)&pl[0], (char *)&pl[lt], sizeof (*pl)*(ml-lt) );
|
|
return ml-lt;
|
|
}
|
|
|
|
|
|
/**************************************************************************/
|
|
/* dump */
|
|
/* dump outputs a range of lines. */
|
|
/* */
|
|
/* INPUTS */
|
|
/* pl pointer to current lineType structure */
|
|
/* start starting line number */
|
|
/* end ending line number */
|
|
/* */
|
|
/* CALLS */
|
|
/* pline, printf */
|
|
/**************************************************************************/
|
|
|
|
void FC::dump (struct lineType *pl, int start, int end)
|
|
{
|
|
if (fAbbrev && end-start > 2)
|
|
{
|
|
pline (pl+start);
|
|
DisplayMessage( MSG_FC_ABBREVIATE_SYMBOL, NORMAL_MESSAGE );
|
|
pline (pl+end);
|
|
}
|
|
else
|
|
{
|
|
while (start <= end)
|
|
pline (pl+start++);
|
|
}
|
|
}
|
|
|
|
|
|
/**************************************************************************/
|
|
/* PrintLINE */
|
|
/* pline prints a single line of output. If the /n flag */
|
|
/* has been specified, the line number of the printed text is added. */
|
|
/* */
|
|
/* Inputs */
|
|
/* pl pointer to current lineType structure */
|
|
/* fNumb TRUE if /n specified */
|
|
/**************************************************************************/
|
|
|
|
void FC::pline (struct lineType *pl)
|
|
{
|
|
if (!fUnicode) {
|
|
if (fNumb)
|
|
DisplayMessage( MSG_FC_NUMBERED_DATA, NORMAL_MESSAGE, "%5d%s",
|
|
pl->line, pl->text );
|
|
else
|
|
DisplayMessage( MSG_FC_DATA, NORMAL_MESSAGE, "%s", pl->text );
|
|
} else {
|
|
FSTRING f;
|
|
if (fNumb)
|
|
DisplayMessage( MSG_FC_NUMBERED_DATA, NORMAL_MESSAGE, "%5d%W",
|
|
pl->line, f.Initialize(pl->wtext) );
|
|
else
|
|
DisplayMessage( MSG_FC_DATA, NORMAL_MESSAGE, "%W",
|
|
f.Initialize(pl->wtext) );
|
|
}
|
|
}
|
|
|
|
|
|
/*********************************************************************/
|
|
/* Routine: ParseFileNames */
|
|
/* */
|
|
/* Function: Parses the two given filenames and then compares the */
|
|
/* appropriate filenames. This routine handles wildcard */
|
|
/* characters in both filenames. */
|
|
/*********************************************************************/
|
|
|
|
INT
|
|
FC::ParseFileNames()
|
|
{
|
|
PATH File1;
|
|
PATH File2;
|
|
FSN_FILTER Filter;
|
|
PWSTRING Name;
|
|
PARRAY NodeArray;
|
|
PITERATOR Iterator;
|
|
PFSN_DIRECTORY Dir;
|
|
PFSNODE File;
|
|
PPATH ExpandedPath;
|
|
PATH TmpPath;
|
|
int result = SUCCESS;
|
|
char *locale;
|
|
|
|
if (!File1.Initialize( &_File1 ) ||
|
|
!File2.Initialize( &_File2 ) ||
|
|
!Filter.Initialize()) {
|
|
DisplayMessage( MSG_FC_OUT_OF_MEMORY, ERROR_MESSAGE );
|
|
return FAILURE;
|
|
}
|
|
|
|
if (!(Name = File1.QueryName())) {
|
|
DisplayMessage( MSG_FC_UNABLE_TO_OPEN, ERROR_MESSAGE, "%W", File1.GetPathString() );
|
|
return FILES_NOT_FOUND;
|
|
}
|
|
|
|
if (!Filter.SetFileName( Name ) ||
|
|
!Filter.SetAttributes( (FSN_ATTRIBUTE)0, // ALL
|
|
FSN_ATTRIBUTE_FILES, // ANY
|
|
FSN_ATTRIBUTE_DIRECTORY )) { // NONE
|
|
DELETE( Name );
|
|
DisplayMessage( MSG_FC_OUT_OF_MEMORY, ERROR_MESSAGE );
|
|
return FAILURE;
|
|
}
|
|
|
|
DELETE( Name );
|
|
|
|
if (!TmpPath.Initialize( &File1, TRUE ) ||
|
|
!TmpPath.TruncateBase()) {
|
|
DisplayMessage( MSG_FC_OUT_OF_MEMORY, ERROR_MESSAGE );
|
|
return FAILURE;
|
|
}
|
|
|
|
if ( !(Dir = SYSTEM::QueryDirectory( &TmpPath )) ||
|
|
!(NodeArray = Dir->QueryFsnodeArray( &Filter )) ||
|
|
!(Iterator = NodeArray->QueryIterator()) ||
|
|
!(File = (PFSNODE)Iterator->GetNext())
|
|
) {
|
|
DisplayMessage( MSG_FC_UNABLE_TO_OPEN, ERROR_MESSAGE, "%W", File1.GetPathString() );
|
|
return FILES_NOT_FOUND;
|
|
}
|
|
|
|
Iterator->Reset();
|
|
|
|
while ( File = (PFSNODE)Iterator->GetNext() ) {
|
|
|
|
PWSTRING Name1;
|
|
PWSTRING Name2;
|
|
|
|
if (!(Name1 = File->QueryName())) {
|
|
DisplayMessage( MSG_FC_UNABLE_TO_OPEN, ERROR_MESSAGE, "%W", File->GetPath()->GetPathString() );
|
|
return FILES_NOT_FOUND;
|
|
}
|
|
|
|
if ( _File2.HasWildCard() ) {
|
|
|
|
if ( !(ExpandedPath = _File2.QueryWCExpansion( (PPATH)File->GetPath() ))) {
|
|
|
|
if (!(Name2 = _File2.QueryName())) {
|
|
DisplayMessage( MSG_FC_UNABLE_TO_OPEN, ERROR_MESSAGE, "%W", _File2.GetPathString() );
|
|
return FILES_NOT_FOUND;
|
|
}
|
|
|
|
DisplayMessage( MSG_FC_CANT_EXPAND_TO_MATCH, ERROR_MESSAGE, "%W%W", Name1, Name2 );
|
|
DELETE( Name2 );
|
|
DELETE( Name1 );
|
|
DELETE( Iterator );
|
|
DELETE( NodeArray );
|
|
DELETE( Dir );
|
|
return FAILURE;
|
|
}
|
|
|
|
} else {
|
|
|
|
if ( !(ExpandedPath = NEW PATH) ||
|
|
!ExpandedPath->Initialize( &_File2 ) ) {
|
|
|
|
DisplayMessage( MSG_FC_OUT_OF_MEMORY, ERROR_MESSAGE );
|
|
DELETE( Name1 );
|
|
DELETE( Iterator );
|
|
DELETE( NodeArray );
|
|
DELETE( Dir );
|
|
return FAILURE;
|
|
|
|
}
|
|
}
|
|
|
|
if (!(Name2 = ExpandedPath->QueryName())) {
|
|
DisplayMessage( MSG_FC_UNABLE_TO_OPEN, ERROR_MESSAGE, "%W", ExpandedPath->GetPathString() );
|
|
return FILES_NOT_FOUND;
|
|
}
|
|
|
|
if (!File1.SetName( Name1 ) ||
|
|
!File2.SetName( Name2 )) {
|
|
DisplayMessage( MSG_FC_OUT_OF_MEMORY, ERROR_MESSAGE );
|
|
return FAILURE;
|
|
}
|
|
|
|
switch (comp( File1.GetPathString(), File2.GetPathString() )) {
|
|
case FAILURE:
|
|
return FAILURE;
|
|
|
|
case SUCCESS:
|
|
break;
|
|
|
|
case FILES_ARE_DIFFERENT:
|
|
result |= FILES_ARE_DIFFERENT;
|
|
break;
|
|
|
|
case FILES_NOT_FOUND:
|
|
result |= FILES_NOT_FOUND;
|
|
break;
|
|
|
|
case FILES_OFFLINE:
|
|
result |= FILES_OFFLINE;
|
|
break;
|
|
|
|
default:
|
|
DebugAssert(FALSE);
|
|
}
|
|
|
|
DELETE( Name2 );
|
|
DELETE( Name1 );
|
|
DELETE( ExpandedPath );
|
|
|
|
}
|
|
|
|
// Print a warning in case that offline files were skipped
|
|
if (fOfflineSkipped) {
|
|
DisplayMessage(MSG_FC_OFFLINE_FILES_SKIPPED, ERROR_MESSAGE);
|
|
}
|
|
|
|
DELETE( Iterator );
|
|
NodeArray->DeleteAllMembers();
|
|
DELETE( NodeArray );
|
|
DELETE( Dir );
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*********************************************************************/
|
|
/* Routine: comp */
|
|
/* */
|
|
/* Function: Compares the two files. */
|
|
/*********************************************************************/
|
|
|
|
INT
|
|
FC::comp(PCWSTRING file1, PCWSTRING file2)
|
|
{
|
|
DisplayMessage( MSG_FC_COMPARING_FILES, NORMAL_MESSAGE, "%W%W", file1, file2 );
|
|
if (fBinary) {
|
|
return BinaryCompare (file1, file2);
|
|
} else {
|
|
return LineCompare (file1, file2);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
#if 0
|
|
/* returns line from file (no CRLFs); returns NULL if EOF */
|
|
FC::fgetl ( char *buf, int len, FILE *fh)
|
|
{
|
|
register int c;
|
|
register char *p;
|
|
|
|
/* remember NUL at end */
|
|
len--;
|
|
p = buf;
|
|
while (len) {
|
|
c = getc (fh);
|
|
if (c == EOF || c == '\n')
|
|
break;
|
|
#if MSDOS
|
|
if (c != '\r')
|
|
#endif
|
|
if (c != '\t') {
|
|
*p++ = (char)c;
|
|
len--;
|
|
}
|
|
else {
|
|
c = min (8 - ((int)(p-buf) & 0x0007), len);
|
|
memset(p, ' ', c);
|
|
p += c;
|
|
len -= c;
|
|
}
|
|
}
|
|
*p = 0;
|
|
return ! ( (c == EOF) && (p == buf) );
|
|
}
|
|
#endif
|