|
|
/*++
Copyright (c) 1990-2000 Microsoft Corporation
Module Name:
Replace
Abstract:
Replace utility
Author:
Ramon Juan San Andres (ramonsa) 01-May-1991
Revision History:
--*/
#include "ulib.hxx"
#include "arrayit.hxx"
#include "dir.hxx"
#include "filter.hxx"
#include "file.hxx"
#include "fsnode.hxx"
#include "stream.hxx"
#include "substrng.hxx"
#include "system.hxx"
#include "replace.hxx"
//
// Pattern that matches all files in a directory
//
#define MATCH_ALL_FILES "*.*"
#define CTRL_C (WCHAR)3
//
// Size of buffers to hold path strings
//
#define INITIAL_PATHSTRING_BUFFER_SIZE MAX_PATH
VOID __cdecl main ( )
/*++
Routine Description:
Main function of the Replace utility
Arguments:
None.
Return Value:
None.
Notes:
--*/
{ //
// Initialize stuff
//
DEFINE_CLASS_DESCRIPTOR( REPLACE );
//
// Now do the replacement
//
{ REPLACE Replace;
//
// Initialize the Replace object.
//
Replace.Initialize();
//
// Do our thing
//
Replace.DoReplace(); } }
DEFINE_CONSTRUCTOR( REPLACE, PROGRAM );
BOOLEAN REPLACE::Initialize ( )
/*++
Routine Description:
Initializes the REPLACE object
Arguments:
None.
Return Value:
TRUE if initialized, FALSE otherwise
Notes:
--*/
{ //
// Initialize program object
//
PROGRAM::Initialize( REPLACE_MESSAGE_USAGE );
//
// Allocate global structures and initialize them
//
InitializeThings();
//
// Parse the arguments
//
SetArguments();
return TRUE; }
REPLACE::~REPLACE ( )
/*++
Routine Description:
Destructs a REPLACE object
Arguments:
None.
Return Value:
None.
Notes:
--*/
{ //
// Deallocate the global structures previously allocated
//
DeallocateThings();
//
// Exit without error
//
DisplayMessageAndExit( 0, NULL, EXIT_NORMAL );
}
VOID REPLACE::InitializeThings ( )
/*++
Routine Description:
Initializes the global variables that need initialization
Arguments:
None.
Return Value:
None.
Notes:
--*/
{
//
// Initialize the path string buffers
//
_PathString1 = (LPWSTR)MALLOC( INITIAL_PATHSTRING_BUFFER_SIZE ); _PathString2 = (LPWSTR)MALLOC( INITIAL_PATHSTRING_BUFFER_SIZE );
_PathString1Size = _PathString2Size = INITIAL_PATHSTRING_BUFFER_SIZE;
_Keyboard = NEW KEYBOARD;
if ( !_PathString1 || !_PathString2 || !_Keyboard ) { DisplayMessageAndExit( REPLACE_ERROR_NO_MEMORY, NULL, EXIT_NO_MEMORY ); }
//
// initialize the keyboard and set ctrl-c handling
//
_Keyboard->Initialize(); _Keyboard->EnableBreakHandling();
//
// Initialize our data
//
_SourcePath = NULL; _DestinationPath = NULL; _FilesAdded = 0; _FilesReplaced = 0; _SourceDirectory = NULL; _Pattern = NULL; _FilesInSrc = NULL;
_AddSwitch = FALSE; // use by DisplayMessageAndExit before any
// of those boolean _*Switch is being initialized
}
VOID REPLACE::DeallocateThings ( )
/*++
Routine Description:
Deallocates the stuff that was initialized in InitializeThings()
Arguments:
None.
Return Value:
None.
Notes:
--*/
{
DELETE( _FilesInSrc ); DELETE( _SourceDirectory ); DELETE( _Pattern ); DELETE( _Keyboard );
FREE( _PathString1 ); FREE( _PathString2 );
}
BOOLEAN REPLACE::DoReplace ( )
/*++
Routine Description:
This is the function that performs the Replace.
Arguments:
None.
Return Value:
TRUE
Notes:
--*/
{
PFSN_DIRECTORY DestinationDirectory; FSN_FILTER Filter; WCHAR Char;
//
// Get the source directory object and the pattern that we will use
// for file matching.
//
GetDirectoryAndPattern( _SourcePath, &_SourceDirectory, &_Pattern );
DebugPtrAssert( _SourceDirectory ); DebugPtrAssert( _Pattern );
//
// Get the destination directory
//
GetDirectory( _DestinationPath, &DestinationDirectory );
DebugPtrAssert( DestinationDirectory );
//
// Wait if requested
//
if ( _WaitSwitch ) {
DisplayMessage( REPLACE_MESSAGE_PRESS_ANY_KEY ); AbortIfCtrlC();
//
// All input is in raw mode.
//
_Keyboard->DisableLineMode(); GetStandardInput()->ReadChar( &Char ); _Keyboard->EnableLineMode();
GetStandardOutput()->WriteChar( Char ); GetStandardOutput()->WriteChar( (WCHAR)'\r'); GetStandardOutput()->WriteChar( (WCHAR)'\n');
//
// Check for ctrl-c
//
if ( Char == CTRL_C ) { exit ( EXIT_PATH_NOT_FOUND ); } }
//
// Get an array containing all the files in the source directory
// that match the pattern.
//
// This is so that Replacer() does not have to get the same
// information over and over when the Subdir switch is set.
//
_FilesInSrc = GetFileArray( _SourceDirectory, _Pattern ); DebugPtrAssert( _FilesInSrc );
if ( _SubdirSwitch ) {
//
// First, replace the files in the directory specified
//
Replacer( this, DestinationDirectory, NULL );
Filter.Initialize(); Filter.SetAttributes( (FSN_ATTRIBUTE)FILE_ATTRIBUTE_DIRECTORY );
//
// Now traverse the destination directory, calling the
// replacer function for each subdirectory.
//
DestinationDirectory->Traverse( this, &Filter, NULL, REPLACE::Replacer ); } else {
//
// Call the replace function, which takes care of replacements
//
Replacer(this, DestinationDirectory, NULL ); } DELETE( DestinationDirectory );
return TRUE;
}
VOID REPLACE::GetDirectoryAndPattern( IN PPATH Path, OUT PFSN_DIRECTORY *Directory, OUT PWSTRING *Pattern )
/*++
Routine Description:
Given a path, this function obtains a directory object and a pattern.
Normally, the pattern is the filename portion of the path, but if the entire path refers to a directory, then the pattern is "*.*"
Arguments:
Path - Supplies pointer to path Directory - Supplies pointer to pointer to directory Pattern - Supplies pointer to pointer to pattern
Return Value:
TRUE
Notes:
--*/
{ PATH TmpPath; PWSTRING Name; PFSN_DIRECTORY Dir; PWSTRING Ptrn;
DebugAssert( Path ); DebugAssert( Directory ); DebugAssert( Pattern );
//
// If the name passed is a directory, it is an error.
// Otherwise, split the path into Directory and Pattern
// portions.
//
Dir = SYSTEM::QueryDirectory( Path );
if ( Dir || (Name = Path->QueryName()) == NULL || (Ptrn = Name->QueryString()) == NULL ) {
DisplayMessageAndExit( REPLACE_ERROR_NO_FILES_FOUND, Path->GetPathString(), EXIT_FILE_NOT_FOUND );
} else {
// We're finished with Name.
//
DELETE( Name );
//
// Get the directory
//
TmpPath.Initialize( Path, TRUE ); TmpPath.TruncateBase();
Dir = SYSTEM::QueryDirectory( &TmpPath );
if ( !Dir ) {
DisplayMessageAndExit( REPLACE_ERROR_PATH_NOT_FOUND, Path->GetPathString(), EXIT_PATH_NOT_FOUND ); }
*Directory = Dir; *Pattern = Ptrn; } }
VOID REPLACE::GetDirectory( IN PCPATH Path, OUT PFSN_DIRECTORY *Directory )
/*++
Routine Description:
Makes a directory out of a path.
Arguments:
Path - Supplies pointer to path Directory - Supplies pointer to pointer to directory
Return Value:
TRUE
Notes:
--*/
{ PFSN_DIRECTORY Dir;
if ( !(Dir = SYSTEM::QueryDirectory( Path )) ) {
DisplayMessageAndExit( REPLACE_ERROR_PATH_NOT_FOUND, Path->GetPathString(), EXIT_PATH_NOT_FOUND ); }
*Directory = Dir;
}
PARRAY REPLACE::GetFileArray( IN PFSN_DIRECTORY Directory, IN PWSTRING Pattern )
/*++
Routine Description:
Gets an array of those files in a directory matching a pattern.
Arguments:
Directory - Supplies pointer to directory Pattern - Supplies pointer to pattern
Return Value:
Pointer to the array of files
Notes:
--*/ {
PARRAY Array; FSN_FILTER Filter;
DebugPtrAssert( Directory ); DebugPtrAssert( Pattern );
Filter.Initialize(); Filter.SetFileName( Pattern ); Filter.SetAttributes( (FSN_ATTRIBUTE)0, (FSN_ATTRIBUTE)0, (FSN_ATTRIBUTE)FILE_ATTRIBUTE_DIRECTORY );
Array = Directory->QueryFsnodeArray( &Filter );
DebugPtrAssert( Array );
return Array;
}
BOOLEAN REPLACE::Replacer ( IN PVOID This, IN OUT PFSNODE DirectoryNode, IN PPATH DummyPath ) /*++
Routine Description:
This is the heart of Replace. Given a destination directory, it performs the replacement/additions according to the global switches and the SourceDirectory and Pattern.
Arguments:
This - Supplies pointer to the REPLACE object Node - Supplies pointer to the directory node. DummyPath - Required by FSN_DIRECTORY::Traverse(), must be NULL.
Return Value:
BOOLEAN - TRUE if operation successful. FALSE otherwise
Notes:
--*/
{
DebugAssert( DummyPath == NULL ); DebugAssert( DirectoryNode->IsDirectory() );
((PREPLACE)This)->AbortIfCtrlC();
if ( ((PREPLACE)This)->_AddSwitch ) { return ((PREPLACE)This)->AddFiles( (PFSN_DIRECTORY)DirectoryNode ); } else { return ((PREPLACE)This)->ReplaceFiles( (PFSN_DIRECTORY)DirectoryNode ); } }
BOOLEAN REPLACE::AddFiles ( IN OUT PFSN_DIRECTORY DestinationDirectory ) /*++
Routine Description:
Adds those files from the SourceDirectory that match Pattern to the DestinationDirectory. The array of files is already in the FilesInSrc array.
Arguments:
DestinationDirectory - Supplies pointer to destination directory.
Return Value:
BOOLEAN - TRUE if operation successful. FALSE otherwise
Notes:
--*/
{
PARRAY_ITERATOR Iterator; PFSN_FILE File; PFSN_FILE FileToCreate; PATH DestinationPath; PWSTRING Name;
DebugPtrAssert( DestinationDirectory ); DebugPtrAssert( _FilesInSrc );
//
// Get destination path
//
DestinationPath.Initialize( DestinationDirectory->GetPath() );
//
// Obtain an iterator for going thru the files
//
Iterator = ( PARRAY_ITERATOR )_FilesInSrc->QueryIterator( ); if (Iterator == NULL) { DisplayMessageAndExit( REPLACE_ERROR_NO_MEMORY, NULL, EXIT_NO_MEMORY ); return FALSE; // help lint
}
//
// For each file in the array, see if it exists in the destination
// directory, and if it does not, then copy it.
//
while ( File = (PFSN_FILE)Iterator->GetNext() ) {
DebugAssert( !(((PFSNODE)File)->IsDirectory()) );
Name = File->QueryName(); if (Name == NULL) { DisplayMessageAndExit( REPLACE_ERROR_NO_MEMORY, NULL, EXIT_NO_MEMORY ); return FALSE; // help lint
}
//
// Form the path in the target file
//
DestinationPath.AppendBase( Name );
DELETE( Name );
//
// See if the file exists
//
FileToCreate = SYSTEM::QueryFile( &DestinationPath );
//
// If the file does not exist, then it has to be added
//
if ( !FileToCreate ) {
if ( !_PromptSwitch || Prompt( REPLACE_MESSAGE_ADD_YES_NO, &DestinationPath ) ) {
DisplayMessage( REPLACE_MESSAGE_ADDING, NORMAL_MESSAGE, "%W", DestinationPath.GetPathString() );
CopyTheFile( File->GetPath(), &DestinationPath );
_FilesAdded++; }
}
DELETE( FileToCreate );
//
// Set the destination path back to what it originally was
// ( i.e. directory specification, no file ).
//
DestinationPath.TruncateBase();
} DELETE( Iterator );
return TRUE; }
BOOLEAN REPLACE::ReplaceFiles ( IN OUT PFSN_DIRECTORY DestinationDirectory ) /*++
Routine Description:
Replaces those files in the DestinationDirectory that match Pattern by the corresponding files in SourceDirectory.
Arguments:
DestinationDirectory - Supplies pointer to destination directory.
Return Value:
BOOLEAN - TRUE if operation successful. FALSE otherwise
Notes:
--*/
{
PARRAY_ITERATOR Iterator; PFSN_FILE File; PFSN_FILE FileToReplace; PATH DestinationPath; PWSTRING Name; PTIMEINFO TimeSrc; PTIMEINFO TimeDst; BOOLEAN Proceed = TRUE;
DebugPtrAssert( DestinationDirectory ); DebugPtrAssert( _FilesInSrc );
//
// Get destination path
//
DestinationPath.Initialize( DestinationDirectory->GetPath() );
//
// Obtain an iterator for going thru the files
//
Iterator = ( PARRAY_ITERATOR )_FilesInSrc->QueryIterator( ); if (Iterator == NULL) { DisplayMessageAndExit( REPLACE_ERROR_NO_MEMORY, NULL, EXIT_NO_MEMORY ); return FALSE; // help lint
}
//
// For each file in the array, see if it exists in the destination
// directory, and if it does, replace it
//
while ( File = (PFSN_FILE)Iterator->GetNext() ) {
AbortIfCtrlC();
DebugAssert( !(((PFSNODE)File)->IsDirectory()) );
Name = File->QueryName(); if (Name == NULL) { DisplayMessageAndExit( REPLACE_ERROR_NO_MEMORY, NULL, EXIT_NO_MEMORY ); return FALSE; // help lint
}
//
// Form the path in the target file
//
DestinationPath.AppendBase( Name );
DELETE( Name );
//
// See if the file exists
//
FileToReplace = SYSTEM::QueryFile( &DestinationPath );
if ( FileToReplace ) {
//
// If the CompareTime switch is set, then we only proceed if
// the destination file is older than the source file.
//
if ( _CompareTimeSwitch ) {
TimeSrc = File->QueryTimeInfo(); TimeDst = FileToReplace->QueryTimeInfo();
if (TimeSrc == NULL) { DisplayMessageAndExit( REPLACE_ERROR_NO_MEMORY, NULL, EXIT_NO_MEMORY ); return FALSE; // help lint
} if (TimeDst == NULL) { DisplayMessageAndExit( REPLACE_ERROR_NO_MEMORY, NULL, EXIT_NO_MEMORY ); return FALSE; // help lint
}
Proceed = *TimeDst < *TimeSrc;
DELETE( TimeSrc ); DELETE( TimeDst );
}
if ( Proceed ) {
//
// We replace the file if it is NOT read-only
// (unless the ReadOnly switch is set )
//
if ( _ReadOnlySwitch || !(FileToReplace->IsReadOnly()) ) {
if ( !_PromptSwitch || Prompt( REPLACE_MESSAGE_REPLACE_YES_NO, &DestinationPath ) ) {
DisplayMessage( REPLACE_MESSAGE_REPLACING, NORMAL_MESSAGE, "%W", DestinationPath.GetPathString() );
//
// If the file is read-only, we reset the read-only attribute
// before copying.
//
if ( FileToReplace->IsReadOnly() ) { FileToReplace->ResetReadOnlyAttribute(); }
CopyTheFile( File->GetPath(), &DestinationPath );
_FilesReplaced++; } } else {
//
// The file is read-only but the ReadOnly flag was
// not set, we error out.
//
DisplayMessageAndExit( REPLACE_ERROR_ACCESS_DENIED, DestinationPath.GetPathString(), EXIT_ACCESS_DENIED ); } } }
DELETE( FileToReplace );
//
// Set the destination path back to what it originally was
// ( i.e. directory specification, no file name part ).
//
DestinationPath.TruncateBase();
}
DELETE( Iterator );
return TRUE; }
BOOLEAN REPLACE::Prompt ( IN MSGID MessageId, IN PCPATH Path )
/*++
Routine Description:
Gets confirmation from the user about a file to be added/replaced
Arguments:
MessageId - Supplies the Id of the message to use for prompting Path - Supplies path to use as parameter for the message.
Return Value:
BOOLEAN - TRUE if the user confirmed the add/replace FALSE otherwise
--*/
{
DisplayMessage( MessageId, NORMAL_MESSAGE, "%W", Path->GetPathString() ); return _Message.IsYesResponse();
}
BOOLEAN REPLACE::CopyTheFile ( IN PCPATH SrcPath, IN PCPATH DstPath )
/*++
Routine Description:
Copies a file
Arguments:
SrcPath - Supplies path of source file DstFile - Supplies path of destination file
Return Value:
BOOLEAN - TRUE
--*/
{
ULONG Size;
DebugPtrAssert( SrcPath ); DebugPtrAssert( DstPath );
//
// Make sure that the buffers are big enough to hold the
// paths
//
Size = (SrcPath->GetPathString()->QueryChCount() + 1) * 2; if ( Size > _PathString1Size ) { _PathString1 = (LPWSTR)REALLOC( _PathString1, (unsigned int)Size ); DebugPtrAssert( _PathString1 ); _PathString1Size = Size; }
Size = (DstPath->GetPathString()->QueryChCount() + 1) * 2; if ( Size > _PathString2Size ) { _PathString2 = (LPWSTR)REALLOC( _PathString2, (unsigned int)Size ); DebugPtrAssert( _PathString2 ); _PathString2Size = Size; }
if ( !_PathString1 || !_PathString2 ) { DisplayMessageAndExit( REPLACE_ERROR_NO_MEMORY, NULL, EXIT_NO_MEMORY );
}
//
// Convert the paths to LPWSTR so that we can call CopyFile()
//
SrcPath->GetPathString()->QueryWSTR( 0, TO_END, _PathString1, _PathString1Size/sizeof(WCHAR) ); DstPath->GetPathString()->QueryWSTR( 0, TO_END, _PathString2, _PathString2Size/sizeof(WCHAR) );
//
// Now do the copy
//
if ( !CopyFile( _PathString1, _PathString2, FALSE ) ) {
ExitWithError( GetLastError() );
}
return TRUE;
}
|