1106 lines
47 KiB
C++
1106 lines
47 KiB
C++
|
/*
|
||
|
** Command & Conquer Renegade(tm)
|
||
|
** Copyright 2025 Electronic Arts Inc.
|
||
|
**
|
||
|
** 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 3 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, see <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
*** Confidential - Westwood Studios ***
|
||
|
***********************************************************************************************
|
||
|
* *
|
||
|
* Project Name : Installer *
|
||
|
* *
|
||
|
* $Archive:: /Commando/Code/Installer/CopyThread.cpp $*
|
||
|
* *
|
||
|
* $Author:: Ian_l $*
|
||
|
* *
|
||
|
* $Modtime:: 1/13/02 5:00p $*
|
||
|
* *
|
||
|
* $Revision:: 10 $*
|
||
|
* *
|
||
|
*---------------------------------------------------------------------------------------------*
|
||
|
* Functions: *
|
||
|
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||
|
|
||
|
// Includes.
|
||
|
#include "CopyThread.h"
|
||
|
#include "ErrorHandler.h"
|
||
|
#include "FDI.h"
|
||
|
#include "Installer.h"
|
||
|
#include "RegistryManager.h"
|
||
|
#include "Resource.h"
|
||
|
#include "Translator.h"
|
||
|
#include "Verchk.h"
|
||
|
#include <io.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <share.h>
|
||
|
|
||
|
|
||
|
// Defines.
|
||
|
#define COPY_MESSAGE_FORMAT_STRING L"%s %s"
|
||
|
|
||
|
|
||
|
// Static data.
|
||
|
CopyThreadClass *CopyThreadClass::_ActiveCopyThread = NULL;
|
||
|
|
||
|
|
||
|
/***************************************************************************
|
||
|
* Memory allocation function
|
||
|
* HUGE * FAR DIAMONDAPI fdi_mem_alloc( ULONG cb )
|
||
|
*=========================================================================*/
|
||
|
FNALLOC( fdi_mem_alloc )
|
||
|
{
|
||
|
return (malloc (cb));
|
||
|
}
|
||
|
|
||
|
|
||
|
/***************************************************************************
|
||
|
* Memory free function
|
||
|
* FAR DIAMONDAPI fdi_mem_free( void HUGE *pv )
|
||
|
*=========================================================================*/
|
||
|
FNFREE( fdi_mem_free )
|
||
|
{
|
||
|
free (pv);
|
||
|
}
|
||
|
|
||
|
|
||
|
/***************************************************************************
|
||
|
* int FAR DIAMONDAPI file_open( char FAR *pszFile, int oflag, int pmode )
|
||
|
*=========================================================================*/
|
||
|
FNOPEN( file_open )
|
||
|
{
|
||
|
HANDLE handle;
|
||
|
|
||
|
WWASSERT ((oflag & (_O_APPEND | _O_TEMPORARY)) == 0x0);
|
||
|
|
||
|
DWORD desiredaccess, creationdisposition, flagsandattributes;
|
||
|
|
||
|
if (oflag & _O_WRONLY) {
|
||
|
desiredaccess = GENERIC_WRITE;
|
||
|
} else {
|
||
|
if (oflag & _O_RDWR) {
|
||
|
desiredaccess = GENERIC_READ | GENERIC_WRITE;
|
||
|
} else {
|
||
|
desiredaccess = GENERIC_READ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (oflag & (_O_CREAT | _O_TRUNC)) {
|
||
|
creationdisposition = OPEN_ALWAYS;
|
||
|
} else {
|
||
|
if (oflag & _O_CREAT) {
|
||
|
creationdisposition = CREATE_NEW;
|
||
|
} else {
|
||
|
if (oflag & _O_TRUNC) {
|
||
|
creationdisposition = TRUNCATE_EXISTING;
|
||
|
} else {
|
||
|
creationdisposition = OPEN_EXISTING;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (pmode & _S_IREAD) {
|
||
|
flagsandattributes = FILE_ATTRIBUTE_READONLY;
|
||
|
} else {
|
||
|
flagsandattributes = FILE_ATTRIBUTE_NORMAL;
|
||
|
}
|
||
|
|
||
|
while ((handle = CreateFile (pszFile, desiredaccess, FILE_SHARE_READ, NULL, creationdisposition, flagsandattributes | FILE_FLAG_WRITE_THROUGH, NULL)) == INVALID_HANDLE_VALUE) {
|
||
|
if (!CopyThreadClass::_ActiveCopyThread->Retry()) break;
|
||
|
}
|
||
|
return ((int) handle);
|
||
|
}
|
||
|
|
||
|
|
||
|
/***************************************************************************
|
||
|
* UINT FAR DIAMONDAPI file_read( int hf, void FAR *pv, UINT cb )
|
||
|
*=========================================================================*/
|
||
|
FNREAD( file_read )
|
||
|
{
|
||
|
// Abort the copying process?
|
||
|
if (!CopyThreadClass::_ActiveCopyThread->Get_Abort (true)) {
|
||
|
|
||
|
unsigned long bytecount;
|
||
|
|
||
|
while (!ReadFile ((void*) hf, pv, cb, &bytecount, NULL)) {
|
||
|
if (!CopyThreadClass::_ActiveCopyThread->Retry()) break;
|
||
|
}
|
||
|
return (bytecount);
|
||
|
|
||
|
} else {
|
||
|
|
||
|
// NOTE: Returning -1 will cause the calling cabinet process to close all
|
||
|
// files, release its resources, and return an error code to the caller of
|
||
|
// FDICopy().
|
||
|
return (-1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/***************************************************************************
|
||
|
* UINT FAR DIAMONDAPI file_write( int hf, void FAR *pv, UINT cb )
|
||
|
*=========================================================================*/
|
||
|
FNWRITE( file_write )
|
||
|
{
|
||
|
// Abort the copying process?
|
||
|
if (!CopyThreadClass::_ActiveCopyThread->Get_Abort (true)) {
|
||
|
|
||
|
unsigned long bytecount;
|
||
|
|
||
|
while (!WriteFile ((void*) hf, pv, cb, &bytecount, NULL)) {
|
||
|
if (!CopyThreadClass::_ActiveCopyThread->Retry()) break;
|
||
|
}
|
||
|
|
||
|
if (bytecount >= 0) {
|
||
|
CopyThreadClass::_ActiveCopyThread->Add_Bytes_Copied ((unsigned)bytecount);
|
||
|
}
|
||
|
return (bytecount);
|
||
|
|
||
|
} else {
|
||
|
|
||
|
// NOTE: Returning -1 will cause the calling cabinet process to close all
|
||
|
// files, release its resources, and return an error code to the caller of
|
||
|
// FDICopy().
|
||
|
return (-1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/***************************************************************************
|
||
|
* int FAR DIAMONDAPI file_close( int hf )
|
||
|
*=========================================================================*/
|
||
|
FNCLOSE( file_close )
|
||
|
{
|
||
|
int result;
|
||
|
|
||
|
while (!(result = CloseHandle ((void*) hf))) {
|
||
|
if (!CopyThreadClass::_ActiveCopyThread->Retry()) break;
|
||
|
}
|
||
|
return (result ? 0 : -1);
|
||
|
}
|
||
|
|
||
|
|
||
|
/***************************************************************************
|
||
|
* long FAR DIAMONDAPI file_seek( int hf, long dist, int seektype )
|
||
|
*=========================================================================*/
|
||
|
FNSEEK( file_seek )
|
||
|
{
|
||
|
long result;
|
||
|
DWORD movemethod;
|
||
|
|
||
|
// Map seek type to Win32.
|
||
|
switch (seektype) {
|
||
|
|
||
|
case SEEK_SET:
|
||
|
movemethod = FILE_BEGIN;
|
||
|
break;
|
||
|
|
||
|
case SEEK_CUR:
|
||
|
movemethod = FILE_CURRENT;
|
||
|
break;
|
||
|
|
||
|
case SEEK_END:
|
||
|
movemethod = FILE_END;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
while ((result = SetFilePointer ((void*) hf, dist, NULL, movemethod)) == 0xffffffff) {
|
||
|
if (!CopyThreadClass::_ActiveCopyThread->Retry()) break;
|
||
|
}
|
||
|
return (result);
|
||
|
}
|
||
|
|
||
|
|
||
|
/******************************************************************************
|
||
|
* int FAR DIAMONDAPI notification_function( FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin )
|
||
|
*=============================================================================*/
|
||
|
FNFDINOTIFY( notification_function )
|
||
|
{
|
||
|
static WideStringClass _targetpathname;
|
||
|
static StringClass _multibytetemporarypathname;
|
||
|
static FILETIME _sourcefiletime;
|
||
|
|
||
|
switch (fdint) {
|
||
|
|
||
|
case fdintCOPY_FILE:
|
||
|
{
|
||
|
FILETIME localfiletime;
|
||
|
|
||
|
// Build target pathname.
|
||
|
// NOTE: If the relative path does not contain subdirectories it will not contain a backslash.
|
||
|
CopyThreadClass::_ActiveCopyThread->Get_Target_Path (_targetpathname);
|
||
|
if (pfdin->psz1 [0] != '\\') _targetpathname += L"\\";
|
||
|
_targetpathname += WideStringClass (pfdin->psz1);
|
||
|
|
||
|
_Installer.Log (_targetpathname, pfdin->cb);
|
||
|
|
||
|
// If target should be overwritten...
|
||
|
if (!DosDateTimeToFileTime (pfdin->date, pfdin->time, &localfiletime)) return (-1);
|
||
|
if (!LocalFileTimeToFileTime (&localfiletime, &_sourcefiletime)) return (-1);
|
||
|
|
||
|
if (CopyThreadClass::Replace_File (_sourcefiletime, pfdin->cb, _targetpathname)) {
|
||
|
|
||
|
WideStringClass statusmessage;
|
||
|
long handle;
|
||
|
WideStringClass targetpath;
|
||
|
WideStringClass filename;
|
||
|
|
||
|
// Update the name of the current file being copied.
|
||
|
filename = WideStringClass (pfdin->psz1);
|
||
|
Extract_Trailing_Name (filename);
|
||
|
statusmessage.Format (COPY_MESSAGE_FORMAT_STRING, TxWideStringClass (IDS_COPYING), filename);
|
||
|
CopyThreadClass::_ActiveCopyThread->Set_Status_Message (statusmessage);
|
||
|
|
||
|
// Create temporary filename.
|
||
|
targetpath = _targetpathname;
|
||
|
Remove_Trailing_Name (targetpath);
|
||
|
if (!Generate_Temporary_Pathname (targetpath, _multibytetemporarypathname)) return (-1);
|
||
|
|
||
|
// Open the temporary file for writing.
|
||
|
handle = file_open (_multibytetemporarypathname.Peek_Buffer(), _O_BINARY | _O_TRUNC | _O_CREAT | _O_WRONLY | _O_SEQUENTIAL, _S_IREAD | _S_IWRITE);
|
||
|
|
||
|
// Add temporary filename to log.
|
||
|
CopyThreadClass::_ActiveCopyThread->Get_Filename_Log().Add (_multibytetemporarypathname);
|
||
|
|
||
|
return (handle);
|
||
|
|
||
|
} else {
|
||
|
|
||
|
// Do not copy file.
|
||
|
CopyThreadClass::_ActiveCopyThread->Add_Bytes_Copied ((unsigned) pfdin->cb);
|
||
|
return (0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
case fdintCLOSE_FILE_INFO:
|
||
|
{
|
||
|
StringClass multibytetargetpathname;
|
||
|
HANDLE targetfile;
|
||
|
WIN32_FIND_DATA targetfiledata;
|
||
|
int count;
|
||
|
|
||
|
// Close the temporary file.
|
||
|
if (file_close (pfdin->hf) != 0) return (-1);
|
||
|
|
||
|
// Delete the original target file (if it exists).
|
||
|
multibytetargetpathname = _targetpathname;
|
||
|
targetfile = FindFirstFile (multibytetargetpathname, &targetfiledata);
|
||
|
if (targetfile != INVALID_HANDLE_VALUE) {
|
||
|
if (!FindClose (targetfile)) return (-1);
|
||
|
if (targetfiledata.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
|
||
|
if (!SetFileAttributes (multibytetargetpathname, FILE_ATTRIBUTE_NORMAL)) return (-1);
|
||
|
}
|
||
|
if (!DeleteFile (multibytetargetpathname)) return (-1);
|
||
|
}
|
||
|
|
||
|
// Rename the temporary file the target file.
|
||
|
if (!MoveFile (_multibytetemporarypathname, multibytetargetpathname)) return (-1);
|
||
|
|
||
|
// Remove the temporary file from the file log (the last item added).
|
||
|
count = CopyThreadClass::_ActiveCopyThread->Get_Filename_Log().Count();
|
||
|
WWASSERT (count > 0);
|
||
|
CopyThreadClass::_ActiveCopyThread->Get_Filename_Log().Delete (count - 1);
|
||
|
|
||
|
// Add the target file to the file log.
|
||
|
CopyThreadClass::_ActiveCopyThread->Get_Filename_Log().Add (multibytetargetpathname);
|
||
|
|
||
|
// Stamp the target file with write time of source.
|
||
|
targetfile = CreateFile (multibytetargetpathname, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||
|
if (targetfile == INVALID_HANDLE_VALUE) return (-1);
|
||
|
if (!SetFileTime (targetfile, NULL, NULL, &_sourcefiletime)) return (-1);
|
||
|
if (!CloseHandle (targetfile)) return (-1);
|
||
|
|
||
|
// Stamp the target file with attributes of source.
|
||
|
if (!SetFileAttributes (multibytetargetpathname, pfdin->attribs & (~FILE_ATTRIBUTE_READONLY))) return (-1);
|
||
|
|
||
|
// Success.
|
||
|
return (1);
|
||
|
}
|
||
|
|
||
|
case fdintNEXT_CABINET:
|
||
|
{
|
||
|
// Nothing to do. Assume that the next cabinet file is in the same directory as the current
|
||
|
// cabinet file.
|
||
|
return (1);
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
return (1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
* CopyThreadClass::CopyThreadClass -- *
|
||
|
* *
|
||
|
* INPUT: *
|
||
|
* *
|
||
|
* OUTPUT: *
|
||
|
* *
|
||
|
* WARNINGS: *
|
||
|
* *
|
||
|
* HISTORY: *
|
||
|
* 08/22/01 IML : Created. *
|
||
|
*=============================================================================================*/
|
||
|
CopyThreadClass::CopyThreadClass (__int64 bytestocopy)
|
||
|
: ThreadClass(),
|
||
|
Status (STATUS_OK),
|
||
|
Abort (false),
|
||
|
CanAbort (true),
|
||
|
AbortLock (NULL),
|
||
|
IsAborting (false),
|
||
|
BytesToCopy (bytestocopy),
|
||
|
BytesCopied (0)
|
||
|
{
|
||
|
// Only one instance can be active.
|
||
|
WWASSERT (_ActiveCopyThread == NULL);
|
||
|
_ActiveCopyThread = this;
|
||
|
}
|
||
|
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
* CopyThreadClass::~CopyThreadClass -- *
|
||
|
* *
|
||
|
* INPUT: *
|
||
|
* *
|
||
|
* OUTPUT: *
|
||
|
* *
|
||
|
* WARNINGS: *
|
||
|
* *
|
||
|
* HISTORY: *
|
||
|
* 08/22/01 IML : Created. *
|
||
|
*=============================================================================================*/
|
||
|
CopyThreadClass::~CopyThreadClass()
|
||
|
{
|
||
|
WWASSERT (_ActiveCopyThread != NULL);
|
||
|
_ActiveCopyThread = NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
* CopyThreadClass::Thread_Function -- *
|
||
|
* *
|
||
|
* INPUT: *
|
||
|
* *
|
||
|
* OUTPUT: *
|
||
|
* *
|
||
|
* WARNINGS: *
|
||
|
* *
|
||
|
* HISTORY: *
|
||
|
* 08/22/01 IML : Created. *
|
||
|
*=============================================================================================*/
|
||
|
void CopyThreadClass::Thread_Function()
|
||
|
{
|
||
|
try {
|
||
|
|
||
|
unsigned i;
|
||
|
WideStringClass sourcepath, targetpath;
|
||
|
int gamesubdirectorycount, gamefilecount;
|
||
|
|
||
|
// Copy the game directory if user requested it.
|
||
|
if (_Installer.Install_Game()) {
|
||
|
|
||
|
// Create game subdirectories on target.
|
||
|
// NOTE 0: This could have been done whilst copying, but is more efficient to do here,
|
||
|
// considering that InstallerClass already has this information.
|
||
|
// NOTE 1: Directory paths should have been validated, so any errors here are fatal.
|
||
|
if (!Create_Directory (_Installer.Get_Target_Game_Path (targetpath), &Get_Subdirectory_Log())) FATAL_SYSTEM_ERROR;
|
||
|
i = 0;
|
||
|
while (_Installer.Get_Target_Sub_Path (i, targetpath)) {
|
||
|
_Installer.Log (targetpath);
|
||
|
if (!Create_Directory (targetpath, &Get_Subdirectory_Log())) FATAL_SYSTEM_ERROR;
|
||
|
i++;
|
||
|
}
|
||
|
|
||
|
Copy_Directory (_Installer.Get_Source_Game_Path (sourcepath), _Installer.Get_Target_Game_Path (targetpath));
|
||
|
}
|
||
|
|
||
|
gamesubdirectorycount = Get_Subdirectory_Log().Count();
|
||
|
gamefilecount = Get_Filename_Log().Count();
|
||
|
|
||
|
if (Is_Aborting()) goto abort;
|
||
|
|
||
|
// Copy the WOL directory if user requested it.
|
||
|
if (_Installer.Install_WOL()) {
|
||
|
Copy_Directory (_Installer.Get_Source_WOL_Path (sourcepath), _Installer.Get_Target_WOL_Path (targetpath));
|
||
|
}
|
||
|
|
||
|
// Find out if the user wants to abort. At the same time disable the user's ability to abort
|
||
|
// because the operations to follow are irreversible ie. this is the last chance for the
|
||
|
// user to abort.
|
||
|
if (Get_Abort (false)) goto abort;
|
||
|
|
||
|
// Update registry and make start menu items.
|
||
|
Set_Status_Message (TxWideStringClass (IDS_UPDATING_REGISTRY));
|
||
|
_Installer.Update_Registry();
|
||
|
|
||
|
// Make menu items and icons.
|
||
|
Set_Status_Message (TxWideStringClass (IDS_MAKING_MENU_ITEMS));
|
||
|
_Installer.Create_Links();
|
||
|
|
||
|
// Create uninstall logs.
|
||
|
Set_Status_Message (TxWideStringClass (IDS_CREATING_UNINSTALL_LOGS));
|
||
|
_Installer.Create_Uninstall_Logs();
|
||
|
|
||
|
// Indicate completion.
|
||
|
Set_Status_Message (TxWideStringClass (IDS_INSTALLATION_COMPLETE));
|
||
|
Status = STATUS_SUCCESS;
|
||
|
return;
|
||
|
|
||
|
abort:
|
||
|
|
||
|
Set_Status_Message (WideStringClass (L""));
|
||
|
Status = STATUS_ABORTED;
|
||
|
|
||
|
// Remove installed components if game or WOL was freshly installed.
|
||
|
if (_Installer.Is_Fresh_Game_Install() || _Installer.Is_Fresh_WOL_Install()) {
|
||
|
|
||
|
int i;
|
||
|
|
||
|
Set_Status_Message (TxWideStringClass (IDS_REMOVING_INSTALLED_COMPONENTS));
|
||
|
|
||
|
// NOTE: Order of removal must be inverse of order of creation to ensure that leaf
|
||
|
// subdirectories are removed before parent directories. To satisfy this
|
||
|
// requirement, remove in the following order:
|
||
|
// 1. WOL files (in any order).
|
||
|
// 2. Game files (in any order).
|
||
|
// 3. WOL subdirectories (in reverse order).
|
||
|
// 4. Game subdirectories (in reverse order).
|
||
|
if (_Installer.Is_Fresh_WOL_Install()) {
|
||
|
for (i = gamefilecount; i < Get_Filename_Log().Count(); i++) {
|
||
|
DeleteFile (Get_Filename_Log() [i]);
|
||
|
}
|
||
|
}
|
||
|
if (_Installer.Is_Fresh_Game_Install()) {
|
||
|
for (i = 0; i < gamefilecount; i++) {
|
||
|
DeleteFile (Get_Filename_Log() [i]);
|
||
|
}
|
||
|
}
|
||
|
if (_Installer.Is_Fresh_WOL_Install()) {
|
||
|
for (i = Get_Subdirectory_Log().Count() - 1; i >= gamesubdirectorycount; i--) {
|
||
|
RemoveDirectory (Get_Subdirectory_Log() [i]);
|
||
|
}
|
||
|
}
|
||
|
if (_Installer.Is_Fresh_Game_Install()) {
|
||
|
for (i = gamesubdirectorycount - 1; i >= 0; i--) {
|
||
|
RemoveDirectory (Get_Subdirectory_Log() [i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Give time for user to read status message.
|
||
|
Sleep (5000);
|
||
|
}
|
||
|
|
||
|
} catch (const WideStringClass &errormessage) {
|
||
|
|
||
|
// Copy off the errormessage so that it can be interrogated by the thread creator.
|
||
|
Set_Status_Message (WideStringClass (L""));
|
||
|
Set_Error_Message (errormessage);
|
||
|
Status = STATUS_FAILURE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
* CopyThreadClass::Copy_Directory -- *
|
||
|
* *
|
||
|
* INPUT: *
|
||
|
* *
|
||
|
* OUTPUT: *
|
||
|
* *
|
||
|
* WARNINGS: *
|
||
|
* *
|
||
|
* HISTORY: *
|
||
|
* 08/22/01 IML : Created. *
|
||
|
*=============================================================================================*/
|
||
|
void CopyThreadClass::Copy_Directory (const WideStringClass &sourcepath, const WideStringClass &targetpath)
|
||
|
{
|
||
|
// WARNING: Do not call SetCurrentDirectory() from this thread because it will affect
|
||
|
// file access for the entire process.
|
||
|
|
||
|
const WCHAR *wildcardname = L"*.*";
|
||
|
|
||
|
HFDI hfdi;
|
||
|
ERF erf;
|
||
|
WideStringClass sourcepathname;
|
||
|
StringClass multibytesourcepathname;
|
||
|
WIN32_FIND_DATA finddata;
|
||
|
HANDLE handle;
|
||
|
bool done = false;
|
||
|
|
||
|
hfdi = FDICreate (fdi_mem_alloc, // Memory allocation function.
|
||
|
fdi_mem_free, // Memory free function.
|
||
|
file_open, // File open function.
|
||
|
file_read, // File read function.
|
||
|
file_write, // File write function.
|
||
|
file_close, // File close function.
|
||
|
file_seek, // File seek function.
|
||
|
cpu80386, // Type of CPU.
|
||
|
&erf); // Pointer to error structure.
|
||
|
|
||
|
WWASSERT (hfdi != NULL);
|
||
|
|
||
|
// Create subdirectory (if it doesn't already exist).
|
||
|
if (!Create_Directory (targetpath, &Get_Subdirectory_Log())) FATAL_SYSTEM_ERROR;
|
||
|
|
||
|
// Iterate over files in source directory and copy to target directory.
|
||
|
sourcepathname = sourcepath;
|
||
|
sourcepathname += L"\\";
|
||
|
sourcepathname += wildcardname;
|
||
|
multibytesourcepathname = sourcepathname;
|
||
|
|
||
|
// Find a source file.
|
||
|
// NOTE At least one file must be in the directory.
|
||
|
while ((handle = FindFirstFile (multibytesourcepathname, &finddata)) == INVALID_HANDLE_VALUE) {
|
||
|
if (!Retry()) FATAL_SYSTEM_ERROR;
|
||
|
}
|
||
|
|
||
|
while (!done && !Is_Aborting()) {
|
||
|
|
||
|
WideStringClass filename (finddata.cFileName);
|
||
|
|
||
|
// Filter out system files.
|
||
|
if (filename [0] != L'.') {
|
||
|
|
||
|
// Is it a subdirectory?
|
||
|
if (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||
|
|
||
|
WideStringClass subsourcepath (sourcepath);
|
||
|
WideStringClass subtargetpath (targetpath);
|
||
|
|
||
|
// Recurse.
|
||
|
subsourcepath += L"\\";
|
||
|
subsourcepath += filename;
|
||
|
subtargetpath += L"\\";
|
||
|
subtargetpath += filename;
|
||
|
|
||
|
_Installer.Log (subtargetpath);
|
||
|
|
||
|
Copy_Directory (subsourcepath, subtargetpath);
|
||
|
|
||
|
} else {
|
||
|
|
||
|
const WCHAR *cabextension = L".cab";
|
||
|
|
||
|
WCHAR extension [_MAX_EXT];
|
||
|
|
||
|
// Is it a CAB file?
|
||
|
_wsplitpath (filename, NULL, NULL, NULL, extension);
|
||
|
if (_wcsicmp (cabextension, extension) == 0) {
|
||
|
|
||
|
StringClass sourcepathbackslash (sourcepath);
|
||
|
|
||
|
// NOTE: FDICopy() requires a trailing backslash on the source path.
|
||
|
sourcepathbackslash += "\\";
|
||
|
|
||
|
Set_Target_Path (targetpath);
|
||
|
|
||
|
// Copy the contents of the cabinet file to the target directory.
|
||
|
// If the function fails then throw a 'cabinet error'.
|
||
|
if (!FDICopy (hfdi, // Handle to FDI context (created by FDICreate()).
|
||
|
finddata.cFileName, // Name of cabinet file, excluding path information.
|
||
|
sourcepathbackslash.Peek_Buffer(), // File path to cabinet file.
|
||
|
0, // Flags to control extract operation.
|
||
|
notification_function, // Ptr to a notification (status update) function.
|
||
|
NULL, // Ptr to a decryption function.
|
||
|
NULL))
|
||
|
|
||
|
// If failure was not due to user aborting then throw an error.
|
||
|
if (!Is_Aborting()) FATAL_CAB_ERROR (erf.erfOper);
|
||
|
|
||
|
} else {
|
||
|
|
||
|
// Update the name of the current file being copied.
|
||
|
WideStringClass statusmessage;
|
||
|
__int64 filesize;
|
||
|
|
||
|
statusmessage.Format (COPY_MESSAGE_FORMAT_STRING, TxWideStringClass (IDS_COPYING), filename);
|
||
|
Set_Status_Message (statusmessage);
|
||
|
|
||
|
// It's a regular file.
|
||
|
WideStringClass sourcepathname (sourcepath);
|
||
|
WideStringClass targetpathname (targetpath);
|
||
|
|
||
|
sourcepathname += L"\\";
|
||
|
sourcepathname += filename;
|
||
|
targetpathname += L"\\";
|
||
|
targetpathname += filename;
|
||
|
|
||
|
filesize = ((((__int64)MAXDWORD) + 1) * ((__int64) finddata.nFileSizeHigh)) + ((__int64) finddata.nFileSizeLow);
|
||
|
_Installer.Log (targetpathname, filesize);
|
||
|
|
||
|
// If target should be overwitten...
|
||
|
if (Replace_File (sourcepathname, targetpathname)) {
|
||
|
Copy_File (sourcepathname, targetpathname);
|
||
|
} else {
|
||
|
BytesCopied += filesize;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (Is_Aborting()) break;
|
||
|
|
||
|
while (done = (FindNextFile (handle, &finddata) == 0)) {
|
||
|
if (GetLastError() == ERROR_NO_MORE_FILES) break;
|
||
|
if (!Retry()) FATAL_SYSTEM_ERROR;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
while (!FindClose (handle)) {
|
||
|
if (!Retry()) FATAL_SYSTEM_ERROR;
|
||
|
}
|
||
|
|
||
|
FDIDestroy (hfdi);
|
||
|
}
|
||
|
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
* CopyThreadClass::Copy_File -- *
|
||
|
* *
|
||
|
* INPUT: *
|
||
|
* *
|
||
|
* OUTPUT: *
|
||
|
* *
|
||
|
* WARNINGS: *
|
||
|
* *
|
||
|
* HISTORY: *
|
||
|
* 08/22/01 IML : Created. *
|
||
|
*=============================================================================================*/
|
||
|
void CopyThreadClass::Copy_File (const WideStringClass &sourcepathname, const WideStringClass &targetpathname)
|
||
|
{
|
||
|
const unsigned buffersize = 32768;
|
||
|
|
||
|
static char _buffer [buffersize];
|
||
|
|
||
|
StringClass multibytesourcepathname, multibytetargetpathname;
|
||
|
WideStringClass targetpath;
|
||
|
StringClass multibytetargetpath, multibytetemporarypathname;
|
||
|
HANDLE sourcefile, temporaryfile, targetfile;
|
||
|
DWORD sourcebytecount, bytecount;
|
||
|
WIN32_FIND_DATA sourcefiledata, targetfiledata;
|
||
|
|
||
|
multibytesourcepathname = sourcepathname;
|
||
|
multibytetargetpathname = targetpathname;
|
||
|
|
||
|
// Open the read file.
|
||
|
while ((sourcefile = CreateFile (multibytesourcepathname, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE) {
|
||
|
if (!Retry()) FATAL_SYSTEM_ERROR;
|
||
|
}
|
||
|
|
||
|
// Create temporary file.
|
||
|
targetpath = targetpathname;
|
||
|
Remove_Trailing_Name (targetpath);
|
||
|
if (!Generate_Temporary_Pathname (targetpath, multibytetemporarypathname)) FATAL_SYSTEM_ERROR;
|
||
|
|
||
|
// Open the temporary file for writing.
|
||
|
temporaryfile = CreateFile (multibytetemporarypathname, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, NULL);
|
||
|
if (temporaryfile == INVALID_HANDLE_VALUE) FATAL_SYSTEM_ERROR;
|
||
|
|
||
|
do {
|
||
|
|
||
|
// Abort the copying process?
|
||
|
if (Get_Abort (true)) break;
|
||
|
|
||
|
while (!ReadFile (sourcefile, _buffer, buffersize, &sourcebytecount, NULL)) {
|
||
|
if (!Retry()) FATAL_SYSTEM_ERROR;
|
||
|
}
|
||
|
if (!WriteFile (temporaryfile, _buffer, sourcebytecount, &bytecount, NULL)) FATAL_SYSTEM_ERROR;
|
||
|
BytesCopied += sourcebytecount;
|
||
|
|
||
|
} while (sourcebytecount == buffersize);
|
||
|
|
||
|
// Close.
|
||
|
while (!CloseHandle (sourcefile)) {
|
||
|
if (!Retry()) FATAL_SYSTEM_ERROR;
|
||
|
}
|
||
|
while (!CloseHandle (temporaryfile)) {
|
||
|
if (!Retry()) FATAL_SYSTEM_ERROR;
|
||
|
}
|
||
|
|
||
|
while ((sourcefile = FindFirstFile (multibytesourcepathname, &sourcefiledata)) == INVALID_HANDLE_VALUE) {
|
||
|
if (!Retry()) FATAL_SYSTEM_ERROR;
|
||
|
}
|
||
|
while (!FindClose (sourcefile)) {
|
||
|
if (!Retry()) FATAL_SYSTEM_ERROR;
|
||
|
}
|
||
|
|
||
|
// Delete the original target file (if it exists).
|
||
|
targetfile = FindFirstFile (multibytetargetpathname, &targetfiledata);
|
||
|
if (targetfile != INVALID_HANDLE_VALUE) {
|
||
|
if (!FindClose (targetfile)) FATAL_SYSTEM_ERROR;
|
||
|
if (targetfiledata.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
|
||
|
if (!SetFileAttributes (multibytetargetpathname, FILE_ATTRIBUTE_NORMAL)) FATAL_SYSTEM_ERROR;
|
||
|
}
|
||
|
if (!DeleteFile (multibytetargetpathname)) FATAL_SYSTEM_ERROR;
|
||
|
}
|
||
|
|
||
|
// Rename the temporary file the original target file.
|
||
|
if (!MoveFile (multibytetemporarypathname, multibytetargetpathname)) FATAL_SYSTEM_ERROR;
|
||
|
|
||
|
// Add the name of the file to the file log.
|
||
|
Get_Filename_Log().Add (multibytetargetpathname);
|
||
|
|
||
|
// Stamp the target file with write time of source.
|
||
|
targetfile = CreateFile (multibytetargetpathname, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||
|
if (targetfile == INVALID_HANDLE_VALUE) FATAL_SYSTEM_ERROR;
|
||
|
if (!SetFileTime (targetfile, NULL, NULL, &sourcefiledata.ftLastWriteTime)) FATAL_SYSTEM_ERROR;
|
||
|
if (!CloseHandle (targetfile)) FATAL_SYSTEM_ERROR;
|
||
|
|
||
|
// Stamp the target file with file attributes of source (but clear the read-only flag to ensure that
|
||
|
// the file can be patched at a later date).
|
||
|
if (!SetFileAttributes (multibytetargetpathname, sourcefiledata.dwFileAttributes & (~FILE_ATTRIBUTE_READONLY))) FATAL_SYSTEM_ERROR;
|
||
|
}
|
||
|
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
* Replace_File -- *
|
||
|
* *
|
||
|
* INPUT: *
|
||
|
* *
|
||
|
* OUTPUT: *
|
||
|
* *
|
||
|
* WARNINGS: *
|
||
|
* *
|
||
|
* HISTORY: *
|
||
|
* 08/22/01 IML : Created. *
|
||
|
*=============================================================================================*/
|
||
|
bool CopyThreadClass::Replace_File (const FILETIME &sourcefiletime, DWORD sourcefilesize, const WideStringClass &targetpathname)
|
||
|
{
|
||
|
StringClass multibytetargetpathname (targetpathname);
|
||
|
WIN32_FIND_DATA targetdata;
|
||
|
HANDLE handle;
|
||
|
long t;
|
||
|
|
||
|
// Does the target file exist?
|
||
|
handle = FindFirstFile (multibytetargetpathname, &targetdata);
|
||
|
if (handle == INVALID_HANDLE_VALUE) return (true);
|
||
|
|
||
|
// Test last write times.
|
||
|
t = CompareFileTime (&sourcefiletime, &targetdata.ftLastWriteTime);
|
||
|
if (t > 0) return (true);
|
||
|
if (t < 0) return (false);
|
||
|
|
||
|
// So far files are deemed equal. To err on the side of caution, test file sizes and elect to replace the file if sizes differ.
|
||
|
return ((sourcefilesize != targetdata.nFileSizeLow) || (targetdata.nFileSizeHigh != 0));
|
||
|
}
|
||
|
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
* Replace_File -- *
|
||
|
* *
|
||
|
* INPUT: *
|
||
|
* *
|
||
|
* OUTPUT: *
|
||
|
* *
|
||
|
* WARNINGS: *
|
||
|
* *
|
||
|
* HISTORY: *
|
||
|
* 08/22/01 IML : Created. *
|
||
|
*=============================================================================================*/
|
||
|
bool CopyThreadClass::Replace_File (const WideStringClass &sourcepathname, const WideStringClass &targetpathname)
|
||
|
{
|
||
|
StringClass multibytesourcepathname (sourcepathname);
|
||
|
StringClass multibytetargetpathname (targetpathname);
|
||
|
WIN32_FIND_DATA sourcedata, targetdata;
|
||
|
HANDLE handle;
|
||
|
VS_FIXEDFILEINFO sourceversion, targetversion;
|
||
|
long t;
|
||
|
|
||
|
// Find source file. It must exist.
|
||
|
while ((handle = FindFirstFile (multibytesourcepathname, &sourcedata)) == INVALID_HANDLE_VALUE) {
|
||
|
if (!_ActiveCopyThread->Retry()) FATAL_SYSTEM_ERROR;
|
||
|
}
|
||
|
|
||
|
// Does the target file exist?
|
||
|
handle = FindFirstFile (multibytetargetpathname, &targetdata);
|
||
|
if (handle == INVALID_HANDLE_VALUE) return (true);
|
||
|
|
||
|
// Test version numbers (if they exist).
|
||
|
if (GetVersionInfo (multibytesourcepathname.Peek_Buffer(), &sourceversion) && GetVersionInfo (multibytetargetpathname.Peek_Buffer(), &targetversion)) {
|
||
|
if (sourceversion.dwFileVersionMS > targetversion.dwFileVersionMS) return (true);
|
||
|
if (sourceversion.dwFileVersionMS < targetversion.dwFileVersionMS) return (false);
|
||
|
if (sourceversion.dwFileVersionLS > targetversion.dwFileVersionLS) return (true);
|
||
|
if (sourceversion.dwFileVersionLS < targetversion.dwFileVersionLS) return (false);
|
||
|
}
|
||
|
|
||
|
// Test last write times.
|
||
|
t = CompareFileTime (&sourcedata.ftLastWriteTime, &targetdata.ftLastWriteTime);
|
||
|
if (t > 0) return (true);
|
||
|
if (t < 0) return (false);
|
||
|
|
||
|
// So far files are deemed equal. To err on the side of caution, test file sizes and elect to replace the file if sizes differ.
|
||
|
return ((sourcedata.nFileSizeLow != targetdata.nFileSizeLow) || (sourcedata.nFileSizeHigh != targetdata.nFileSizeHigh));
|
||
|
}
|
||
|
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
* CopyThreadClass::Can_Abort -- *
|
||
|
* *
|
||
|
* INPUT: *
|
||
|
* *
|
||
|
* OUTPUT: *
|
||
|
* *
|
||
|
* WARNINGS: *
|
||
|
* *
|
||
|
* HISTORY: *
|
||
|
* 08/22/01 IML : Created. *
|
||
|
*=============================================================================================*/
|
||
|
bool CopyThreadClass::Can_Abort (bool lock)
|
||
|
{
|
||
|
if (lock) {
|
||
|
|
||
|
if (AbortLock == NULL) {
|
||
|
AbortLock = new FastCriticalSectionClass::LockClass (SectionAbort);
|
||
|
}
|
||
|
return (CanAbort);
|
||
|
|
||
|
} else {
|
||
|
|
||
|
FastCriticalSectionClass::LockClass cs (SectionAbort);
|
||
|
|
||
|
return (CanAbort);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
* CopyThreadClass::Set_Abort -- *
|
||
|
* *
|
||
|
* INPUT: *
|
||
|
* *
|
||
|
* OUTPUT: *
|
||
|
* *
|
||
|
* WARNINGS: *
|
||
|
* *
|
||
|
* HISTORY: *
|
||
|
* 08/22/01 IML : Created. *
|
||
|
*=============================================================================================*/
|
||
|
void CopyThreadClass::Set_Abort (bool abort)
|
||
|
{
|
||
|
Abort = abort;
|
||
|
WWASSERT (AbortLock != NULL);
|
||
|
delete AbortLock;
|
||
|
AbortLock = NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
* CopyThreadClass::Get_Abort -- *
|
||
|
* *
|
||
|
* INPUT: *
|
||
|
* *
|
||
|
* OUTPUT: *
|
||
|
* *
|
||
|
* WARNINGS: *
|
||
|
* *
|
||
|
* HISTORY: *
|
||
|
* 08/22/01 IML : Created. *
|
||
|
*=============================================================================================*/
|
||
|
bool CopyThreadClass::Get_Abort (bool canabort)
|
||
|
{
|
||
|
FastCriticalSectionClass::LockClass cs (SectionAbort);
|
||
|
|
||
|
CanAbort = canabort;
|
||
|
if (Abort) IsAborting = true;
|
||
|
return (Abort);
|
||
|
}
|
||
|
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
* CopyThreadClass::Add_Bytes_Copied -- *
|
||
|
* *
|
||
|
* INPUT: *
|
||
|
* *
|
||
|
* OUTPUT: *
|
||
|
* *
|
||
|
* WARNINGS: *
|
||
|
* *
|
||
|
* HISTORY: *
|
||
|
* 08/22/01 IML : Created. *
|
||
|
*=============================================================================================*/
|
||
|
void CopyThreadClass::Add_Bytes_Copied (unsigned bytecount)
|
||
|
{
|
||
|
FastCriticalSectionClass::LockClass cs (SectionBytesCopied);
|
||
|
|
||
|
BytesCopied += bytecount;
|
||
|
}
|
||
|
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
* CopyThreadClass::Get_Fraction_Complete -- *
|
||
|
* *
|
||
|
* INPUT: *
|
||
|
* *
|
||
|
* OUTPUT: *
|
||
|
* *
|
||
|
* WARNINGS: *
|
||
|
* *
|
||
|
* HISTORY: *
|
||
|
* 08/22/01 IML : Created. *
|
||
|
*=============================================================================================*/
|
||
|
float CopyThreadClass::Get_Fraction_Complete()
|
||
|
{
|
||
|
FastCriticalSectionClass::LockClass cs (SectionBytesCopied);
|
||
|
|
||
|
float fraction;
|
||
|
|
||
|
fraction = ((double) BytesCopied) / ((double) BytesToCopy);
|
||
|
|
||
|
// Clamp to valid range.
|
||
|
fraction = MAX (0.0f, MIN (1.0f, fraction));
|
||
|
|
||
|
return (fraction);
|
||
|
}
|
||
|
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
* CopyThreadClass::Set\Get_Target_Path -- *
|
||
|
* *
|
||
|
* INPUT: *
|
||
|
* *
|
||
|
* OUTPUT: *
|
||
|
* *
|
||
|
* WARNINGS: *
|
||
|
* *
|
||
|
* HISTORY: *
|
||
|
* 08/22/01 IML : Created. *
|
||
|
*=============================================================================================*/
|
||
|
void CopyThreadClass::Set_Target_Path (const WideStringClass &targetpath)
|
||
|
{
|
||
|
FastCriticalSectionClass::LockClass cs (SectionTargetPath);
|
||
|
|
||
|
TargetPath = targetpath;
|
||
|
}
|
||
|
|
||
|
WCHAR *CopyThreadClass::Get_Target_Path (WideStringClass &targetpath)
|
||
|
{
|
||
|
FastCriticalSectionClass::LockClass cs (SectionTargetPath);
|
||
|
|
||
|
targetpath = TargetPath;
|
||
|
return (targetpath.Peek_Buffer());
|
||
|
}
|
||
|
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
* CopyThreadClass::Set\Get_Status_Message -- *
|
||
|
* *
|
||
|
* INPUT: *
|
||
|
* *
|
||
|
* OUTPUT: *
|
||
|
* *
|
||
|
* WARNINGS: *
|
||
|
* *
|
||
|
* HISTORY: *
|
||
|
* 08/22/01 IML : Created. *
|
||
|
*=============================================================================================*/
|
||
|
void CopyThreadClass::Set_Status_Message (const WideStringClass &statusmessage)
|
||
|
{
|
||
|
FastCriticalSectionClass::LockClass cs (SectionStatusMessage);
|
||
|
|
||
|
StatusMessage = statusmessage;
|
||
|
}
|
||
|
|
||
|
WCHAR *CopyThreadClass::Get_Status_Message (WideStringClass &statusmessage)
|
||
|
{
|
||
|
FastCriticalSectionClass::LockClass cs (SectionStatusMessage);
|
||
|
|
||
|
statusmessage = StatusMessage;
|
||
|
return (statusmessage.Peek_Buffer());
|
||
|
}
|
||
|
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
* CopyThreadClass::Set\Get_Error_Message -- *
|
||
|
* *
|
||
|
* INPUT: *
|
||
|
* *
|
||
|
* OUTPUT: *
|
||
|
* *
|
||
|
* WARNINGS: *
|
||
|
* *
|
||
|
* HISTORY: *
|
||
|
* 08/22/01 IML : Created. *
|
||
|
*=============================================================================================*/
|
||
|
void CopyThreadClass::Set_Error_Message (const WideStringClass &errormessage)
|
||
|
{
|
||
|
FastCriticalSectionClass::LockClass cs (SectionErrorMessage);
|
||
|
|
||
|
ErrorMessage = errormessage;
|
||
|
}
|
||
|
|
||
|
WCHAR *CopyThreadClass::Get_Error_Message (WideStringClass &errormessage)
|
||
|
{
|
||
|
FastCriticalSectionClass::LockClass cs (SectionErrorMessage);
|
||
|
|
||
|
errormessage = ErrorMessage;
|
||
|
return (errormessage.Peek_Buffer());
|
||
|
}
|
||
|
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
* CopyThreadClass::Set\Get_Status -- *
|
||
|
* *
|
||
|
* INPUT: *
|
||
|
* *
|
||
|
* OUTPUT: *
|
||
|
* *
|
||
|
* WARNINGS: *
|
||
|
* *
|
||
|
* HISTORY: *
|
||
|
* 08/22/01 IML : Created. *
|
||
|
*=============================================================================================*/
|
||
|
void CopyThreadClass::Set_Status (StatusEnum status)
|
||
|
{
|
||
|
FastCriticalSectionClass::LockClass cs (SectionStatus);
|
||
|
|
||
|
Status = status;
|
||
|
}
|
||
|
|
||
|
CopyThreadClass::StatusEnum CopyThreadClass::Get_Status()
|
||
|
{
|
||
|
FastCriticalSectionClass::LockClass cs (SectionStatus);
|
||
|
|
||
|
return (Status);
|
||
|
}
|
||
|
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
* CopyThreadClass::Retry -- *
|
||
|
* *
|
||
|
* INPUT: *
|
||
|
* *
|
||
|
* OUTPUT: *
|
||
|
* *
|
||
|
* WARNINGS: *
|
||
|
* *
|
||
|
* HISTORY: *
|
||
|
* 08/22/01 IML : Created. *
|
||
|
*=============================================================================================*/
|
||
|
bool CopyThreadClass::Retry()
|
||
|
{
|
||
|
CopyThreadClass::StatusEnum status;
|
||
|
|
||
|
Set_Status (STATUS_ERROR);
|
||
|
while ((status = Get_Status()) == STATUS_ERROR) {
|
||
|
Sleep (50);
|
||
|
}
|
||
|
return (status == STATUS_RETRY);
|
||
|
}
|
||
|
|
||
|
|
||
|
|