Serious-Engine/Sources/Depend/Dependency.cpp
Ryan C. Gordon 24cb244d43 First attempt to hand-merge Ryan's Linux and Mac OS X port.
This was a _ton_ of changes, made 15 years ago, so there are probably some
problems to work out still.

Among others: Engine/Base/Stream.* was mostly abandoned and will need to be
re-ported.

Still, this is a pretty good start, and probably holds a world record for
lines of changes or something.  :)
2016-03-28 23:46:13 -04:00

574 lines
16 KiB
C++

/* Copyright (c) 2002-2012 Croteam Ltd. All rights reserved. */
#include "StdH.h"
#include "Dependency.h"
#include <Engine/Base/TranslationPair.h>
// adjust file path automatically for application path prefix
void AdjustFilePath_t(CTFileName &fnm)
{
// if filename contains a colon or double backslash
if (strchr(fnm, ':')!=NULL
||strstr(fnm, "\\\\")!=NULL) {
// it must be prefixed with application path
fnm.RemoveApplicationPath_t();
}
}
// class constructor
CDependInfo::CDependInfo(CTFileName fnFileName, CTFileName fnParent)
{
// copy file name
di_fnFileName = fnFileName;
di_fnParent = fnParent;
}
BOOL CDependInfo::IsFileOnDiskUpdated(void)
{
int file_handle;
// try to open file for reading
file_handle = _open( _fnmApplicationPath + di_fnFileName, _O_RDONLY | _O_BINARY);
// mark as it is not updated
BOOL bUpdated = FALSE;
// if opened succesefully
if( file_handle != -1)
{
struct stat statFileStatus;
// get file status
fstat( file_handle, &statFileStatus);
ASSERT(statFileStatus.st_mtime<=time(NULL));
// if last modification time is same as remembered in dependency info object
if( difftime( statFileStatus.st_mtime, di_tTime) >= 0)
{
// mark file as updated
bUpdated = TRUE;
}
}
if(file_handle!=-1) {
_close(file_handle);
}
return bUpdated;
}
// if given file allready has its own DependInfo object linked in list
BOOL CDependencyList::ExistsInList(CListHead &lh, CTFileName fnTestName) const
{
// for all members in depend list
FOREACHINLIST( CDependInfo, di_Node, lh, itDependInfo)
{
// if this DependInfo is describing searching file
if( itDependInfo->di_fnFileName == fnTestName)
{
// return true
return TRUE;
}
}
// if none of linked DepenInfo objects represent our testing file
return FALSE;
}
// extract all dependencies from list
void CDependencyList::ExtractDependencies()
{
int file_handle;
volatile ULONG ulDFNM = 'NFD';
volatile ULONG ulTFNM = 'NFT';
volatile ULONG ulEFNM = 'NFE';
char strDFNM[]="DFNM";
char strTFNM[]="TFNM";
char strEFNM[]="EFNM";
ulDFNM |= ULONG('M')<<24;
ulTFNM |= ULONG('M')<<24;
ulEFNM |= ULONG('M')<<24;
// for all list members
CListHead lhToProcess;
lhToProcess.MoveList(dl_ListHead);
while(!lhToProcess.IsEmpty()) {
CDependInfo *pdi = LIST_HEAD(lhToProcess, CDependInfo, di_Node);
pdi->di_Node.Remove();
dl_ListHead.AddTail(pdi->di_Node);
CTFileName fnFileName = pdi->di_fnFileName;
// try to open file for reading
file_handle = _open( _fnmApplicationPath + fnFileName, _O_RDONLY | _O_BINARY);
// if an error occured
if( file_handle == -1)
{
// if file is not available remove it from list
//FatalError( "File %s can't be opened!", (CTString&)(_fnmApplicationPath + fnFileName));
printf( "warning, cannot open: %s (referenced from %s)\n", (CTString&)(fnFileName), (CTString&)(pdi->di_fnParent));
delete pdi;
}
// if file is opened properly
else
{
struct stat statFileStatus;
// get file status
fstat( file_handle, &statFileStatus);
pdi->di_tTime = statFileStatus.st_mtime;
ASSERT(pdi->di_tTime<=time(NULL));
// get size
SLONG ulSize = statFileStatus.st_size;
// read file into memory
char *pFileInMemory = (char *) AllocMemory( ulSize);
// must be allocated properly
ASSERT( pFileInMemory != NULL);
// read file
if( _read( file_handle, pFileInMemory, ulSize) != ulSize)
{
FatalError( "Fatal error ocured while reading file: %s.", (CTString&)pdi->di_fnFileName);
}
if(file_handle!=-1) {
_close(file_handle);
}
// find all file name indentifiers in memory file ("EFNM" or "DFNM")
for( INDEX charCt=0; charCt<ulSize-4; charCt++)
{
ULONG ulMemValue = *((ULONG *)(pFileInMemory + charCt));
char *pchrDependentFile;
// if we found file name inside exe file (notice little-big indian convetion)
if( ulMemValue == ulEFNM)
{
// after describing long we will find file name
pchrDependentFile = pFileInMemory + charCt + 4;
// create full file name
CTFileName fnTestName = CTString(pchrDependentFile);
if( strlen(fnTestName) > 254) {
continue;
}
// if found file name does not yet exists in dependacy list
if( (fnTestName!="") &&
(!ExistsInList(dl_ListHead, fnTestName)) &&
(!ExistsInList(lhToProcess, fnTestName)))
{
// create new depend info object
CDependInfo *pDI = new CDependInfo( fnTestName, fnFileName);
// add it at tail of dependency list
lhToProcess.AddTail( pDI->di_Node);
}
}
// if we found file name inside data file (notice little-big indian convetion)
else if( ulMemValue == ulDFNM)
{
char chrFileName[ 256];
INDEX iStringLenght = *(INDEX *)(pFileInMemory + charCt + 4);
if( iStringLenght > 254 || iStringLenght<0) {
continue;
}
memcpy( chrFileName, pFileInMemory + charCt + 8, iStringLenght);
chrFileName[ iStringLenght] = 0;
// create full file name
CTFileName fnTestName = CTString(chrFileName);
if( fnTestName!="" &&
(!ExistsInList(dl_ListHead, fnTestName)) &&
(!ExistsInList(lhToProcess, fnTestName)))
{
// create new depend info object
// NOTICE: string containing file name starts after ULONG determing its lenght (+8!)
CDependInfo *pDI = new CDependInfo( fnTestName, fnFileName);
// add it at tail of dependency list
lhToProcess.AddTail( pDI->di_Node);
}
}
// if we found file name inside text file (notice little-big indian convetion)
else if( ulMemValue == ulTFNM)
{
// after describing long and one space we will find file name
pchrDependentFile = pFileInMemory + charCt + 4 + 1;
// copy file name from the file, until newline
char chrFileName[ 256];
char *chrSrc = pchrDependentFile;
char *chrDst = chrFileName;
while(*chrSrc!='\n' && *chrSrc!='\r' && chrSrc-pchrDependentFile<254) {
*chrDst++ = *chrSrc++;
}
*chrDst = 0;
CTFileName fnTestName = CTString(chrFileName);
if( strlen(fnTestName) > 254) {
continue;
}
// if found file name does not yet exists in dependacy list
if( (fnTestName!="") &&
(!ExistsInList(dl_ListHead, fnTestName)) &&
(!ExistsInList(lhToProcess, fnTestName)))
{
// create new depend info object
CDependInfo *pDI = new CDependInfo( fnTestName, fnFileName);
// add it at tail of dependency list
lhToProcess.AddTail( pDI->di_Node);
}
}
}
// free file from memory
FreeMemory(pFileInMemory);
}
}
}
// substracts given dependecy list from this
void CDependencyList::Substract( CDependencyList &dlToSubstract)
{
// for all files in this dependency
FORDELETELIST( CDependInfo, di_Node, dl_ListHead, itThis)
{
// see if file exists in other dependency
FOREACHINLIST( CDependInfo, di_Node, dlToSubstract.dl_ListHead, itOther)
{
// if we found same file in other dependency list
if( itThis->di_fnFileName == itOther->di_fnFileName)
{
// if file is updated
if( itOther->IsUpdated( *itThis))
{
// remove it from list
delete &itThis.Current();
break;
}
// if file to substract is newer than this one
else if( itThis->IsOlder( *itOther))
{
FatalError( "File \"%s\" is newer in substracting dependency file.",
(CTString&)itThis->di_fnFileName);
}
}
}
}
}
// create list from ascii file
void CDependencyList::ImportASCII( CTFileName fnAsciiFile)
{
char chrOneLine[ 256];
CTFileStream file;
int file_handle;
// try to
try
{
// open file list for reading
file.Open_t( fnAsciiFile);
}
catch( char *pError)
{
FatalError( "Error opening file %s. Error: %s.", (CTString&)fnAsciiFile, pError);
}
// loop loading lines until EOF reached ("catched")
FOREVER
{
try {
// load one line from file
file.GetLine_t( chrOneLine, 256);
// create file name from loaded line
CTFileName fnFileName = CTString(chrOneLine);
AdjustFilePath_t(fnFileName);
// try to open file for reading
file_handle = _open( _fnmApplicationPath+fnFileName, _O_RDONLY | _O_BINARY);
// if opened succesefully
if( file_handle != -1) {
// create new depend info object
CDependInfo *pDI = new CDependInfo( fnFileName, fnAsciiFile);
struct stat statFileStatus;
// get file status
fstat( file_handle, &statFileStatus);
// obtain last modification time
pDI->di_tTime = statFileStatus.st_mtime;
ASSERT(pDI->di_tTime<=time(NULL));
// add it at tail of dependency list
dl_ListHead.AddTail( pDI->di_Node);
// close file
_close( file_handle);
} else {
CPrintF("cannot open file '%s'\n", chrOneLine);
}
}
// error, EOF catched
catch( char *pFinished)
{
if (!file.AtEOF()) {
CPrintF("%s\n", pFinished);
}
break;
}
}
}
// remove updated files from list
void CDependencyList::RemoveUpdatedFiles()
{
// for all list members
FORDELETELIST( CDependInfo, di_Node, dl_ListHead, itDependInfo)
{
// if file is updated
if( itDependInfo->IsFileOnDiskUpdated())
{
// remove it from list
delete &itDependInfo.Current();
}
}
}
// clear dependency list
void CDependencyList::Clear( void)
{
// for all list members
FORDELETELIST( CDependInfo, di_Node, dl_ListHead, itDependInfo)
{
// delete member
delete &itDependInfo.Current();
}
}
// export list members into ascii file in form sutable for archivers
void CDependencyList::ExportASCII_t( CTFileName fnAsciiFile)
{
CTFileStream strmFile;
char line[ 256];
// try to
try
{
// create exporting text file
strmFile.Create_t( fnAsciiFile);
}
catch( char *pError)
{
FatalError( "Error creating file %s. Error: %s.", (CTString&)fnAsciiFile, pError);
}
// for all members in depend list
FOREACHINLIST( CDependInfo, di_Node, dl_ListHead, itDependInfo)
{
// prepare line of text
sprintf( line, "%s\n", (CTString&)itDependInfo->di_fnFileName);
// write text line into file
strmFile.Write_t( line, strlen( line));
}
}
// dynamic array of translation pairs
CDynamicArray<CTranslationPair> _atpPairs;
// add one string to array
static void AddStringForTranslation(const CTString &str)
{
// for each existing pair
_atpPairs.Lock();
INDEX ct = _atpPairs.Count();
for(INDEX i=0; i<ct; i++) {
// if it is that one
if (strcmp(_atpPairs[i].tp_strSrc, str)==0) {
// just mark it as used
_atpPairs[i].m_bUsed = TRUE;
// don't search any more
_atpPairs.Unlock();
return;
}
}
_atpPairs.Unlock();
// if not found, add it
CTranslationPair &tp = *_atpPairs.New(1);
tp.tp_strSrc = str;
tp.tp_strDst = CTString("$TODO$") + str;
tp.m_bUsed = TRUE;
}
static void WriteTranslationToken_t(CTStream &strm, CTString str)
{
strm.PutString_t(str);
}
static void WriteTranslationString_t(CTStream &strm, CTString str)
{
const char *s = str;
INDEX iLen=strlen(s);
for (INDEX i=0; i<iLen; i++) {
char c = s[i];
switch(c) {
case '\n':
strm<<UBYTE('\\')<<UBYTE('n')<<UBYTE('\n');
break;
case '\\':
strm<<UBYTE('\\')<<UBYTE('\\');
break;
default:
strm<<UBYTE(c);
}
}
}
// extract translation strings from all files in list
void CDependencyList::ExtractTranslations_t( const CTFileName &fnTranslations)
{
_atpPairs.Clear();
// read original translation table
ReadTranslationTable_t(_atpPairs, fnTranslations);
// for each file in list
FOREACHINLIST( CDependInfo, di_Node, dl_ListHead, itDependInfo) {
CDependInfo &di = *itDependInfo;
// open the file
CTFileStream strm;
strm.Open_t(di.di_fnFileName);
// load it in memory
SLONG slSize = strm.GetStreamSize();
UBYTE *pubFile = (UBYTE *)AllocMemory(slSize);
strm.Read_t(pubFile, slSize);
// for each byte in file
for(INDEX iByte=0; iByte<slSize-4; iByte++) {
UBYTE *pub = pubFile+iByte;
ULONG ul = *((ULONG*)pub);
BYTESWAP(ul);
// if exe translatable string is here
if (ul=='SRTE') {
// get it
CTString str = (char*)(pub+4);
// add it
AddStringForTranslation(str);
// if data translatable string is here
} else if (ul=='SRTD') {
char achr[ 256];
INDEX iStrLen = *(INDEX *)(pub + 4);
BYTESWAP(iStrLen);
if( iStrLen > 254 || iStrLen<0) {
continue;
}
memcpy( achr, pub + 8, iStrLen);
achr[ iStrLen] = 0;
// get it
CTString str = achr;
// add it
AddStringForTranslation(str);
// if text translatable string is here
} else if (ul=='SRTT') {
// after describing long and one space we will find file name
char *pchrStart = (char*)pub + 4 + 1;
// copy file name from the file, until newline
char chrString[ 256];
char *chrSrc = pchrStart;
char *chrDst = chrString;
while(*chrSrc!='\n' && *chrSrc!='\r' && chrSrc-pchrStart<254) {
*chrDst++ = *chrSrc++;
}
*chrDst = 0;
CTString str = CTString(chrString);
if( strlen(str) > 254) {
continue;
}
// add it
AddStringForTranslation(str);
}
}
// free file from memory
FreeMemory(pubFile);
}
// count used pairs
_atpPairs.Lock();
INDEX ctUsedPairs = 0;
{INDEX ct = _atpPairs.Count();
for(INDEX i=0; i<ct; i++) {
if(_atpPairs[i].m_bUsed) {
ctUsedPairs++;
}
}}
_atpPairs.Unlock();
// create output file
CTFileStream strm;
strm.Create_t(fnTranslations);
// write number of used pairs
CTString strNo;
strNo.PrintF("%d", ctUsedPairs);
WriteTranslationString_t(strm, strNo);
WriteTranslationToken_t(strm, "\n");
// for each used pair
_atpPairs.Lock();
{INDEX ct = _atpPairs.Count();
for(INDEX i=0; i<ct; i++) {
CTranslationPair &tp = _atpPairs[i];
if(!tp.m_bUsed) {
continue;
}
// write it, as source and destination, with tags in front
WriteTranslationToken_t(strm, "\n\\0\\<\n");
WriteTranslationString_t(strm, tp.tp_strSrc);
WriteTranslationToken_t(strm, "\n\\0\\>\n");
WriteTranslationString_t(strm, tp.tp_strDst);
}}
_atpPairs.Unlock();
// write eof token
WriteTranslationToken_t(strm, "\n\\0\\$\n");
}
// read operation
void CDependencyList::Read_t( CTStream *istrFile)
{
// count of files
INDEX ctFiles;
// expect file ID
istrFile->ExpectID_t( CChunkID( "DEPD"));
// read number of entries in list
*istrFile >> ctFiles;
// load all list members
for( INDEX i=0; i<ctFiles; i++)
{
// create new depend info object
CDependInfo *pDI = new CDependInfo( CTString(""), istrFile->GetDescription());
// read dependency object
pDI->Read_t( istrFile);
// add it at tail of dependency list
dl_ListHead.AddTail( pDI->di_Node);
}
}
// write opertaion
void CDependencyList::Write_t( CTStream *ostrFile)
{
// write file ID
ostrFile->WriteID_t( CChunkID( "DEPD"));
// write number of entries in list
*ostrFile << dl_ListHead.Count();
// for all members in dependency list
FOREACHINLIST( CDependInfo, di_Node, dl_ListHead, itDependInfo)
{
// write one depend info object
itDependInfo->Write_t( ostrFile);
}
}
// make a new directory recursively
static void MakeDirectory_t(const CTFileName &fnm)
{
if (fnm=="") {
return;
}
// remove trailing backslash
CTFileName fnmDir = fnm;
((char *)(const char*)fnmDir)[strlen(fnmDir)-1] = 0;
// get the path part
CTFileName fnmDirPath = fnmDir.FileDir();
// if there is a path part
if (fnmDirPath!="") {
// create that first
MakeDirectory_t(fnmDirPath);
}
// try to create the directory
int iRes = _mkdir(_fnmApplicationPath+fnmDir);
}