2572 lines
66 KiB
C++
2572 lines
66 KiB
C++
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
|
|
// Copyright (C) 2010 Winch Gate Property Limited
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
#include "stdmisc.h"
|
|
|
|
#include "nel/misc/path.h"
|
|
#include "nel/misc/big_file.h"
|
|
#include "nel/misc/hierarchical_timer.h"
|
|
#include "nel/misc/progress_callback.h"
|
|
#include "nel/misc/file.h"
|
|
#include "nel/misc/xml_pack.h"
|
|
|
|
#ifdef NL_OS_WINDOWS
|
|
# ifndef NL_COMP_MINGW
|
|
# define NOMINMAX
|
|
# endif
|
|
# include <windows.h>
|
|
# include <sys/types.h>
|
|
# include <sys/stat.h>
|
|
# include <direct.h>
|
|
# include <io.h>
|
|
# include <fcntl.h>
|
|
# include <sys/types.h>
|
|
# include <sys/stat.h>
|
|
# include <shlobj.h>
|
|
#else
|
|
# include <sys/types.h>
|
|
# include <sys/stat.h>
|
|
# include <dirent.h>
|
|
# include <unistd.h>
|
|
# include <cstdio>
|
|
# include <cerrno>
|
|
# include <sys/types.h>
|
|
# include <utime.h>
|
|
#endif // NL_OS_WINDOWS
|
|
|
|
using namespace std;
|
|
|
|
#ifdef DEBUG_NEW
|
|
#define new DEBUG_NEW
|
|
#endif
|
|
|
|
namespace NLMISC {
|
|
|
|
//
|
|
// Macros
|
|
//
|
|
|
|
// Use this define if you want to display info about the CPath.
|
|
//#define NL_DEBUG_PATH
|
|
|
|
#ifdef NL_DEBUG_PATH
|
|
# define NL_DISPLAY_PATH nlinfo
|
|
#else
|
|
# ifdef __GNUC__
|
|
# define NL_DISPLAY_PATH(format, args...)
|
|
# else // __GNUC__
|
|
# define NL_DISPLAY_PATH if(false)
|
|
# endif // __GNUC__
|
|
#endif
|
|
|
|
|
|
//
|
|
// Variables
|
|
//
|
|
|
|
NLMISC_SAFE_SINGLETON_IMPL(CPath);
|
|
|
|
|
|
//
|
|
// Functions
|
|
//
|
|
|
|
CFileContainer::~CFileContainer()
|
|
{
|
|
if( _AllFileNames )
|
|
{
|
|
delete[] _AllFileNames;
|
|
_AllFileNames = NULL;
|
|
}
|
|
}
|
|
|
|
void CPath::releaseInstance()
|
|
{
|
|
if (_Instance)
|
|
{
|
|
NLMISC::INelContext::getInstance().releaseSingletonPointer("CPath", _Instance);
|
|
delete _Instance;
|
|
_Instance = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
void CPath::getFileList(const std::string &extension, std::vector<std::string> &filenames)
|
|
{
|
|
getInstance()->_FileContainer.getFileList(extension, filenames);
|
|
}
|
|
|
|
void CFileContainer::getFileList(const std::string &extension, std::vector<std::string> &filenames)
|
|
{
|
|
if (!_MemoryCompressed)
|
|
{
|
|
TFiles::iterator first(_Files.begin()), last(_Files.end());
|
|
|
|
if( !extension.empty() )
|
|
{
|
|
for (; first != last; ++ first)
|
|
{
|
|
string ext = SSMext.get(first->second.idExt);
|
|
if (ext == extension)
|
|
{
|
|
filenames.push_back(first->first);
|
|
}
|
|
}
|
|
}
|
|
// if extension is empty we keep all files
|
|
else
|
|
{
|
|
for (; first != last; ++ first)
|
|
{
|
|
filenames.push_back(first->first);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// compressed memory version
|
|
std::vector<CFileContainer::CMCFileEntry>::iterator first(_MCFiles.begin()), last(_MCFiles.end());
|
|
|
|
if( !extension.empty() )
|
|
{
|
|
for (; first != last; ++ first)
|
|
{
|
|
string ext = SSMext.get(first->idExt);
|
|
if (ext == extension)
|
|
{
|
|
filenames.push_back(first->Name);
|
|
}
|
|
}
|
|
}
|
|
// if extension is empty we keep all files
|
|
else
|
|
{
|
|
for (; first != last; ++ first)
|
|
{
|
|
filenames.push_back(first->Name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPath::getFileListByName(const std::string &extension, const std::string &name, std::vector<std::string> &filenames)
|
|
{
|
|
getInstance()->_FileContainer.getFileListByName(extension, name, filenames);
|
|
}
|
|
|
|
void CFileContainer::getFileListByName(const std::string &extension, const std::string &name, std::vector<std::string> &filenames)
|
|
{
|
|
if (!_MemoryCompressed)
|
|
{
|
|
TFiles::iterator first(_Files.begin()), last(_Files.end());
|
|
|
|
if( !name.empty() )
|
|
{
|
|
for (; first != last; ++ first)
|
|
{
|
|
string ext = SSMext.get(first->second.idExt);
|
|
if (first->first.find(name) != string::npos && (ext == extension || extension.empty()))
|
|
{
|
|
filenames.push_back(first->first);
|
|
}
|
|
}
|
|
}
|
|
// if extension is empty we keep all files
|
|
else
|
|
{
|
|
for (; first != last; ++ first)
|
|
{
|
|
filenames.push_back(first->first);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// compressed memory version
|
|
std::vector<CFileContainer::CMCFileEntry>::iterator first(_MCFiles.begin()), last(_MCFiles.end());
|
|
|
|
if( !name.empty() )
|
|
{
|
|
for (; first != last; ++ first)
|
|
{
|
|
string ext = SSMext.get(first->idExt);
|
|
if (strstr(first->Name, name.c_str()) != NULL && (ext == extension || extension.empty()))
|
|
{
|
|
filenames.push_back(first->Name);
|
|
}
|
|
}
|
|
}
|
|
// if extension is empty we keep all files
|
|
else
|
|
{
|
|
for (; first != last; ++ first)
|
|
{
|
|
filenames.push_back(first->Name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPath::clearMap ()
|
|
{
|
|
getInstance()->_FileContainer.clearMap();
|
|
}
|
|
|
|
void CFileContainer::clearMap ()
|
|
{
|
|
nlassert(!_MemoryCompressed);
|
|
_Files.clear ();
|
|
CBigFile::getInstance().removeAll ();
|
|
NL_DISPLAY_PATH("PATH: CPath::clearMap(): map directory cleared");
|
|
}
|
|
|
|
CFileContainer::CMCFileEntry *CFileContainer::MCfind (const std::string &filename)
|
|
{
|
|
nlassert(_MemoryCompressed);
|
|
vector<CMCFileEntry>::iterator it;
|
|
CMCFileEntry temp_cmc_file;
|
|
temp_cmc_file.Name = (char*)filename.c_str();
|
|
it = lower_bound(_MCFiles.begin(), _MCFiles.end(), temp_cmc_file, CMCFileComp());
|
|
if (it != _MCFiles.end())
|
|
{
|
|
CMCFileComp FileComp;
|
|
if (FileComp.specialCompare(*it, filename.c_str()) == 0)
|
|
return &(*it);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
sint CFileContainer::findExtension (const string &ext1, const string &ext2)
|
|
{
|
|
for (uint i = 0; i < _Extensions.size (); i++)
|
|
{
|
|
if (_Extensions[i].first == ext1 && _Extensions[i].second == ext2)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void CPath::remapExtension (const string &ext1, const string &ext2, bool substitute)
|
|
{
|
|
getInstance()->_FileContainer.remapExtension(ext1, ext2, substitute);
|
|
}
|
|
|
|
void CFileContainer::remapExtension (const string &ext1, const string &ext2, bool substitute)
|
|
{
|
|
nlassert(!_MemoryCompressed);
|
|
|
|
string ext1lwr = toLower(ext1);
|
|
string ext2lwr = toLower(ext2);
|
|
|
|
if (ext1lwr.empty() || ext2lwr.empty())
|
|
{
|
|
nlwarning ("PATH: CPath::remapExtension(%s, %s, %d): can't remap empty extension", ext1lwr.c_str(), ext2lwr.c_str(), substitute);
|
|
}
|
|
|
|
if (ext1lwr == "bnp" || ext2lwr == "bnp")
|
|
{
|
|
nlwarning ("PATH: CPath::remapExtension(%s, %s, %d): you can't remap a big file", ext1lwr.c_str(), ext2lwr.c_str(), substitute);
|
|
}
|
|
|
|
if (!substitute)
|
|
{
|
|
// remove the mapping from the mapping list
|
|
sint n = findExtension (ext1lwr, ext2lwr);
|
|
nlassert (n != -1);
|
|
_Extensions.erase (_Extensions.begin() + n);
|
|
|
|
// remove mapping in the map
|
|
TFiles::iterator it = _Files.begin();
|
|
TFiles::iterator nit = it;
|
|
while (it != _Files.end ())
|
|
{
|
|
nit++;
|
|
string ext = SSMext.get((*it).second.idExt);
|
|
if ((*it).second.Remapped && ext == ext2lwr)
|
|
{
|
|
_Files.erase (it);
|
|
}
|
|
it = nit;
|
|
}
|
|
NL_DISPLAY_PATH("PATH: CPath::remapExtension(%s, %s, %d): extension removed", ext1lwr.c_str(), ext2lwr.c_str(), substitute);
|
|
}
|
|
else
|
|
{
|
|
sint n = findExtension (ext1lwr, ext2lwr);
|
|
if (n != -1)
|
|
{
|
|
nlwarning ("PATH: CPath::remapExtension(%s, %s, %d): remapping already set", ext1lwr.c_str(), ext2lwr.c_str(), substitute);
|
|
return;
|
|
}
|
|
|
|
// adding mapping into the mapping list
|
|
_Extensions.push_back (make_pair (ext1lwr, ext2lwr));
|
|
|
|
// adding mapping into the map
|
|
vector<string> newFiles;
|
|
TFiles::iterator it = _Files.begin();
|
|
while (it != _Files.end ())
|
|
{
|
|
string ext = SSMext.get((*it).second.idExt);
|
|
if (!(*it).second.Remapped && ext == ext1lwr)
|
|
{
|
|
// find if already exist
|
|
string::size_type pos = (*it).first.find_last_of (".");
|
|
if (pos != string::npos)
|
|
{
|
|
string file = (*it).first.substr (0, pos + 1);
|
|
file += ext2lwr;
|
|
|
|
// TODO perhaps a problem because I insert in the current map that I process
|
|
string path = SSMpath.get((*it).second.idPath);
|
|
insertFileInMap (file, path+file, true, ext1lwr);
|
|
}
|
|
}
|
|
it++;
|
|
}
|
|
NL_DISPLAY_PATH("PATH: CPath::remapExtension(%s, %s, %d): extension added", ext1lwr.c_str(), ext2lwr.c_str(), substitute);
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void CPath::remapFile (const std::string &file1, const std::string &file2)
|
|
{
|
|
getInstance()->_FileContainer.remapFile(file1, file2);
|
|
}
|
|
|
|
void CFileContainer::remapFile (const std::string &file1, const std::string &file2)
|
|
{
|
|
if (file1.empty()) return;
|
|
if (file2.empty()) return;
|
|
_RemappedFiles[toLower(file1)] = toLower(file2);
|
|
}
|
|
|
|
// ***************************************************************************
|
|
static void removeAllUnusedChar(string &str)
|
|
{
|
|
uint32 i = 0;
|
|
while (!str.empty() && (i != str.size()))
|
|
{
|
|
if ((str[i] == ' ' || str[i] == '\t' || str[i] == '\r' || str[i] == '\n'))
|
|
str.erase(str.begin()+i);
|
|
else
|
|
i++;
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void CPath::loadRemappedFiles (const std::string &file)
|
|
{
|
|
getInstance()->_FileContainer.loadRemappedFiles(file);
|
|
}
|
|
|
|
void CFileContainer::loadRemappedFiles (const std::string &file)
|
|
{
|
|
string fullName = lookup(file, false, true, true);
|
|
CIFile f;
|
|
f.setCacheFileOnOpen (true);
|
|
|
|
if (!f.open (fullName))
|
|
return;
|
|
|
|
char sTmp[514];
|
|
string str;
|
|
|
|
while (!f.eof())
|
|
{
|
|
f.getline(sTmp, 512);
|
|
str = sTmp;
|
|
std::string::size_type pos = str.find(',');
|
|
if (pos != string::npos)
|
|
{
|
|
removeAllUnusedChar(str);
|
|
if (!str.empty())
|
|
{
|
|
pos = str.find(',');
|
|
remapFile( str.substr(0,pos), str.substr(pos+1, str.size()) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
string CPath::lookup (const string &filename, bool throwException, bool displayWarning, bool lookupInLocalDirectory)
|
|
{
|
|
return getInstance()->_FileContainer.lookup(filename, throwException, displayWarning, lookupInLocalDirectory);
|
|
}
|
|
|
|
string CFileContainer::lookup (const string &filename, bool throwException, bool displayWarning, bool lookupInLocalDirectory)
|
|
{
|
|
/* ***********************************************
|
|
* WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance
|
|
* It can be loaded/called through CAsyncFileManager for instance
|
|
* ***********************************************/
|
|
/*
|
|
NB: CPath access static instance getInstance() of course, so user must ensure
|
|
that no mutator is called while async loading
|
|
*/
|
|
|
|
|
|
// If the file already contains a @, it means that a lookup already proceed and returning a big file, do nothing
|
|
if (filename.find ("@") != string::npos)
|
|
{
|
|
NL_DISPLAY_PATH("PATH: CPath::lookup(%s): already found", filename.c_str());
|
|
return filename;
|
|
}
|
|
|
|
// Try to find in the map directories
|
|
|
|
// If filename contains a path, we get only the filename to look inside paths
|
|
string str = CFile::getFilename(toLower(filename));
|
|
|
|
// Remove end spaces
|
|
while ((!str.empty()) && (str[str.size()-1] == ' '))
|
|
{
|
|
str.resize (str.size()-1);
|
|
}
|
|
|
|
map<string, string>::iterator itss = _RemappedFiles.find(str);
|
|
if (itss != _RemappedFiles.end())
|
|
str = itss->second;
|
|
|
|
if (_MemoryCompressed)
|
|
{
|
|
CMCFileEntry *pMCFE = MCfind(str);
|
|
// If found in the map, returns it
|
|
if (pMCFE != NULL)
|
|
{
|
|
string fname, path = SSMpath.get(pMCFE->idPath);
|
|
if (pMCFE->Remapped)
|
|
fname = CFile::getFilenameWithoutExtension(pMCFE->Name) + "." + SSMext.get(pMCFE->idExt);
|
|
else
|
|
fname = pMCFE->Name;
|
|
|
|
NL_DISPLAY_PATH("PATH: CPath::lookup(%s): found in the map directory: '%s'", fname.c_str(), path.c_str());
|
|
return path + fname;
|
|
}
|
|
}
|
|
else // NOT memory compressed
|
|
{
|
|
|
|
TFiles::iterator it = _Files.find (str);
|
|
// If found in the map, returns it
|
|
if (it != _Files.end())
|
|
{
|
|
string fname, path = SSMpath.get((*it).second.idPath);
|
|
if (it->second.Remapped)
|
|
fname = CFile::getFilenameWithoutExtension((*it).second.Name) + "." + SSMext.get((*it).second.idExt);
|
|
else
|
|
fname = (*it).second.Name;
|
|
|
|
NL_DISPLAY_PATH("PATH: CPath::lookup(%s): found in the map directory: '%s'", fname.c_str(), path.c_str());
|
|
return path + fname;
|
|
}
|
|
}
|
|
|
|
|
|
// Try to find in the alternative directories
|
|
for (uint i = 0; i < _AlternativePaths.size(); i++)
|
|
{
|
|
string s = _AlternativePaths[i] + str;
|
|
if ( CFile::fileExists(s) )
|
|
{
|
|
NL_DISPLAY_PATH("PATH: CPath::lookup(%s): found in the alternative directory: '%s'", str.c_str(), s.c_str());
|
|
return s;
|
|
}
|
|
|
|
// try with the remapping
|
|
for (uint j = 0; j < _Extensions.size(); j++)
|
|
{
|
|
if (toLower(CFile::getExtension (str)) == _Extensions[j].second)
|
|
{
|
|
string rs = _AlternativePaths[i] + CFile::getFilenameWithoutExtension (filename) + "." + _Extensions[j].first;
|
|
if ( CFile::fileExists(rs) )
|
|
{
|
|
NL_DISPLAY_PATH("PATH: CPath::lookup(%s): found in the alternative directory: '%s'", str.c_str(), rs.c_str());
|
|
return rs;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try to find in the current directory
|
|
if ( lookupInLocalDirectory && CFile::fileExists(filename) )
|
|
{
|
|
NL_DISPLAY_PATH("PATH: CPath::lookup(%s): found in the current directory: '%s'", str.c_str(), filename.c_str());
|
|
return filename;
|
|
}
|
|
|
|
// Not found
|
|
if (displayWarning)
|
|
{
|
|
if(filename.empty())
|
|
nlwarning ("PATH: Try to lookup for an empty filename. TODO: check why.");
|
|
else
|
|
nlwarning ("PATH: File (%s) not found (%s)", str.c_str(), filename.c_str());
|
|
}
|
|
|
|
if (throwException)
|
|
throw EPathNotFound (filename);
|
|
|
|
return "";
|
|
}
|
|
|
|
bool CPath::exists (const std::string &filename)
|
|
{
|
|
return getInstance()->_FileContainer.exists(filename);
|
|
}
|
|
|
|
bool CFileContainer::exists (const std::string &filename)
|
|
{
|
|
// Try to find in the map directories
|
|
string str = toLower(filename);
|
|
|
|
// Remove end spaces
|
|
while ((!str.empty()) && (str[str.size()-1] == ' '))
|
|
{
|
|
str.resize (str.size()-1);
|
|
}
|
|
|
|
if (_MemoryCompressed)
|
|
{
|
|
CMCFileEntry *pMCFE = MCfind(str);
|
|
// If found in the vector, returns it
|
|
if (pMCFE != NULL)
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
TFiles::iterator it = _Files.find (str);
|
|
// If found in the map, returns it
|
|
if (it != _Files.end())
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
string CPath::standardizePath (const string &path, bool addFinalSlash)
|
|
{
|
|
return getInstance()->_FileContainer.standardizePath(path, addFinalSlash);
|
|
}
|
|
|
|
string CFileContainer::standardizePath (const string &path, bool addFinalSlash)
|
|
{
|
|
// check empty path
|
|
if (path.empty())
|
|
return "";
|
|
|
|
string newPath(path);
|
|
|
|
for (uint i = 0; i < path.size(); i++)
|
|
{
|
|
// don't transform the first \\ for windows network path
|
|
if (path[i] == '\\')
|
|
newPath[i] = '/';
|
|
}
|
|
|
|
// add terminal slash
|
|
if (addFinalSlash && newPath[path.size()-1] != '/')
|
|
newPath += '/';
|
|
|
|
return newPath;
|
|
}
|
|
|
|
// replace / with backslash
|
|
std::string CPath::standardizeDosPath (const std::string &path)
|
|
{
|
|
return getInstance()->_FileContainer.standardizeDosPath(path);
|
|
}
|
|
|
|
std::string CFileContainer::standardizeDosPath (const std::string &path)
|
|
{
|
|
string newPath;
|
|
|
|
for (uint i = 0; i < path.size(); i++)
|
|
{
|
|
if (path[i] == '/')
|
|
newPath += '\\';
|
|
// Yoyo: supress toLower. Not useful!?!
|
|
/*else if (isupper(path[i]))
|
|
newPath += tolower(path[i]);*/
|
|
else
|
|
newPath += path[i];
|
|
}
|
|
|
|
if (CFile::isExists(path) && CFile::isDirectory(path) && newPath[newPath.size()-1] != '\\')
|
|
newPath += '\\';
|
|
|
|
return newPath;
|
|
}
|
|
|
|
|
|
std::string CPath::getCurrentPath ()
|
|
{
|
|
return getInstance()->_FileContainer.getCurrentPath();
|
|
}
|
|
|
|
std::string CFileContainer::getCurrentPath ()
|
|
{
|
|
char buffer [1024];
|
|
|
|
#ifdef NL_OS_WINDOWS
|
|
return standardizePath(_getcwd(buffer, 1024), false);
|
|
#else
|
|
return standardizePath(getcwd(buffer, 1024), false);
|
|
#endif
|
|
}
|
|
|
|
bool CPath::setCurrentPath (const std::string &path)
|
|
{
|
|
return getInstance()->_FileContainer.setCurrentPath(path);
|
|
}
|
|
|
|
bool CFileContainer::setCurrentPath (const std::string &path)
|
|
{
|
|
int res;
|
|
//nldebug("Change current path to '%s' (current path is '%s')", path.c_str(), getCurrentPath().c_str());
|
|
#ifdef NL_OS_WINDOWS
|
|
res = _chdir(path.c_str());
|
|
#else
|
|
res = chdir(path.c_str());
|
|
#endif
|
|
if(res != 0) nlwarning("Cannot change current path to '%s' (current path is '%s') res: %d errno: %d (%s)", path.c_str(), getCurrentPath().c_str(), res, errno, strerror(errno));
|
|
return res == 0;
|
|
}
|
|
|
|
std::string CPath::getFullPath (const std::string &path, bool addFinalSlash)
|
|
{
|
|
return getInstance()->_FileContainer.getFullPath(path, addFinalSlash);
|
|
}
|
|
|
|
std::string CFileContainer::getFullPath (const std::string &path, bool addFinalSlash)
|
|
{
|
|
string currentPath = standardizePath (getCurrentPath ());
|
|
string sPath = standardizePath (path, addFinalSlash);
|
|
|
|
// current path
|
|
if (path.empty() || sPath == "." || sPath == "./")
|
|
{
|
|
return currentPath;
|
|
}
|
|
|
|
// windows full path
|
|
if (path.size() >= 2 && path[1] == ':')
|
|
{
|
|
return sPath;
|
|
}
|
|
|
|
if (path.size() >= 2 && (path[0] == '/' || path[0] == '\\') && (path[1] == '/' || path[1] == '\\'))
|
|
{
|
|
return sPath;
|
|
}
|
|
|
|
|
|
// from root
|
|
if (path [0] == '/' || path[0] == '\\')
|
|
{
|
|
if (currentPath.size() > 2 && currentPath[1] == ':')
|
|
{
|
|
return currentPath.substr(0,3) + sPath.substr(1);
|
|
}
|
|
else
|
|
{
|
|
return sPath;
|
|
}
|
|
}
|
|
|
|
// default case
|
|
return currentPath + sPath;
|
|
}
|
|
|
|
|
|
|
|
#ifdef NL_OS_WINDOWS
|
|
# define dirent WIN32_FIND_DATA
|
|
# define DIR void
|
|
|
|
static string sDir;
|
|
static WIN32_FIND_DATA findData;
|
|
static HANDLE hFind;
|
|
|
|
DIR *opendir (const char *path)
|
|
{
|
|
nlassert (path != NULL);
|
|
nlassert (path[0] != '\0');
|
|
|
|
if (!CFile::isDirectory(path))
|
|
return NULL;
|
|
|
|
sDir = path;
|
|
|
|
hFind = NULL;
|
|
|
|
return (void *)1;
|
|
}
|
|
|
|
int closedir (DIR *dir)
|
|
{
|
|
FindClose(hFind);
|
|
return 0;
|
|
}
|
|
|
|
dirent *readdir (DIR *dir)
|
|
{
|
|
// FIX : call to SetCurrentDirectory() and SetCurrentDirectory() removed to improve speed
|
|
nlassert (!sDir.empty());
|
|
|
|
// first visit in this directory : FindFirstFile()
|
|
if (hFind == NULL)
|
|
{
|
|
string fullPath = CPath::standardizePath(sDir) + "*";
|
|
hFind = FindFirstFileA (fullPath.c_str(), &findData);
|
|
}
|
|
// directory already visited : FindNextFile()
|
|
else
|
|
{
|
|
if (!FindNextFileA (hFind, &findData))
|
|
return NULL;
|
|
}
|
|
|
|
return &findData;
|
|
}
|
|
|
|
|
|
#endif // NL_OS_WINDOWS
|
|
|
|
#ifndef NL_OS_WINDOWS
|
|
string BasePathgetPathContent;
|
|
#endif
|
|
|
|
bool isdirectory (dirent *de)
|
|
{
|
|
nlassert (de != NULL);
|
|
#ifdef NL_OS_WINDOWS
|
|
return ((de->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) && ((de->dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) == 0);
|
|
#else
|
|
//nlinfo ("isdirectory filename %s -> 0x%08x", de->d_name, de->d_type);
|
|
// we can't use "de->d_type & DT_DIR" because it s always NULL on libc2.1
|
|
//return (de->d_type & DT_DIR) != 0;
|
|
|
|
return CFile::isDirectory (BasePathgetPathContent + de->d_name);
|
|
|
|
#endif // NL_OS_WINDOWS
|
|
}
|
|
|
|
bool isfile (dirent *de)
|
|
{
|
|
nlassert (de != NULL);
|
|
#ifdef NL_OS_WINDOWS
|
|
return ((de->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) && ((de->dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) == 0);
|
|
#else
|
|
// we can't use "de->d_type & DT_DIR" because it s always NULL on libc2.1
|
|
//return (de->d_type & DT_DIR) == 0;
|
|
|
|
return !CFile::isDirectory (BasePathgetPathContent + de->d_name);
|
|
|
|
#endif // NL_OS_WINDOWS
|
|
}
|
|
|
|
string getname (dirent *de)
|
|
{
|
|
nlassert (de != NULL);
|
|
#ifdef NL_OS_WINDOWS
|
|
return de->cFileName;
|
|
#else
|
|
return de->d_name;
|
|
#endif // NL_OS_WINDOWS
|
|
}
|
|
|
|
void CPath::getPathContent (const string &path, bool recurse, bool wantDir, bool wantFile, vector<string> &result, class IProgressCallback *progressCallBack, bool showEverything)
|
|
{
|
|
getInstance()->_FileContainer.getPathContent(path, recurse, wantDir, wantFile, result, progressCallBack, showEverything);
|
|
}
|
|
|
|
void CFileContainer::getPathContent (const string &path, bool recurse, bool wantDir, bool wantFile, vector<string> &result, class IProgressCallback *progressCallBack, bool showEverything)
|
|
{
|
|
if( path.empty() )
|
|
{
|
|
NL_DISPLAY_PATH("PATH: CPath::getPathContent(): Empty input Path");
|
|
return;
|
|
}
|
|
|
|
#ifndef NL_OS_WINDOWS
|
|
BasePathgetPathContent = CPath::standardizePath (path);
|
|
#endif
|
|
|
|
DIR *dir = opendir (path.c_str());
|
|
|
|
if (dir == NULL)
|
|
{
|
|
NL_DISPLAY_PATH("PATH: CPath::getPathContent(%s, %d, %d, %d): could not open the directory", path.c_str(), recurse, wantDir, wantFile);
|
|
return;
|
|
}
|
|
|
|
// contains path that we have to recurs into
|
|
vector<string> recursPath;
|
|
|
|
for(;;)
|
|
{
|
|
dirent *de = readdir(dir);
|
|
if (de == NULL)
|
|
{
|
|
NL_DISPLAY_PATH("PATH: CPath::getPathContent(%s, %d, %d, %d): end of directory", path.c_str(), recurse, wantDir, wantFile);
|
|
break;
|
|
}
|
|
|
|
string fn = getname (de);
|
|
|
|
// skip . and ..
|
|
if (fn == "." || fn == "..")
|
|
continue;
|
|
|
|
if (isdirectory(de))
|
|
{
|
|
// skip CVS and .svn directory
|
|
if ((!showEverything) && (fn == "CVS" || fn == ".svn"))
|
|
{
|
|
NL_DISPLAY_PATH("PATH: CPath::getPathContent(%s, %d, %d, %d): skip CVS and .svn directory", path.c_str(), recurse, wantDir, wantFile);
|
|
continue;
|
|
}
|
|
|
|
string stdName = standardizePath(standardizePath(path) + fn);
|
|
if (recurse)
|
|
{
|
|
NL_DISPLAY_PATH("PATH: CPath::getPathContent(%s, %d, %d, %d): need to recurse into '%s'", path.c_str(), recurse, wantDir, wantFile, stdName.c_str());
|
|
recursPath.push_back (stdName);
|
|
}
|
|
|
|
if (wantDir)
|
|
{
|
|
NL_DISPLAY_PATH("PATH: CPath::getPathContent(%s, %d, %d, %d): adding path '%s'", path.c_str(), recurse, wantDir, wantFile, stdName.c_str());
|
|
result.push_back (stdName);
|
|
}
|
|
}
|
|
|
|
if (wantFile && isfile(de))
|
|
{
|
|
if ( (!showEverything) && (fn.size() >= 4 && fn.substr (fn.size()-4) == ".log"))
|
|
{
|
|
NL_DISPLAY_PATH("PATH: CPath::getPathContent(%s, %d, %d, %d): skip *.log files (%s)", path.c_str(), recurse, wantDir, wantFile, fn.c_str());
|
|
continue;
|
|
}
|
|
|
|
string stdName = standardizePath(path) + getname(de);
|
|
|
|
NL_DISPLAY_PATH("PATH: CPath::getPathContent(%s, %d, %d, %d): adding file '%s'", path.c_str(), recurse, wantDir, wantFile, stdName.c_str());
|
|
result.push_back (stdName);
|
|
}
|
|
}
|
|
|
|
closedir (dir);
|
|
|
|
#ifndef NL_OS_WINDOWS
|
|
BasePathgetPathContent = "";
|
|
#endif
|
|
|
|
// let s recurse
|
|
for (uint i = 0; i < recursPath.size (); i++)
|
|
{
|
|
// Progress bar
|
|
if (progressCallBack)
|
|
{
|
|
progressCallBack->progress ((float)i/(float)recursPath.size ());
|
|
progressCallBack->pushCropedValues ((float)i/(float)recursPath.size (), (float)(i+1)/(float)recursPath.size ());
|
|
}
|
|
|
|
getPathContent (recursPath[i], recurse, wantDir, wantFile, result, progressCallBack, showEverything);
|
|
|
|
// Progress bar
|
|
if (progressCallBack)
|
|
{
|
|
progressCallBack->popCropedValues ();
|
|
}
|
|
}
|
|
|
|
sort(result.begin(), result.end());
|
|
}
|
|
|
|
void CPath::removeAllAlternativeSearchPath ()
|
|
{
|
|
getInstance()->_FileContainer.removeAllAlternativeSearchPath();
|
|
}
|
|
|
|
void CFileContainer::removeAllAlternativeSearchPath ()
|
|
{
|
|
_AlternativePaths.clear ();
|
|
NL_DISPLAY_PATH("PATH: CPath::RemoveAllAternativeSearchPath(): removed");
|
|
}
|
|
|
|
|
|
void CPath::addSearchPath (const string &path, bool recurse, bool alternative, class IProgressCallback *progressCallBack)
|
|
{
|
|
getInstance()->_FileContainer.addSearchPath(path, recurse, alternative, progressCallBack);
|
|
}
|
|
|
|
void CFileContainer::addSearchPath (const string &path, bool recurse, bool alternative, class IProgressCallback *progressCallBack)
|
|
{
|
|
//H_AUTO_INST(addSearchPath);
|
|
|
|
nlassert(!_MemoryCompressed);
|
|
|
|
// check empty directory
|
|
if (path.empty())
|
|
{
|
|
nlwarning ("PATH: CPath::addSearchPath(%s, %s, %s): can't add empty directory, skip it",
|
|
path.c_str(),
|
|
recurse ? "recursive" : "not recursive",
|
|
alternative ? "alternative" : "not alternative");
|
|
return;
|
|
}
|
|
|
|
// check if it s a directory
|
|
if (!CFile::isDirectory (path))
|
|
{
|
|
nlinfo ("PATH: CPath::addSearchPath(%s, %s, %s): '%s' is not a directory, I'll call addSearchFile()",
|
|
path.c_str(),
|
|
recurse ? "recursive" : "not recursive",
|
|
alternative ? "alternative" : "not alternative",
|
|
path.c_str());
|
|
addSearchFile (path, false, "", progressCallBack);
|
|
return;
|
|
}
|
|
|
|
string newPath = standardizePath(path);
|
|
|
|
// check if it s a directory
|
|
if (!CFile::isExists (newPath))
|
|
{
|
|
nlwarning ("PATH: CPath::addSearchPath(%s, %s, %s): '%s' is not found, skip it",
|
|
path.c_str(),
|
|
recurse ? "recursive" : "not recursive",
|
|
alternative ? "alternative" : "not alternative",
|
|
newPath.c_str());
|
|
return;
|
|
}
|
|
|
|
nlinfo ("PATH: CPath::addSearchPath(%s, %d, %d): adding the path '%s'", path.c_str(), recurse, alternative, newPath.c_str());
|
|
|
|
NL_DISPLAY_PATH("PATH: CPath::addSearchPath(%s, %d, %d): try to add '%s'", path.c_str(), recurse, alternative, newPath.c_str());
|
|
|
|
if (alternative)
|
|
{
|
|
vector<string> pathsToProcess;
|
|
|
|
// add the current path
|
|
pathsToProcess.push_back (newPath);
|
|
|
|
if (recurse)
|
|
{
|
|
// find all path and subpath
|
|
getPathContent (newPath, recurse, true, false, pathsToProcess, progressCallBack);
|
|
}
|
|
|
|
for (uint p = 0; p < pathsToProcess.size(); p++)
|
|
{
|
|
// check if the path not already in the vector
|
|
uint i;
|
|
for (i = 0; i < _AlternativePaths.size(); i++)
|
|
{
|
|
if (_AlternativePaths[i] == pathsToProcess[p])
|
|
break;
|
|
}
|
|
if (i == _AlternativePaths.size())
|
|
{
|
|
// add them in the alternative directory
|
|
_AlternativePaths.push_back (pathsToProcess[p]);
|
|
NL_DISPLAY_PATH("PATH: CPath::addSearchPath(%s, %s, %s): path '%s' added",
|
|
newPath.c_str(),
|
|
recurse ? "recursive" : "not recursive",
|
|
alternative ? "alternative" : "not alternative",
|
|
pathsToProcess[p].c_str());
|
|
}
|
|
else
|
|
{
|
|
nlwarning ("PATH: CPath::addSearchPath(%s, %s, %s): path '%s' already added",
|
|
newPath.c_str(),
|
|
recurse ? "recursive" : "not recursive",
|
|
alternative ? "alternative" : "not alternative",
|
|
pathsToProcess[p].c_str());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vector<string> filesToProcess;
|
|
|
|
// Progress bar
|
|
if (progressCallBack)
|
|
{
|
|
progressCallBack->progress (0);
|
|
progressCallBack->pushCropedValues (0, 0.5f);
|
|
}
|
|
|
|
// find all files in the path and subpaths
|
|
getPathContent (newPath, recurse, false, true, filesToProcess, progressCallBack);
|
|
|
|
// Progree bar
|
|
if (progressCallBack)
|
|
{
|
|
progressCallBack->popCropedValues ();
|
|
progressCallBack->progress (0.5);
|
|
progressCallBack->pushCropedValues (0.5f, 1);
|
|
}
|
|
|
|
// add them in the map
|
|
for (uint f = 0; f < filesToProcess.size(); f++)
|
|
{
|
|
// Progree bar
|
|
if (progressCallBack)
|
|
{
|
|
progressCallBack->progress ((float)f/(float)filesToProcess.size());
|
|
progressCallBack->pushCropedValues ((float)f/(float)filesToProcess.size(), (float)(f+1)/(float)filesToProcess.size());
|
|
}
|
|
|
|
string filename = CFile::getFilename (filesToProcess[f]);
|
|
string filepath = CFile::getPath (filesToProcess[f]);
|
|
// insertFileInMap (filename, filepath, false, CFile::getExtension(filename));
|
|
addSearchFile (filesToProcess[f], false, "", progressCallBack);
|
|
|
|
// Progress bar
|
|
if (progressCallBack)
|
|
{
|
|
progressCallBack->popCropedValues ();
|
|
}
|
|
}
|
|
|
|
// Progress bar
|
|
if (progressCallBack)
|
|
{
|
|
progressCallBack->popCropedValues ();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPath::addSearchFile (const string &file, bool remap, const string &virtual_ext, NLMISC::IProgressCallback *progressCallBack)
|
|
{
|
|
getInstance()->_FileContainer.addSearchFile(file, remap, virtual_ext, progressCallBack);
|
|
}
|
|
|
|
void CFileContainer::addSearchFile (const string &file, bool remap, const string &virtual_ext, NLMISC::IProgressCallback *progressCallBack)
|
|
{
|
|
nlassert(!_MemoryCompressed);
|
|
|
|
string newFile = standardizePath(file, false);
|
|
|
|
// check empty file
|
|
if (newFile.empty())
|
|
{
|
|
nlwarning ("PATH: CPath::addSearchFile(%s, %d, '%s'): can't add empty file, skip it", file.c_str(), remap, virtual_ext.c_str());
|
|
return;
|
|
}
|
|
|
|
// check if the file exists
|
|
if (!CFile::isExists (newFile))
|
|
{
|
|
nlwarning ("PATH: CPath::addSearchFile(%s, %d, '%s'): '%s' is not found, skip it (current dir is '%s'",
|
|
file.c_str(),
|
|
remap,
|
|
virtual_ext.c_str(),
|
|
newFile.c_str(),
|
|
CPath::getCurrentPath().c_str());
|
|
return;
|
|
}
|
|
|
|
// check if it s a file
|
|
if (CFile::isDirectory (newFile))
|
|
{
|
|
nlwarning ("PATH: CPath::addSearchFile(%s, %d, '%s'): '%s' is not a file, skip it",
|
|
file.c_str(),
|
|
remap,
|
|
virtual_ext.c_str(),
|
|
newFile.c_str());
|
|
return;
|
|
}
|
|
|
|
// check if it s a big file
|
|
if (CFile::getExtension(newFile) == "bnp")
|
|
{
|
|
NL_DISPLAY_PATH ("PATH: CPath::addSearchFile(%s, %d, '%s'): '%s' is a big file, add it", file.c_str(), remap, virtual_ext.c_str(), newFile.c_str());
|
|
addSearchBigFile(file, false, false, progressCallBack);
|
|
return;
|
|
}
|
|
|
|
// check if it s an xml pack file
|
|
if (CFile::getExtension(newFile) == "xml_pack")
|
|
{
|
|
NL_DISPLAY_PATH ("PATH: CPath::addSearchFile(%s, %d, '%s'): '%s' is an xml pack file, add it", file.c_str(), remap, virtual_ext.c_str(), newFile.c_str());
|
|
addSearchXmlpackFile(file, false, false, progressCallBack);
|
|
return;
|
|
}
|
|
|
|
string filenamewoext = CFile::getFilenameWithoutExtension (newFile);
|
|
string filename, ext;
|
|
|
|
if (virtual_ext.empty())
|
|
{
|
|
filename = CFile::getFilename (newFile);
|
|
ext = CFile::getExtension (filename);
|
|
}
|
|
else
|
|
{
|
|
filename = filenamewoext + "." + virtual_ext;
|
|
ext = CFile::getExtension (newFile);
|
|
}
|
|
|
|
insertFileInMap (filename, newFile, remap, ext);
|
|
|
|
if (!remap && !ext.empty())
|
|
{
|
|
// now, we have to see extension and insert in the map the remapped files
|
|
for (uint i = 0; i < _Extensions.size (); i++)
|
|
{
|
|
if (_Extensions[i].first == toLower(ext))
|
|
{
|
|
// need to remap
|
|
addSearchFile (newFile, true, _Extensions[i].second, progressCallBack);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPath::addSearchListFile (const string &filename, bool recurse, bool alternative)
|
|
{
|
|
getInstance()->_FileContainer.addSearchListFile(filename, recurse, alternative);
|
|
}
|
|
|
|
void CFileContainer::addSearchListFile (const string &filename, bool recurse, bool alternative)
|
|
{
|
|
// check empty file
|
|
if (filename.empty())
|
|
{
|
|
nlwarning ("PATH: CPath::addSearchListFile(%s, %d, %d): can't add empty file, skip it", filename.c_str(), recurse, alternative);
|
|
return;
|
|
}
|
|
|
|
// check if the file exists
|
|
if (!CFile::isExists (filename))
|
|
{
|
|
nlwarning ("PATH: CPath::addSearchListFile(%s, %d, %d): '%s' is not found, skip it", filename.c_str(), recurse, alternative, filename.c_str());
|
|
return;
|
|
}
|
|
|
|
// check if it s a file
|
|
if (CFile::isDirectory (filename))
|
|
{
|
|
nlwarning ("PATH: CPath::addSearchListFile(%s, %d, %d): '%s' is not a file, skip it", filename.c_str(), recurse, alternative, filename.c_str());
|
|
return;
|
|
}
|
|
|
|
// TODO read the file and add files that are inside
|
|
}
|
|
|
|
// WARNING : recurse is not used
|
|
void CPath::addSearchBigFile (const string &sBigFilename, bool recurse, bool alternative, NLMISC::IProgressCallback *progressCallBack)
|
|
{
|
|
getInstance()->_FileContainer.addSearchBigFile(sBigFilename, recurse, alternative, progressCallBack);
|
|
}
|
|
|
|
void CFileContainer::addSearchBigFile (const string &sBigFilename, bool recurse, bool alternative, NLMISC::IProgressCallback *progressCallBack)
|
|
{
|
|
// Check if filename is not empty
|
|
if (sBigFilename.empty())
|
|
{
|
|
nlwarning ("PATH: CPath::addSearchBigFile(%s, %d, %d): can't add empty file, skip it", sBigFilename.c_str(), recurse, alternative);
|
|
return;
|
|
}
|
|
// Check if the file exists
|
|
if (!CFile::isExists (sBigFilename))
|
|
{
|
|
nlwarning ("PATH: CPath::addSearchBigFile(%s, %d, %d): '%s' is not found, skip it", sBigFilename.c_str(), recurse, alternative, sBigFilename.c_str());
|
|
return;
|
|
}
|
|
// Check if it s a file
|
|
if (CFile::isDirectory (sBigFilename))
|
|
{
|
|
nlwarning ("PATH: CPath::addSearchBigFile(%s, %d, %d): '%s' is not a file, skip it", sBigFilename.c_str(), recurse, alternative, sBigFilename.c_str());
|
|
return;
|
|
}
|
|
// Open and read the big file header
|
|
nlassert(!_MemoryCompressed);
|
|
|
|
FILE *Handle = fopen (sBigFilename.c_str(), "rb");
|
|
if (Handle == NULL)
|
|
{
|
|
nlwarning ("PATH: CPath::addSearchBigFile(%s, %d, %d): can't open file, skip it", sBigFilename.c_str(), recurse, alternative);
|
|
return;
|
|
}
|
|
|
|
// add the link with the CBigFile singleton
|
|
if (CBigFile::getInstance().add (sBigFilename, BF_ALWAYS_OPENED | BF_CACHE_FILE_ON_OPEN))
|
|
{
|
|
// also add the bigfile name in the map to retrieve the full path of a .bnp when we want modification date of the bnp for example
|
|
insertFileInMap (CFile::getFilename (sBigFilename), sBigFilename, false, CFile::getExtension(sBigFilename));
|
|
|
|
// parse the big file to add file in the map
|
|
uint32 nFileSize=CFile::getFileSize (Handle);
|
|
//nlfseek64 (Handle, 0, SEEK_END);
|
|
//uint32 nFileSize = ftell (Handle);
|
|
nlfseek64 (Handle, nFileSize-4, SEEK_SET);
|
|
uint32 nOffsetFromBeginning;
|
|
if (fread (&nOffsetFromBeginning, sizeof(uint32), 1, Handle) != 1)
|
|
{
|
|
fclose(Handle);
|
|
return;
|
|
}
|
|
|
|
#ifdef NL_BIG_ENDIAN
|
|
NLMISC_BSWAP32(nOffsetFromBeginning);
|
|
#endif
|
|
|
|
nlfseek64 (Handle, nOffsetFromBeginning, SEEK_SET);
|
|
uint32 nNbFile;
|
|
if (fread (&nNbFile, sizeof(uint32), 1, Handle) != 1)
|
|
{
|
|
fclose(Handle);
|
|
return;
|
|
}
|
|
|
|
#ifdef NL_BIG_ENDIAN
|
|
NLMISC_BSWAP32(nNbFile);
|
|
#endif
|
|
|
|
for (uint32 i = 0; i < nNbFile; ++i)
|
|
{
|
|
// Progress bar
|
|
if (progressCallBack)
|
|
{
|
|
progressCallBack->progress ((float)i/(float)nNbFile);
|
|
progressCallBack->pushCropedValues ((float)i/(float)nNbFile, (float)(i+1)/(float)nNbFile);
|
|
}
|
|
|
|
char FileName[256];
|
|
uint8 nStringSize;
|
|
if (fread (&nStringSize, 1, 1, Handle) != 1)
|
|
{
|
|
fclose(Handle);
|
|
return;
|
|
}
|
|
if (fread (FileName, 1, nStringSize, Handle) != nStringSize)
|
|
{
|
|
fclose(Handle);
|
|
return;
|
|
}
|
|
FileName[nStringSize] = 0;
|
|
uint32 nFileSize2;
|
|
if (fread (&nFileSize2, sizeof(uint32), 1, Handle) != 1)
|
|
{
|
|
fclose(Handle);
|
|
return;
|
|
}
|
|
|
|
#ifdef NL_BIG_ENDIAN
|
|
NLMISC_BSWAP32(nFileSize2);
|
|
#endif
|
|
|
|
uint32 nFilePos;
|
|
if (fread (&nFilePos, sizeof(uint32), 1, Handle) != 1)
|
|
{
|
|
fclose(Handle);
|
|
return;
|
|
}
|
|
|
|
#ifdef NL_BIG_ENDIAN
|
|
NLMISC_BSWAP32(nFilePos);
|
|
#endif
|
|
|
|
string sTmp = toLower(string(FileName));
|
|
if (sTmp.empty())
|
|
{
|
|
nlwarning ("PATH: CPath::addSearchBigFile(%s, %d, %d): can't add empty file, skip it", sBigFilename.c_str(), recurse, alternative);
|
|
continue;
|
|
}
|
|
string bigfilenamealone = CFile::getFilename (sBigFilename);
|
|
string filenamewoext = CFile::getFilenameWithoutExtension (sTmp);
|
|
string ext = toLower(CFile::getExtension(sTmp));
|
|
|
|
insertFileInMap (sTmp, bigfilenamealone + "@" + sTmp, false, ext);
|
|
|
|
for (uint j = 0; j < _Extensions.size (); j++)
|
|
{
|
|
if (_Extensions[j].first == ext)
|
|
{
|
|
// need to remap
|
|
insertFileInMap (filenamewoext+"."+_Extensions[j].second,
|
|
bigfilenamealone + "@" + sTmp,
|
|
true,
|
|
_Extensions[j].first);
|
|
}
|
|
}
|
|
|
|
// Progress bar
|
|
if (progressCallBack)
|
|
{
|
|
progressCallBack->popCropedValues ();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nlwarning ("PATH: CPath::addSearchBigFile(%s, %d, %d): can't add the big file", sBigFilename.c_str(), recurse, alternative);
|
|
}
|
|
|
|
fclose (Handle);
|
|
}
|
|
|
|
// WARNING : recurse is not used
|
|
void CPath::addSearchXmlpackFile (const string &sXmlpackFilename, bool recurse, bool alternative, NLMISC::IProgressCallback *progressCallBack)
|
|
{
|
|
getInstance()->_FileContainer.addSearchXmlpackFile(sXmlpackFilename, recurse, alternative, progressCallBack);
|
|
}
|
|
|
|
void CFileContainer::addSearchXmlpackFile (const string &sXmlpackFilename, bool recurse, bool alternative, NLMISC::IProgressCallback *progressCallBack)
|
|
{
|
|
// Check if filename is not empty
|
|
if (sXmlpackFilename.empty())
|
|
{
|
|
nlwarning ("PATH: CPath::addSearchXmlpackFile(%s, %d, %d): can't add empty file, skip it", sXmlpackFilename.c_str(), recurse, alternative);
|
|
return;
|
|
}
|
|
// Check if the file exists
|
|
if (!CFile::isExists (sXmlpackFilename))
|
|
{
|
|
nlwarning ("PATH: CPath::addSearchXmlpackFile(%s, %d, %d): '%s' is not found, skip it", sXmlpackFilename.c_str(), recurse, alternative, sXmlpackFilename.c_str());
|
|
return;
|
|
}
|
|
// Check if it s a file
|
|
if (CFile::isDirectory (sXmlpackFilename))
|
|
{
|
|
nlwarning ("PATH: CPath::addSearchXmlpackFile(%s, %d, %d): '%s' is not a file, skip it", sXmlpackFilename.c_str(), recurse, alternative, sXmlpackFilename.c_str());
|
|
return;
|
|
}
|
|
// Open and read the xmlpack file header
|
|
|
|
FILE *Handle = fopen (sXmlpackFilename.c_str(), "rb");
|
|
if (Handle == NULL)
|
|
{
|
|
nlwarning ("PATH: CPath::addSearchXmlpackFile(%s, %d, %d): can't open file, skip it", sXmlpackFilename.c_str(), recurse, alternative);
|
|
return;
|
|
}
|
|
|
|
// add the link with the CXMLPack singleton
|
|
if (CXMLPack::getInstance().add (sXmlpackFilename))
|
|
{
|
|
// also add the xmlpack file name in the map to retrieve the full path of a .xml_pack when we want modification date of the xml_pack for example
|
|
insertFileInMap (sXmlpackFilename, sXmlpackFilename, false, CFile::getExtension(sXmlpackFilename));
|
|
|
|
|
|
vector<string> filenames;
|
|
CXMLPack::getInstance().list(sXmlpackFilename, filenames);
|
|
|
|
for (uint i=0; i<filenames.size(); ++i)
|
|
{
|
|
// Progress bar
|
|
if (progressCallBack)
|
|
{
|
|
progressCallBack->progress ((float)i/(float)filenames.size());
|
|
progressCallBack->pushCropedValues ((float)i/(float)filenames.size(), (float)(i+1)/(float)filenames.size());
|
|
}
|
|
|
|
string packfilenamealone = sXmlpackFilename;
|
|
string filenamewoext = CFile::getFilenameWithoutExtension (filenames[i]);
|
|
string ext = toLower(CFile::getExtension(filenames[i]));
|
|
|
|
insertFileInMap (filenames[i], packfilenamealone + "@@" + filenames[i], false, ext);
|
|
|
|
for (uint j = 0; j < _Extensions.size (); j++)
|
|
{
|
|
if (_Extensions[j].first == ext)
|
|
{
|
|
// need to remap
|
|
insertFileInMap (filenamewoext+"."+_Extensions[j].second,
|
|
packfilenamealone + "@@" + filenames[i],
|
|
true,
|
|
_Extensions[j].first);
|
|
}
|
|
}
|
|
|
|
// Progress bar
|
|
if (progressCallBack)
|
|
{
|
|
progressCallBack->popCropedValues ();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nlwarning ("PATH: CPath::addSearchXmlpackFile(%s, %d, %d): can't add the xml pack file", sXmlpackFilename.c_str(), recurse, alternative);
|
|
}
|
|
|
|
fclose (Handle);
|
|
}
|
|
|
|
void CPath::addIgnoredDoubleFile(const std::string &ignoredFile)
|
|
{
|
|
getInstance()->_FileContainer.addIgnoredDoubleFile(ignoredFile);
|
|
}
|
|
|
|
void CFileContainer::addIgnoredDoubleFile(const std::string &ignoredFile)
|
|
{
|
|
IgnoredFiles.push_back(ignoredFile);
|
|
}
|
|
|
|
void CFileContainer::insertFileInMap (const string &filename, const string &filepath, bool remap, const string &extension)
|
|
{
|
|
nlassert(!_MemoryCompressed);
|
|
// find if the file already exist
|
|
TFiles::iterator it = _Files.find (toLower(filename));
|
|
if (it != _Files.end ())
|
|
{
|
|
string path = SSMpath.get((*it).second.idPath);
|
|
if (path.find("@") != string::npos && filepath.find("@") == string::npos)
|
|
{
|
|
// if there's a file in a big file and a file in a path, the file in path wins
|
|
// replace with the new one
|
|
nlinfo ("PATH: CPath::insertFileInMap(%s, %s, %d, %s): already inserted from '%s' but special case so override it", filename.c_str(), filepath.c_str(), remap, extension.c_str(), path.c_str());
|
|
string sTmp = filepath.substr(0,filepath.rfind('/')+1);
|
|
(*it).second.idPath = SSMpath.add(sTmp);
|
|
(*it).second.Remapped = remap;
|
|
(*it).second.idExt = SSMext.add(extension);
|
|
(*it).second.Name = filename;
|
|
}
|
|
else
|
|
{
|
|
for(uint i = 0; i < IgnoredFiles.size(); i++)
|
|
{
|
|
// if we don't want to display a warning, skip it
|
|
if(filename == IgnoredFiles[i])
|
|
return;
|
|
}
|
|
// if the path is the same, don't warn
|
|
string path2 = SSMpath.get((*it).second.idPath);
|
|
string sPathOnly;
|
|
if(filepath.rfind("@@") != string::npos)
|
|
sPathOnly = filepath.substr(0,filepath.rfind("@@")+2);
|
|
else if(filepath.rfind('@') != string::npos)
|
|
sPathOnly = filepath.substr(0,filepath.rfind('@')+1);
|
|
else
|
|
sPathOnly = CFile::getPath(filepath);
|
|
|
|
if (path2 == sPathOnly)
|
|
return;
|
|
nlwarning ("PATH: CPath::insertFileInMap(%s, %s, %d, %s): already inserted from '%s', skip it",
|
|
filename.c_str(),
|
|
filepath.c_str(),
|
|
remap,
|
|
extension.c_str(),
|
|
path2.c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CFileEntry fe;
|
|
fe.idExt = SSMext.add(extension);
|
|
fe.Remapped = remap;
|
|
string sTmp;
|
|
if (filepath.find("@") == string::npos)
|
|
sTmp = filepath.substr(0,filepath.rfind('/')+1);
|
|
else if (filepath.find("@@") != string::npos)
|
|
sTmp = filepath.substr(0,filepath.rfind("@@")+2);
|
|
else
|
|
sTmp = filepath.substr(0,filepath.rfind('@')+1);
|
|
|
|
fe.idPath = SSMpath.add(sTmp);
|
|
fe.Name = filename;
|
|
|
|
_Files.insert (make_pair(toLower(filename), fe));
|
|
NL_DISPLAY_PATH("PATH: CPath::insertFileInMap(%s, %s, %d, %s): added", toLower(filename).c_str(), filepath.c_str(), remap, toLower(extension).c_str());
|
|
}
|
|
}
|
|
|
|
void CPath::display ()
|
|
{
|
|
getInstance()->_FileContainer.display();
|
|
}
|
|
|
|
void CFileContainer::display ()
|
|
{
|
|
nlinfo ("PATH: Contents of the map:");
|
|
nlinfo ("PATH: %-25s %-5s %-5s %s", "filename", "ext", "remap", "full path");
|
|
nlinfo ("PATH: ----------------------------------------------------");
|
|
if (_MemoryCompressed)
|
|
{
|
|
for (uint i = 0; i < _MCFiles.size(); ++i)
|
|
{
|
|
const CMCFileEntry &fe = _MCFiles[i];
|
|
string ext = SSMext.get(fe.idExt);
|
|
string path = SSMpath.get(fe.idPath);
|
|
nlinfo ("PATH: %-25s %-5s %-5d %s", fe.Name, ext.c_str(), fe.Remapped, path.c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (TFiles::iterator it = _Files.begin(); it != _Files.end (); it++)
|
|
{
|
|
string ext = SSMext.get((*it).second.idExt);
|
|
string path = SSMpath.get((*it).second.idPath);
|
|
nlinfo ("PATH: %-25s %-5s %-5d %s", (*it).first.c_str(), ext.c_str(), (*it).second.Remapped, path.c_str());
|
|
}
|
|
}
|
|
nlinfo ("PATH: ");
|
|
nlinfo ("PATH: Contents of the alternative directory:");
|
|
for (uint i = 0; i < _AlternativePaths.size(); i++)
|
|
{
|
|
nlinfo ("PATH: '%s'", _AlternativePaths[i].c_str ());
|
|
}
|
|
nlinfo ("PATH: ");
|
|
nlinfo ("PATH: Contents of the remapped entension table:");
|
|
for (uint j = 0; j < _Extensions.size(); j++)
|
|
{
|
|
nlinfo ("PATH: '%s' -> '%s'", _Extensions[j].first.c_str (), _Extensions[j].second.c_str ());
|
|
}
|
|
nlinfo ("PATH: End of display");
|
|
}
|
|
|
|
void CPath::removeBigFiles(const std::vector<std::string> &bnpFilenames)
|
|
{
|
|
getInstance()->_FileContainer.removeBigFiles(bnpFilenames);
|
|
}
|
|
|
|
void CFileContainer::removeBigFiles(const std::vector<std::string> &bnpFilenames)
|
|
{
|
|
nlassert(!isMemoryCompressed());
|
|
CHashSet<TSStringId> bnpStrIds;
|
|
TFiles::iterator fileIt, fileCurrIt;
|
|
for (uint k = 0; k < bnpFilenames.size(); ++k)
|
|
{
|
|
std::string completeBNPName = toLower(bnpFilenames[k]) + "@";
|
|
if (SSMpath.isAdded(completeBNPName))
|
|
{
|
|
bnpStrIds.insert(SSMpath.add(completeBNPName));
|
|
}
|
|
CBigFile::getInstance().remove(bnpFilenames[k]);
|
|
fileIt = _Files.find(toLower(bnpFilenames[k]));
|
|
if (fileIt != _Files.end())
|
|
{
|
|
_Files.erase(fileIt);
|
|
}
|
|
}
|
|
if (bnpStrIds.empty()) return;
|
|
// remove remapped files
|
|
std::map<std::string, std::string>::iterator remapIt, remapCurrIt;
|
|
for(remapIt = _RemappedFiles.begin(); remapIt != _RemappedFiles.end();)
|
|
{
|
|
remapCurrIt = remapIt;
|
|
++ remapIt;
|
|
const std::string &filename = remapCurrIt->second;
|
|
fileIt = _Files.find(filename);
|
|
if (fileIt != _Files.end())
|
|
{
|
|
if (bnpStrIds.count(fileIt->second.idPath))
|
|
{
|
|
_Files.erase(fileIt);
|
|
_RemappedFiles.erase(remapCurrIt);
|
|
}
|
|
}
|
|
}
|
|
// remove file entries
|
|
for(fileIt = _Files.begin(); fileIt != _Files.end();)
|
|
{
|
|
fileCurrIt = fileIt;
|
|
++ fileIt;
|
|
if (bnpStrIds.count(fileCurrIt->second.idPath))
|
|
{
|
|
_Files.erase(fileCurrIt);
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
void CPath::memoryCompress()
|
|
{
|
|
getInstance()->_FileContainer.memoryCompress();
|
|
}
|
|
|
|
void CFileContainer::memoryCompress()
|
|
{
|
|
|
|
SSMext.memoryCompress();
|
|
SSMpath.memoryCompress();
|
|
uint nDbg = (uint)_Files.size();
|
|
uint nDbg2 = SSMext.getCount();
|
|
uint nDbg3 = SSMpath.getCount();
|
|
nlinfo ("PATH: Number of file: %d, extension: %d, path: %d", nDbg, nDbg2, nDbg3);
|
|
|
|
// Convert from _Files to _MCFiles
|
|
uint nSize = 0, nNb = 0;
|
|
TFiles::iterator it = _Files.begin();
|
|
while (it != _Files.end())
|
|
{
|
|
string sTmp = SSMpath.get(it->second.idPath);
|
|
if ((sTmp.find("@@") == string::npos) && (sTmp.find('@') != string::npos) && !it->second.Remapped)
|
|
{
|
|
// This is a file included in a bigfile (so the name is in the bigfile manager)
|
|
}
|
|
else
|
|
{
|
|
nSize += (uint)it->second.Name.size() + 1;
|
|
}
|
|
nNb++;
|
|
it++;
|
|
}
|
|
|
|
_AllFileNames = new char[nSize];
|
|
memset(_AllFileNames, 0, nSize);
|
|
_MCFiles.resize(nNb);
|
|
|
|
it = _Files.begin();
|
|
nSize = 0;
|
|
nNb = 0;
|
|
while (it != _Files.end())
|
|
{
|
|
CFileEntry &rFE = it->second;
|
|
string sTmp = SSMpath.get(rFE.idPath);
|
|
if (sTmp.find("@") == string::npos || sTmp.find("@@") != string::npos || rFE.Remapped)
|
|
{
|
|
strcpy(_AllFileNames+nSize, rFE.Name.c_str());
|
|
_MCFiles[nNb].Name = _AllFileNames+nSize;
|
|
nSize += (uint)rFE.Name.size() + 1;
|
|
}
|
|
else
|
|
{
|
|
// This is a file included in a bigfile (so the name is in the bigfile manager)
|
|
sTmp = sTmp.substr(0, sTmp.size()-1);
|
|
_MCFiles[nNb].Name = CBigFile::getInstance().getFileNamePtr(rFE.Name, sTmp);
|
|
if (_MCFiles[nNb].Name == NULL)
|
|
{
|
|
nlerror("memoryCompress: failed to find named file in big file: %s",SSMpath.get(rFE.idPath));
|
|
}
|
|
}
|
|
|
|
_MCFiles[nNb].idExt = rFE.idExt;
|
|
_MCFiles[nNb].idPath = rFE.idPath;
|
|
_MCFiles[nNb].Remapped = rFE.Remapped;
|
|
|
|
nNb++;
|
|
it++;
|
|
}
|
|
|
|
contReset(_Files);
|
|
_MemoryCompressed = true;
|
|
}
|
|
|
|
void CPath::memoryUncompress()
|
|
{
|
|
getInstance()->_FileContainer.memoryUncompress();
|
|
}
|
|
|
|
void CFileContainer::memoryUncompress()
|
|
{
|
|
SSMext.memoryUncompress();
|
|
SSMpath.memoryUncompress();
|
|
for(std::vector<CMCFileEntry>::iterator it = _MCFiles.begin(); it != _MCFiles.end(); ++it)
|
|
{
|
|
CFileEntry fe;
|
|
fe.Name = it->Name;
|
|
fe.idExt = it->idExt;
|
|
fe.idPath = it->idPath;
|
|
fe.Remapped = it->Remapped;
|
|
|
|
_Files[toLower(CFile::getFilename(fe.Name))] = fe;
|
|
}
|
|
contReset(_MCFiles);
|
|
_MemoryCompressed = false;
|
|
}
|
|
|
|
std::string CPath::getWindowsDirectory()
|
|
{
|
|
return getInstance()->_FileContainer.getWindowsDirectory();
|
|
}
|
|
|
|
std::string CFileContainer::getWindowsDirectory()
|
|
{
|
|
#ifndef NL_OS_WINDOWS
|
|
nlwarning("not a ms windows platform");
|
|
return "";
|
|
#else
|
|
char winDir[MAX_PATH];
|
|
UINT numChar = ::GetWindowsDirectory(winDir, MAX_PATH);
|
|
if (numChar > MAX_PATH || numChar == 0)
|
|
{
|
|
nlwarning("Couldn't retrieve windows directory");
|
|
return "";
|
|
}
|
|
return CPath::standardizePath(winDir);
|
|
#endif
|
|
}
|
|
|
|
std::string CPath::getApplicationDirectory(const std::string &appName)
|
|
{
|
|
return getInstance()->_FileContainer.getApplicationDirectory(appName);
|
|
}
|
|
|
|
std::string CFileContainer::getApplicationDirectory(const std::string &appName)
|
|
{
|
|
static std::string appPath;
|
|
if (appPath.empty())
|
|
{
|
|
#ifdef NL_OS_WINDOWS
|
|
wchar_t buffer[MAX_PATH];
|
|
SHGetSpecialFolderPathW(NULL, buffer, CSIDL_APPDATA, TRUE);
|
|
appPath = CPath::standardizePath(ucstring((ucchar*)buffer).toUtf8());
|
|
#elif defined(NL_OS_MAC)
|
|
appPath = CPath::standardizePath(getenv("HOME"));
|
|
appPath += "/Library/Application Support/";
|
|
#else
|
|
appPath = CPath::standardizePath(getenv("HOME"));
|
|
#endif
|
|
}
|
|
|
|
std::string path = appPath;
|
|
#ifdef NL_OS_WINDOWS
|
|
if (!appName.empty())
|
|
path = CPath::standardizePath(path + appName);
|
|
#elif defined(NL_OS_MAC)
|
|
path = CPath::standardizePath(path + appName);
|
|
#else
|
|
if (!appName.empty())
|
|
path = CPath::standardizePath(path + "." + toLower(appName));
|
|
#endif
|
|
|
|
return path;
|
|
}
|
|
|
|
std::string CPath::getTemporaryDirectory()
|
|
{
|
|
return getInstance()->_FileContainer.getTemporaryDirectory();
|
|
}
|
|
|
|
std::string CFileContainer::getTemporaryDirectory()
|
|
{
|
|
static std::string path;
|
|
if (path.empty())
|
|
{
|
|
const char *temp = getenv("TEMP");
|
|
const char *tmp = getenv("TMP");
|
|
|
|
std::string tempDir;
|
|
|
|
if (temp)
|
|
tempDir = temp;
|
|
|
|
if (tempDir.empty() && tmp)
|
|
tempDir = tmp;
|
|
|
|
#ifdef NL_OS_UNIX
|
|
if (tempDir.empty())
|
|
tempDir = "/tmp";
|
|
#else
|
|
if (tempDir.empty())
|
|
tempDir = ".";
|
|
#endif
|
|
|
|
path = CPath::standardizePath(tempDir);
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int CFile::getLastSeparator (const string &filename)
|
|
{
|
|
string::size_type pos = filename.find_last_of ('/');
|
|
if (pos == string::npos)
|
|
{
|
|
pos = filename.find_last_of ('\\');
|
|
if (pos == string::npos)
|
|
{
|
|
pos = filename.find_last_of ('@');
|
|
}
|
|
}
|
|
return (int)pos;
|
|
}
|
|
|
|
string CFile::getFilename (const string &filename)
|
|
{
|
|
string::size_type pos = CFile::getLastSeparator(filename);
|
|
if (pos != string::npos)
|
|
return filename.substr (pos + 1);
|
|
else
|
|
return filename;
|
|
}
|
|
|
|
string CFile::getFilenameWithoutExtension (const string &filename)
|
|
{
|
|
string filename2 = getFilename (filename);
|
|
string::size_type pos = filename2.find_last_of ('.');
|
|
if (pos == string::npos)
|
|
return filename2;
|
|
else
|
|
return filename2.substr (0, pos);
|
|
}
|
|
|
|
string CFile::getExtension (const string &filename)
|
|
{
|
|
string::size_type pos = filename.find_last_of ('.');
|
|
if (pos == string::npos)
|
|
return "";
|
|
else
|
|
return filename.substr (pos + 1);
|
|
}
|
|
|
|
string CFile::getPath (const string &filename)
|
|
{
|
|
string::size_type pos = CFile::getLastSeparator(filename);
|
|
if (pos != string::npos)
|
|
return filename.substr (0, pos + 1);
|
|
else
|
|
return "";
|
|
}
|
|
|
|
bool CFile::isDirectory (const string &filename)
|
|
{
|
|
#ifdef NL_OS_WINDOWS
|
|
DWORD res = GetFileAttributes(filename.c_str());
|
|
if (res == INVALID_FILE_ATTRIBUTES)
|
|
{
|
|
// nlwarning ("PATH: '%s' is not a valid file or directory name", filename.c_str ());
|
|
return false;
|
|
}
|
|
return (res & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
|
#else // NL_OS_WINDOWS
|
|
struct stat buf;
|
|
int res = stat (filename.c_str (), &buf);
|
|
if (res == -1)
|
|
{
|
|
// There was previously a warning message here but that was incorrect as it is defined that isDirectory returns false if the directory doesn't exist
|
|
// nlwarning ("PATH: can't stat '%s' error %d '%s'", filename.c_str(), errno, strerror(errno));
|
|
return false;
|
|
}
|
|
return (buf.st_mode & S_IFDIR) != 0;
|
|
#endif // NL_OS_WINDOWS
|
|
}
|
|
|
|
bool CFile::isExists (const string &filename)
|
|
{
|
|
#ifdef NL_OS_WINDOWS
|
|
return (GetFileAttributes(filename.c_str()) != INVALID_FILE_ATTRIBUTES);
|
|
#else // NL_OS_WINDOWS
|
|
struct stat buf;
|
|
return stat (filename.c_str (), &buf) == 0;
|
|
#endif // NL_OS_WINDOWS
|
|
}
|
|
|
|
bool CFile::createEmptyFile (const std::string& filename)
|
|
{
|
|
FILE *file = fopen (filename.c_str(), "wb");
|
|
|
|
if (file)
|
|
{
|
|
fclose (file);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CFile::fileExists (const string& filename)
|
|
{
|
|
//H_AUTO(FileExists);
|
|
return ! ! fstream( filename.c_str(), ios::in );
|
|
}
|
|
|
|
|
|
string CFile::findNewFile (const string &filename)
|
|
{
|
|
string::size_type pos = filename.find_last_of ('.');
|
|
if (pos == string::npos)
|
|
return filename;
|
|
|
|
string start = filename.substr (0, pos);
|
|
string end = filename.substr (pos);
|
|
|
|
uint num = 0;
|
|
char numchar[4];
|
|
string npath;
|
|
do
|
|
{
|
|
npath = start;
|
|
smprintf(numchar,4,"%03d",num++);
|
|
npath += numchar;
|
|
npath += end;
|
|
if (!CFile::fileExists(npath)) break;
|
|
}
|
|
while (num<999);
|
|
return npath;
|
|
}
|
|
|
|
// \warning doesn't work with big file
|
|
uint32 CFile::getFileSize (const std::string &filename)
|
|
{
|
|
if (filename.find("@@") != string::npos)
|
|
{
|
|
uint32 fs = 0, bfo;
|
|
bool c, d;
|
|
CXMLPack::getInstance().getFile (filename, fs, bfo, c, d);
|
|
return fs;
|
|
}
|
|
else if (filename.find('@') != string::npos)
|
|
{
|
|
uint32 fs = 0, bfo;
|
|
bool c, d;
|
|
CBigFile::getInstance().getFile (filename, fs, bfo, c, d);
|
|
return fs;
|
|
}
|
|
else
|
|
{
|
|
#if defined (NL_OS_WINDOWS)
|
|
struct _stat buf;
|
|
int result = _stat (filename.c_str (), &buf);
|
|
#elif defined (NL_OS_UNIX)
|
|
struct stat buf;
|
|
int result = stat (filename.c_str (), &buf);
|
|
#endif
|
|
if (result != 0) return 0;
|
|
else return buf.st_size;
|
|
}
|
|
}
|
|
|
|
uint32 CFile::getFileSize (FILE *f)
|
|
{
|
|
#if defined (NL_OS_WINDOWS)
|
|
struct _stat buf;
|
|
int result = _fstat (fileno(f), &buf);
|
|
#elif defined (NL_OS_UNIX)
|
|
struct stat buf;
|
|
int result = fstat (fileno(f), &buf);
|
|
#endif
|
|
if (result != 0) return 0;
|
|
else return buf.st_size;
|
|
}
|
|
|
|
uint32 CFile::getFileModificationDate(const std::string &filename)
|
|
{
|
|
string::size_type pos;
|
|
string fn;
|
|
if ((pos=filename.find("@@")) != string::npos)
|
|
{
|
|
fn = filename.substr (0, pos);
|
|
}
|
|
else if ((pos=filename.find('@')) != string::npos)
|
|
{
|
|
fn = CPath::lookup(filename.substr (0, pos));
|
|
}
|
|
else
|
|
{
|
|
fn = filename;
|
|
}
|
|
|
|
#if defined (NL_OS_WINDOWS)
|
|
// struct _stat buf;
|
|
// int result = _stat (fn.c_str (), &buf);
|
|
// Changed 06-06-2007 : boris : _stat have an incoherent and hard to reproduce
|
|
// on windows : if the system clock is adjusted according to daylight saving
|
|
// time, the file date reported by _stat may (not always!) be adjusted by 3600s
|
|
// This is a bad behavior because file time should always be reported as UTC time value
|
|
|
|
// Use the WIN32 API to read the file times in UTC
|
|
|
|
// create a file handle (this does not open the file)
|
|
HANDLE h = CreateFile(fn.c_str(), 0, 0, NULL, OPEN_EXISTING, 0, 0);
|
|
if (h == INVALID_HANDLE_VALUE)
|
|
{
|
|
nlwarning("Can't get modification date on file '%s' : %s", fn.c_str(), NLMISC::formatErrorMessage(NLMISC::getLastError()).c_str());
|
|
return 0;
|
|
}
|
|
FILETIME creationTime;
|
|
FILETIME accesstime;
|
|
FILETIME modTime;
|
|
|
|
// get the files times
|
|
BOOL res = GetFileTime(h, &creationTime, &accesstime, &modTime);
|
|
if (res == 0)
|
|
{
|
|
nlwarning("Can't get modification date on file '%s' : %s", fn.c_str(), NLMISC::formatErrorMessage(NLMISC::getLastError()).c_str());
|
|
CloseHandle(h);
|
|
return 0;
|
|
}
|
|
// close the handle
|
|
CloseHandle(h);
|
|
|
|
// win32 file times are in 10th of micro sec (100ns resolution), starting at jan 1, 1601
|
|
// hey Mr Gates, why 1601 ?
|
|
|
|
// first, convert it into second since jan1, 1601
|
|
uint64 t = modTime.dwLowDateTime | (uint64(modTime.dwHighDateTime)<<32);
|
|
|
|
// adjust time base to unix epoch base
|
|
t -= CTime::getWindowsToUnixBaseTimeOffset();
|
|
|
|
// convert the resulting time into seconds
|
|
t /= 10; // microsec
|
|
t /= 1000; // millisec
|
|
t /= 1000; // sec
|
|
|
|
// return the resulting time
|
|
return uint32(t);
|
|
|
|
#elif defined (NL_OS_UNIX)
|
|
struct stat buf;
|
|
int result = stat (fn.c_str (), &buf);
|
|
if (result != 0)
|
|
{
|
|
nlwarning("Can't get modification date on file '%s' : %s", fn.c_str(), NLMISC::formatErrorMessage(NLMISC::getLastError()).c_str());
|
|
return 0;
|
|
}
|
|
else
|
|
return (uint32)buf.st_mtime;
|
|
#endif
|
|
|
|
}
|
|
|
|
bool CFile::setFileModificationDate(const std::string &filename, uint32 modTime)
|
|
{
|
|
string::size_type pos;
|
|
string fn;
|
|
if ((pos=filename.find('@')) != string::npos)
|
|
{
|
|
fn = CPath::lookup(filename.substr (0, pos));
|
|
}
|
|
else
|
|
{
|
|
fn = filename;
|
|
}
|
|
|
|
#if defined (NL_OS_WINDOWS)
|
|
|
|
// Use the WIN32 API to set the file times in UTC
|
|
|
|
// create a file handle (this does not open the file)
|
|
HANDLE h = CreateFile(fn.c_str(), GENERIC_WRITE|GENERIC_READ, FILE_SHARE_WRITE|FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
|
|
if (h == INVALID_HANDLE_VALUE)
|
|
{
|
|
nlwarning("Can't set modification date on file '%s' (error accessing file) : %s", fn.c_str(), NLMISC::formatErrorMessage(NLMISC::getLastError()).c_str());
|
|
return false;
|
|
}
|
|
FILETIME creationFileTime;
|
|
FILETIME accessFileTime;
|
|
FILETIME modFileTime;
|
|
|
|
// read the current the files times
|
|
if (GetFileTime(h, &creationFileTime, &accessFileTime, &modFileTime) == 0)
|
|
{
|
|
nlwarning("Can't set modification date on file '%s' : %s", fn.c_str(), formatErrorMessage(getLastError()).c_str());
|
|
CloseHandle(h);
|
|
return false;
|
|
}
|
|
|
|
// win32 file times are in 10th of micro sec (100ns resolution), starting at jan 1, 1601
|
|
// hey Mr Gates, why 1601 ?
|
|
|
|
// convert the unix time into a windows file time
|
|
uint64 t = modTime;
|
|
// convert to 10th of microsec
|
|
t *= 1000; // millisec
|
|
t *= 1000; // microsec
|
|
t *= 10; // 10th of micro sec (rez of windows file time is 100ns <=> 1/10 us
|
|
|
|
// apply the windows to unix base time offset
|
|
t += CTime::getWindowsToUnixBaseTimeOffset();
|
|
|
|
// update the windows modTime structure
|
|
modFileTime.dwLowDateTime = uint32(t & 0xffffffff);
|
|
modFileTime.dwHighDateTime = uint32(t >> 32);
|
|
|
|
// update the file time on disk
|
|
BOOL rez = SetFileTime(h, &creationFileTime, &accessFileTime, &modFileTime);
|
|
if (rez == 0)
|
|
{
|
|
nlwarning("Can't set modification date on file '%s': %s", fn.c_str(), formatErrorMessage(getLastError()).c_str());
|
|
|
|
CloseHandle(h);
|
|
return false;
|
|
}
|
|
|
|
// close the handle
|
|
CloseHandle(h);
|
|
|
|
return true;
|
|
|
|
#elif defined (NL_OS_UNIX)
|
|
// first, read the current time of the file
|
|
struct stat buf;
|
|
int result = stat (fn.c_str (), &buf);
|
|
if (result != 0)
|
|
return false;
|
|
|
|
// prepare the new time to apply
|
|
utimbuf tb;
|
|
tb.actime = buf.st_atime;
|
|
tb.modtime = modTime;
|
|
// set eh new time
|
|
int res = utime(fn.c_str(), &tb);
|
|
if (res == -1)
|
|
nlwarning("Can't set modification date on file '%s': %s", fn.c_str(), formatErrorMessage(getLastError()).c_str());
|
|
return res != -1;
|
|
#endif
|
|
|
|
}
|
|
|
|
uint32 CFile::getFileCreationDate(const std::string &filename)
|
|
{
|
|
string::size_type pos;
|
|
string fn;
|
|
if ((pos=filename.find('@')) != string::npos)
|
|
{
|
|
fn = CPath::lookup(filename.substr (0, pos));
|
|
}
|
|
else
|
|
{
|
|
fn = filename;
|
|
}
|
|
|
|
#if defined (NL_OS_WINDOWS)
|
|
struct _stat buf;
|
|
int result = _stat (fn.c_str (), &buf);
|
|
#elif defined (NL_OS_UNIX)
|
|
struct stat buf;
|
|
int result = stat (fn.c_str (), &buf);
|
|
#endif
|
|
|
|
if (result != 0) return 0;
|
|
else return (uint32)buf.st_ctime;
|
|
}
|
|
|
|
struct CFileEntry
|
|
{
|
|
CFileEntry (const string &filename, void (*callback)(const string &filename)) : FileName (filename), Callback (callback)
|
|
{
|
|
LastModified = CFile::getFileModificationDate(filename);
|
|
}
|
|
string FileName;
|
|
void (*Callback)(const string &filename);
|
|
uint32 LastModified;
|
|
};
|
|
|
|
static vector <CFileEntry> FileToCheck;
|
|
|
|
void CFile::removeFileChangeCallback (const std::string &filename)
|
|
{
|
|
string fn = CPath::lookup(filename, false, false);
|
|
if (fn.empty())
|
|
{
|
|
fn = filename;
|
|
}
|
|
for (uint i = 0; i < FileToCheck.size(); i++)
|
|
{
|
|
if(FileToCheck[i].FileName == fn)
|
|
{
|
|
nlinfo ("PATH: CFile::removeFileChangeCallback: '%s' is removed from checked files modification", fn.c_str());
|
|
FileToCheck.erase(FileToCheck.begin()+i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CFile::addFileChangeCallback (const std::string &filename, void (*cb)(const string &filename))
|
|
{
|
|
string fn = CPath::lookup(filename, false, false);
|
|
if (fn.empty())
|
|
{
|
|
fn = filename;
|
|
}
|
|
nlinfo ("PATH: CFile::addFileChangeCallback: I'll check the modification date for this file '%s'", fn.c_str());
|
|
FileToCheck.push_back(CFileEntry(fn, cb));
|
|
}
|
|
|
|
void CFile::checkFileChange (TTime frequency)
|
|
{
|
|
static TTime lastChecked = CTime::getLocalTime();
|
|
|
|
if (CTime::getLocalTime() > lastChecked + frequency)
|
|
{
|
|
for (uint i = 0; i < FileToCheck.size(); i++)
|
|
{
|
|
if(CFile::getFileModificationDate(FileToCheck[i].FileName) != FileToCheck[i].LastModified)
|
|
{
|
|
// need to reload it
|
|
if(FileToCheck[i].Callback != NULL)
|
|
FileToCheck[i].Callback(FileToCheck[i].FileName);
|
|
|
|
FileToCheck[i].LastModified = CFile::getFileModificationDate(FileToCheck[i].FileName);
|
|
}
|
|
}
|
|
|
|
lastChecked = CTime::getLocalTime();
|
|
}
|
|
}
|
|
|
|
static bool CopyMoveFile(const std::string &dest, const std::string &src, bool copyFile, bool failIfExists = false, IProgressCallback *progress = NULL)
|
|
{
|
|
if (dest.empty() || src.empty()) return false;
|
|
std::string sdest = CPath::standardizePath(dest,false);
|
|
std::string ssrc = CPath::standardizePath(src,false);
|
|
|
|
// return copyFile ? CopyFile(dossrc.c_str(), dosdest.c_str(), failIfExists) != FALSE
|
|
// : MoveFile(dossrc.c_str(), dosdest.c_str()) != FALSE;
|
|
|
|
if (progress) progress->progress(0.f);
|
|
if(copyFile)
|
|
{
|
|
uint32 totalSize = 0;
|
|
uint32 readSize = 0;
|
|
if (progress)
|
|
{
|
|
totalSize = CFile::getFileSize(ssrc);
|
|
}
|
|
FILE *fp1 = fopen(ssrc.c_str(), "rb");
|
|
if (fp1 == NULL)
|
|
{
|
|
nlwarning ("PATH: CopyMoveFile error: can't fopen in read mode '%s'", ssrc.c_str());
|
|
return false;
|
|
}
|
|
FILE *fp2 = fopen(sdest.c_str(), "wb");
|
|
if (fp2 == NULL)
|
|
{
|
|
nlwarning ("PATH: CopyMoveFile error: can't fopen in read write mode '%s'", sdest.c_str());
|
|
return false;
|
|
}
|
|
static char buffer [1000];
|
|
size_t s;
|
|
|
|
s = fread(buffer, 1, sizeof(buffer), fp1);
|
|
while (s != 0)
|
|
{
|
|
if (progress)
|
|
{
|
|
readSize += (uint32)s;
|
|
progress->progress((float) readSize / totalSize);
|
|
}
|
|
size_t ws = fwrite(buffer, s, 1, fp2);
|
|
if (ws != 1)
|
|
{
|
|
nlwarning("Error copying '%s' to '%s', trying to write %u bytes failed.",
|
|
ssrc.c_str(),
|
|
sdest.c_str(),
|
|
s);
|
|
fclose(fp1);
|
|
fclose(fp2);
|
|
nlwarning("Errno = %d", errno);
|
|
return false;
|
|
}
|
|
s = fread(buffer, 1, sizeof(buffer), fp1);
|
|
}
|
|
|
|
fclose(fp1);
|
|
fclose(fp2);
|
|
if (progress) progress->progress(1.f);
|
|
}
|
|
else
|
|
{
|
|
#ifdef NL_OS_WINDOWS
|
|
if (MoveFile(ssrc.c_str(), sdest.c_str()) == 0)
|
|
{
|
|
LPVOID lpMsgBuf;
|
|
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL,
|
|
GetLastError(),
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
|
|
(LPTSTR) &lpMsgBuf,
|
|
0,
|
|
NULL );
|
|
nlwarning ("PATH: CopyMoveFile error: can't link/move '%s' into '%s', error %u (%s)",
|
|
ssrc.c_str(),
|
|
sdest.c_str(),
|
|
GetLastError(),
|
|
lpMsgBuf);
|
|
|
|
LocalFree(lpMsgBuf);
|
|
return false;
|
|
}
|
|
#else
|
|
if (rename (ssrc.c_str(), sdest.c_str()) == -1)
|
|
{
|
|
nlwarning ("PATH: CopyMoveFile error: can't rename '%s' into '%s', error %u",
|
|
ssrc.c_str(),
|
|
sdest.c_str(),
|
|
errno);
|
|
return false;
|
|
}
|
|
#endif
|
|
}
|
|
if (progress) progress->progress(1.f);
|
|
return true;
|
|
}
|
|
|
|
bool CFile::copyFile(const std::string &dest, const std::string &src, bool failIfExists /*=false*/, IProgressCallback *progress)
|
|
{
|
|
return CopyMoveFile(dest, src, true, failIfExists, progress);
|
|
}
|
|
|
|
bool CFile::quickFileCompare(const std::string &fileName0, const std::string &fileName1)
|
|
{
|
|
// make sure the files both exist
|
|
if (!fileExists(fileName0.c_str()) || !fileExists(fileName1.c_str()))
|
|
return false;
|
|
|
|
// compare time stamps
|
|
if (getFileModificationDate(fileName0.c_str()) != getFileModificationDate(fileName1.c_str()))
|
|
return false;
|
|
|
|
// compare file sizes
|
|
if (getFileSize(fileName0.c_str()) != getFileSize(fileName1.c_str()))
|
|
return false;
|
|
|
|
// everything matched so return true
|
|
return true;
|
|
}
|
|
|
|
bool CFile::thoroughFileCompare(const std::string &fileName0, const std::string &fileName1,uint32 maxBufSize)
|
|
{
|
|
// make sure the files both exist
|
|
if (!fileExists(fileName0.c_str()) || !fileExists(fileName1.c_str()))
|
|
return false;
|
|
|
|
// setup the size variable from file length of first file
|
|
uint32 fileSize=getFileSize(fileName0.c_str());
|
|
|
|
// compare file sizes
|
|
if (fileSize != getFileSize(fileName1.c_str()))
|
|
return false;
|
|
|
|
// allocate a couple of data buffers for our 2 files
|
|
uint32 bufSize= maxBufSize/2;
|
|
nlassert(sint32(bufSize)>0);
|
|
std::vector<uint8> buf0(bufSize);
|
|
std::vector<uint8> buf1(bufSize);
|
|
|
|
// open the two files for input
|
|
CIFile file0(fileName0);
|
|
CIFile file1(fileName1);
|
|
|
|
for (uint32 i=0;i<fileSize;i+=bufSize)
|
|
{
|
|
// for the last block in the file reduce buf size to represent the amount of data left in file
|
|
if (i+bufSize>fileSize)
|
|
{
|
|
bufSize= fileSize-i;
|
|
buf0.resize(bufSize);
|
|
buf1.resize(bufSize);
|
|
}
|
|
|
|
// read in the next data block from disk
|
|
file0.serialBuffer(&buf0[0], bufSize);
|
|
file1.serialBuffer(&buf1[0], bufSize);
|
|
|
|
// compare the contents of the 2 data buffers
|
|
if (buf0!=buf1)
|
|
return false;
|
|
}
|
|
|
|
// everything matched so return true
|
|
return true;
|
|
}
|
|
|
|
bool CFile::moveFile(const char *dest,const char *src)
|
|
{
|
|
return CopyMoveFile(dest, src, false);
|
|
}
|
|
|
|
bool CFile::createDirectory(const std::string &filename)
|
|
{
|
|
#ifdef NL_OS_WINDOWS
|
|
return _mkdir(filename.c_str())==0;
|
|
#else
|
|
// Set full permissions....
|
|
return mkdir(filename.c_str(), 0xFFFF)==0;
|
|
#endif
|
|
}
|
|
|
|
bool CFile::createDirectoryTree(const std::string &filename)
|
|
{
|
|
bool lastResult=true;
|
|
uint32 i=0;
|
|
|
|
// skip dos drive name eg "a:"
|
|
if (filename.size()>1 && filename[1]==':')
|
|
i=2;
|
|
|
|
// iterate over the set of directories in the routine's argument
|
|
while (i<filename.size())
|
|
{
|
|
// skip passed leading slashes
|
|
for (;i<filename.size();++i)
|
|
if (filename[i]!='\\' && filename[i]!='/')
|
|
break;
|
|
|
|
// if the file name ended with a '/' then there's no extra directory to create
|
|
if (i==filename.size())
|
|
break;
|
|
|
|
// skip forwards to next slash
|
|
for (;i<filename.size();++i)
|
|
if (filename[i]=='\\' || filename[i]=='/')
|
|
break;
|
|
|
|
// try to create directory
|
|
std::string s= filename.substr(0,i);
|
|
lastResult= createDirectory(s);
|
|
}
|
|
|
|
return lastResult;
|
|
}
|
|
|
|
bool CPath::makePathRelative (const char *basePath, std::string &relativePath)
|
|
{
|
|
// Standard path with final slash
|
|
string tmp = standardizePath (basePath, true);
|
|
string src = standardizePath (relativePath, true);
|
|
string prefix;
|
|
|
|
for(;;)
|
|
{
|
|
// Compare with relativePath
|
|
if (strncmp (tmp.c_str (), src.c_str (), tmp.length ()) == 0)
|
|
{
|
|
// Truncate
|
|
uint size = (uint)tmp.length ();
|
|
|
|
// Same path ?
|
|
if (size == src.length ())
|
|
{
|
|
relativePath = ".";
|
|
return true;
|
|
}
|
|
|
|
relativePath = prefix+relativePath.substr (size, relativePath.length () - size);
|
|
return true;
|
|
}
|
|
|
|
// Too small ?
|
|
if (tmp.length ()<2)
|
|
break;
|
|
|
|
// Remove last directory
|
|
string::size_type lastPos = tmp.rfind ('/', tmp.length ()-2);
|
|
string::size_type previousPos = tmp.find ('/');
|
|
if ((lastPos == previousPos) || (lastPos == string::npos))
|
|
break;
|
|
|
|
// Troncate
|
|
tmp = tmp.substr (0, lastPos+1);
|
|
|
|
// New prefix
|
|
prefix += "../";
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CFile::setRWAccess(const std::string &filename)
|
|
{
|
|
#ifdef NL_OS_WINDOWS
|
|
// if the file exists and there's no write access
|
|
if (_access (filename.c_str(), 00) == 0 && _access (filename.c_str(), 06) == -1)
|
|
{
|
|
// try to set the read/write access
|
|
if (_chmod (filename.c_str(), _S_IREAD | _S_IWRITE) == -1)
|
|
{
|
|
if (INelContext::getInstance().getAlreadyCreateSharedAmongThreads())
|
|
{
|
|
nlwarning ("PATH: Can't set RW access to file '%s': %d %s", filename.c_str(), errno, strerror(errno));
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
#else
|
|
// if the file exists and there's no write access
|
|
if (access (filename.c_str(), F_OK) == 0)
|
|
{
|
|
// try to set the read/write access
|
|
if (chmod (filename.c_str(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH) == -1)
|
|
{
|
|
if (INelContext::getInstance().getAlreadyCreateSharedAmongThreads())
|
|
{
|
|
nlwarning ("PATH: Can't set RW access to file '%s': %d %s", filename.c_str(), errno, strerror(errno));
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (INelContext::getInstance().getAlreadyCreateSharedAmongThreads())
|
|
{
|
|
nlwarning("PATH: Can't access to file '%s'", filename.c_str());
|
|
}
|
|
// return false;
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
|
|
#ifdef NL_OS_WINDOWS
|
|
#define unlink _unlink
|
|
#endif
|
|
|
|
bool CFile::deleteFile(const std::string &filename)
|
|
{
|
|
setRWAccess(filename);
|
|
int res = unlink (filename.c_str());
|
|
if (res == -1)
|
|
{
|
|
if (INelContext::getInstance().getAlreadyCreateSharedAmongThreads())
|
|
{
|
|
nlwarning ("PATH: Can't delete file '%s': (errno %d) %s", filename.c_str(), errno, strerror(errno));
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#ifdef NL_OS_WINDOWS
|
|
#define rmdir _rmdir
|
|
#endif
|
|
|
|
bool CFile::deleteDirectory(const std::string &filename)
|
|
{
|
|
setRWAccess(filename);
|
|
int res = rmdir (filename.c_str());
|
|
if (res == -1)
|
|
{
|
|
nlwarning ("PATH: Can't delete directory '%s': (errno %d) %s", filename.c_str(), errno, strerror(errno));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void CFile::getTemporaryOutputFilename (const std::string &originalFilename, std::string &tempFilename)
|
|
{
|
|
uint i = 0;
|
|
do
|
|
tempFilename = originalFilename+".tmp"+toString (i++);
|
|
while (CFile::isExists(tempFilename));
|
|
}
|
|
|
|
} // NLMISC
|