mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-22 18:15:31 +01:00
--HG-- branch : trunk rename : opt/cleanup/tab2spaces.sh => contrib/cleanup/tab2spaces.sh rename : opt/cleanup/whitespace.sh => contrib/cleanup/whitespace.sh rename : opt/conf_merge/README => contrib/conf_merge/README rename : opt/conf_merge/index.php => contrib/conf_merge/index.php rename : opt/conf_merge/merge.php => contrib/conf_merge/merge.php rename : doc/AuctionHouseBot.txt => docs/AuctionHouseBot.txt rename : doc/DocStructure.dox => docs/DocStructure.dox rename : doc/Doxyfile.in => docs/Doxyfile.in rename : doc/EventAI.txt => docs/EventAI.txt rename : doc/HowToScript.txt => docs/HowToScript.txt rename : doc/TextTables.txt => docs/TextTables.txt rename : doc/UnixInstall.txt => docs/UnixInstall.txt rename : externals/jemalloc/include/internal/arena.h => externals/jemalloc/jemalloc/internal/arena.h rename : externals/jemalloc/include/internal/base.h => externals/jemalloc/jemalloc/internal/base.h rename : externals/jemalloc/include/internal/chunk.h => externals/jemalloc/jemalloc/internal/chunk.h rename : externals/jemalloc/include/internal/chunk_dss.h => externals/jemalloc/jemalloc/internal/chunk_dss.h rename : externals/jemalloc/include/internal/chunk_mmap.h => externals/jemalloc/jemalloc/internal/chunk_mmap.h rename : externals/jemalloc/include/internal/chunk_swap.h => externals/jemalloc/jemalloc/internal/chunk_swap.h rename : externals/jemalloc/include/internal/ckh.h => externals/jemalloc/jemalloc/internal/ckh.h rename : externals/jemalloc/include/internal/ctl.h => externals/jemalloc/jemalloc/internal/ctl.h rename : externals/jemalloc/include/internal/extent.h => externals/jemalloc/jemalloc/internal/extent.h rename : externals/jemalloc/include/internal/hash.h => externals/jemalloc/jemalloc/internal/hash.h rename : externals/jemalloc/include/internal/huge.h => externals/jemalloc/jemalloc/internal/huge.h rename : externals/jemalloc/include/internal/jemalloc_internal.h => externals/jemalloc/jemalloc/internal/jemalloc_internal.h rename : externals/jemalloc/include/internal/jemalloc_internal.h.in => externals/jemalloc/jemalloc/internal/jemalloc_internal.h.in rename : externals/jemalloc/include/internal/mb.h => externals/jemalloc/jemalloc/internal/mb.h rename : externals/jemalloc/include/internal/mutex.h => externals/jemalloc/jemalloc/internal/mutex.h rename : externals/jemalloc/include/internal/prof.h => externals/jemalloc/jemalloc/internal/prof.h rename : externals/jemalloc/include/internal/ql.h => externals/jemalloc/jemalloc/internal/ql.h rename : externals/jemalloc/include/internal/qr.h => externals/jemalloc/jemalloc/internal/qr.h rename : externals/jemalloc/include/internal/rb.h => externals/jemalloc/jemalloc/internal/rb.h rename : externals/jemalloc/include/internal/stats.h => externals/jemalloc/jemalloc/internal/stats.h rename : externals/jemalloc/include/internal/tcache.h => externals/jemalloc/jemalloc/internal/tcache.h rename : externals/jemalloc/include/internal/totally_not_p_r_n.h => externals/jemalloc/jemalloc/internal/totally_not_p_r_n.h rename : externals/jemalloc/include/jemalloc.h => externals/jemalloc/jemalloc/jemalloc.h rename : externals/jemalloc/include/jemalloc.h.in => externals/jemalloc/jemalloc/jemalloc.h.in rename : externals/jemalloc/include/jemalloc_defs.h => externals/jemalloc/jemalloc/jemalloc_defs.h rename : externals/jemalloc/include/jemalloc_defs.h.in => externals/jemalloc/jemalloc/jemalloc_defs.h.in
1166 lines
30 KiB
C++
1166 lines
30 KiB
C++
/**
|
|
@file fileutils.cpp
|
|
|
|
@author Morgan McGuire, graphics3d.com
|
|
|
|
@author 2002-06-06
|
|
@edited 2010-02-05
|
|
*/
|
|
|
|
#include <cstring>
|
|
#include <cstdio>
|
|
#include "G3D/platform.h"
|
|
#include "G3D/fileutils.h"
|
|
#include "G3D/BinaryInput.h"
|
|
#include "G3D/BinaryOutput.h"
|
|
#include "G3D/g3dmath.h"
|
|
#include "G3D/stringutils.h"
|
|
#include "G3D/Set.h"
|
|
#include "G3D/g3dfnmatch.h"
|
|
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#if _HAVE_ZIP
|
|
#include "zip.h"
|
|
#endif
|
|
|
|
#ifdef G3D_WIN32
|
|
// Needed for _getcwd
|
|
#include <direct.h>
|
|
#include <io.h>
|
|
#else
|
|
#include <dirent.h>
|
|
#include <fnmatch.h>
|
|
#include <unistd.h>
|
|
#define _getcwd getcwd
|
|
#define _stat stat
|
|
#endif
|
|
|
|
|
|
namespace G3D {
|
|
|
|
namespace _internal {
|
|
Set<std::string> currentFilesUsed;
|
|
}
|
|
|
|
std::string pathConcat(const std::string& dirname, const std::string& file) {
|
|
// Ensure that the directory ends in a slash
|
|
if ((dirname.size() != 0) &&
|
|
(dirname[dirname.size() - 1] != '/') &&
|
|
(dirname[dirname.size() - 1] != '\\') &&
|
|
(dirname[dirname.size() - 1] != ':')) {
|
|
return dirname + '/' + file;
|
|
} else {
|
|
return dirname + file;
|
|
}
|
|
}
|
|
|
|
std::string resolveFilename(const std::string& filename) {
|
|
if (filename.size() >= 1) {
|
|
if ((filename[0] == '/') || (filename[0] == '\\')) {
|
|
// Already resolved
|
|
return filename;
|
|
} else {
|
|
|
|
#ifdef G3D_WIN32
|
|
if ((filename.size() >= 2) && (filename[1] == ':')) {
|
|
// There is a drive spec on the front.
|
|
if ((filename.size() >= 3) && ((filename[2] == '\\') ||
|
|
(filename[2] == '/'))) {
|
|
// Already fully qualified
|
|
return filename;
|
|
} else {
|
|
// The drive spec is relative to the
|
|
// working directory on that drive.
|
|
debugAssertM(false, "Files of the form d:path are"
|
|
" not supported (use a fully qualified"
|
|
" name).");
|
|
return filename;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
char buffer[1024];
|
|
|
|
// Prepend the working directory.
|
|
_getcwd(buffer, 1024);
|
|
|
|
return format("%s/%s", buffer, filename.c_str());
|
|
}
|
|
|
|
bool zipfileExists(const std::string& filename) {
|
|
std::string outZipfile;
|
|
std::string outInternalFile;
|
|
return zipfileExists(filename, outZipfile, outInternalFile);
|
|
}
|
|
|
|
std::string readWholeFile(
|
|
const std::string& filename) {
|
|
|
|
_internal::currentFilesUsed.insert(filename);
|
|
|
|
std::string s;
|
|
|
|
debugAssert(filename != "");
|
|
if (fileExists(filename, false)) {
|
|
|
|
int64 length = fileLength(filename);
|
|
|
|
char* buffer = (char*)System::alignedMalloc(length + 1, 16);
|
|
debugAssert(buffer);
|
|
FILE* f = fopen(filename.c_str(), "rb");
|
|
debugAssert(f);
|
|
int ret = fread(buffer, 1, length, f);
|
|
debugAssert(ret == length);(void)ret;
|
|
fclose(f);
|
|
|
|
buffer[length] = '\0';
|
|
s = std::string(buffer);
|
|
|
|
System::alignedFree(buffer);
|
|
|
|
} else if (zipfileExists(filename)) {
|
|
|
|
void* zipBuffer;
|
|
size_t length;
|
|
zipRead(filename, zipBuffer, length);
|
|
|
|
char* buffer = (char*)System::alignedMalloc(length + 1, 16);
|
|
System::memcpy(buffer,zipBuffer, length + 1);
|
|
zipClose(zipBuffer);
|
|
|
|
buffer[length] = '\0';
|
|
s = std::string(buffer);
|
|
System::alignedFree(buffer);
|
|
} else {
|
|
debugAssertM(false, filename + " not found");
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
|
|
void zipRead(const std::string& file,
|
|
void*& data,
|
|
size_t& length) {
|
|
std::string zip, desiredFile;
|
|
#if _HAVE_ZIP
|
|
if (zipfileExists(file, zip, desiredFile)) {
|
|
struct zip *z = zip_open( zip.c_str(), ZIP_CHECKCONS, NULL );
|
|
{
|
|
struct zip_stat info;
|
|
zip_stat_init( &info ); // TODO: Docs unclear if zip_stat_init is required.
|
|
zip_stat( z, desiredFile.c_str(), ZIP_FL_NOCASE, &info );
|
|
length = info.size;
|
|
// sets machines up to use MMX, if they want
|
|
data = System::alignedMalloc(length, 16);
|
|
struct zip_file *zf = zip_fopen( z, desiredFile.c_str(), ZIP_FL_NOCASE );
|
|
{
|
|
int test = zip_fread( zf, data, length );
|
|
debugAssertM((size_t)test == length,
|
|
desiredFile + " was corrupt because it unzipped to the wrong size.");
|
|
(void)test;
|
|
}
|
|
zip_fclose( zf );
|
|
}
|
|
zip_close( z );
|
|
} else {
|
|
data = NULL;
|
|
}
|
|
#else
|
|
data = NULL;
|
|
#endif
|
|
}
|
|
|
|
|
|
void zipClose(void* data) {
|
|
System::alignedFree(data);
|
|
}
|
|
|
|
|
|
int64 fileLength(const std::string& filename) {
|
|
struct _stat st;
|
|
int result = _stat(filename.c_str(), &st);
|
|
|
|
if (result == -1) {
|
|
#if _HAVE_ZIP
|
|
std::string zip, contents;
|
|
if(zipfileExists(filename, zip, contents)){
|
|
int64 requiredMem;
|
|
|
|
struct zip *z = zip_open( zip.c_str(), ZIP_CHECKCONS, NULL );
|
|
debugAssertM(z != NULL, zip + ": zip open failed.");
|
|
{
|
|
struct zip_stat info;
|
|
zip_stat_init( &info ); // TODO: Docs unclear if zip_stat_init is required.
|
|
int success = zip_stat( z, contents.c_str(), ZIP_FL_NOCASE, &info );
|
|
debugAssertM(success == 0, zip + ": " + contents + ": zip stat failed.");
|
|
requiredMem = info.size;
|
|
}
|
|
zip_close( z );
|
|
return requiredMem;
|
|
} else {
|
|
return -1;
|
|
}
|
|
#else
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
return st.st_size;
|
|
}
|
|
|
|
/** Used by robustTmpfile. Returns nonzero if fread, fwrite, and fseek all
|
|
succeed on the file.
|
|
@author Morgan McGuire, http://graphics.cs.williams.edu */
|
|
static int isFileGood(FILE* f) {
|
|
|
|
int x, n, result;
|
|
|
|
/* Must be a valid file handle */
|
|
if (f == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
/* Try to write */
|
|
x = 1234;
|
|
n = fwrite(&x, sizeof(int), 1, f);
|
|
|
|
if (n != 1) {
|
|
return 0;
|
|
}
|
|
|
|
/* Seek back to the beginning */
|
|
result = fseek(f, 0, SEEK_SET);
|
|
if (result != 0) {
|
|
return 0;
|
|
}
|
|
|
|
/* Read */
|
|
n = fread(&x, sizeof(int), 1, f);
|
|
if (n != 1) {
|
|
return 0;
|
|
}
|
|
|
|
/* Seek back to the beginning again */
|
|
fseek(f, 0, SEEK_SET);
|
|
|
|
return 1;
|
|
}
|
|
|
|
FILE* createTempFile() {
|
|
FILE* t = NULL;
|
|
|
|
//# ifdef G3D_WIN32
|
|
t = tmpfile();
|
|
//# else
|
|
// // On Unix, tmpfile generates a warning for any code that links against it.
|
|
// const char* tempfilename = "/tmp/g3dtemp.XXXXXXXX";
|
|
// mktemp(tempfilename);
|
|
// t = fopen(tempfilename, "w");
|
|
//# endif
|
|
|
|
# ifdef _WIN32
|
|
char* n = NULL;
|
|
# endif
|
|
char name[256];
|
|
|
|
if (isFileGood(t)) {
|
|
return t;
|
|
}
|
|
|
|
# ifdef G3D_WIN32
|
|
/* tmpfile failed; try the tmpnam routine */
|
|
t = fopen(tmpnam(NULL), "w+");
|
|
if (isFileGood(t)) {
|
|
return t;
|
|
}
|
|
|
|
n = _tempnam("c:/tmp/", "t");
|
|
/* Try to create something in C:\tmp */
|
|
t = fopen(n, "w+");
|
|
if (isFileGood(t)) {
|
|
return t;
|
|
}
|
|
|
|
/* Try c:\temp */
|
|
n = _tempnam("c:/temp/", "t");
|
|
t = fopen(n, "w+");
|
|
if (isFileGood(t)) {
|
|
return t;
|
|
}
|
|
|
|
/* try the current directory */
|
|
n = _tempnam("./", "t");
|
|
t = fopen(n, "w+");
|
|
if (isFileGood(t)) {
|
|
return t;
|
|
}
|
|
|
|
sprintf(name, "%s/tmp%d", "c:/temp", rand());
|
|
t = fopen(name, "w+");
|
|
if (isFileGood(t)) {
|
|
return t;
|
|
}
|
|
|
|
/* Try some hardcoded paths */
|
|
sprintf(name, "%s/tmp%d", "c:/tmp", rand());
|
|
t = fopen(name, "w+");
|
|
if (isFileGood(t)) {
|
|
return t;
|
|
}
|
|
# else
|
|
sprintf(name, "%s/tmp%d", "/tmp", rand());
|
|
t = fopen(name, "w+");
|
|
if (isFileGood(t)) {
|
|
return t;
|
|
}
|
|
#endif
|
|
|
|
sprintf(name, "tmp%d", rand());
|
|
t = fopen(name, "w+");
|
|
if (isFileGood(t)) {
|
|
return t;
|
|
}
|
|
|
|
fprintf(stderr, "Unable to create a temporary file; robustTmpfile returning NULL\n");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void writeWholeFile(
|
|
const std::string& filename,
|
|
const std::string& str,
|
|
bool flush) {
|
|
|
|
// Make sure the directory exists.
|
|
std::string root, base, ext, path;
|
|
Array<std::string> pathArray;
|
|
parseFilename(filename, root, pathArray, base, ext);
|
|
|
|
path = root + stringJoin(pathArray, '/');
|
|
if (! fileExists(path, false)) {
|
|
createDirectory(path);
|
|
}
|
|
|
|
FILE* file = fopen(filename.c_str(), "wb");
|
|
|
|
debugAssert(file);
|
|
|
|
fwrite(str.c_str(), str.size(), 1, file);
|
|
|
|
if (flush) {
|
|
fflush(file);
|
|
}
|
|
fclose(file);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
Creates the directory (which may optionally end in a /)
|
|
and any parents needed to reach it.
|
|
*/
|
|
void createDirectory(
|
|
const std::string& dir) {
|
|
|
|
if (dir == "") {
|
|
return;
|
|
}
|
|
|
|
std::string d;
|
|
|
|
// Add a trailing / if there isn't one.
|
|
switch (dir[dir.size() - 1]) {
|
|
case '/':
|
|
case '\\':
|
|
d = dir;
|
|
break;
|
|
|
|
default:
|
|
d = dir + "/";
|
|
}
|
|
|
|
// If it already exists, do nothing
|
|
if (fileExists(d.substr(0, d.size() - 1)), false) {
|
|
return;
|
|
}
|
|
|
|
// Parse the name apart
|
|
std::string root, base, ext;
|
|
Array<std::string> path;
|
|
|
|
std::string lead;
|
|
parseFilename(d, root, path, base, ext);
|
|
debugAssert(base == "");
|
|
debugAssert(ext == "");
|
|
|
|
// Begin with an extra period so "c:\" becomes "c:\.\" after
|
|
// appending a path and "c:" becomes "c:.\", not root: "c:\"
|
|
std::string p = root + ".";
|
|
|
|
// Create any intermediate that doesn't exist
|
|
for (int i = 0; i < path.size(); ++i) {
|
|
p += "/" + path[i];
|
|
if (! fileExists(p, false)) {
|
|
// Windows only requires one argument to mkdir,
|
|
// where as unix also requires the permissions.
|
|
# ifndef G3D_WIN32
|
|
mkdir(p.c_str(), 0777);
|
|
# else
|
|
_mkdir(p.c_str());
|
|
# endif
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
class FileSystemCache {
|
|
private:
|
|
|
|
Table<std::string, Array<std::string> > m_files;
|
|
|
|
public:
|
|
|
|
bool fileExists(const std::string& filename) {
|
|
const std::string& path = resolveFilename(filenamePath(filename));
|
|
const std::string& name = filenameBaseExt(filename);
|
|
|
|
bool neverBeforeSeen = false;
|
|
Array<std::string>& fileList = m_files.getCreate(path, neverBeforeSeen);
|
|
if (neverBeforeSeen) {
|
|
if (! G3D::fileExists(path, true, false)) {
|
|
// The path itself doesn't exist... back out our insertion (which makes fileList& invalid)
|
|
m_files.remove(path);
|
|
return false;
|
|
}
|
|
|
|
std::string spec = pathConcat(path, "*");
|
|
|
|
// Will automatically recurse into zipfiles
|
|
getFiles(spec, fileList);
|
|
getDirs(spec, fileList);
|
|
|
|
# ifdef G3D_WIN32
|
|
{
|
|
// Case insensitive
|
|
for (int i = 0; i < fileList.size(); ++i) {
|
|
fileList[i] = toLower(fileList[i]);
|
|
}
|
|
}
|
|
# endif
|
|
}
|
|
|
|
if (filenameContainsWildcards(name)) {
|
|
// See if anything matches
|
|
for (int i = 0; i < fileList.size(); ++i) {
|
|
if (g3dfnmatch(name.c_str(), fileList[i].c_str(), 0) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
} else {
|
|
// On windows, this is a lower-lower comparison, so it is case insensitive
|
|
return fileList.contains(name);
|
|
}
|
|
}
|
|
|
|
void clear() {
|
|
m_files.clear();
|
|
}
|
|
|
|
static FileSystemCache& instance() {
|
|
static FileSystemCache i;
|
|
return i;
|
|
}
|
|
};
|
|
|
|
|
|
void clearFileSystemCache() {
|
|
FileSystemCache::instance().clear();
|
|
}
|
|
|
|
bool fileExists
|
|
(const std::string& _filename,
|
|
bool lookInZipfiles,
|
|
bool trustCache) {
|
|
|
|
if (_filename.empty()) {
|
|
return false;
|
|
}
|
|
|
|
// Remove trailing slash from directories
|
|
const std::string& filename = (endsWith(_filename, "/") || endsWith(_filename, "\\")) ? _filename.substr(0, _filename.length() - 1) : _filename;
|
|
|
|
if (trustCache && lookInZipfiles) {
|
|
# ifdef G3D_WIN32
|
|
// Case insensitive
|
|
return FileSystemCache::instance().fileExists(toLower(filename));
|
|
# else
|
|
return FileSystemCache::instance().fileExists(filename);
|
|
# endif
|
|
}
|
|
|
|
// Useful for debugging
|
|
//char curdir[1024]; _getcwd(curdir, 1024);
|
|
|
|
struct _stat st;
|
|
int ret = _stat(filename.c_str(), &st);
|
|
|
|
// _stat returns zero on success
|
|
bool exists = (ret == 0);
|
|
|
|
if (! exists && lookInZipfiles) {
|
|
// Does not exist standalone, but might exist in a zipfile
|
|
|
|
// These output arguments will be ignored
|
|
std::string zipDir, internalPath;
|
|
return zipfileExists(filename, zipDir, internalPath);
|
|
} else {
|
|
return exists;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#if _HAVE_ZIP
|
|
/* Helper methods for zipfileExists()*/
|
|
// Given a string (the drive) and an array (the path), computes the directory
|
|
static void _zip_resolveDirectory(std::string& completeDir, const std::string& drive, const Array<std::string>& path, const int length){
|
|
completeDir = drive;
|
|
int tempLength;
|
|
// if the given length is longer than the array, we correct it
|
|
if(length > path.length()){
|
|
tempLength = path.length();
|
|
} else{
|
|
tempLength = length;
|
|
}
|
|
|
|
for(int t = 0; t < tempLength; ++t){
|
|
if(t > 0){
|
|
completeDir += "/";
|
|
}
|
|
completeDir += path[t];
|
|
}
|
|
}
|
|
|
|
|
|
// assumes that zipDir references a .zip file
|
|
static bool _zip_zipContains(const std::string& zipDir, const std::string& desiredFile){
|
|
struct zip *z = zip_open( zipDir.c_str(), ZIP_CHECKCONS, NULL );
|
|
//the last parameter, an int, determines case sensitivity:
|
|
//1 is sensitive, 2 is not, 0 is default
|
|
int test = zip_name_locate( z, desiredFile.c_str(), ZIP_FL_NOCASE );
|
|
zip_close( z );
|
|
if(test == -1){
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
// If no zipfile exists, outZipfile and outInternalFile are unchanged
|
|
bool zipfileExists(const std::string& filename, std::string& outZipfile,
|
|
std::string& outInternalFile){
|
|
#if _HAVE_ZIP
|
|
Array<std::string> path;
|
|
std::string drive, base, ext, zipfile, infile;
|
|
parseFilename(filename, drive, path, base, ext);
|
|
|
|
// Put the filename back together
|
|
if ((base != "") && (ext != "")) {
|
|
infile = base + "." + ext;
|
|
} else {
|
|
infile = base + ext;
|
|
}
|
|
|
|
// Remove "." from path
|
|
for (int i = 0; i < path.length(); ++i) {
|
|
if (path[i] == ".") {
|
|
path.remove(i);
|
|
--i;
|
|
}
|
|
}
|
|
|
|
// Remove ".." from path
|
|
for (int i = 1; i < path.length(); ++i) {
|
|
if ((path[i] == "..") && (i > 0) && (path[i - 1] != "..")) {
|
|
// Remove both i and i - 1
|
|
path.remove(i - 1, 2);
|
|
i -= 2;
|
|
}
|
|
}
|
|
|
|
// Walk the path backwards, accumulating pieces onto the infile until
|
|
// we find a zipfile that contains it
|
|
for (int t = 0; t < path.length(); ++t){
|
|
_zip_resolveDirectory(zipfile, drive, path, path.length() - t);
|
|
if (t > 0) {
|
|
infile = path[path.length() - t] + "/" + infile;
|
|
}
|
|
|
|
if (endsWith(zipfile, "..")) {
|
|
return false;
|
|
}
|
|
|
|
if (fileExists(zipfile, false)) {
|
|
// test if it actually is a zipfile
|
|
// if not, return false, a bad
|
|
// directory structure has been given,
|
|
// not a .zip
|
|
if (isZipfile(zipfile)){
|
|
|
|
if (_zip_zipContains(zipfile, infile)){
|
|
outZipfile = zipfile;
|
|
outInternalFile = infile;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
// the directory structure was valid but did not point to a .zip
|
|
return false;
|
|
}
|
|
}
|
|
|
|
}
|
|
#endif
|
|
// not a valid directory structure ever,
|
|
// obviously no .zip was found within the path
|
|
return false;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
std::string generateFilenameBase(const std::string& prefix, const std::string& suffix) {
|
|
Array<std::string> exist;
|
|
|
|
// Note "template" is a reserved word in C++
|
|
std::string templat = prefix + System::currentDateString() + "_";
|
|
getFiles(templat + "*", exist);
|
|
|
|
// Remove extensions
|
|
for (int i = 0; i < exist.size(); ++i) {
|
|
exist[i] = filenameBase(exist[i]);
|
|
}
|
|
|
|
int num = 0;
|
|
std::string result;
|
|
templat += "%03d" + suffix;
|
|
do {
|
|
result = format(templat.c_str(), num);
|
|
++num;
|
|
} while (exist.contains(result));
|
|
|
|
return result;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void copyFile(
|
|
const std::string& source,
|
|
const std::string& dest) {
|
|
|
|
#ifdef G3D_WIN32
|
|
CopyFileA(source.c_str(), dest.c_str(), FALSE);
|
|
#else
|
|
// TODO: don't use BinaryInput and BinaryOutput
|
|
// Read it all in, then dump it out
|
|
BinaryInput in(source, G3D_LITTLE_ENDIAN);
|
|
BinaryOutput out(dest, G3D_LITTLE_ENDIAN);
|
|
out.writeBytes(in.getCArray(), in.size());
|
|
out.commit(false);
|
|
#endif
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
void parseFilename(
|
|
const std::string& filename,
|
|
std::string& root,
|
|
Array<std::string>& path,
|
|
std::string& base,
|
|
std::string& ext) {
|
|
|
|
std::string f = filename;
|
|
|
|
root = "";
|
|
path.clear();
|
|
base = "";
|
|
ext = "";
|
|
|
|
if (f == "") {
|
|
// Empty filename
|
|
return;
|
|
}
|
|
|
|
// See if there is a root/drive spec.
|
|
if ((f.size() >= 2) && (f[1] == ':')) {
|
|
|
|
if ((f.size() > 2) && isSlash(f[2])) {
|
|
|
|
// e.g. c:\foo
|
|
root = f.substr(0, 3);
|
|
f = f.substr(3, f.size() - 3);
|
|
|
|
} else {
|
|
|
|
// e.g. c:foo
|
|
root = f.substr(2);
|
|
f = f.substr(2, f.size() - 2);
|
|
|
|
}
|
|
|
|
} else if ((f.size() >= 2) & isSlash(f[0]) && isSlash(f[1])) {
|
|
|
|
// e.g. //foo
|
|
root = f.substr(0, 2);
|
|
f = f.substr(2, f.size() - 2);
|
|
|
|
} else if (isSlash(f[0])) {
|
|
|
|
root = f.substr(0, 1);
|
|
f = f.substr(1, f.size() - 1);
|
|
|
|
}
|
|
|
|
// Pull the extension off
|
|
{
|
|
// Find the period
|
|
size_t i = f.rfind('.');
|
|
|
|
if (i != std::string::npos) {
|
|
// Make sure it is after the last slash!
|
|
size_t j = iMax(f.rfind('/'), f.rfind('\\'));
|
|
if ((j == std::string::npos) || (i > j)) {
|
|
ext = f.substr(i + 1, f.size() - i - 1);
|
|
f = f.substr(0, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pull the basename off
|
|
{
|
|
// Find the last slash
|
|
size_t i = iMax(f.rfind('/'), f.rfind('\\'));
|
|
|
|
if (i == std::string::npos) {
|
|
|
|
// There is no slash; the basename is the whole thing
|
|
base = f;
|
|
f = "";
|
|
|
|
} else if ((i != std::string::npos) && (i < f.size() - 1)) {
|
|
|
|
base = f.substr(i + 1, f.size() - i - 1);
|
|
f = f.substr(0, i);
|
|
|
|
}
|
|
}
|
|
|
|
// Parse what remains into path.
|
|
size_t prev, cur = 0;
|
|
|
|
while (cur < f.size()) {
|
|
prev = cur;
|
|
|
|
// Allow either slash
|
|
size_t i = f.find('/', prev + 1);
|
|
size_t j = f.find('\\', prev + 1);
|
|
if (i == std::string::npos) {
|
|
i = f.size();
|
|
}
|
|
|
|
if (j == std::string::npos) {
|
|
j = f.size();
|
|
}
|
|
|
|
cur = iMin(i, j);
|
|
|
|
if (cur == std::string::npos) {
|
|
cur = f.size();
|
|
}
|
|
|
|
path.append(f.substr(prev, cur - prev));
|
|
++cur;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Helper for getFileList and getDirectoryList.
|
|
|
|
@param wantFiles If false, returns the directories, otherwise
|
|
returns the files.
|
|
@param includePath If true, the names include paths
|
|
*/
|
|
static void getFileOrDirListNormal
|
|
(const std::string& filespec,
|
|
Array<std::string>& files,
|
|
bool wantFiles,
|
|
bool includePath) {
|
|
|
|
bool test = wantFiles ? true : false;
|
|
|
|
std::string path = "";
|
|
|
|
// Find the place where the path ends and the file-spec begins
|
|
size_t i = filespec.rfind('/');
|
|
size_t j = filespec.rfind('\\');
|
|
|
|
// Drive letters on Windows can separate a path
|
|
size_t k = filespec.rfind(':');
|
|
|
|
if (((j != std::string::npos) && (j > i)) ||
|
|
(i == std::string::npos)) {
|
|
i = j;
|
|
}
|
|
|
|
if (((k != std::string::npos) && (k > i)) ||
|
|
(i == std::string::npos)) {
|
|
i = k;
|
|
}
|
|
|
|
// If there is a path, pull it off
|
|
if (i != std::string::npos) {
|
|
path = filespec.substr(0, i + 1);
|
|
}
|
|
|
|
std::string prefix = path;
|
|
|
|
if (path.size() > 0) {
|
|
// Strip the trailing character
|
|
path = path.substr(0, path.size() - 1);
|
|
}
|
|
|
|
# ifdef G3D_WIN32
|
|
{
|
|
struct _finddata_t fileinfo;
|
|
|
|
long handle = _findfirst(filespec.c_str(), &fileinfo);
|
|
int result = handle;
|
|
|
|
while (result != -1) {
|
|
if ((((fileinfo.attrib & _A_SUBDIR) == 0) == test) &&
|
|
strcmp(fileinfo.name, ".") &&
|
|
strcmp(fileinfo.name, "..")) {
|
|
|
|
if (includePath) {
|
|
files.append(prefix + fileinfo.name);
|
|
} else {
|
|
files.append(fileinfo.name);
|
|
}
|
|
}
|
|
|
|
result = _findnext(handle, &fileinfo);
|
|
}
|
|
}
|
|
# else
|
|
{
|
|
if (path == "") {
|
|
// Empty paths don't work on Unix
|
|
path = ".";
|
|
}
|
|
|
|
// Unix implementation
|
|
DIR* dir = opendir(path.c_str());
|
|
|
|
if (dir != NULL) {
|
|
struct dirent* entry = readdir(dir);
|
|
|
|
while (entry != NULL) {
|
|
|
|
// Exclude '.' and '..'
|
|
if ((strcmp(entry->d_name, ".") != 0) &&
|
|
(strcmp(entry->d_name, "..") != 0)) {
|
|
|
|
// Form a name with a path
|
|
std::string filename = prefix + entry->d_name;
|
|
// See if this is a file or a directory
|
|
struct _stat st;
|
|
bool exists = _stat(filename.c_str(), &st) != -1;
|
|
|
|
if (exists &&
|
|
|
|
// Make sure it has the correct type
|
|
(((st.st_mode & S_IFDIR) == 0) == test) &&
|
|
|
|
// Make sure it matches the wildcard
|
|
(fnmatch(filespec.c_str(),
|
|
filename.c_str(),
|
|
FNM_PATHNAME) == 0)) {
|
|
|
|
if (includePath) {
|
|
files.append(filename);
|
|
} else {
|
|
files.append(entry->d_name);
|
|
}
|
|
}
|
|
}
|
|
|
|
entry = readdir(dir);
|
|
}
|
|
closedir(dir);
|
|
}
|
|
}
|
|
# endif
|
|
}
|
|
|
|
#if _HAVE_ZIP
|
|
/**
|
|
@param path The zipfile name (no trailing slash)
|
|
@param prefix Directory inside the zipfile. No leading slash, must have trailing slash if non-empty.
|
|
@param file Name inside the zipfile that we are testing to see if it matches prefix + "*"
|
|
*/
|
|
static void _zip_addEntry(const std::string& path,
|
|
const std::string& prefix,
|
|
const std::string& file,
|
|
Set<std::string>& files,
|
|
bool wantFiles,
|
|
bool includePath) {
|
|
|
|
// Make certain we are within the desired parent folder (prefix)
|
|
if (beginsWith(file, prefix)) {
|
|
// validityTest was prefix/file
|
|
|
|
// Extract everything to the right of the prefix
|
|
std::string s = file.substr(prefix.length());
|
|
|
|
if (s == "") {
|
|
// This was the name of the prefix
|
|
return;
|
|
}
|
|
|
|
// See if there are any slashes
|
|
size_t slashPos = s.find('/');
|
|
|
|
bool add = false;
|
|
|
|
if (slashPos == std::string::npos) {
|
|
// No slashes, so s must be a file
|
|
add = wantFiles;
|
|
} else if (! wantFiles) {
|
|
// Not all zipfiles list directories as explicit entries.
|
|
// Because of this, if we're looking for directories and see
|
|
// any path longer than prefix, we must add the subdirectory.
|
|
// The Set will fix duplicates for us.
|
|
s = s.substr(0, slashPos);
|
|
add = true;
|
|
}
|
|
|
|
if (add) {
|
|
if (includePath) {
|
|
files.insert(path + "/" + prefix + s);
|
|
} else {
|
|
files.insert(s);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void getFileOrDirListZip(const std::string& path,
|
|
const std::string& prefix,
|
|
Array<std::string>& files,
|
|
bool wantFiles,
|
|
bool includePath){
|
|
#if _HAVE_ZIP
|
|
struct zip *z = zip_open( path.c_str(), ZIP_CHECKCONS, NULL );
|
|
|
|
Set<std::string> fileSet;
|
|
|
|
int count = zip_get_num_files( z );
|
|
for( int i = 0; i < count; ++i ) {
|
|
struct zip_stat info;
|
|
zip_stat_init( &info ); // TODO: Docs unclear if zip_stat_init is required.
|
|
zip_stat_index( z, i, ZIP_FL_NOCASE, &info );
|
|
_zip_addEntry(path, prefix, info.name, fileSet, wantFiles, includePath);
|
|
}
|
|
|
|
zip_close( z );
|
|
|
|
fileSet.getMembers(files);
|
|
#endif
|
|
}
|
|
|
|
|
|
static void determineFileOrDirList(
|
|
const std::string& filespec,
|
|
Array<std::string>& files,
|
|
bool wantFiles,
|
|
bool includePath) {
|
|
|
|
// if it is a .zip, prefix will specify the folder within
|
|
// whose contents we want to see
|
|
std::string prefix = "";
|
|
std::string path = filenamePath(filespec);
|
|
|
|
if ((path.size() > 0) && isSlash(path[path.size() - 1])) {
|
|
// Strip the trailing slash
|
|
path = path.substr(0, path.length() -1);
|
|
}
|
|
|
|
if ((path == "") || fileExists(path, false)) {
|
|
if ((path != "") && isZipfile(path)) {
|
|
// .zip should only work if * is specified as the Base + Ext
|
|
// Here, we have been asked for the root's contents
|
|
debugAssertM(filenameBaseExt(filespec) == "*", "Can only call getFiles/getDirs on zipfiles using '*' wildcard");
|
|
getFileOrDirListZip(path, prefix, files, wantFiles, includePath);
|
|
} else {
|
|
// It is a normal directory
|
|
getFileOrDirListNormal(filespec, files, wantFiles, includePath);
|
|
}
|
|
} else if (zipfileExists(filenamePath(filespec), path, prefix)) {
|
|
// .zip should only work if * is specified as the Base + Ext
|
|
// Here, we have been asked for the contents of a folder within the .zip
|
|
debugAssertM(filenameBaseExt(filespec) == "*", "Can only call getFiles/getDirs on zipfiles using '*' wildcard");
|
|
getFileOrDirListZip(path, prefix, files, wantFiles, includePath);
|
|
}
|
|
}
|
|
|
|
|
|
void getFiles(const std::string& filespec,
|
|
Array<std::string>& files,
|
|
bool includePath) {
|
|
|
|
determineFileOrDirList(filespec, files, true, includePath);
|
|
}
|
|
|
|
|
|
void getDirs(
|
|
const std::string& filespec,
|
|
Array<std::string>& files,
|
|
bool includePath) {
|
|
|
|
determineFileOrDirList(filespec, files, false, includePath);
|
|
}
|
|
|
|
|
|
std::string filenameBaseExt(const std::string& filename) {
|
|
int i = filename.rfind("/");
|
|
int j = filename.rfind("\\");
|
|
|
|
if ((j > i) && (j >= 0)) {
|
|
i = j;
|
|
}
|
|
|
|
# ifdef G3D_WIN32
|
|
j = filename.rfind(":");
|
|
if ((i == -1) && (j >= 0)) {
|
|
i = j;
|
|
}
|
|
# endif
|
|
|
|
if (i == -1) {
|
|
return filename;
|
|
} else {
|
|
return filename.substr(i + 1, filename.length() - i);
|
|
}
|
|
}
|
|
|
|
|
|
std::string filenameBase(const std::string& s) {
|
|
std::string drive;
|
|
std::string base;
|
|
std::string ext;
|
|
Array<std::string> path;
|
|
|
|
parseFilename(s, drive, path, base, ext);
|
|
return base;
|
|
}
|
|
|
|
|
|
std::string filenameExt(const std::string& filename) {
|
|
int i = filename.rfind(".");
|
|
if (i >= 0) {
|
|
return filename.substr(i + 1, filename.length() - i);
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
|
|
std::string filenamePath(const std::string& filename) {
|
|
int i = filename.rfind("/");
|
|
int j = filename.rfind("\\");
|
|
|
|
if ((j > i) && (j >= 0)) {
|
|
i = j;
|
|
}
|
|
|
|
# ifdef G3D_WIN32
|
|
j = filename.rfind(":");
|
|
if ((i == -1) && (j >= 0)) {
|
|
i = j;
|
|
}
|
|
# endif
|
|
|
|
if (i == -1) {
|
|
return "";
|
|
} else {
|
|
return filename.substr(0, i+1);
|
|
}
|
|
}
|
|
|
|
|
|
bool isZipfile(const std::string& filename) {
|
|
|
|
FILE* f = fopen(filename.c_str(), "r");
|
|
if (f == NULL) {
|
|
return false;
|
|
}
|
|
uint8 header[4];
|
|
fread(header, 4, 1, f);
|
|
|
|
const uint8 zipHeader[4] = {0x50, 0x4b, 0x03, 0x04};
|
|
for (int i = 0; i < 4; ++i) {
|
|
if (header[i] != zipHeader[i]) {
|
|
fclose(f);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
fclose(f);
|
|
return true;
|
|
}
|
|
|
|
|
|
bool isDirectory(const std::string& filename) {
|
|
struct _stat st;
|
|
bool exists = _stat(filename.c_str(), &st) != -1;
|
|
return exists && ((st.st_mode & S_IFDIR) != 0);
|
|
}
|
|
|
|
|
|
bool filenameContainsWildcards(const std::string& filename) {
|
|
return (filename.find('*') != std::string::npos) || (filename.find('?') != std::string::npos);
|
|
}
|
|
|
|
|
|
bool fileIsNewer(const std::string& src, const std::string& dst) {
|
|
struct _stat sts;
|
|
bool sexists = _stat(src.c_str(), &sts) != -1;
|
|
|
|
struct _stat dts;
|
|
bool dexists = _stat(dst.c_str(), &dts) != -1;
|
|
|
|
return sexists && ((! dexists) || (sts.st_mtime > dts.st_mtime));
|
|
}
|
|
|
|
|
|
Array<std::string> filesUsed() {
|
|
Array<std::string> f;
|
|
_internal::currentFilesUsed.getMembers(f);
|
|
return f;
|
|
}
|
|
|
|
}
|
|
|
|
#ifndef G3D_WIN32
|
|
#undef _stat
|
|
#endif
|