Merge branch 'ryzom/ark-features' into main/gingo-test
[ryzomcore.git] / nel / src / misc / path.cpp
blobce787b1960194b5fdb7611d1d52ba08d0bc8e0cf
1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010-2020 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2012-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 // Copyright (C) 2014-2015 Laszlo KIS-ADAM (dfighter) <dfighter1985@gmail.com>
7 //
8 // This program is free software: you can redistribute it and/or modify
9 // it under the terms of the GNU Affero General Public License as
10 // published by the Free Software Foundation, either version 3 of the
11 // License, or (at your option) any later version.
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU Affero General Public License for more details.
18 // You should have received a copy of the GNU Affero General Public License
19 // along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "stdmisc.h"
24 #include "nel/misc/path.h"
25 #include "nel/misc/big_file.h"
26 #include "nel/misc/hierarchical_timer.h"
27 #include "nel/misc/progress_callback.h"
28 #include "nel/misc/file.h"
29 #include "nel/misc/xml_pack.h"
30 #include "nel/misc/streamed_package_manager.h"
32 #ifdef NL_OS_WINDOWS
33 # include <sys/types.h>
34 # include <sys/stat.h>
35 # include <direct.h>
36 # include <io.h>
37 # include <fcntl.h>
38 # include <sys/types.h>
39 # include <sys/stat.h>
40 # include <shlobj.h>
41 #else
42 # include <sys/types.h>
43 # include <sys/stat.h>
44 # include <dirent.h>
45 # include <unistd.h>
46 # include <cstdio>
47 # include <cerrno>
48 # include <sys/types.h>
49 # include <utime.h>
50 #endif // NL_OS_WINDOWS
52 using namespace std;
54 #ifdef DEBUG_NEW
55 #define new DEBUG_NEW
56 #endif
58 namespace NLMISC {
61 // Macros
64 // Use this define if you want to display info about the CPath.
65 //#define NL_DEBUG_PATH
67 #ifdef NL_DEBUG_PATH
68 # define NL_DISPLAY_PATH nlinfo
69 #else
70 # ifdef __GNUC__
71 # define NL_DISPLAY_PATH(format, args...)
72 # else // __GNUC__
73 # define NL_DISPLAY_PATH if(false)
74 # endif // __GNUC__
75 #endif
79 // Variables
82 NLMISC_SAFE_SINGLETON_IMPL(CPath);
86 // Functions
89 CFileContainer::~CFileContainer()
91 if( _AllFileNames )
93 delete[] _AllFileNames;
94 _AllFileNames = NULL;
98 void CPath::releaseInstance()
100 if (_Instance)
102 NLMISC::INelContext::getInstance().releaseSingletonPointer("CPath", _Instance);
103 delete _Instance;
104 _Instance = NULL;
109 void CPath::getFileList(const std::string &extension, std::vector<std::string> &filenames)
111 getInstance()->_FileContainer.getFileList(extension, filenames);
114 void CFileContainer::getFileList(const std::string &extension, std::vector<std::string> &filenames)
116 if (!_MemoryCompressed)
118 TFiles::iterator first(_Files.begin()), last(_Files.end());
120 if( !extension.empty() )
122 for (; first != last; ++ first)
124 string ext = SSMext.get(first->second.idExt);
125 if (ext == extension)
127 filenames.push_back(first->first);
131 // if extension is empty we keep all files
132 else
134 for (; first != last; ++ first)
136 filenames.push_back(first->first);
140 else
142 // compressed memory version
143 std::vector<CFileContainer::CMCFileEntry>::iterator first(_MCFiles.begin()), last(_MCFiles.end());
145 if( !extension.empty() )
147 for (; first != last; ++ first)
149 string ext = SSMext.get(first->idExt);
150 if (ext == extension)
152 filenames.push_back(first->Name);
156 // if extension is empty we keep all files
157 else
159 for (; first != last; ++ first)
161 filenames.push_back(first->Name);
167 void CPath::getFileListByName(const std::string &extension, const std::string &name, std::vector<std::string> &filenames)
169 getInstance()->_FileContainer.getFileListByName(extension, name, filenames);
172 void CFileContainer::getFileListByName(const std::string &extension, const std::string &name, std::vector<std::string> &filenames)
174 if (!_MemoryCompressed)
176 TFiles::iterator first(_Files.begin()), last(_Files.end());
178 if( !name.empty() )
180 for (; first != last; ++ first)
182 string ext = SSMext.get(first->second.idExt);
183 if (first->first.find(name) != string::npos && (ext == extension || extension.empty()))
185 filenames.push_back(first->first);
189 // if extension is empty we keep all files
190 else
192 for (; first != last; ++ first)
194 filenames.push_back(first->first);
198 else
200 // compressed memory version
201 std::vector<CFileContainer::CMCFileEntry>::iterator first(_MCFiles.begin()), last(_MCFiles.end());
203 if( !name.empty() )
205 for (; first != last; ++ first)
207 string ext = SSMext.get(first->idExt);
208 if (strstr(first->Name, name.c_str()) != NULL && (ext == extension || extension.empty()))
210 filenames.push_back(first->Name);
214 // if extension is empty we keep all files
215 else
217 for (; first != last; ++ first)
219 filenames.push_back(first->Name);
225 void CPath::getFileListByPath(const std::string &extension, const std::string &path, std::vector<std::string> &filenames)
227 getInstance()->_FileContainer.getFileListByPath(extension, path, filenames);
230 void CFileContainer::getFileListByPath(const std::string &extension, const std::string &path, std::vector<std::string> &filenames)
232 if (!_MemoryCompressed)
234 TFiles::iterator first(_Files.begin()), last(_Files.end());
236 if( !path.empty() )
238 for (; first != last; ++ first)
240 string ext = SSMext.get(first->second.idExt);
241 string p = SSMpath.get(first->second.idPath);
242 if (p.find(path) != string::npos && (ext == extension || extension.empty()))
244 filenames.push_back(first->first);
248 // if extension is empty we keep all files
249 else
251 for (; first != last; ++ first)
253 filenames.push_back(first->first);
257 else
259 // compressed memory version
260 std::vector<CFileContainer::CMCFileEntry>::iterator first(_MCFiles.begin()), last(_MCFiles.end());
262 if( !path.empty() )
264 for (; first != last; ++ first)
266 string ext = SSMext.get(first->idExt);
267 string p = SSMpath.get(first->idPath);
269 if (strstr(p.c_str(), path.c_str()) != NULL && (ext == extension || extension.empty()))
271 filenames.push_back(first->Name);
275 // if extension is empty we keep all files
276 else
278 for (; first != last; ++ first)
280 filenames.push_back(first->Name);
286 void CPath::clearMap ()
288 getInstance()->_FileContainer.clearMap();
291 void CFileContainer::clearMap ()
293 nlassert(!_MemoryCompressed);
294 _Files.clear ();
295 CBigFile::getInstance().removeAll ();
296 CStreamedPackageManager::getInstance().unloadAll ();
297 NL_DISPLAY_PATH("PATH: CPath::clearMap(): map directory cleared");
300 CFileContainer::CMCFileEntry *CFileContainer::MCfind (const std::string &filename)
302 nlassert(_MemoryCompressed);
303 vector<CMCFileEntry>::iterator it;
304 CMCFileEntry temp_cmc_file;
305 temp_cmc_file.Name = (char*)filename.c_str();
306 it = lower_bound(_MCFiles.begin(), _MCFiles.end(), temp_cmc_file, CMCFileComp());
307 if (it != _MCFiles.end())
309 CMCFileComp FileComp;
310 if (FileComp.specialCompare(*it, filename.c_str()) == 0)
311 return &(*it);
313 return NULL;
316 sint CFileContainer::findExtension (const string &ext1, const string &ext2)
318 for (uint i = 0; i < _Extensions.size (); i++)
320 if (_Extensions[i].first == ext1 && _Extensions[i].second == ext2)
322 return i;
325 return -1;
328 void CPath::remapExtension (const string &ext1, const string &ext2, bool substitute)
330 getInstance()->_FileContainer.remapExtension(ext1, ext2, substitute);
333 void CFileContainer::remapExtension (const string &ext1, const string &ext2, bool substitute)
335 nlassert(!_MemoryCompressed);
337 string ext1lwr = toLowerAscii(ext1);
338 string ext2lwr = toLowerAscii(ext2);
340 if (ext1lwr.empty() || ext2lwr.empty())
342 nlwarning ("PATH: CPath::remapExtension(%s, %s, %d): can't remap empty extension", ext1lwr.c_str(), ext2lwr.c_str(), substitute);
345 if (ext1lwr == "bnp" || ext2lwr == "bnp" || ext1lwr == "snp" || ext2lwr == "snp")
347 nlwarning ("PATH: CPath::remapExtension(%s, %s, %d): you can't remap a big file", ext1lwr.c_str(), ext2lwr.c_str(), substitute);
350 if (!substitute)
352 // remove the mapping from the mapping list
353 sint n = findExtension (ext1lwr, ext2lwr);
354 nlassert (n != -1);
355 _Extensions.erase (_Extensions.begin() + n);
357 // remove mapping in the map
358 TFiles::iterator it = _Files.begin();
359 TFiles::iterator nit = it;
360 while (it != _Files.end ())
362 nit++;
363 string ext = SSMext.get((*it).second.idExt);
364 if ((*it).second.Remapped && ext == ext2lwr)
366 _Files.erase (it);
368 it = nit;
370 NL_DISPLAY_PATH("PATH: CPath::remapExtension(%s, %s, %d): extension removed", ext1lwr.c_str(), ext2lwr.c_str(), substitute);
372 else
374 sint n = findExtension (ext1lwr, ext2lwr);
375 if (n != -1)
377 nlwarning ("PATH: CPath::remapExtension(%s, %s, %d): remapping already set", ext1lwr.c_str(), ext2lwr.c_str(), substitute);
378 return;
381 // adding mapping into the mapping list
382 _Extensions.push_back (make_pair (ext1lwr, ext2lwr));
384 // adding mapping into the map
385 vector<string> newFiles;
386 TFiles::iterator it = _Files.begin();
387 while (it != _Files.end ())
389 string ext = SSMext.get((*it).second.idExt);
390 if (!(*it).second.Remapped && ext == ext1lwr)
392 // find if already exist
393 string::size_type pos = (*it).first.find_last_of (".");
394 if (pos != string::npos)
396 string file = (*it).first.substr (0, pos + 1);
397 file += ext2lwr;
399 // TODO perhaps a problem because I insert in the current map that I process
400 string path = SSMpath.get((*it).second.idPath);
401 insertFileInMap (file, path+file, true, ext1lwr);
404 it++;
406 NL_DISPLAY_PATH("PATH: CPath::remapExtension(%s, %s, %d): extension added", ext1lwr.c_str(), ext2lwr.c_str(), substitute);
410 // ***************************************************************************
411 void CPath::remapFile (const std::string &file1, const std::string &file2)
413 getInstance()->_FileContainer.remapFile(file1, file2);
416 void CFileContainer::remapFile (const std::string &file1, const std::string &file2)
418 if (file1.empty()) return;
419 if (file2.empty()) return;
420 _RemappedFiles[toLowerAscii(file1)] = toLowerAscii(file2);
423 // ***************************************************************************
424 static void removeAllUnusedChar(string &str)
426 uint32 i = 0;
427 while (!str.empty() && (i != str.size()))
429 if ((str[i] == ' ' || str[i] == '\t' || str[i] == '\r' || str[i] == '\n'))
430 str.erase(str.begin()+i);
431 else
432 i++;
436 // ***************************************************************************
437 void CPath::loadRemappedFiles (const std::string &file)
439 getInstance()->_FileContainer.loadRemappedFiles(file);
442 void CFileContainer::loadRemappedFiles (const std::string &file)
444 string fullName = lookup(file, false, true, true);
445 CIFile f;
446 f.setCacheFileOnOpen (true);
448 if (!f.open (fullName))
449 return;
451 char sTmp[514];
452 string str;
454 while (!f.eof())
456 f.getline(sTmp, 512);
457 str = sTmp;
458 std::string::size_type pos = str.find(',');
459 if (pos != string::npos)
461 removeAllUnusedChar(str);
462 if (!str.empty())
464 pos = str.find(',');
465 remapFile( str.substr(0,pos), str.substr(pos+1, str.size()) );
472 // ***************************************************************************
473 string CPath::lookup (const string &filename, bool throwException, bool displayWarning, bool lookupInLocalDirectory)
475 return getInstance()->_FileContainer.lookup(filename, throwException, displayWarning, lookupInLocalDirectory);
478 string CFileContainer::lookup (const string &filename, bool throwException, bool displayWarning, bool lookupInLocalDirectory)
480 /* ***********************************************
481 * WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance
482 * It can be loaded/called through CAsyncFileManager for instance
483 * ***********************************************/
485 NB: CPath access static instance getInstance() of course, so user must ensure
486 that no mutator is called while async loading
490 // If the file already contains a @, it means that a lookup already proceed and returning a big file, do nothing
491 if (filename.find ("@") != string::npos)
493 NL_DISPLAY_PATH("PATH: CPath::lookup(%s): already found", filename.c_str());
494 return filename;
497 // Try to find in the map directories
499 // If filename contains a path, we get only the filename to look inside paths
500 string str = CFile::getFilename(toLowerAscii(filename));
502 // Remove end spaces
503 while ((!str.empty()) && (str[str.size()-1] == ' '))
505 str.resize (str.size()-1);
508 map<string, string>::iterator itss = _RemappedFiles.find(str);
509 if (itss != _RemappedFiles.end())
510 str = itss->second;
512 if (_MemoryCompressed)
514 CMCFileEntry *pMCFE = MCfind(str);
515 // If found in the map, returns it
516 if (pMCFE != NULL)
518 string fname, path = SSMpath.get(pMCFE->idPath);
519 if (pMCFE->Remapped)
520 fname = CFile::getFilenameWithoutExtension(pMCFE->Name) + "." + SSMext.get(pMCFE->idExt);
521 else
522 fname = pMCFE->Name;
524 NL_DISPLAY_PATH("PATH: CPath::lookup(%s): found in the map directory: '%s'", fname.c_str(), path.c_str());
525 return path + fname;
528 else // NOT memory compressed
531 TFiles::iterator it = _Files.find (str);
532 // If found in the map, returns it
533 if (it != _Files.end())
535 string fname, path = SSMpath.get((*it).second.idPath);
536 if (it->second.Remapped)
537 fname = CFile::getFilenameWithoutExtension((*it).second.Name) + "." + SSMext.get((*it).second.idExt);
538 else
539 fname = (*it).second.Name;
541 NL_DISPLAY_PATH("PATH: CPath::lookup(%s): found in the map directory: '%s'", fname.c_str(), path.c_str());
542 return path + fname;
547 // Try to find in the alternative directories
548 for (uint i = 0; i < _AlternativePaths.size(); i++)
550 string s = _AlternativePaths[i] + str;
551 if ( CFile::fileExists(s) )
553 NL_DISPLAY_PATH("PATH: CPath::lookup(%s): found in the alternative directory: '%s'", str.c_str(), s.c_str());
554 return s;
557 // try with the remapping
558 for (uint j = 0; j < _Extensions.size(); j++)
560 if (toLowerAscii(CFile::getExtension (str)) == _Extensions[j].second)
562 string rs = _AlternativePaths[i] + CFile::getFilenameWithoutExtension (filename) + "." + _Extensions[j].first;
563 if ( CFile::fileExists(rs) )
565 NL_DISPLAY_PATH("PATH: CPath::lookup(%s): found in the alternative directory: '%s'", str.c_str(), rs.c_str());
566 return rs;
572 // Try to find in the current directory
573 if ( lookupInLocalDirectory && CFile::fileExists(filename) )
575 NL_DISPLAY_PATH("PATH: CPath::lookup(%s): found in the current directory: '%s'", str.c_str(), filename.c_str());
576 return filename;
579 // Not found
580 if (displayWarning)
582 if(filename.empty())
583 nlwarning ("PATH: Try to lookup for an empty filename. TODO: check why.");
584 else
585 nlwarning ("PATH: File (%s) not found (%s)", str.c_str(), filename.c_str());
588 if (throwException)
589 throw EPathNotFound (filename);
591 return "";
594 bool CPath::exists (const std::string &filename)
596 return getInstance()->_FileContainer.exists(filename);
599 bool CFileContainer::exists (const std::string &filename)
601 // Try to find in the map directories
602 string str = toLowerAscii(filename);
604 // Remove end spaces
605 while ((!str.empty()) && (str[str.size()-1] == ' '))
607 str.resize (str.size()-1);
610 if (_MemoryCompressed)
612 CMCFileEntry *pMCFE = MCfind(str);
613 // If found in the vector, returns it
614 if (pMCFE != NULL)
615 return true;
617 else
619 TFiles::iterator it = _Files.find (str);
620 // If found in the map, returns it
621 if (it != _Files.end())
622 return true;
625 return false;
628 string CPath::standardizePath (const string &path, bool addFinalSlash)
630 return getInstance()->_FileContainer.standardizePath(path, addFinalSlash);
633 string CFileContainer::standardizePath (const string &path, bool addFinalSlash)
635 // check empty path
636 if (path.empty())
637 return "";
639 string newPath(path);
641 for (uint i = 0; i < path.size(); i++)
643 // don't transform the first \\ for windows network path
644 if (path[i] == '\\')
645 newPath[i] = '/';
648 // add terminal slash
649 if (addFinalSlash && newPath[path.size()-1] != '/')
650 newPath += '/';
652 return newPath;
655 // replace / with backslash
656 std::string CPath::standardizeDosPath (const std::string &path)
658 return getInstance()->_FileContainer.standardizeDosPath(path);
661 std::string CFileContainer::standardizeDosPath (const std::string &path)
663 string newPath;
665 for (uint i = 0; i < path.size(); i++)
667 if (path[i] == '/')
668 newPath += '\\';
669 // Yoyo: supress toLower. Not useful!?!
670 /*else if (isupper(path[i]))
671 newPath += tolower(path[i]);*/
672 else
673 newPath += path[i];
676 if (CFile::isExists(path) && CFile::isDirectory(path) && newPath[newPath.size()-1] != '\\')
677 newPath += '\\';
679 return newPath;
683 std::string CPath::getCurrentPath ()
685 return getInstance()->_FileContainer.getCurrentPath();
688 std::string CFileContainer::getCurrentPath ()
690 #ifdef NL_OS_WINDOWS
691 wchar_t buffer[1024];
692 return standardizePath(wideToUtf8(_wgetcwd(buffer, 1024)), false);
693 #else
694 char buffer [1024];
695 return standardizePath(getcwd(buffer, 1024), false);
696 #endif
699 bool CPath::setCurrentPath (const std::string &path)
701 return getInstance()->_FileContainer.setCurrentPath(path);
704 bool CFileContainer::setCurrentPath (const std::string &path)
706 int res;
707 //nldebug("Change current path to '%s' (current path is '%s')", path.c_str(), getCurrentPath().c_str());
708 #ifdef NL_OS_WINDOWS
709 res = _wchdir(nlUtf8ToWide(path));
710 #else
711 res = chdir(path.c_str());
712 #endif
713 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));
714 return res == 0;
717 std::string CPath::getFullPath (const std::string &path, bool addFinalSlash)
719 return getInstance()->_FileContainer.getFullPath(path, addFinalSlash);
722 std::string CFileContainer::getFullPath (const std::string &path, bool addFinalSlash)
724 string currentPath = standardizePath (getCurrentPath ());
725 string sPath = standardizePath (path, addFinalSlash);
727 // current path
728 if (path.empty() || sPath == "." || sPath == "./")
730 return currentPath;
733 // windows full path
734 if (path.size() >= 2 && path[1] == ':')
736 return sPath;
739 if (path.size() >= 2 && (path[0] == '/' || path[0] == '\\') && (path[1] == '/' || path[1] == '\\'))
741 return sPath;
745 // from root
746 if (path [0] == '/' || path[0] == '\\')
748 if (currentPath.size() > 2 && currentPath[1] == ':')
750 return currentPath.substr(0,3) + sPath.substr(1);
752 else
754 return sPath;
758 // default case
759 return currentPath + sPath;
764 #ifdef NL_OS_WINDOWS
765 # define dirent WIN32_FIND_DATAW
766 # define DIR void
768 static string sDir;
769 static WIN32_FIND_DATAW findData;
770 static HANDLE hFind;
772 DIR *opendir (const char *path)
774 nlassert (path != NULL);
775 nlassert (path[0] != '\0');
777 if (!CFile::isDirectory(path))
778 return NULL;
780 sDir = path;
782 hFind = NULL;
784 return (void *)1;
787 int closedir (DIR *dir)
789 FindClose(hFind);
790 return 0;
793 dirent *readdir (DIR *dir)
795 // FIX : call to SetCurrentDirectory() and SetCurrentDirectory() removed to improve speed
796 nlassert (!sDir.empty());
798 // first visit in this directory : FindFirstFile()
799 if (hFind == NULL)
801 hFind = FindFirstFileW(nlUtf8ToWide(CPath::standardizePath(sDir) + "*"), &findData);
803 // directory already visited : FindNextFile()
804 else
806 if (!FindNextFileW (hFind, &findData))
807 return NULL;
810 return &findData;
814 #endif // NL_OS_WINDOWS
816 #ifndef NL_OS_WINDOWS
817 string BasePathgetPathContent;
818 #endif
820 bool isdirectory (dirent *de)
822 nlassert (de != NULL);
823 #ifdef NL_OS_WINDOWS
824 return ((de->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) && ((de->dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) == 0);
825 #else
826 //nlinfo ("isdirectory filename %s -> 0x%08x", de->d_name, de->d_type);
827 // we can't use "de->d_type & DT_DIR" because it s always NULL on libc2.1
828 //return (de->d_type & DT_DIR) != 0;
830 return CFile::isDirectory (BasePathgetPathContent + de->d_name);
832 #endif // NL_OS_WINDOWS
835 bool isfile (dirent *de)
837 nlassert (de != NULL);
838 #ifdef NL_OS_WINDOWS
839 return ((de->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) && ((de->dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) == 0);
840 #else
841 // we can't use "de->d_type & DT_DIR" because it s always NULL on libc2.1
842 //return (de->d_type & DT_DIR) == 0;
844 return !CFile::isDirectory (BasePathgetPathContent + de->d_name);
846 #endif // NL_OS_WINDOWS
849 string getname (dirent *de)
851 nlassert (de != NULL);
852 #ifdef NL_OS_WINDOWS
853 return wideToUtf8(de->cFileName);
854 #else
855 return de->d_name;
856 #endif // NL_OS_WINDOWS
859 void CPath::getPathContent (const string &path, bool recurse, bool wantDir, bool wantFile, vector<string> &result, class IProgressCallback *progressCallBack, bool showEverything)
861 getInstance()->_FileContainer.getPathContent(path, recurse, wantDir, wantFile, result, progressCallBack, showEverything);
863 sort(result.begin(), result.end());
866 void CFileContainer::getPathContent (const string &path, bool recurse, bool wantDir, bool wantFile, vector<string> &result, class IProgressCallback *progressCallBack, bool showEverything)
868 if( path.empty() )
870 NL_DISPLAY_PATH("PATH: CPath::getPathContent(): Empty input Path");
871 return;
874 #ifndef NL_OS_WINDOWS
875 BasePathgetPathContent = CPath::standardizePath (path);
876 #endif
878 DIR *dir = opendir (path.c_str());
880 if (dir == NULL)
882 NL_DISPLAY_PATH("PATH: CPath::getPathContent(%s, %d, %d, %d): could not open the directory", path.c_str(), recurse, wantDir, wantFile);
883 return;
886 // contains path that we have to recurs into
887 vector<string> recursPath;
889 for(;;)
891 dirent *de = readdir(dir);
892 if (de == NULL)
894 NL_DISPLAY_PATH("PATH: CPath::getPathContent(%s, %d, %d, %d): end of directory", path.c_str(), recurse, wantDir, wantFile);
895 break;
898 string fn = getname (de);
900 // skip . and ..
901 if (fn == "." || fn == "..")
902 continue;
904 if (isdirectory(de))
906 // skip CVS, .svn and .hg directory
907 if ((!showEverything) && (fn == "CVS" || fn == ".svn" || fn == ".hg" || fn == ".git"))
909 NL_DISPLAY_PATH("PATH: CPath::getPathContent(%s, %d, %d, %d): skip '%s' directory", path.c_str(), recurse, wantDir, wantFile, fn.c_str());
910 continue;
913 string stdName = standardizePath(standardizePath(path) + fn);
914 if (recurse)
916 NL_DISPLAY_PATH("PATH: CPath::getPathContent(%s, %d, %d, %d): need to recurse into '%s'", path.c_str(), recurse, wantDir, wantFile, stdName.c_str());
917 recursPath.push_back (stdName);
920 if (wantDir)
922 NL_DISPLAY_PATH("PATH: CPath::getPathContent(%s, %d, %d, %d): adding path '%s'", path.c_str(), recurse, wantDir, wantFile, stdName.c_str());
923 result.push_back (stdName);
927 if (wantFile && isfile(de))
929 if ( (!showEverything) && (fn.size() >= 4 && fn.substr (fn.size()-4) == ".log"))
931 NL_DISPLAY_PATH("PATH: CPath::getPathContent(%s, %d, %d, %d): skip *.log files (%s)", path.c_str(), recurse, wantDir, wantFile, fn.c_str());
932 continue;
935 string stdName = standardizePath(path) + getname(de);
937 NL_DISPLAY_PATH("PATH: CPath::getPathContent(%s, %d, %d, %d): adding file '%s'", path.c_str(), recurse, wantDir, wantFile, stdName.c_str());
938 result.push_back (stdName);
942 closedir (dir);
944 #ifndef NL_OS_WINDOWS
945 BasePathgetPathContent.clear();
946 #endif
948 // let's recurse
949 for (uint i = 0; i < recursPath.size (); i++)
951 // Progress bar
952 if (progressCallBack)
954 progressCallBack->progress ((float)i/(float)recursPath.size ());
955 progressCallBack->pushCropedValues ((float)i/(float)recursPath.size (), (float)(i+1)/(float)recursPath.size ());
958 getPathContent (recursPath[i], recurse, wantDir, wantFile, result, progressCallBack, showEverything);
960 // Progress bar
961 if (progressCallBack)
963 progressCallBack->popCropedValues ();
968 void CPath::removeAllAlternativeSearchPath ()
970 getInstance()->_FileContainer.removeAllAlternativeSearchPath();
973 void CFileContainer::removeAllAlternativeSearchPath ()
975 _AlternativePaths.clear ();
976 NL_DISPLAY_PATH("PATH: CPath::RemoveAllAternativeSearchPath(): removed");
980 void CPath::addSearchPath (const string &path, bool recurse, bool alternative, class IProgressCallback *progressCallBack)
982 getInstance()->_FileContainer.addSearchPath(path, recurse, alternative, progressCallBack);
985 void CFileContainer::addSearchPath (const string &path, bool recurse, bool alternative, class IProgressCallback *progressCallBack)
987 //H_AUTO_INST(addSearchPath);
989 nlassert(!_MemoryCompressed);
991 // check empty directory
992 if (path.empty())
994 nlwarning ("PATH: CPath::addSearchPath(%s, %s, %s): can't add empty directory, skip it",
995 path.c_str(),
996 recurse ? "recursive" : "not recursive",
997 alternative ? "alternative" : "not alternative");
998 return;
1001 // check if it s a directory
1002 if (!CFile::isDirectory (path))
1004 nlinfo ("PATH: CPath::addSearchPath(%s, %s, %s): '%s' is not a directory, I'll call addSearchFile()",
1005 path.c_str(),
1006 recurse ? "recursive" : "not recursive",
1007 alternative ? "alternative" : "not alternative",
1008 path.c_str());
1009 addSearchFile (path, false, "", progressCallBack);
1010 return;
1013 string newPath = standardizePath(path);
1015 // check if it s a directory
1016 if (!CFile::isExists (newPath))
1018 nlwarning ("PATH: CPath::addSearchPath(%s, %s, %s): '%s' is not found, skip it",
1019 path.c_str(),
1020 recurse ? "recursive" : "not recursive",
1021 alternative ? "alternative" : "not alternative",
1022 newPath.c_str());
1023 return;
1026 nlinfo ("PATH: CPath::addSearchPath(%s, %d, %d): adding the path '%s'", path.c_str(), recurse, alternative, newPath.c_str());
1028 NL_DISPLAY_PATH("PATH: CPath::addSearchPath(%s, %d, %d): try to add '%s'", path.c_str(), recurse, alternative, newPath.c_str());
1030 if (alternative)
1032 vector<string> pathsToProcess;
1034 // add the current path
1035 pathsToProcess.push_back (newPath);
1037 if (recurse)
1039 // find all path and subpath
1040 getPathContent (newPath, recurse, true, false, pathsToProcess, progressCallBack);
1042 // sort files
1043 sort(pathsToProcess.begin(), pathsToProcess.end());
1046 for (uint p = 0; p < pathsToProcess.size(); p++)
1048 // check if the path not already in the vector
1049 uint i;
1050 for (i = 0; i < _AlternativePaths.size(); i++)
1052 if (_AlternativePaths[i] == pathsToProcess[p])
1053 break;
1055 if (i == _AlternativePaths.size())
1057 // add them in the alternative directory
1058 _AlternativePaths.push_back (pathsToProcess[p]);
1059 NL_DISPLAY_PATH("PATH: CPath::addSearchPath(%s, %s, %s): path '%s' added",
1060 newPath.c_str(),
1061 recurse ? "recursive" : "not recursive",
1062 alternative ? "alternative" : "not alternative",
1063 pathsToProcess[p].c_str());
1065 else
1067 nlwarning ("PATH: CPath::addSearchPath(%s, %s, %s): path '%s' already added",
1068 newPath.c_str(),
1069 recurse ? "recursive" : "not recursive",
1070 alternative ? "alternative" : "not alternative",
1071 pathsToProcess[p].c_str());
1075 else
1077 vector<string> filesToProcess;
1079 // Progress bar
1080 if (progressCallBack)
1082 progressCallBack->progress (0);
1083 progressCallBack->pushCropedValues (0, 0.5f);
1086 // find all files in the path and subpaths
1087 getPathContent (newPath, recurse, false, true, filesToProcess, progressCallBack);
1089 // sort files
1090 sort(filesToProcess.begin(), filesToProcess.end());
1092 // Progress bar
1093 if (progressCallBack)
1095 progressCallBack->popCropedValues ();
1096 progressCallBack->progress (0.5);
1097 progressCallBack->pushCropedValues (0.5f, 1);
1100 // add them in the map
1101 for (uint f = 0; f < filesToProcess.size(); f++)
1103 // Progree bar
1104 if (progressCallBack)
1106 progressCallBack->progress ((float)f/(float)filesToProcess.size());
1107 progressCallBack->pushCropedValues ((float)f/(float)filesToProcess.size(), (float)(f+1)/(float)filesToProcess.size());
1110 string filename = CFile::getFilename (filesToProcess[f]);
1111 string filepath = CFile::getPath (filesToProcess[f]);
1112 // insertFileInMap (filename, filepath, false, CFile::getExtension(filename));
1113 addSearchFile (filesToProcess[f], false, "", progressCallBack);
1115 // Progress bar
1116 if (progressCallBack)
1118 progressCallBack->popCropedValues ();
1122 // Progress bar
1123 if (progressCallBack)
1125 progressCallBack->popCropedValues ();
1130 void CPath::addSearchFile (const string &file, bool remap, const string &virtual_ext, NLMISC::IProgressCallback *progressCallBack)
1132 getInstance()->_FileContainer.addSearchFile(file, remap, virtual_ext, progressCallBack);
1135 void CFileContainer::addSearchFile (const string &file, bool remap, const string &virtual_ext, NLMISC::IProgressCallback *progressCallBack)
1137 nlassert(!_MemoryCompressed);
1139 string newFile = standardizePath(file, false);
1141 // check empty file
1142 if (newFile.empty())
1144 nlwarning ("PATH: CPath::addSearchFile(%s, %d, '%s'): can't add empty file, skip it", file.c_str(), remap, virtual_ext.c_str());
1145 return;
1148 // check if the file exists
1149 if (!CFile::isExists (newFile))
1151 nlwarning ("PATH: CPath::addSearchFile(%s, %d, '%s'): '%s' is not found, skip it (current dir is '%s'",
1152 file.c_str(),
1153 remap,
1154 virtual_ext.c_str(),
1155 newFile.c_str(),
1156 CPath::getCurrentPath().c_str());
1157 return;
1160 // check if it s a file
1161 if (CFile::isDirectory (newFile))
1163 nlwarning ("PATH: CPath::addSearchFile(%s, %d, '%s'): '%s' is not a file, skip it",
1164 file.c_str(),
1165 remap,
1166 virtual_ext.c_str(),
1167 newFile.c_str());
1168 return;
1171 std::string fileExtension = CFile::getExtension(newFile);
1173 // check if it s a big file
1174 if (fileExtension == "bnp")
1176 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());
1177 addSearchBigFile(file, false, false, progressCallBack);
1178 return;
1181 // check if it s a streamed package
1182 if (fileExtension == "snp")
1184 NL_DISPLAY_PATH ("PATH: CPath::addSearchStreamedPackage(%s, %d, '%s'): '%s' is a streamed package, add it", file.c_str(), remap, virtual_ext.c_str(), newFile.c_str());
1185 addSearchStreamedPackage(file, false, false, progressCallBack);
1186 return;
1189 // check if it s an xml pack file
1190 if (fileExtension == "xml_pack")
1192 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());
1193 addSearchXmlpackFile(file, false, false, progressCallBack);
1194 return;
1197 string filenamewoext = CFile::getFilenameWithoutExtension (newFile);
1198 string filename, ext;
1200 if (virtual_ext.empty())
1202 filename = CFile::getFilename (newFile);
1203 ext = CFile::getExtension (filename);
1205 else
1207 filename = filenamewoext + "." + virtual_ext;
1208 ext = CFile::getExtension (newFile);
1211 insertFileInMap (filename, newFile, remap, ext);
1213 if (!remap && !ext.empty())
1215 // now, we have to see extension and insert in the map the remapped files
1216 for (uint i = 0; i < _Extensions.size (); i++)
1218 if (_Extensions[i].first == toLowerAscii(ext))
1220 // need to remap
1221 addSearchFile (newFile, true, _Extensions[i].second, progressCallBack);
1227 void CPath::addSearchListFile (const string &filename, bool recurse, bool alternative)
1229 getInstance()->_FileContainer.addSearchListFile(filename, recurse, alternative);
1232 void CFileContainer::addSearchListFile (const string &filename, bool recurse, bool alternative)
1234 // check empty file
1235 if (filename.empty())
1237 nlwarning ("PATH: CPath::addSearchListFile(%s, %d, %d): can't add empty file, skip it", filename.c_str(), recurse, alternative);
1238 return;
1241 // check if the file exists
1242 if (!CFile::isExists (filename))
1244 nlwarning ("PATH: CPath::addSearchListFile(%s, %d, %d): '%s' is not found, skip it", filename.c_str(), recurse, alternative, filename.c_str());
1245 return;
1248 // check if it s a file
1249 if (CFile::isDirectory (filename))
1251 nlwarning ("PATH: CPath::addSearchListFile(%s, %d, %d): '%s' is not a file, skip it", filename.c_str(), recurse, alternative, filename.c_str());
1252 return;
1255 // TODO read the file and add files that are inside
1258 // WARNING : recurse is not used
1259 void CPath::addSearchBigFile (const string &sBigFilename, bool recurse, bool alternative, NLMISC::IProgressCallback *progressCallBack)
1261 getInstance()->_FileContainer.addSearchBigFile(sBigFilename, recurse, alternative, progressCallBack);
1264 void CFileContainer::addSearchBigFile (const string &sBigFilename, bool recurse, bool alternative, NLMISC::IProgressCallback *progressCallBack)
1266 // Check if filename is not empty
1267 if (sBigFilename.empty())
1269 nlwarning ("PATH: CPath::addSearchBigFile(%s, %d, %d): can't add empty file, skip it", sBigFilename.c_str(), recurse, alternative);
1270 return;
1272 // Check if the file exists
1273 if (!CFile::isExists (sBigFilename))
1275 nlwarning ("PATH: CPath::addSearchBigFile(%s, %d, %d): '%s' is not found, skip it", sBigFilename.c_str(), recurse, alternative, sBigFilename.c_str());
1276 return;
1278 // Check if it s a file
1279 if (CFile::isDirectory (sBigFilename))
1281 nlwarning ("PATH: CPath::addSearchBigFile(%s, %d, %d): '%s' is not a file, skip it", sBigFilename.c_str(), recurse, alternative, sBigFilename.c_str());
1282 return;
1284 // Open and read the big file header
1285 nlassert(!_MemoryCompressed);
1287 FILE *Handle = nlfopen (sBigFilename, "rb");
1288 if (Handle == NULL)
1290 nlwarning ("PATH: CPath::addSearchBigFile(%s, %d, %d): can't open file, skip it", sBigFilename.c_str(), recurse, alternative);
1291 return;
1294 // add the link with the CBigFile singleton
1295 if (CBigFile::getInstance().add (sBigFilename, BF_ALWAYS_OPENED | BF_CACHE_FILE_ON_OPEN))
1297 // 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
1298 insertFileInMap (CFile::getFilename (sBigFilename), sBigFilename, false, CFile::getExtension(sBigFilename));
1300 // parse the big file to add file in the map
1301 uint32 nFileSize=CFile::getFileSize (Handle);
1302 //nlfseek64 (Handle, 0, SEEK_END);
1303 //uint32 nFileSize = ftell (Handle);
1304 nlfseek64 (Handle, nFileSize-4, SEEK_SET);
1305 uint32 nOffsetFromBeginning;
1306 if (fread (&nOffsetFromBeginning, sizeof(uint32), 1, Handle) != 1)
1308 fclose(Handle);
1309 return;
1312 #ifdef NL_BIG_ENDIAN
1313 NLMISC_BSWAP32(nOffsetFromBeginning);
1314 #endif
1316 nlfseek64 (Handle, nOffsetFromBeginning, SEEK_SET);
1317 uint32 nNbFile;
1318 if (fread (&nNbFile, sizeof(uint32), 1, Handle) != 1)
1320 fclose(Handle);
1321 return;
1324 #ifdef NL_BIG_ENDIAN
1325 NLMISC_BSWAP32(nNbFile);
1326 #endif
1328 for (uint32 i = 0; i < nNbFile; ++i)
1330 // Progress bar
1331 if (progressCallBack)
1333 progressCallBack->progress ((float)i/(float)nNbFile);
1334 progressCallBack->pushCropedValues ((float)i/(float)nNbFile, (float)(i+1)/(float)nNbFile);
1337 char FileName[256];
1338 uint8 nStringSize;
1339 if (fread (&nStringSize, 1, 1, Handle) != 1)
1341 fclose(Handle);
1342 return;
1344 if (nStringSize)
1346 if (fread(FileName, 1, nStringSize, Handle) != nStringSize)
1348 fclose(Handle);
1349 return;
1352 FileName[nStringSize] = 0;
1353 uint32 nFileSize2;
1354 if (fread (&nFileSize2, sizeof(uint32), 1, Handle) != 1)
1356 fclose(Handle);
1357 return;
1360 #ifdef NL_BIG_ENDIAN
1361 NLMISC_BSWAP32(nFileSize2);
1362 #endif
1364 uint32 nFilePos;
1365 if (fread (&nFilePos, sizeof(uint32), 1, Handle) != 1)
1367 fclose(Handle);
1368 return;
1371 #ifdef NL_BIG_ENDIAN
1372 NLMISC_BSWAP32(nFilePos);
1373 #endif
1375 string sTmp = toLowerAscii(string(FileName));
1376 if (sTmp.empty())
1378 nlwarning ("PATH: CPath::addSearchBigFile(%s, %d, %d): can't add empty file, skip it", sBigFilename.c_str(), recurse, alternative);
1379 continue;
1381 string bigfilenamealone = CFile::getFilename (sBigFilename);
1382 string filenamewoext = CFile::getFilenameWithoutExtension (sTmp);
1383 string ext = toLowerAscii(CFile::getExtension(sTmp));
1385 insertFileInMap (sTmp, bigfilenamealone + "@" + sTmp, false, ext);
1387 for (uint j = 0; j < _Extensions.size (); j++)
1389 if (_Extensions[j].first == ext)
1391 // need to remap
1392 insertFileInMap (filenamewoext+"."+_Extensions[j].second,
1393 bigfilenamealone + "@" + sTmp,
1394 true,
1395 _Extensions[j].first);
1399 // Progress bar
1400 if (progressCallBack)
1402 progressCallBack->popCropedValues ();
1406 else
1408 nlwarning ("PATH: CPath::addSearchBigFile(%s, %d, %d): can't add the big file", sBigFilename.c_str(), recurse, alternative);
1411 fclose (Handle);
1414 void CPath::addSearchStreamedPackage (const string &filename, bool recurse, bool alternative, NLMISC::IProgressCallback *progressCallBack)
1416 getInstance()->_FileContainer.addSearchBigFile(filename, recurse, alternative, progressCallBack);
1419 void CFileContainer::addSearchStreamedPackage (const string &filename, bool recurse, bool alternative, NLMISC::IProgressCallback *progressCallBack)
1421 // Check if filename is not empty
1422 if (filename.empty())
1424 nlwarning ("PATH: CPath::addSearchStreamedPackage(%s, %d, %d): can't add empty file, skip it", filename.c_str(), recurse, alternative);
1425 return;
1427 // Check if the file exists
1428 if (!CFile::isExists(filename))
1430 nlwarning ("PATH: CPath::addSearchStreamedPackage(%s, %d, %d): '%s' is not found, skip it", filename.c_str(), recurse, alternative, filename.c_str());
1431 return;
1433 // Check if it s a file
1434 if (CFile::isDirectory(filename))
1436 nlwarning ("PATH: CPath::addSearchStreamedPackage(%s, %d, %d): '%s' is not a file, skip it", filename.c_str(), recurse, alternative, filename.c_str());
1437 return;
1440 // Add the file itself
1441 std::string packname = NLMISC::toLowerAscii(CFile::getFilename(filename));
1442 insertFileInMap(packname, filename, false, CFile::getExtension(filename));
1444 // Add the package to the package manager
1445 if (CStreamedPackageManager::getInstance().loadPackage(filename))
1447 std::vector<std::string> fileNames;
1448 CStreamedPackageManager::getInstance().list(fileNames, packname);
1450 for (std::vector<std::string>::iterator it(fileNames.begin()), end(fileNames.end()); it != end; ++it)
1452 // Add the file to the lookup
1453 std::string filePackageName = packname + "@" + (*it);
1454 // nldebug("Insert '%s'", filePackageName.c_str());
1455 insertFileInMap((*it), filePackageName, false, CFile::getExtension(*it));
1457 // Remapped extensions
1458 std::string ext = CFile::getExtension(*it);
1459 for (uint j = 0; j < _Extensions.size (); j++)
1461 if (_Extensions[j].first == ext)
1463 // Need to remap
1464 insertFileInMap(CFile::getFilenameWithoutExtension(*it) + "." + _Extensions[j].second,
1465 filePackageName, true, _Extensions[j].first);
1472 // WARNING : recurse is not used
1473 void CPath::addSearchXmlpackFile (const string &sXmlpackFilename, bool recurse, bool alternative, NLMISC::IProgressCallback *progressCallBack)
1475 getInstance()->_FileContainer.addSearchXmlpackFile(sXmlpackFilename, recurse, alternative, progressCallBack);
1478 void CFileContainer::addSearchXmlpackFile (const string &sXmlpackFilename, bool recurse, bool alternative, NLMISC::IProgressCallback *progressCallBack)
1480 // Check if filename is not empty
1481 if (sXmlpackFilename.empty())
1483 nlwarning ("PATH: CPath::addSearchXmlpackFile(%s, %d, %d): can't add empty file, skip it", sXmlpackFilename.c_str(), recurse, alternative);
1484 return;
1486 // Check if the file exists
1487 if (!CFile::isExists (sXmlpackFilename))
1489 nlwarning ("PATH: CPath::addSearchXmlpackFile(%s, %d, %d): '%s' is not found, skip it", sXmlpackFilename.c_str(), recurse, alternative, sXmlpackFilename.c_str());
1490 return;
1492 // Check if it s a file
1493 if (CFile::isDirectory (sXmlpackFilename))
1495 nlwarning ("PATH: CPath::addSearchXmlpackFile(%s, %d, %d): '%s' is not a file, skip it", sXmlpackFilename.c_str(), recurse, alternative, sXmlpackFilename.c_str());
1496 return;
1498 // Open and read the xmlpack file header
1500 FILE *Handle = nlfopen (sXmlpackFilename, "rb");
1501 if (Handle == NULL)
1503 nlwarning ("PATH: CPath::addSearchXmlpackFile(%s, %d, %d): can't open file, skip it", sXmlpackFilename.c_str(), recurse, alternative);
1504 return;
1507 // add the link with the CXMLPack singleton
1508 if (CXMLPack::getInstance().add (sXmlpackFilename))
1510 // 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
1511 insertFileInMap (sXmlpackFilename, sXmlpackFilename, false, CFile::getExtension(sXmlpackFilename));
1514 vector<string> filenames;
1515 CXMLPack::getInstance().list(sXmlpackFilename, filenames);
1517 for (uint i=0; i<filenames.size(); ++i)
1519 // Progress bar
1520 if (progressCallBack)
1522 progressCallBack->progress ((float)i/(float)filenames.size());
1523 progressCallBack->pushCropedValues ((float)i/(float)filenames.size(), (float)(i+1)/(float)filenames.size());
1526 string packfilenamealone = sXmlpackFilename;
1527 string filenamewoext = CFile::getFilenameWithoutExtension (filenames[i]);
1528 string ext = toLowerAscii(CFile::getExtension(filenames[i]));
1530 insertFileInMap (filenames[i], packfilenamealone + "@@" + filenames[i], false, ext);
1532 for (uint j = 0; j < _Extensions.size (); j++)
1534 if (_Extensions[j].first == ext)
1536 // need to remap
1537 insertFileInMap (filenamewoext+"."+_Extensions[j].second,
1538 packfilenamealone + "@@" + filenames[i],
1539 true,
1540 _Extensions[j].first);
1544 // Progress bar
1545 if (progressCallBack)
1547 progressCallBack->popCropedValues ();
1551 else
1553 nlwarning ("PATH: CPath::addSearchXmlpackFile(%s, %d, %d): can't add the xml pack file", sXmlpackFilename.c_str(), recurse, alternative);
1556 fclose (Handle);
1559 void CPath::addIgnoredDoubleFile(const std::string &ignoredFile)
1561 getInstance()->_FileContainer.addIgnoredDoubleFile(ignoredFile);
1564 void CFileContainer::addIgnoredDoubleFile(const std::string &ignoredFile)
1566 IgnoredFiles.push_back(ignoredFile);
1569 void CFileContainer::insertFileInMap (const string &filename, const string &filepath, bool remap, const string &extension)
1571 nlassert(!_MemoryCompressed);
1572 // find if the file already exist
1573 TFiles::iterator it = _Files.find (toLowerAscii(filename));
1574 if (it != _Files.end ())
1576 string path = SSMpath.get((*it).second.idPath);
1577 if (path.find("@") != string::npos && filepath.find("@") == string::npos)
1579 // if there's a file in a big file and a file in a path, the file in path wins
1580 // replace with the new one
1581 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());
1582 string sTmp = filepath.substr(0,filepath.rfind('/')+1);
1583 (*it).second.idPath = SSMpath.add(sTmp);
1584 (*it).second.Remapped = remap;
1585 (*it).second.idExt = SSMext.add(extension);
1586 (*it).second.Name = filename;
1588 else
1590 for(uint i = 0; i < IgnoredFiles.size(); i++)
1592 // if we don't want to display a warning, skip it
1593 if(filename == IgnoredFiles[i])
1594 return;
1596 // if the path is the same, don't warn
1597 string path2 = SSMpath.get((*it).second.idPath);
1598 string sPathOnly;
1599 if(filepath.rfind("@@") != string::npos)
1600 sPathOnly = filepath.substr(0,filepath.rfind("@@")+2);
1601 else if(filepath.rfind('@') != string::npos)
1602 sPathOnly = filepath.substr(0,filepath.rfind('@')+1);
1603 else
1604 sPathOnly = CFile::getPath(filepath);
1606 if (path2 == sPathOnly)
1607 return;
1608 nlwarning ("PATH: CPath::insertFileInMap(%s, %s, %d, %s): already inserted from '%s', skip it",
1609 filename.c_str(),
1610 filepath.c_str(),
1611 remap,
1612 extension.c_str(),
1613 path2.c_str());
1616 else
1618 CFileEntry fe;
1619 fe.idExt = SSMext.add(extension);
1620 fe.Remapped = remap;
1621 string sTmp;
1622 if (filepath.find("@") == string::npos)
1623 sTmp = filepath.substr(0,filepath.rfind('/')+1);
1624 else if (filepath.find("@@") != string::npos)
1625 sTmp = filepath.substr(0,filepath.rfind("@@")+2);
1626 else
1627 sTmp = filepath.substr(0,filepath.rfind('@')+1);
1629 fe.idPath = SSMpath.add(sTmp);
1630 fe.Name = filename;
1632 _Files.insert (make_pair(toLowerAscii(filename), fe));
1633 NL_DISPLAY_PATH("PATH: CPath::insertFileInMap(%s, %s, %d, %s): added", toLowerAscii(filename).c_str(), filepath.c_str(), remap, toLowerAscii(extension).c_str());
1637 void CPath::display ()
1639 getInstance()->_FileContainer.display();
1642 void CFileContainer::display ()
1644 nlinfo ("PATH: Contents of the map:");
1645 nlinfo ("PATH: %-25s %-5s %-5s %s", "filename", "ext", "remap", "full path");
1646 nlinfo ("PATH: ----------------------------------------------------");
1647 if (_MemoryCompressed)
1649 for (uint i = 0; i < _MCFiles.size(); ++i)
1651 const CMCFileEntry &fe = _MCFiles[i];
1652 string ext = SSMext.get(fe.idExt);
1653 string path = SSMpath.get(fe.idPath);
1654 nlinfo ("PATH: %-25s %-5s %-5d %s", fe.Name, ext.c_str(), fe.Remapped, path.c_str());
1657 else
1659 for (TFiles::iterator it = _Files.begin(); it != _Files.end (); it++)
1661 string ext = SSMext.get((*it).second.idExt);
1662 string path = SSMpath.get((*it).second.idPath);
1663 nlinfo ("PATH: %-25s %-5s %-5d %s", (*it).first.c_str(), ext.c_str(), (*it).second.Remapped, path.c_str());
1666 nlinfo ("PATH: ");
1667 nlinfo ("PATH: Contents of the alternative directory:");
1668 for (uint i = 0; i < _AlternativePaths.size(); i++)
1670 nlinfo ("PATH: '%s'", _AlternativePaths[i].c_str ());
1672 nlinfo ("PATH: ");
1673 nlinfo ("PATH: Contents of the remapped entension table:");
1674 for (uint j = 0; j < _Extensions.size(); j++)
1676 nlinfo ("PATH: '%s' -> '%s'", _Extensions[j].first.c_str (), _Extensions[j].second.c_str ());
1678 nlinfo ("PATH: End of display");
1681 void CPath::removeBigFiles(const std::vector<std::string> &bnpFilenames)
1683 getInstance()->_FileContainer.removeBigFiles(bnpFilenames);
1686 void CFileContainer::removeBigFiles(const std::vector<std::string> &bnpFilenames)
1688 nlassert(!isMemoryCompressed());
1689 CHashSet<TSStringId> bnpStrIds;
1690 TFiles::iterator fileIt, fileCurrIt;
1691 for (uint k = 0; k < bnpFilenames.size(); ++k)
1693 std::string completeBNPName = toLowerAscii(bnpFilenames[k]) + "@";
1694 if (SSMpath.isAdded(completeBNPName))
1696 bnpStrIds.insert(SSMpath.add(completeBNPName));
1698 CBigFile::getInstance().remove(bnpFilenames[k]);
1699 fileIt = _Files.find(toLowerAscii(bnpFilenames[k]));
1700 if (fileIt != _Files.end())
1702 _Files.erase(fileIt);
1705 if (bnpStrIds.empty()) return;
1706 // remove remapped files
1707 std::map<std::string, std::string>::iterator remapIt, remapCurrIt;
1708 for(remapIt = _RemappedFiles.begin(); remapIt != _RemappedFiles.end();)
1710 remapCurrIt = remapIt;
1711 ++ remapIt;
1712 const std::string &filename = remapCurrIt->second;
1713 fileIt = _Files.find(filename);
1714 if (fileIt != _Files.end())
1716 if (bnpStrIds.count(fileIt->second.idPath))
1718 _Files.erase(fileIt);
1719 _RemappedFiles.erase(remapCurrIt);
1723 // remove file entries
1724 for(fileIt = _Files.begin(); fileIt != _Files.end();)
1726 fileCurrIt = fileIt;
1727 ++ fileIt;
1728 if (bnpStrIds.count(fileCurrIt->second.idPath))
1730 _Files.erase(fileCurrIt);
1738 void CPath::memoryCompress()
1740 getInstance()->_FileContainer.memoryCompress();
1743 void CFileContainer::memoryCompress()
1746 SSMext.memoryCompress();
1747 SSMpath.memoryCompress();
1748 uint nDbg = (uint)_Files.size();
1749 uint nDbg2 = SSMext.getCount();
1750 uint nDbg3 = SSMpath.getCount();
1751 nlinfo ("PATH: Number of file: %d, extension: %d, path: %d", nDbg, nDbg2, nDbg3);
1753 // Convert from _Files to _MCFiles
1754 uint nSize = 0, nNb = 0;
1755 TFiles::iterator it = _Files.begin();
1756 while (it != _Files.end())
1758 string sTmp = SSMpath.get(it->second.idPath);
1759 if ((sTmp.find("@@") == string::npos) && (sTmp.find('@') != string::npos) && (sTmp.find("snp@") == string::npos) && !it->second.Remapped)
1761 // This is a file included in a bigfile (so the name is in the bigfile manager)
1763 else
1765 nSize += (uint)it->second.Name.size() + 1;
1767 nNb++;
1768 it++;
1771 _AllFileNames = new char[nSize];
1772 memset(_AllFileNames, 0, nSize);
1773 _MCFiles.resize(nNb);
1775 it = _Files.begin();
1776 nSize = 0;
1777 nNb = 0;
1778 while (it != _Files.end())
1780 CFileEntry &rFE = it->second;
1781 string sTmp = SSMpath.get(rFE.idPath);
1782 if ((sTmp.find("@") == string::npos) || (sTmp.find("@@") != string::npos) || (sTmp.find("snp@") != string::npos) || rFE.Remapped)
1784 strcpy(_AllFileNames+nSize, rFE.Name.c_str());
1785 _MCFiles[nNb].Name = _AllFileNames+nSize;
1786 nSize += (uint)rFE.Name.size() + 1;
1788 else
1790 // This is a file included in a bigfile (so the name is in the bigfile manager)
1791 sTmp = sTmp.substr(0, sTmp.size()-1);
1792 _MCFiles[nNb].Name = CBigFile::getInstance().getFileNamePtr(rFE.Name, sTmp);
1793 if (_MCFiles[nNb].Name == NULL)
1795 nlerror("memoryCompress: failed to find named file in big file: %s",SSMpath.get(rFE.idPath));
1799 _MCFiles[nNb].idExt = rFE.idExt;
1800 _MCFiles[nNb].idPath = rFE.idPath;
1801 _MCFiles[nNb].Remapped = rFE.Remapped;
1803 nNb++;
1804 it++;
1807 contReset(_Files);
1808 _MemoryCompressed = true;
1811 void CPath::memoryUncompress()
1813 getInstance()->_FileContainer.memoryUncompress();
1816 void CFileContainer::memoryUncompress()
1818 SSMext.memoryUncompress();
1819 SSMpath.memoryUncompress();
1820 for(std::vector<CMCFileEntry>::iterator it = _MCFiles.begin(); it != _MCFiles.end(); ++it)
1822 CFileEntry fe;
1823 fe.Name = it->Name;
1824 fe.idExt = it->idExt;
1825 fe.idPath = it->idPath;
1826 fe.Remapped = it->Remapped;
1828 _Files[toLowerAscii(CFile::getFilename(fe.Name))] = fe;
1830 contReset(_MCFiles);
1831 _MemoryCompressed = false;
1834 std::string CPath::getWindowsDirectory()
1836 return getInstance()->_FileContainer.getWindowsDirectory();
1839 std::string CFileContainer::getWindowsDirectory()
1841 #ifndef NL_OS_WINDOWS
1842 nlwarning("not a ms windows platform");
1843 return "";
1844 #else
1845 wchar_t winDir[MAX_PATH];
1846 UINT numChar = GetWindowsDirectoryW(winDir, MAX_PATH);
1847 if (numChar > MAX_PATH || numChar == 0)
1849 nlwarning("Couldn't retrieve windows directory");
1850 return "";
1852 return CPath::standardizePath(wideToUtf8(winDir));
1853 #endif
1856 std::string CPath::getApplicationDirectory(const std::string &appName, bool local)
1858 return getInstance()->_FileContainer.getApplicationDirectory(appName, local);
1861 std::string CFileContainer::getApplicationDirectory(const std::string &appName, bool local)
1863 static std::string appPaths[2];
1865 std::string &appPath = appPaths[local ? 1 : 0];
1867 if (appPath.empty())
1869 #ifdef NL_OS_WINDOWS
1870 wchar_t buffer[MAX_PATH];
1871 #ifdef CSIDL_LOCAL_APPDATA
1872 if (local)
1874 SHGetSpecialFolderPathW(NULL, buffer, CSIDL_LOCAL_APPDATA, TRUE);
1876 else
1877 #endif
1879 SHGetSpecialFolderPathW(NULL, buffer, CSIDL_APPDATA, TRUE);
1881 appPath = CPath::standardizePath(wideToUtf8(buffer));
1882 #else
1883 // get user home directory from HOME environment variable
1884 const char* homePath = getenv("HOME");
1885 appPath = CPath::standardizePath(homePath ? homePath : ".");
1887 #if defined(NL_OS_MAC)
1888 appPath += "Library/Application Support/";
1889 #else
1890 // recommended for applications data that are owned by user
1891 appPath += ".local/share/";
1892 #endif
1893 #endif
1896 return CPath::standardizePath(appPath + appName);
1899 std::string CPath::getTemporaryDirectory()
1901 return getInstance()->_FileContainer.getTemporaryDirectory();
1904 std::string CFileContainer::getTemporaryDirectory()
1906 static std::string path;
1907 if (path.empty())
1909 const char *temp = getenv("TEMP");
1910 const char *tmp = getenv("TMP");
1912 std::string tempDir;
1914 if (temp)
1915 tempDir = temp;
1917 if (tempDir.empty() && tmp)
1918 tempDir = tmp;
1920 #ifdef NL_OS_UNIX
1921 if (tempDir.empty())
1922 tempDir = "/tmp";
1923 #else
1924 if (tempDir.empty())
1925 tempDir = ".";
1926 #endif
1928 path = CPath::standardizePath(tempDir);
1931 return path;
1934 //////////////////////////////////////////////////////////////////////////////////////////////////////
1935 //////////////////////////////////////////////////////////////////////////////////////////////////////
1936 //////////////////////////////////////////////////////////////////////////////////////////////////////
1937 //////////////////////////////////////////////////////////////////////////////////////////////////////
1938 //////////////////////////////////////////////////////////////////////////////////////////////////////
1940 std::string::size_type CFile::getLastSeparator (const string &filename)
1942 string::size_type pos = filename.find_last_of ('/');
1943 if (pos == string::npos)
1945 pos = filename.find_last_of ('\\');
1946 if (pos == string::npos)
1948 pos = filename.find_last_of ('@');
1951 return pos;
1954 string CFile::getFilename (const string &filename)
1956 string::size_type pos = CFile::getLastSeparator(filename);
1957 if (pos != string::npos)
1958 return filename.substr (pos + 1);
1959 else
1960 return filename;
1963 string CFile::getFilenameWithoutExtension (const string &filename)
1965 string filename2 = getFilename (filename);
1966 string::size_type pos = filename2.find_last_of ('.');
1967 if (pos == string::npos)
1968 return filename2;
1969 else
1970 return filename2.substr (0, pos);
1973 string CFile::getExtension (const string &filename)
1975 string::size_type pos = filename.find_last_of ('.');
1976 if (pos == string::npos)
1977 return "";
1978 else
1979 return filename.substr (pos + 1);
1982 string CFile::getPath (const string &filename)
1984 string::size_type pos = CFile::getLastSeparator(filename);
1985 if (pos != string::npos)
1986 return filename.substr (0, pos + 1);
1987 else
1988 return "";
1991 bool CFile::isDirectory (const string &filename)
1993 #ifdef NL_OS_WINDOWS
1994 DWORD res = GetFileAttributesW(nlUtf8ToWide(filename));
1995 if (res == INVALID_FILE_ATTRIBUTES)
1997 // nlwarning ("PATH: '%s' is not a valid file or directory name", filename.c_str ());
1998 return false;
2000 return (res & FILE_ATTRIBUTE_DIRECTORY) != 0;
2001 #else // NL_OS_WINDOWS
2002 struct stat buf;
2003 int res = stat (filename.c_str (), &buf);
2004 if (res == -1)
2006 // 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
2007 // nlwarning ("PATH: can't stat '%s' error %d '%s'", filename.c_str(), errno, strerror(errno));
2008 return false;
2010 return (buf.st_mode & S_IFDIR) != 0;
2011 #endif // NL_OS_WINDOWS
2014 bool CFile::isExists (const string &filename)
2016 #ifdef NL_OS_WINDOWS
2017 return GetFileAttributesW(nlUtf8ToWide(filename)) != INVALID_FILE_ATTRIBUTES;
2018 #else // NL_OS_WINDOWS
2019 struct stat buf;
2020 return stat (filename.c_str (), &buf) == 0;
2021 #endif // NL_OS_WINDOWS
2024 bool CFile::createEmptyFile (const std::string& filename)
2026 FILE *file = nlfopen (filename, "wb");
2028 if (file)
2030 fclose (file);
2031 return true;
2034 return false;
2037 bool CFile::fileExists (const string& filename)
2039 //H_AUTO(FileExists);
2040 FILE *file = nlfopen(filename, "rb");
2042 if (file)
2044 fclose(file);
2045 return true;
2048 return false;
2052 string CFile::findNewFile (const string &filename)
2054 string::size_type pos = filename.find_last_of ('.');
2055 if (pos == string::npos)
2056 return filename;
2058 string start = filename.substr (0, pos);
2059 string end = filename.substr (pos);
2061 uint num = 0;
2062 char numchar[4];
2063 string npath;
2066 npath = start;
2067 smprintf(numchar,4,"%03d",num++);
2068 npath += numchar;
2069 npath += end;
2070 if (!CFile::fileExists(npath)) break;
2072 while (num<999);
2073 return npath;
2076 // \warning doesn't work with big file
2077 uint32 CFile::getFileSize (const std::string &filename)
2079 std::string::size_type pos;
2080 if (filename.find("@@") != string::npos)
2082 uint32 fs = 0, bfo;
2083 bool c, d;
2084 CXMLPack::getInstance().getFile (filename, fs, bfo, c, d);
2085 return fs;
2087 else if ((pos = filename.find('@')) != string::npos)
2089 if (pos > 3 && filename[pos-3] == 's' && filename[pos-2] == 'n' && filename[pos-1] == 'p')
2091 uint32 fs = 0;
2092 CStreamedPackageManager::getInstance().getFileSize (fs, filename.substr(pos+1));
2093 return fs;
2095 else
2097 uint32 fs = 0, bfo;
2098 bool c, d;
2099 CBigFile::getInstance().getFile (filename, fs, bfo, c, d);
2100 return fs;
2103 else
2105 #if defined (NL_OS_WINDOWS)
2106 struct _stat buf;
2107 int result = _wstat(nlUtf8ToWide(filename), &buf);
2108 #elif defined (NL_OS_UNIX)
2109 struct stat buf;
2110 int result = stat (filename.c_str (), &buf);
2111 #endif
2112 if (result != 0) return 0;
2113 else return buf.st_size;
2117 uint32 CFile::getFileSize (FILE *f)
2119 #if defined (NL_OS_WINDOWS)
2120 struct _stat buf;
2121 int result = _fstat (fileno(f), &buf);
2122 #elif defined (NL_OS_UNIX)
2123 struct stat buf;
2124 int result = fstat (fileno(f), &buf);
2125 #endif
2126 if (result != 0) return 0;
2127 else return buf.st_size;
2130 uint32 CFile::getFileModificationDate(const std::string &filename)
2132 string::size_type pos;
2133 string fn;
2134 if ((pos=filename.find("@@")) != string::npos)
2136 fn = filename.substr (0, pos);
2138 else if ((pos=filename.find('@')) != string::npos)
2140 fn = CPath::lookup(filename.substr (0, pos));
2142 else
2144 fn = filename;
2147 #if defined (NL_OS_WINDOWS)
2148 // struct _stat buf;
2149 // int result = _stat (fn.c_str (), &buf);
2150 // Changed 06-06-2007 : boris : _stat have an incoherent and hard to reproduce
2151 // on windows : if the system clock is adjusted according to daylight saving
2152 // time, the file date reported by _stat may (not always!) be adjusted by 3600s
2153 // This is a bad behavior because file time should always be reported as UTC time value
2155 // Use the WIN32 API to read the file times in UTC
2157 // create a file handle (this does not open the file)
2158 HANDLE h = CreateFileW(nlUtf8ToWide(fn), 0, 0, NULL, OPEN_EXISTING, 0, 0);
2159 if (h == INVALID_HANDLE_VALUE)
2161 nlwarning("Can't get modification date on file '%s' : %s", fn.c_str(), NLMISC::formatErrorMessage(NLMISC::getLastError()).c_str());
2162 return 0;
2164 FILETIME creationTime;
2165 FILETIME accesstime;
2166 FILETIME modTime;
2168 // get the files times
2169 BOOL res = GetFileTime(h, &creationTime, &accesstime, &modTime);
2170 if (res == 0)
2172 nlwarning("Can't get modification date on file '%s' : %s", fn.c_str(), NLMISC::formatErrorMessage(NLMISC::getLastError()).c_str());
2173 CloseHandle(h);
2174 return 0;
2176 // close the handle
2177 CloseHandle(h);
2179 // win32 file times are in 10th of micro sec (100ns resolution), starting at jan 1, 1601
2180 // hey Mr Gates, why 1601 ?
2182 // first, convert it into second since jan1, 1601
2183 uint64 t = modTime.dwLowDateTime | (uint64(modTime.dwHighDateTime)<<32);
2185 // adjust time base to unix epoch base
2186 t -= CTime::getWindowsToUnixBaseTimeOffset();
2188 // convert the resulting time into seconds
2189 t /= 10; // microsec
2190 t /= 1000; // millisec
2191 t /= 1000; // sec
2193 // return the resulting time
2194 return uint32(t);
2196 #elif defined (NL_OS_UNIX)
2197 struct stat buf;
2198 int result = stat (fn.c_str (), &buf);
2199 if (result != 0)
2201 nlwarning("Can't get modification date on file '%s' : %s", fn.c_str(), NLMISC::formatErrorMessage(NLMISC::getLastError()).c_str());
2202 return 0;
2204 else
2205 return (uint32)buf.st_mtime;
2206 #endif
2210 bool CFile::setFileModificationDate(const std::string &filename, uint32 modTime)
2212 string::size_type pos;
2213 string fn;
2214 if ((pos=filename.find('@')) != string::npos)
2216 fn = CPath::lookup(filename.substr (0, pos));
2218 else
2220 fn = filename;
2223 #if defined (NL_OS_WINDOWS)
2225 // Use the WIN32 API to set the file times in UTC
2227 // create a file handle (this does not open the file)
2228 HANDLE h = CreateFileW(nlUtf8ToWide(fn), GENERIC_WRITE | GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
2229 if (h == INVALID_HANDLE_VALUE)
2231 nlwarning("Can't set modification date on file '%s' (error accessing file) : %s", fn.c_str(), NLMISC::formatErrorMessage(NLMISC::getLastError()).c_str());
2232 return false;
2234 FILETIME creationFileTime;
2235 FILETIME accessFileTime;
2236 FILETIME modFileTime;
2238 // read the current file time
2239 if (GetFileTime(h, &creationFileTime, &accessFileTime, &modFileTime) == 0)
2241 nlwarning("Can't set modification date on file '%s' : %s", fn.c_str(), formatErrorMessage(getLastError()).c_str());
2242 CloseHandle(h);
2243 return false;
2246 // win32 file times are in 10th of micro sec (100ns resolution), starting at jan 1, 1601
2247 // hey Mr Gates, why 1601 ?
2249 // convert the unix time into a windows file time
2250 uint64 t = modTime;
2251 // convert to 10th of microsec
2252 t *= 1000; // millisec
2253 t *= 1000; // microsec
2254 t *= 10; // 10th of micro sec (rez of windows file time is 100ns <=> 1/10 us
2256 // apply the windows to unix base time offset
2257 t += CTime::getWindowsToUnixBaseTimeOffset();
2259 // update the windows modTime structure
2260 modFileTime.dwLowDateTime = uint32(t & 0xffffffff);
2261 modFileTime.dwHighDateTime = uint32(t >> 32);
2263 // update the file time on disk
2264 BOOL rez = SetFileTime(h, &creationFileTime, &accessFileTime, &modFileTime);
2265 if (rez == 0)
2267 nlwarning("Can't set modification date on file '%s': %s", fn.c_str(), formatErrorMessage(getLastError()).c_str());
2269 CloseHandle(h);
2270 return false;
2273 // close the handle
2274 CloseHandle(h);
2276 return true;
2278 #elif defined (NL_OS_UNIX)
2279 // first, read the current time of the file
2280 struct stat buf;
2281 int result = stat (fn.c_str (), &buf);
2282 if (result != 0)
2283 return false;
2285 // prepare the new time to apply
2286 utimbuf tb;
2287 tb.actime = buf.st_atime;
2288 tb.modtime = modTime;
2289 // set eh new time
2290 int res = utime(fn.c_str(), &tb);
2291 if (res == -1)
2292 nlwarning("Can't set modification date on file '%s': %s", fn.c_str(), formatErrorMessage(getLastError()).c_str());
2293 return res != -1;
2294 #endif
2298 uint32 CFile::getFileCreationDate(const std::string &filename)
2300 string::size_type pos;
2301 string fn;
2302 if ((pos=filename.find('@')) != string::npos)
2304 fn = CPath::lookup(filename.substr (0, pos));
2306 else
2308 fn = filename;
2311 #if defined (NL_OS_WINDOWS)
2312 struct _stat buf;
2313 int result = _wstat(nlUtf8ToWide(fn), &buf);
2314 #elif defined (NL_OS_UNIX)
2315 struct stat buf;
2316 int result = stat(fn.c_str (), &buf);
2317 #endif
2319 if (result != 0) return 0;
2320 else return (uint32)buf.st_ctime;
2323 struct CFileEntry
2325 CFileEntry (const string &filename, void (*callback)(const string &filename)) : FileName (filename), Callback (callback)
2327 LastModified = CFile::getFileModificationDate(filename);
2329 string FileName;
2330 void (*Callback)(const string &filename);
2331 uint32 LastModified;
2334 static vector <CFileEntry> FileToCheck;
2336 void CFile::removeFileChangeCallback (const std::string &filename)
2338 string fn = CPath::lookup(filename, false, false);
2339 if (fn.empty())
2341 fn = filename;
2343 for (uint i = 0; i < FileToCheck.size(); i++)
2345 if(FileToCheck[i].FileName == fn)
2347 nlinfo ("PATH: CFile::removeFileChangeCallback: '%s' is removed from checked files modification", fn.c_str());
2348 FileToCheck.erase(FileToCheck.begin()+i);
2349 return;
2354 void CFile::addFileChangeCallback (const std::string &filename, void (*cb)(const string &filename))
2356 string fn = CPath::lookup(filename, false, false);
2357 if (fn.empty())
2359 fn = filename;
2361 nlinfo ("PATH: CFile::addFileChangeCallback: I'll check the modification date for this file '%s'", fn.c_str());
2362 FileToCheck.push_back(CFileEntry(fn, cb));
2365 void CFile::checkFileChange (TTime frequency)
2367 static TTime lastChecked = CTime::getLocalTime();
2369 if (CTime::getLocalTime() > lastChecked + frequency)
2371 for (uint i = 0; i < FileToCheck.size(); i++)
2373 if(CFile::getFileModificationDate(FileToCheck[i].FileName) != FileToCheck[i].LastModified)
2375 // need to reload it
2376 if(FileToCheck[i].Callback != NULL)
2377 FileToCheck[i].Callback(FileToCheck[i].FileName);
2379 FileToCheck[i].LastModified = CFile::getFileModificationDate(FileToCheck[i].FileName);
2383 lastChecked = CTime::getLocalTime();
2387 static bool CopyMoveFile(const std::string &dest, const std::string &src, bool copyFile, bool failIfExists = false, IProgressCallback *progress = NULL)
2389 if (dest.empty() || src.empty()) return false;
2390 std::string sdest = CPath::standardizePath(dest,false);
2391 std::string ssrc = CPath::standardizePath(src,false);
2393 if (progress) progress->progress(0.f);
2394 if(copyFile)
2396 uint32 totalSize = 0;
2397 uint32 readSize = 0;
2398 if (progress)
2400 totalSize = CFile::getFileSize(ssrc);
2402 FILE *fp1 = nlfopen(ssrc, "rb");
2403 if (fp1 == NULL)
2405 nlwarning ("PATH: CopyMoveFile error: can't fopen in read mode '%s'", ssrc.c_str());
2406 return false;
2408 FILE *fp2 = nlfopen(sdest, "wb");
2409 if (fp2 == NULL)
2411 nlwarning ("PATH: CopyMoveFile error: can't fopen in read write mode '%s'", sdest.c_str());
2412 return false;
2414 static char buffer [1000];
2415 size_t s;
2417 s = fread(buffer, 1, sizeof(buffer), fp1);
2418 while (s != 0)
2420 if (progress)
2422 readSize += (uint32)s;
2423 progress->progress((float) readSize / totalSize);
2425 size_t ws = fwrite(buffer, s, 1, fp2);
2426 if (ws != 1)
2428 nlwarning("Error copying '%s' to '%s', trying to write %u bytes failed.",
2429 ssrc.c_str(),
2430 sdest.c_str(),
2432 fclose(fp1);
2433 fclose(fp2);
2434 nlwarning("Errno = %d", errno);
2435 return false;
2437 s = fread(buffer, 1, sizeof(buffer), fp1);
2440 fclose(fp1);
2441 fclose(fp2);
2442 if (progress) progress->progress(1.f);
2444 else
2446 #ifdef NL_OS_WINDOWS
2447 if (MoveFileW(nlUtf8ToWide(ssrc), nlUtf8ToWide(sdest)) == 0)
2449 sint lastError = NLMISC::getLastError();
2450 nlwarning ("PATH: CopyMoveFile error: can't link/move '%s' into '%s', error %u (%s)",
2451 ssrc.c_str(),
2452 sdest.c_str(),
2453 lastError,
2454 NLMISC::formatErrorMessage(lastError).c_str());
2456 return false;
2458 #else
2459 if (rename (ssrc.c_str(), sdest.c_str()) == -1)
2461 // unable to move because file systems are different
2462 if (errno == EXDEV)
2464 // different file system, we need to copy and delete file manually
2465 if (!CopyMoveFile(dest, src, true, failIfExists, progress)) return false;
2467 // get modification time
2468 uint32 modificationTime = CFile::getFileModificationDate(src);
2470 // delete original file
2471 if (!CFile::deleteFile(src)) return false;
2473 // set same modification time
2474 if (!CFile::setFileModificationDate(dest, modificationTime))
2476 nlwarning("Unable to set modification time %s (%u) for %s", timestampToHumanReadable(modificationTime).c_str(), modificationTime, dest.c_str());
2479 else
2481 nlwarning("PATH: CopyMoveFile error: can't rename '%s' into '%s', error %u",
2482 ssrc.c_str(),
2483 sdest.c_str(),
2484 errno);
2486 return false;
2489 #endif
2491 if (progress) progress->progress(1.f);
2492 return true;
2495 bool CFile::copyFile(const std::string &dest, const std::string &src, bool failIfExists /*=false*/, IProgressCallback *progress)
2497 return CopyMoveFile(dest, src, true, failIfExists, progress);
2500 bool CFile::quickFileCompare(const std::string &fileName0, const std::string &fileName1)
2502 // make sure the files both exist
2503 if (!fileExists(fileName0) || !fileExists(fileName1))
2504 return false;
2506 // compare time stamps
2507 if (getFileModificationDate(fileName0) != getFileModificationDate(fileName1))
2508 return false;
2510 // compare file sizes
2511 if (getFileSize(fileName0) != getFileSize(fileName1))
2512 return false;
2514 // everything matched so return true
2515 return true;
2518 bool CFile::thoroughFileCompare(const std::string &fileName0, const std::string &fileName1,uint32 maxBufSize)
2520 // make sure the files both exist
2521 if (!fileExists(fileName0) || !fileExists(fileName1))
2522 return false;
2524 // setup the size variable from file length of first file
2525 uint32 fileSize=getFileSize(fileName0);
2527 // compare file sizes
2528 if (fileSize != getFileSize(fileName1))
2529 return false;
2531 // allocate a couple of data buffers for our 2 files
2532 uint32 bufSize= maxBufSize/2;
2533 nlassert(sint32(bufSize)>0);
2534 std::vector<uint8> buf0(bufSize);
2535 std::vector<uint8> buf1(bufSize);
2537 // open the two files for input
2538 CIFile file0(fileName0);
2539 CIFile file1(fileName1);
2541 for (uint32 i=0;i<fileSize;i+=bufSize)
2543 // for the last block in the file reduce buf size to represent the amount of data left in file
2544 if (i+bufSize>fileSize)
2546 bufSize= fileSize-i;
2547 buf0.resize(bufSize);
2548 buf1.resize(bufSize);
2551 // read in the next data block from disk
2552 file0.serialBuffer(&buf0[0], bufSize);
2553 file1.serialBuffer(&buf1[0], bufSize);
2555 // compare the contents of the 2 data buffers
2556 if (buf0!=buf1)
2557 return false;
2560 // everything matched so return true
2561 return true;
2564 bool CFile::moveFile(const std::string &dest, const std::string &src)
2566 return CopyMoveFile(dest, src, false);
2569 bool CFile::createDirectory(const std::string &filename)
2571 #ifdef NL_OS_WINDOWS
2572 return _wmkdir(nlUtf8ToWide(filename)) == 0;
2573 #else
2574 // set rwxrwxr-x permissions
2575 return mkdir(filename.c_str(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IXOTH)==0;
2576 #endif
2579 bool CFile::createDirectoryTree(const std::string &filename)
2581 bool lastResult=true;
2582 uint32 i=0;
2584 // skip dos drive name eg "a:"
2585 if (filename.size()>1 && filename[1]==':')
2586 i=2;
2588 // iterate over the set of directories in the routine's argument
2589 while (i<filename.size())
2591 // skip passed leading slashes
2592 for (;i<filename.size();++i)
2593 if (filename[i]!='\\' && filename[i]!='/')
2594 break;
2596 // if the file name ended with a '/' then there's no extra directory to create
2597 if (i==filename.size())
2598 break;
2600 // skip forwards to next slash
2601 for (;i<filename.size();++i)
2602 if (filename[i]=='\\' || filename[i]=='/')
2603 break;
2605 // try to create directory
2606 std::string s= filename.substr(0,i);
2607 lastResult= createDirectory(s);
2610 return lastResult;
2613 bool CPath::makePathRelative (const std::string &basePath, std::string &relativePath)
2615 // Standard path with final slash
2616 string tmp = standardizePath (basePath, true);
2617 string src = standardizePath (relativePath, true);
2618 string prefix;
2620 for(;;)
2622 // Compare with relativePath
2623 if (strncmp (tmp.c_str (), src.c_str (), tmp.length ()) == 0)
2625 // Truncate
2626 uint size = (uint)tmp.length ();
2628 // Same path ?
2629 if (size == src.length ())
2631 relativePath = ".";
2632 return true;
2635 relativePath = prefix+relativePath.substr (size, relativePath.length () - size);
2636 return true;
2639 // Too small ?
2640 if (tmp.length ()<2)
2641 break;
2643 // Remove last directory
2644 string::size_type lastPos = tmp.rfind ('/', tmp.length ()-2);
2645 string::size_type previousPos = tmp.find ('/');
2646 if ((lastPos == previousPos) || (lastPos == string::npos))
2647 break;
2649 // Troncate
2650 tmp = tmp.substr (0, lastPos+1);
2652 // New prefix
2653 prefix += "../";
2656 return false;
2659 std::string CPath::makePathAbsolute( const std::string &relativePath, const std::string &directory, bool simplify )
2661 if( relativePath.empty() )
2662 return "";
2663 if( directory.empty() )
2664 return "";
2666 std::string absolutePath;
2668 #ifdef NL_OS_WINDOWS
2669 // Windows network address. Eg.: \\someshare\path
2670 if ((relativePath[0] == '\\') && (relativePath[1] == '\\'))
2672 absolutePath = relativePath;
2675 // Normal Windows absolute path. Eg.: C:\something
2677 else if (isalpha(relativePath[0]) && (relativePath[1] == ':') && ((relativePath[2] == '\\') || (relativePath[2] == '/')))
2679 absolutePath = relativePath;
2681 else
2682 #endif
2683 // Unix filesystem absolute path (works also under Windows)
2684 if (relativePath[0] == '/')
2686 absolutePath = relativePath;
2688 else
2690 // Add a slash to the directory if necessary.
2691 // If the relative path starts with dots we need a slash.
2692 // If the relative path starts with a slash we don't.
2693 // If it starts with neither, we need a slash.
2694 bool needSlash = true;
2695 char c = relativePath[0];
2696 if ((c == '\\') || (c == '/'))
2697 needSlash = false;
2699 bool hasSlash = false;
2700 absolutePath = directory;
2701 c = absolutePath[absolutePath.size() - 1];
2702 if ((c == '\\') || (c == '/'))
2703 hasSlash = true;
2705 if (needSlash && !hasSlash)
2706 absolutePath += '/';
2707 else
2708 if (hasSlash && !needSlash)
2709 absolutePath.resize(absolutePath.size() - 1);
2711 // Now build the new absolute path
2712 absolutePath += relativePath;
2715 absolutePath = standardizePath(absolutePath, true);
2717 if (simplify)
2719 // split all components path to manage parent directories
2720 std::vector<std::string> tokens;
2721 explode(absolutePath, std::string("/"), tokens, false);
2723 std::vector<std::string> directoryParts;
2725 // process all components
2726 for(uint i = 0, len = tokens.size(); i < len; ++i)
2728 std::string token = tokens[i];
2730 // current directory
2731 if (token != ".")
2733 // parent directory
2734 if (token == "..")
2736 // remove last directory
2737 directoryParts.pop_back();
2739 else
2741 // append directory
2742 directoryParts.push_back(token);
2747 if (!directoryParts.empty())
2749 absolutePath = directoryParts[0];
2751 // rebuild the whole absolute path
2752 for(uint i = 1, len = directoryParts.size(); i < len; ++i)
2754 if (!directoryParts[i].empty())
2755 absolutePath += "/" + directoryParts[i];
2758 // add trailing slash
2759 absolutePath += "/";
2761 else
2763 // invalid path
2764 absolutePath.clear();
2768 return absolutePath;
2771 bool CPath::isAbsolutePath(const std::string &path)
2773 if (path.empty()) return false;
2775 #ifdef NL_OS_WINDOWS
2776 // Windows root of current disk. Eg.: "\" or
2777 // Windows network address. Eg.: \\someshare\path
2778 if (path[0] == '\\') return true;
2780 // Normal Windows absolute path. Eg.: C:\something
2781 if (path.length() > 2 && isalpha(path[0]) && (path[1] == ':' ) && ((path[2] == '\\') || (path[2] == '/' ))) return true;
2782 #endif
2784 // Unix filesystem absolute path (works also under Windows)
2785 if (path[0] == '/') return true;
2787 return false;
2790 bool CFile::setRWAccess(const std::string &filename)
2792 #ifdef NL_OS_WINDOWS
2793 std::wstring wideFile = NLMISC::utf8ToWide(filename);
2795 // if the file exists and there's no write access
2796 if (_waccess (wideFile.c_str(), 00) == 0 && _waccess (wideFile.c_str(), 06) == -1)
2798 // try to set the read/write access
2799 if (_wchmod (wideFile.c_str(), _S_IREAD | _S_IWRITE) == -1)
2801 if (INelContext::getInstance().getAlreadyCreateSharedAmongThreads())
2803 nlwarning ("PATH: Can't set RW access to file '%s': %d %s", filename.c_str(), errno, strerror(errno));
2805 return false;
2808 #else
2809 // if the file exists and there's no write access
2810 if (access (filename.c_str(), F_OK) == 0)
2812 // try to set the read/write access
2813 // rw-rw-r--
2814 mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH;
2816 // set +x only for directory
2817 // rwxrwxr-x
2818 if (CFile::isDirectory(filename))
2820 mode |= S_IXUSR|S_IXGRP|S_IXOTH;
2823 if (chmod (filename.c_str(), mode) == -1)
2825 if (INelContext::getInstance().getAlreadyCreateSharedAmongThreads())
2827 nlwarning ("PATH: Can't set RW access to file '%s': %d %s", filename.c_str(), errno, strerror(errno));
2829 return false;
2832 #endif
2833 else
2835 if (INelContext::getInstance().getAlreadyCreateSharedAmongThreads())
2837 nlwarning("PATH: Can't access to file '%s'", filename.c_str());
2839 // return false;
2841 return true;
2844 bool CFile::setExecutable(const std::string &filename)
2846 #ifndef NL_OS_WINDOWS
2847 struct stat buf;
2848 if (stat(filename.c_str (), &buf) == 0)
2850 mode_t mode = buf.st_mode | S_IXUSR | S_IXGRP | S_IXOTH;
2851 if (chmod(filename.c_str(), mode) == -1)
2853 if (INelContext::getInstance().getAlreadyCreateSharedAmongThreads())
2855 nlwarning ("PATH: Can't set +x flag on file '%s': %d %s", filename.c_str(), errno, strerror(errno));
2857 return false;
2860 else
2862 if (INelContext::getInstance().getAlreadyCreateSharedAmongThreads())
2864 nlwarning("PATH: Can't access file '%s': %d %s", filename.c_str(), errno, strerror(errno));
2866 return false;
2868 #endif
2869 return true;
2872 bool CFile::deleteFile(const std::string &filename)
2874 setRWAccess(filename);
2875 #ifdef NL_OS_WINDOWS
2876 sint res = _wunlink(nlUtf8ToWide(filename));
2877 #else
2878 sint res = unlink(filename.c_str());
2879 #endif
2880 if (res == -1)
2882 if (INelContext::getInstance().getAlreadyCreateSharedAmongThreads())
2884 nlwarning ("PATH: Can't delete file '%s': (errno %d) %s", filename.c_str(), errno, strerror(errno));
2886 return false;
2888 return true;
2891 bool CFile::deleteDirectory(const std::string &filename)
2893 setRWAccess(filename);
2894 #ifdef NL_OS_WINDOWS
2895 sint res = _wrmdir(nlUtf8ToWide(filename));
2896 #else
2897 sint res = rmdir(filename.c_str());
2898 #endif
2899 if (res == -1)
2901 nlwarning ("PATH: Can't delete directory '%s': (errno %d) %s", filename.c_str(), errno, strerror(errno));
2902 return false;
2904 return true;
2907 void CFile::getTemporaryOutputFilename (const std::string &originalFilename, std::string &tempFilename)
2909 uint i = 0;
2911 tempFilename = originalFilename+".tmp"+toString (i++);
2912 while (CFile::isExists(tempFilename));
2915 } // NLMISC