#include "scriptfilesystem.h" #include "../autowrapper/aswrappedcall.h" #if defined(_WIN32) #include // _getcwd #include // FindFirstFile, GetFileAttributes #undef DeleteFile #undef CopyFile #else #include // getcwd #include // opendir, readdir, closedir #include // stat #endif #include // assert using namespace std; BEGIN_AS_NAMESPACE // TODO: The file system should have a way to allow the application to define in // which sub directories it is allowed to make changes and/or read CScriptFileSystem *ScriptFileSystem_Factory() { return new CScriptFileSystem(); } void RegisterScriptFileSystem_Native(asIScriptEngine *engine) { int r; assert( engine->GetTypeInfoByName("string") ); assert( engine->GetTypeInfoByDecl("array") ); assert( engine->GetTypeInfoByName("datetime") ); r = engine->RegisterObjectType("filesystem", 0, asOBJ_REF); assert( r >= 0 ); r = engine->RegisterObjectBehaviour("filesystem", asBEHAVE_FACTORY, "filesystem @f()", asFUNCTION(ScriptFileSystem_Factory), asCALL_CDECL); assert( r >= 0 ); r = engine->RegisterObjectBehaviour("filesystem", asBEHAVE_ADDREF, "void f()", asMETHOD(CScriptFileSystem,AddRef), asCALL_THISCALL); assert( r >= 0 ); r = engine->RegisterObjectBehaviour("filesystem", asBEHAVE_RELEASE, "void f()", asMETHOD(CScriptFileSystem,Release), asCALL_THISCALL); assert( r >= 0 ); r = engine->RegisterObjectMethod("filesystem", "bool changeCurrentPath(const string &in)", asMETHOD(CScriptFileSystem, ChangeCurrentPath), asCALL_THISCALL); assert( r >= 0 ); r = engine->RegisterObjectMethod("filesystem", "string getCurrentPath() const", asMETHOD(CScriptFileSystem, GetCurrentPath), asCALL_THISCALL); assert( r >= 0 ); r = engine->RegisterObjectMethod("filesystem", "array @getDirs() const", asMETHOD(CScriptFileSystem, GetDirs), asCALL_THISCALL); assert( r >= 0 ); r = engine->RegisterObjectMethod("filesystem", "array @getFiles() const", asMETHOD(CScriptFileSystem, GetFiles), asCALL_THISCALL); assert( r >= 0 ); r = engine->RegisterObjectMethod("filesystem", "bool isDir(const string &in) const", asMETHOD(CScriptFileSystem, IsDir), asCALL_THISCALL); assert( r >= 0 ); r = engine->RegisterObjectMethod("filesystem", "bool isLink(const string &in) const", asMETHOD(CScriptFileSystem, IsLink), asCALL_THISCALL); assert(r >= 0); r = engine->RegisterObjectMethod("filesystem", "int64 getSize(const string &in) const", asMETHOD(CScriptFileSystem, GetSize), asCALL_THISCALL); assert(r >= 0); r = engine->RegisterObjectMethod("filesystem", "int makeDir(const string &in)", asMETHOD(CScriptFileSystem, MakeDir), asCALL_THISCALL); assert(r >= 0); r = engine->RegisterObjectMethod("filesystem", "int removeDir(const string &in)", asMETHOD(CScriptFileSystem, RemoveDir), asCALL_THISCALL); assert(r >= 0); r = engine->RegisterObjectMethod("filesystem", "int deleteFile(const string &in)", asMETHOD(CScriptFileSystem, DeleteFile), asCALL_THISCALL); assert(r >= 0); r = engine->RegisterObjectMethod("filesystem", "int copyFile(const string &in, const string &in)", asMETHOD(CScriptFileSystem, CopyFile), asCALL_THISCALL); assert(r >= 0); r = engine->RegisterObjectMethod("filesystem", "int move(const string &in, const string &in)", asMETHOD(CScriptFileSystem, Move), asCALL_THISCALL); assert(r >= 0); r = engine->RegisterObjectMethod("filesystem", "datetime getCreateDateTime(const string &in) const", asMETHOD(CScriptFileSystem, GetCreateDateTime), asCALL_THISCALL); assert(r >= 0); r = engine->RegisterObjectMethod("filesystem", "datetime getModifyDateTime(const string &in) const", asMETHOD(CScriptFileSystem, GetModifyDateTime), asCALL_THISCALL); assert(r >= 0); } void RegisterScriptFileSystem_Generic(asIScriptEngine *engine) { int r; assert( engine->GetTypeInfoByName("string") ); assert( engine->GetTypeInfoByDecl("array") ); assert( engine->GetTypeInfoByName("datetime") ); r = engine->RegisterObjectType("filesystem", 0, asOBJ_REF); assert( r >= 0 ); r = engine->RegisterObjectBehaviour("filesystem", asBEHAVE_FACTORY, "filesystem @f()", WRAP_FN(ScriptFileSystem_Factory), asCALL_GENERIC); assert( r >= 0 ); r = engine->RegisterObjectBehaviour("filesystem", asBEHAVE_ADDREF, "void f()", WRAP_MFN(CScriptFileSystem,AddRef), asCALL_GENERIC); assert( r >= 0 ); r = engine->RegisterObjectBehaviour("filesystem", asBEHAVE_RELEASE, "void f()", WRAP_MFN(CScriptFileSystem,Release), asCALL_GENERIC); assert( r >= 0 ); r = engine->RegisterObjectMethod("filesystem", "bool changeCurrentPath(const string &in)", WRAP_MFN(CScriptFileSystem, ChangeCurrentPath), asCALL_GENERIC); assert( r >= 0 ); r = engine->RegisterObjectMethod("filesystem", "string getCurrentPath() const", WRAP_MFN(CScriptFileSystem, GetCurrentPath), asCALL_GENERIC); assert( r >= 0 ); r = engine->RegisterObjectMethod("filesystem", "array @getDirs() const", WRAP_MFN(CScriptFileSystem, GetDirs), asCALL_GENERIC); assert( r >= 0 ); r = engine->RegisterObjectMethod("filesystem", "array @getFiles() const", WRAP_MFN(CScriptFileSystem, GetFiles), asCALL_GENERIC); assert( r >= 0 ); r = engine->RegisterObjectMethod("filesystem", "bool isDir(const string &in) const", WRAP_MFN(CScriptFileSystem, IsDir), asCALL_GENERIC); assert( r >= 0 ); r = engine->RegisterObjectMethod("filesystem", "bool isLink(const string &in) const", WRAP_MFN(CScriptFileSystem, IsLink), asCALL_GENERIC); assert(r >= 0); r = engine->RegisterObjectMethod("filesystem", "int64 getSize(const string &in) const", WRAP_MFN(CScriptFileSystem, GetSize), asCALL_GENERIC); assert(r >= 0); r = engine->RegisterObjectMethod("filesystem", "int makeDir(const string &in)", WRAP_MFN(CScriptFileSystem, MakeDir), asCALL_GENERIC); assert(r >= 0); r = engine->RegisterObjectMethod("filesystem", "int removeDir(const string &in)", WRAP_MFN(CScriptFileSystem, RemoveDir), asCALL_GENERIC); assert(r >= 0); r = engine->RegisterObjectMethod("filesystem", "int deleteFile(const string &in)", WRAP_MFN(CScriptFileSystem, DeleteFile), asCALL_GENERIC); assert(r >= 0); r = engine->RegisterObjectMethod("filesystem", "int copyFile(const string &in, const string &in)", WRAP_MFN(CScriptFileSystem, CopyFile), asCALL_GENERIC); assert(r >= 0); r = engine->RegisterObjectMethod("filesystem", "int move(const string &in, const string &in)", WRAP_MFN(CScriptFileSystem, Move), asCALL_GENERIC); assert(r >= 0); r = engine->RegisterObjectMethod("filesystem", "datetime getCreateDateTime(const string &in) const", WRAP_MFN(CScriptFileSystem, GetCreateDateTime), asCALL_GENERIC); assert(r >= 0); r = engine->RegisterObjectMethod("filesystem", "datetime getModifyDateTime(const string &in) const", WRAP_MFN(CScriptFileSystem, GetModifyDateTime), asCALL_GENERIC); assert(r >= 0); } void RegisterScriptFileSystem(asIScriptEngine *engine) { if( strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") ) RegisterScriptFileSystem_Generic(engine); else RegisterScriptFileSystem_Native(engine); } CScriptFileSystem::CScriptFileSystem() { refCount = 1; // Gets the application's current working directory as the starting point // TODO: Replace backslash with slash to keep a unified naming convention char buffer[1000]; #if defined(_WIN32) currentPath = _getcwd(buffer, 1000); #else currentPath = getcwd(buffer, 1000); #endif } CScriptFileSystem::~CScriptFileSystem() { } void CScriptFileSystem::AddRef() const { asAtomicInc(refCount); } void CScriptFileSystem::Release() const { if( asAtomicDec(refCount) == 0 ) delete this; } CScriptArray *CScriptFileSystem::GetFiles() const { // Obtain a pointer to the engine asIScriptContext *ctx = asGetActiveContext(); asIScriptEngine *engine = ctx->GetEngine(); // TODO: This should only be done once // TODO: This assumes that CScriptArray was already registered asITypeInfo *arrayType = engine->GetTypeInfoByDecl("array"); // Create the array object CScriptArray *array = CScriptArray::Create(arrayType); #if defined(_WIN32) // Windows uses UTF16 so it is necessary to convert the string wchar_t bufUTF16[10000]; string searchPattern = currentPath + "/*"; MultiByteToWideChar(CP_UTF8, 0, searchPattern.c_str(), -1, bufUTF16, 10000); WIN32_FIND_DATAW ffd; HANDLE hFind = FindFirstFileW(bufUTF16, &ffd); if( INVALID_HANDLE_VALUE == hFind ) return array; do { // Skip directories if( (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ) continue; // Convert the file name back to UTF8 char bufUTF8[10000]; WideCharToMultiByte(CP_UTF8, 0, ffd.cFileName, -1, bufUTF8, 10000, 0, 0); // Add the file to the array array->Resize(array->GetSize()+1); ((string*)(array->At(array->GetSize()-1)))->assign(bufUTF8); } while( FindNextFileW(hFind, &ffd) != 0 ); FindClose(hFind); #else dirent *ent = 0; DIR *dir = opendir(currentPath.c_str()); while( (ent = readdir(dir)) != NULL ) { const string filename = ent->d_name; // Skip . and .. if( filename[0] == '.' ) continue; // Skip sub directories const string fullname = currentPath + "/" + filename; struct stat st; if( stat(fullname.c_str(), &st) == -1 ) continue; if( (st.st_mode & S_IFDIR) != 0 ) continue; // Add the file to the array array->Resize(array->GetSize()+1); ((string*)(array->At(array->GetSize()-1)))->assign(filename); } closedir(dir); #endif return array; } CScriptArray *CScriptFileSystem::GetDirs() const { // Obtain a pointer to the engine asIScriptContext *ctx = asGetActiveContext(); asIScriptEngine *engine = ctx->GetEngine(); // TODO: This should only be done once // TODO: This assumes that CScriptArray was already registered asITypeInfo *arrayType = engine->GetTypeInfoByDecl("array"); // Create the array object CScriptArray *array = CScriptArray::Create(arrayType); #if defined(_WIN32) // Windows uses UTF16 so it is necessary to convert the string wchar_t bufUTF16[10000]; string searchPattern = currentPath + "/*"; MultiByteToWideChar(CP_UTF8, 0, searchPattern.c_str(), -1, bufUTF16, 10000); WIN32_FIND_DATAW ffd; HANDLE hFind = FindFirstFileW(bufUTF16, &ffd); if( INVALID_HANDLE_VALUE == hFind ) return array; do { // Skip files if( !(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ) continue; // Convert the file name back to UTF8 char bufUTF8[10000]; WideCharToMultiByte(CP_UTF8, 0, ffd.cFileName, -1, bufUTF8, 10000, 0, 0); if( strcmp(bufUTF8, ".") == 0 || strcmp(bufUTF8, "..") == 0 ) continue; // Add the dir to the array array->Resize(array->GetSize()+1); ((string*)(array->At(array->GetSize()-1)))->assign(bufUTF8); } while( FindNextFileW(hFind, &ffd) != 0 ); FindClose(hFind); #else dirent *ent = 0; DIR *dir = opendir(currentPath.c_str()); while( (ent = readdir(dir)) != NULL ) { const string filename = ent->d_name; // Skip . and .. if( filename[0] == '.' ) continue; // Skip files const string fullname = currentPath + "/" + filename; struct stat st; if( stat(fullname.c_str(), &st) == -1 ) continue; if( (st.st_mode & S_IFDIR) == 0 ) continue; // Add the dir to the array array->Resize(array->GetSize()+1); ((string*)(array->At(array->GetSize()-1)))->assign(filename); } closedir(dir); #endif return array; } // Doesn't change anything if the new path is not valid bool CScriptFileSystem::ChangeCurrentPath(const string &path) { string newPath; if( path.find(":") != string::npos || path.find("/") == 0 || path.find("\\") == 0 ) newPath = path; else newPath = currentPath + "/" + path; // TODO: Resolve internal /./ and /../ // TODO: Replace backslash with slash to keep a unified naming convention // Remove trailing slashes from the path while(newPath.length() && (newPath[newPath.length()-1] == '/' || newPath[newPath.length()-1] == '\\') ) newPath.resize(newPath.length()-1); if (!IsDir(newPath)) return false; currentPath = newPath; return true; } bool CScriptFileSystem::IsDir(const string &path) const { string search; if( path.find(":") != string::npos || path.find("/") == 0 || path.find("\\") == 0 ) search = path; else search = currentPath + "/" + path; #if defined(_WIN32) // Windows uses UTF16 so it is necessary to convert the string wchar_t bufUTF16[10000]; MultiByteToWideChar(CP_UTF8, 0, search.c_str(), -1, bufUTF16, 10000); // Check if the path exists and is a directory DWORD attrib = GetFileAttributesW(bufUTF16); if( attrib == INVALID_FILE_ATTRIBUTES || !(attrib & FILE_ATTRIBUTE_DIRECTORY) ) return false; #else // Check if the path exists and is a directory struct stat st; if( stat(search.c_str(), &st) == -1 ) return false; if( (st.st_mode & S_IFDIR) == 0 ) return false; #endif return true; } bool CScriptFileSystem::IsLink(const string &path) const { string search; if (path.find(":") != string::npos || path.find("/") == 0 || path.find("\\") == 0) search = path; else search = currentPath + "/" + path; #if defined(_WIN32) // Windows uses UTF16 so it is necessary to convert the string wchar_t bufUTF16[10000]; MultiByteToWideChar(CP_UTF8, 0, search.c_str(), -1, bufUTF16, 10000); // Check if the path exists and is a link DWORD attrib = GetFileAttributesW(bufUTF16); if (attrib == INVALID_FILE_ATTRIBUTES || !(attrib & FILE_ATTRIBUTE_REPARSE_POINT)) return false; #else // Check if the path exists and is a link struct stat st; if (stat(search.c_str(), &st) == -1) return false; if ((st.st_mode & S_IFLNK) == 0) return false; #endif return true; } asINT64 CScriptFileSystem::GetSize(const string &path) const { string search; if (path.find(":") != string::npos || path.find("/") == 0 || path.find("\\") == 0) search = path; else search = currentPath + "/" + path; #if defined(_WIN32) // Windows uses UTF16 so it is necessary to convert the string wchar_t bufUTF16[10000]; MultiByteToWideChar(CP_UTF8, 0, search.c_str(), -1, bufUTF16, 10000); // Get the size of the file LARGE_INTEGER largeInt; HANDLE file = CreateFileW(bufUTF16, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); BOOL success = GetFileSizeEx(file, &largeInt); CloseHandle(file); if( !success ) return -1; return asINT64(largeInt.QuadPart); #else // Get the size of the file struct stat st; if (stat(search.c_str(), &st) == -1) return -1; return asINT64(st.st_size); #endif } // TODO: Should be able to return different codes for // - directory exists // - path not found // - access denied // TODO: Should be able to define the permissions for the directory int CScriptFileSystem::MakeDir(const string &path) { string search; if (path.find(":") != string::npos || path.find("/") == 0 || path.find("\\") == 0) search = path; else search = currentPath + "/" + path; #if defined(_WIN32) // Windows uses UTF16 so it is necessary to convert the string wchar_t bufUTF16[10000]; MultiByteToWideChar(CP_UTF8, 0, search.c_str(), -1, bufUTF16, 10000); // Create the directory BOOL success = CreateDirectoryW(bufUTF16, 0); return success ? 0 : -1; #else // Create the directory int failure = mkdir(search.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); return !failure ? 0 : -1; #endif } // TODO: Should be able to return different codes for // - directory doesn't exist // - directory is not empty // - access denied // TODO: Should have an option to remove the directory and all content recursively int CScriptFileSystem::RemoveDir(const string &path) { string search; if (path.find(":") != string::npos || path.find("/") == 0 || path.find("\\") == 0) search = path; else search = currentPath + "/" + path; #if defined(_WIN32) // Windows uses UTF16 so it is necessary to convert the string wchar_t bufUTF16[10000]; MultiByteToWideChar(CP_UTF8, 0, search.c_str(), -1, bufUTF16, 10000); // Remove the directory BOOL success = RemoveDirectoryW(bufUTF16); return success ? 0 : -1; #else // Remove the directory int failure = rmdir(search.c_str()); return !failure ? 0 : -1; #endif } int CScriptFileSystem::DeleteFile(const string &path) { string search; if (path.find(":") != string::npos || path.find("/") == 0 || path.find("\\") == 0) search = path; else search = currentPath + "/" + path; #if defined(_WIN32) // Windows uses UTF16 so it is necessary to convert the string wchar_t bufUTF16[10000]; MultiByteToWideChar(CP_UTF8, 0, search.c_str(), -1, bufUTF16, 10000); // Remove the file BOOL success = DeleteFileW(bufUTF16); return success ? 0 : -1; #else // Remove the file int failure = unlink(search.c_str()); return !failure ? 0 : -1; #endif } int CScriptFileSystem::CopyFile(const string &source, const string &target) { string search1; if (source.find(":") != string::npos || source.find("/") == 0 || source.find("\\") == 0) search1 = source; else search1 = currentPath + "/" + source; string search2; if (target.find(":") != string::npos || target.find("/") == 0 || target.find("\\") == 0) search2 = target; else search2 = currentPath + "/" + target; #if defined(_WIN32) // Windows uses UTF16 so it is necessary to convert the string wchar_t bufUTF16_1[10000]; MultiByteToWideChar(CP_UTF8, 0, search1.c_str(), -1, bufUTF16_1, 10000); wchar_t bufUTF16_2[10000]; MultiByteToWideChar(CP_UTF8, 0, search2.c_str(), -1, bufUTF16_2, 10000); // Copy the file BOOL success = CopyFileW(bufUTF16_1, bufUTF16_2, TRUE); return success ? 0 : -1; #else // Copy the file manually as there is no posix function for this bool failure = false; FILE *src = 0, *tgt = 0; src = fopen(search1.c_str(), "r"); if (src == 0) failure = true; if( !failure ) tgt = fopen(search2.c_str(), "w"); if (tgt == 0) failure = true; char buf[1024]; size_t n; while (!failure && (n = fread(buf, sizeof(char), sizeof(buf), src)) > 0) { if (fwrite(buf, sizeof(char), n, tgt) != n) failure = true; } if (src) fclose(src); if (tgt) fclose(tgt); return !failure ? 0 : -1; #endif } int CScriptFileSystem::Move(const string &source, const string &target) { string search1; if (source.find(":") != string::npos || source.find("/") == 0 || source.find("\\") == 0) search1 = source; else search1 = currentPath + "/" + source; string search2; if (target.find(":") != string::npos || target.find("/") == 0 || target.find("\\") == 0) search2 = target; else search2 = currentPath + "/" + target; #if defined(_WIN32) // Windows uses UTF16 so it is necessary to convert the string wchar_t bufUTF16_1[10000]; MultiByteToWideChar(CP_UTF8, 0, search1.c_str(), -1, bufUTF16_1, 10000); wchar_t bufUTF16_2[10000]; MultiByteToWideChar(CP_UTF8, 0, search2.c_str(), -1, bufUTF16_2, 10000); // Move the file or directory BOOL success = MoveFileW(bufUTF16_1, bufUTF16_2); return success ? 0 : -1; #else // Move the file or directory int failure = rename(search1.c_str(), search2.c_str()); return !failure ? 0 : -1; #endif } string CScriptFileSystem::GetCurrentPath() const { return currentPath; } CDateTime CScriptFileSystem::GetCreateDateTime(const string &path) const { string search; if (path.find(":") != string::npos || path.find("/") == 0 || path.find("\\") == 0) search = path; else search = currentPath + "/" + path; #if defined(_WIN32) // Windows uses UTF16 so it is necessary to convert the string wchar_t bufUTF16[10000]; MultiByteToWideChar(CP_UTF8, 0, search.c_str(), -1, bufUTF16, 10000); // Get the create date/time of the file FILETIME createTm; HANDLE file = CreateFileW(bufUTF16, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); BOOL success = GetFileTime(file, &createTm, 0, 0); CloseHandle(file); if( !success ) { asIScriptContext *ctx = asGetActiveContext(); if( ctx ) ctx->SetException("Failed to get file creation date/time"); return CDateTime(); } SYSTEMTIME tm; FileTimeToSystemTime(&createTm, &tm); return CDateTime(tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond); #else // Get the create date/time of the file struct stat st; if (stat(search.c_str(), &st) == -1) { asIScriptContext *ctx = asGetActiveContext(); if( ctx ) ctx->SetException("Failed to get file creation date/time"); return CDateTime(); } tm *t = localtime(&st.st_ctime); return CDateTime(t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec); #endif } CDateTime CScriptFileSystem::GetModifyDateTime(const string &path) const { string search; if (path.find(":") != string::npos || path.find("/") == 0 || path.find("\\") == 0) search = path; else search = currentPath + "/" + path; #if defined(_WIN32) // Windows uses UTF16 so it is necessary to convert the string wchar_t bufUTF16[10000]; MultiByteToWideChar(CP_UTF8, 0, search.c_str(), -1, bufUTF16, 10000); // Get the last modify date/time of the file FILETIME modifyTm; HANDLE file = CreateFileW(bufUTF16, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); BOOL success = GetFileTime(file, 0, 0, &modifyTm); CloseHandle(file); if( !success ) { asIScriptContext *ctx = asGetActiveContext(); if( ctx ) ctx->SetException("Failed to get file modify date/time"); return CDateTime(); } SYSTEMTIME tm; FileTimeToSystemTime(&modifyTm, &tm); return CDateTime(tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond); #else // Get the last modify date/time of the file struct stat st; if (stat(search.c_str(), &st) == -1) { asIScriptContext *ctx = asGetActiveContext(); if( ctx ) ctx->SetException("Failed to get file modify date/time"); return CDateTime(); } tm *t = localtime(&st.st_mtime); return CDateTime(t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec); #endif } END_AS_NAMESPACE