mirror of
https://github.com/Relintai/sfw.git
synced 2024-12-20 21:06:49 +01:00
1197 lines
26 KiB
C++
1197 lines
26 KiB
C++
|
|
//--STRIP
|
|
#include "dir_access.h"
|
|
|
|
#include <cstdio>
|
|
//--STRIP
|
|
|
|
/*************************************************************************/
|
|
/* dir_access.cpp */
|
|
/* From https://github.com/Relintai/pandemonium_engine (MIT) */
|
|
/*************************************************************************/
|
|
|
|
//--STRIP
|
|
#include "dir_access.h"
|
|
|
|
#include "core/list.h"
|
|
|
|
#include "core/file_access.h"
|
|
#include "core/local_vector.h"
|
|
#include "core/memory.h"
|
|
//--STRIP
|
|
|
|
#if defined(_WIN64) || defined(_WIN32)
|
|
|
|
#include <stdio.h>
|
|
#include <wchar.h>
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
|
|
#else
|
|
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/statvfs.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#ifdef HAVE_MNTENT
|
|
#include <mntent.h>
|
|
#endif
|
|
|
|
#endif
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma comment(lib, "Shlwapi")
|
|
#endif
|
|
|
|
#if defined(_WIN64) || defined(_WIN32)
|
|
|
|
bool DirAccess::is_link(String p_file) {
|
|
return false;
|
|
};
|
|
String DirAccess::read_link(String p_file) {
|
|
return p_file;
|
|
};
|
|
Error DirAccess::create_link(String p_source, String p_target) {
|
|
return FAILED;
|
|
};
|
|
|
|
/*
|
|
|
|
[03:57] <reduz> yessopie, so i don't havemak to rely on unicows
|
|
[03:58] <yessopie> reduz- yeah, all of the functions fail, and then you can call GetLastError () which will return 120
|
|
[03:58] <drumstick> CategoryApl, hehe, what? :)
|
|
[03:59] <CategoryApl> didn't Verona lead to some trouble
|
|
[03:59] <yessopie> 120 = ERROR_CALL_NOT_IMPLEMENTED
|
|
[03:59] <yessopie> (you can use that constant if you include winerr.h)
|
|
[03:59] <CategoryApl> well answer with winning a compo
|
|
|
|
[04:02] <yessopie> if ( SetCurrentDirectoryW ( L"." ) == FALSE && GetLastError () == ERROR_CALL_NOT_IMPLEMENTED ) { use ANSI }
|
|
*/
|
|
|
|
struct DirAccessWindowsPrivate {
|
|
HANDLE h; //handle for findfirstfile
|
|
WIN32_FIND_DATA f;
|
|
WIN32_FIND_DATAW fu; //unicode version
|
|
};
|
|
|
|
// CreateFolderAsync
|
|
|
|
Error DirAccess::list_dir_begin(bool skip_specials) {
|
|
_cisdir = false;
|
|
_cishidden = false;
|
|
_skip_specials = skip_specials;
|
|
|
|
list_dir_end();
|
|
p->h = FindFirstFileExW((LPCWSTR)(String(current_dir + "\\*").utf16().get_data()), FindExInfoStandard, &p->fu, FindExSearchNameMatch, NULL, 0);
|
|
|
|
return (p->h == INVALID_HANDLE_VALUE) ? ERR_CANT_OPEN : OK;
|
|
}
|
|
|
|
String DirAccess::get_next() {
|
|
if (p->h == INVALID_HANDLE_VALUE) {
|
|
return "";
|
|
}
|
|
|
|
_cisdir = (p->fu.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
|
|
_cishidden = (p->fu.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN);
|
|
|
|
String name = String::utf16((const char16_t *)(p->fu.cFileName));
|
|
|
|
if (FindNextFileW(p->h, &p->fu) == 0) {
|
|
FindClose(p->h);
|
|
p->h = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
bool DirAccess::current_is_dir() const {
|
|
return _cisdir;
|
|
}
|
|
|
|
bool DirAccess::current_is_hidden() const {
|
|
return _cishidden;
|
|
}
|
|
|
|
bool DirAccess::current_is_file() const {
|
|
return !_cisdir;
|
|
}
|
|
|
|
bool DirAccess::current_is_special_dir() const {
|
|
return _cisspecial;
|
|
}
|
|
|
|
void DirAccess::list_dir_end() {
|
|
if (p->h != INVALID_HANDLE_VALUE) {
|
|
FindClose(p->h);
|
|
p->h = INVALID_HANDLE_VALUE;
|
|
}
|
|
}
|
|
int DirAccess::get_drive_count() {
|
|
return drive_count;
|
|
}
|
|
String DirAccess::get_drive(int p_drive) {
|
|
if (p_drive < 0 || p_drive >= drive_count) {
|
|
return "";
|
|
}
|
|
|
|
return String::chr(drives[p_drive]) + ":";
|
|
}
|
|
|
|
Error DirAccess::change_dir(String p_dir) {
|
|
GLOBAL_LOCK_FUNCTION
|
|
|
|
WCHAR real_current_dir_name[2048];
|
|
GetCurrentDirectoryW(2048, real_current_dir_name);
|
|
String prev_dir = String::utf16((const char16_t *)real_current_dir_name);
|
|
|
|
SetCurrentDirectoryW((LPCWSTR)(current_dir.utf16().get_data()));
|
|
bool worked = (SetCurrentDirectoryW((LPCWSTR)(p_dir.utf16().get_data())) != 0);
|
|
|
|
String base;
|
|
if (base != "") {
|
|
GetCurrentDirectoryW(2048, real_current_dir_name);
|
|
String new_dir = String::utf16((const char16_t *)real_current_dir_name).replace("\\", "/");
|
|
if (!new_dir.begins_with(base)) {
|
|
worked = false;
|
|
}
|
|
}
|
|
|
|
if (worked) {
|
|
GetCurrentDirectoryW(2048, real_current_dir_name);
|
|
current_dir = String::utf16((const char16_t *)real_current_dir_name);
|
|
current_dir = current_dir.replace("\\", "/");
|
|
}
|
|
|
|
SetCurrentDirectoryW((LPCWSTR)(prev_dir.utf16().get_data()));
|
|
|
|
return worked ? OK : ERR_INVALID_PARAMETER;
|
|
}
|
|
|
|
Error DirAccess::make_dir(String p_dir) {
|
|
GLOBAL_LOCK_FUNCTION
|
|
|
|
if (p_dir.is_rel_path()) {
|
|
p_dir = current_dir.plus_file(p_dir);
|
|
}
|
|
|
|
p_dir = p_dir.simplify_path().replace("/", "\\");
|
|
|
|
bool success;
|
|
int err;
|
|
|
|
if (!p_dir.is_network_share_path()) {
|
|
p_dir = "\\\\?\\" + p_dir;
|
|
// Add "\\?\" to the path to extend max. path length past 248, if it's not a network share UNC path.
|
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa363855(v=vs.85).aspx
|
|
}
|
|
|
|
success = CreateDirectoryW((LPCWSTR)(p_dir.utf16().get_data()), NULL);
|
|
err = GetLastError();
|
|
|
|
if (success) {
|
|
return OK;
|
|
}
|
|
|
|
if (err == ERROR_ALREADY_EXISTS || err == ERROR_ACCESS_DENIED) {
|
|
return ERR_ALREADY_EXISTS;
|
|
}
|
|
|
|
return ERR_CANT_CREATE;
|
|
}
|
|
|
|
String DirAccess::get_current_dir() {
|
|
String base;
|
|
if (base != "") {
|
|
String bd = current_dir.replace("\\", "/").replace_first(base, "");
|
|
if (bd.begins_with("/")) {
|
|
return bd.substr(1, bd.length());
|
|
} else {
|
|
return bd;
|
|
}
|
|
}
|
|
|
|
return current_dir;
|
|
}
|
|
|
|
String DirAccess::get_current_dir_without_drive() {
|
|
String dir = get_current_dir();
|
|
|
|
int p = current_dir.find(":");
|
|
if (p != -1) {
|
|
dir = dir.right(p + 1);
|
|
}
|
|
|
|
return dir;
|
|
}
|
|
|
|
bool DirAccess::file_exists(String p_file) {
|
|
GLOBAL_LOCK_FUNCTION
|
|
|
|
if (!p_file.is_abs_path()) {
|
|
p_file = get_current_dir().plus_file(p_file);
|
|
}
|
|
|
|
DWORD fileAttr;
|
|
|
|
fileAttr = GetFileAttributesW((LPCWSTR)(p_file.utf16().get_data()));
|
|
if (INVALID_FILE_ATTRIBUTES == fileAttr) {
|
|
return false;
|
|
}
|
|
|
|
return !(fileAttr & FILE_ATTRIBUTE_DIRECTORY);
|
|
}
|
|
|
|
bool DirAccess::dir_exists(String p_dir) {
|
|
GLOBAL_LOCK_FUNCTION
|
|
|
|
if (p_dir.is_rel_path()) {
|
|
p_dir = get_current_dir().plus_file(p_dir);
|
|
}
|
|
|
|
DWORD fileAttr;
|
|
|
|
fileAttr = GetFileAttributesW((LPCWSTR)(p_dir.utf16().get_data()));
|
|
if (INVALID_FILE_ATTRIBUTES == fileAttr) {
|
|
return false;
|
|
}
|
|
|
|
return (fileAttr & FILE_ATTRIBUTE_DIRECTORY);
|
|
}
|
|
|
|
Error DirAccess::rename(String p_path, String p_new_path) {
|
|
if (p_path.is_rel_path()) {
|
|
p_path = get_current_dir().plus_file(p_path);
|
|
}
|
|
|
|
if (p_new_path.is_rel_path()) {
|
|
p_new_path = get_current_dir().plus_file(p_new_path);
|
|
}
|
|
|
|
// If we're only changing file name case we need to do a little juggling
|
|
if (p_path.to_lower() == p_new_path.to_lower()) {
|
|
if (dir_exists(p_path)) {
|
|
// The path is a dir; just rename
|
|
return ::_wrename((LPCWSTR)(p_path.utf16().get_data()), (LPCWSTR)(p_new_path.utf16().get_data())) == 0 ? OK : FAILED;
|
|
}
|
|
|
|
// The path is a file; juggle
|
|
WCHAR tmpfile[MAX_PATH];
|
|
|
|
if (!GetTempFileNameW((LPCWSTR)(get_current_dir().utf16().get_data()), NULL, 0, tmpfile)) {
|
|
return FAILED;
|
|
}
|
|
|
|
if (!::ReplaceFileW(tmpfile, (LPCWSTR)(p_path.utf16().get_data()), NULL, 0, NULL, NULL)) {
|
|
DeleteFileW(tmpfile);
|
|
return FAILED;
|
|
}
|
|
|
|
return ::_wrename(tmpfile, (LPCWSTR)(p_new_path.utf16().get_data())) == 0 ? OK : FAILED;
|
|
|
|
} else {
|
|
if (file_exists(p_new_path)) {
|
|
if (remove(p_new_path) != OK) {
|
|
return FAILED;
|
|
}
|
|
}
|
|
|
|
return ::_wrename((LPCWSTR)(p_path.utf16().get_data()), (LPCWSTR)(p_new_path.utf16().get_data())) == 0 ? OK : FAILED;
|
|
}
|
|
}
|
|
|
|
Error DirAccess::remove(String p_path) {
|
|
if (p_path.is_rel_path()) {
|
|
p_path = get_current_dir().plus_file(p_path);
|
|
}
|
|
|
|
DWORD fileAttr;
|
|
|
|
fileAttr = GetFileAttributesW((LPCWSTR)(p_path.utf16().get_data()));
|
|
if (INVALID_FILE_ATTRIBUTES == fileAttr) {
|
|
return FAILED;
|
|
}
|
|
|
|
if ((fileAttr & FILE_ATTRIBUTE_DIRECTORY)) {
|
|
return ::_wrmdir((LPCWSTR)(p_path.utf16().get_data())) == 0 ? OK : FAILED;
|
|
} else {
|
|
return ::_wunlink((LPCWSTR)(p_path.utf16().get_data())) == 0 ? OK : FAILED;
|
|
}
|
|
}
|
|
/*
|
|
|
|
FileType DirAccess::get_file_type(const String& p_file) const {
|
|
WCHAR real_current_dir_name[2048];
|
|
GetCurrentDirectoryW(2048, real_current_dir_name);
|
|
String prev_dir = Strong::utf16((const char16_t *)real_current_dir_name);
|
|
|
|
bool worked = SetCurrentDirectoryW((LPCWSTR)(current_dir.utf16().get_data()));
|
|
|
|
DWORD attr;
|
|
if (worked) {
|
|
WIN32_FILE_ATTRIBUTE_DATA fileInfo;
|
|
attr = GetFileAttributesExW((LPCWSTR)(p_file.utf16().get_data()), GetFileExInfoStandard, &fileInfo);
|
|
}
|
|
|
|
SetCurrentDirectoryW((LPCWSTR)(prev_dir.utf16().get_data()));
|
|
|
|
if (!worked) {
|
|
return FILE_TYPE_NONE;
|
|
}
|
|
|
|
return (attr & FILE_ATTRIBUTE_DIRECTORY) ? FILE_TYPE_
|
|
}
|
|
*/
|
|
|
|
uint64_t DirAccess::get_space_left() {
|
|
uint64_t bytes = 0;
|
|
|
|
if (!GetDiskFreeSpaceEx(NULL, (PULARGE_INTEGER)&bytes, NULL, NULL)) {
|
|
return 0;
|
|
}
|
|
|
|
//this is either 0 or a value in bytes.
|
|
return bytes;
|
|
}
|
|
|
|
String DirAccess::get_filesystem_type() const {
|
|
String path = const_cast<DirAccess *>(this)->get_current_dir();
|
|
|
|
if (path.is_network_share_path()) {
|
|
return "Network Share";
|
|
}
|
|
|
|
int unit_end = path.find(":");
|
|
ERR_FAIL_COND_V(unit_end == -1, String());
|
|
String unit = path.substr(0, unit_end + 1) + "\\";
|
|
|
|
WCHAR szVolumeName[100];
|
|
WCHAR szFileSystemName[10];
|
|
DWORD dwSerialNumber = 0;
|
|
DWORD dwMaxFileNameLength = 0;
|
|
DWORD dwFileSystemFlags = 0;
|
|
|
|
if (::GetVolumeInformationW((LPCWSTR)(unit.utf16().get_data()),
|
|
szVolumeName,
|
|
sizeof(szVolumeName),
|
|
&dwSerialNumber,
|
|
&dwMaxFileNameLength,
|
|
&dwFileSystemFlags,
|
|
szFileSystemName,
|
|
sizeof(szFileSystemName)) == TRUE) {
|
|
return String::utf16((const char16_t *)szFileSystemName);
|
|
}
|
|
|
|
ERR_FAIL_V("");
|
|
}
|
|
|
|
int DirAccess::get_current_drive() {
|
|
String path = get_current_dir().to_lower();
|
|
for (int i = 0; i < get_drive_count(); i++) {
|
|
String d = get_drive(i).to_lower();
|
|
if (path.begins_with(d)) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool DirAccess::drives_are_shortcuts() {
|
|
return false;
|
|
}
|
|
|
|
uint64_t DirAccess::get_modified_time(String p_file) {
|
|
return 0;
|
|
};
|
|
|
|
DirAccess::DirAccess() {
|
|
p = memnew(DirAccessWindowsPrivate);
|
|
p->h = INVALID_HANDLE_VALUE;
|
|
current_dir = ".";
|
|
|
|
drive_count = 0;
|
|
|
|
#ifdef UWP_ENABLED
|
|
Windows::Storage::StorageFolder ^ install_folder = Windows::ApplicationModel::Package::Current->InstalledLocation;
|
|
change_dir(install_folder->Path->Data());
|
|
|
|
#else
|
|
|
|
DWORD mask = GetLogicalDrives();
|
|
|
|
for (int i = 0; i < MAX_DRIVES; i++) {
|
|
if (mask & (1 << i)) { //DRIVE EXISTS
|
|
|
|
drives[drive_count] = 'A' + i;
|
|
drive_count++;
|
|
}
|
|
}
|
|
|
|
change_dir(".");
|
|
#endif
|
|
}
|
|
|
|
DirAccess::~DirAccess() {
|
|
list_dir_end();
|
|
|
|
memdelete(p);
|
|
}
|
|
|
|
#else
|
|
|
|
Error DirAccess::list_dir_begin(bool skip_specials) {
|
|
list_dir_end(); //close any previous dir opening!
|
|
|
|
_skip_specials = skip_specials;
|
|
|
|
//char real_current_dir_name[2048]; //is this enough?!
|
|
//getcwd(real_current_dir_name,2048);
|
|
//chdir(current_path.utf8().get_data());
|
|
dir_stream = opendir(current_dir.utf8().get_data());
|
|
//chdir(real_current_dir_name);
|
|
if (!dir_stream) {
|
|
return ERR_CANT_OPEN; //error!
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
bool DirAccess::file_exists(String p_file) {
|
|
GLOBAL_LOCK_FUNCTION
|
|
|
|
if (p_file.is_rel_path()) {
|
|
p_file = current_dir.plus_file(p_file);
|
|
}
|
|
|
|
struct stat flags;
|
|
bool success = (stat(p_file.utf8().get_data(), &flags) == 0);
|
|
|
|
if (success && S_ISDIR(flags.st_mode)) {
|
|
success = false;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
bool DirAccess::dir_exists(String p_dir) {
|
|
GLOBAL_LOCK_FUNCTION
|
|
|
|
if (p_dir.is_rel_path()) {
|
|
p_dir = get_current_dir().plus_file(p_dir);
|
|
}
|
|
|
|
struct stat flags;
|
|
bool success = (stat(p_dir.utf8().get_data(), &flags) == 0);
|
|
|
|
return (success && S_ISDIR(flags.st_mode));
|
|
}
|
|
|
|
uint64_t DirAccess::get_modified_time(String p_file) {
|
|
if (p_file.is_rel_path()) {
|
|
p_file = current_dir.plus_file(p_file);
|
|
}
|
|
|
|
struct stat flags;
|
|
bool success = (stat(p_file.utf8().get_data(), &flags) == 0);
|
|
|
|
if (success) {
|
|
return flags.st_mtime;
|
|
} else {
|
|
ERR_FAIL_V(0);
|
|
};
|
|
return 0;
|
|
};
|
|
|
|
String DirAccess::get_next() {
|
|
if (!dir_stream) {
|
|
return "";
|
|
}
|
|
|
|
dirent *entry = readdir(dir_stream);
|
|
|
|
if (entry == nullptr) {
|
|
list_dir_end();
|
|
return "";
|
|
}
|
|
|
|
String fname = fix_unicode_name(entry->d_name);
|
|
|
|
// Look at d_type to determine if the entry is a directory, unless
|
|
// its type is unknown (the file system does not support it) or if
|
|
// the type is a link, in that case we want to resolve the link to
|
|
// known if it points to a directory. stat() will resolve the link
|
|
// for us.
|
|
if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK) {
|
|
String f = current_dir.plus_file(fname);
|
|
|
|
struct stat flags;
|
|
if (stat(f.utf8().get_data(), &flags) == 0) {
|
|
_cisdir = S_ISDIR(flags.st_mode);
|
|
} else {
|
|
_cisdir = false;
|
|
}
|
|
} else {
|
|
_cisdir = (entry->d_type == DT_DIR);
|
|
}
|
|
|
|
_cishidden = is_hidden(fname);
|
|
|
|
_cisspecial = is_special(fname);
|
|
|
|
if (_skip_specials && _cisspecial) {
|
|
// Should only happen 2 times max
|
|
return get_next();
|
|
}
|
|
|
|
return fname;
|
|
}
|
|
|
|
bool DirAccess::current_is_dir() const {
|
|
return _cisdir;
|
|
}
|
|
|
|
bool DirAccess::current_is_file() const {
|
|
return !_cisdir;
|
|
}
|
|
|
|
bool DirAccess::current_is_special_dir() const {
|
|
return _cisspecial;
|
|
}
|
|
|
|
bool DirAccess::current_is_hidden() const {
|
|
return _cishidden;
|
|
}
|
|
|
|
void DirAccess::list_dir_end() {
|
|
if (dir_stream) {
|
|
closedir(dir_stream);
|
|
}
|
|
dir_stream = nullptr;
|
|
_cisdir = false;
|
|
}
|
|
|
|
#if defined(HAVE_MNTENT) && defined(X11_ENABLED)
|
|
static bool _filter_drive(struct mntent *mnt) {
|
|
// Ignore devices that don't point to /dev
|
|
if (strncmp(mnt->mnt_fsname, "/dev", 4) != 0) {
|
|
return false;
|
|
}
|
|
|
|
// Accept devices mounted at common locations
|
|
if (strncmp(mnt->mnt_dir, "/media", 6) == 0 ||
|
|
strncmp(mnt->mnt_dir, "/mnt", 4) == 0 ||
|
|
strncmp(mnt->mnt_dir, "/home", 5) == 0 ||
|
|
strncmp(mnt->mnt_dir, "/run/media", 10) == 0) {
|
|
return true;
|
|
}
|
|
|
|
// Ignore everything else
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
static void _get_drives(List<String> *list) {
|
|
list->push_back("/");
|
|
|
|
#if defined(HAVE_MNTENT) && defined(X11_ENABLED)
|
|
// Check /etc/mtab for the list of mounted partitions
|
|
FILE *mtab = setmntent("/etc/mtab", "r");
|
|
if (mtab) {
|
|
struct mntent mnt;
|
|
char strings[4096];
|
|
|
|
while (getmntent_r(mtab, &mnt, strings, sizeof(strings))) {
|
|
if (mnt.mnt_dir != nullptr && _filter_drive(&mnt)) {
|
|
// Avoid duplicates
|
|
if (!list->find(mnt.mnt_dir)) {
|
|
list->push_back(mnt.mnt_dir);
|
|
}
|
|
}
|
|
}
|
|
|
|
endmntent(mtab);
|
|
}
|
|
#endif
|
|
|
|
// Add $HOME
|
|
const char *home = getenv("HOME");
|
|
if (home) {
|
|
// Only add if it's not a duplicate
|
|
if (!list->find(home)) {
|
|
list->push_back(home);
|
|
}
|
|
|
|
// Check $HOME/.config/gtk-3.0/bookmarks
|
|
char path[1024];
|
|
snprintf(path, 1024, "%s/.config/gtk-3.0/bookmarks", home);
|
|
FILE *fd = fopen(path, "r");
|
|
if (fd) {
|
|
char string[1024];
|
|
while (fgets(string, 1024, fd)) {
|
|
// Parse only file:// links
|
|
if (strncmp(string, "file://", 7) == 0) {
|
|
// Strip any unwanted edges on the strings and push_back if it's not a duplicate
|
|
String fpath = String(string + 7).strip_edges().split_spaces()[0].percent_decode();
|
|
if (!list->find(fpath)) {
|
|
list->push_back(fpath);
|
|
}
|
|
}
|
|
}
|
|
|
|
fclose(fd);
|
|
}
|
|
}
|
|
|
|
list->sort();
|
|
}
|
|
|
|
int DirAccess::get_drive_count() {
|
|
List<String> list;
|
|
_get_drives(&list);
|
|
|
|
return list.size();
|
|
}
|
|
|
|
String DirAccess::get_drive(int p_drive) {
|
|
List<String> list;
|
|
_get_drives(&list);
|
|
|
|
ERR_FAIL_INDEX_V(p_drive, list.size(), "");
|
|
|
|
return list[p_drive];
|
|
}
|
|
|
|
int DirAccess::get_current_drive() {
|
|
int drive = 0;
|
|
int max_length = -1;
|
|
const String path = get_current_dir().to_lower();
|
|
for (int i = 0; i < get_drive_count(); i++) {
|
|
const String d = get_drive(i).to_lower();
|
|
if (max_length < d.length() && path.begins_with(d)) {
|
|
max_length = d.length();
|
|
drive = i;
|
|
}
|
|
}
|
|
return drive;
|
|
}
|
|
|
|
bool DirAccess::drives_are_shortcuts() {
|
|
return true;
|
|
}
|
|
|
|
Error DirAccess::make_dir(String p_dir) {
|
|
GLOBAL_LOCK_FUNCTION
|
|
|
|
if (p_dir.is_rel_path()) {
|
|
p_dir = get_current_dir().plus_file(p_dir);
|
|
}
|
|
|
|
bool success = (mkdir(p_dir.utf8().get_data(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0);
|
|
int err = errno;
|
|
|
|
if (success) {
|
|
return OK;
|
|
};
|
|
|
|
if (err == EEXIST) {
|
|
return ERR_ALREADY_EXISTS;
|
|
};
|
|
|
|
return ERR_CANT_CREATE;
|
|
}
|
|
|
|
Error DirAccess::change_dir(String p_dir) {
|
|
GLOBAL_LOCK_FUNCTION
|
|
|
|
// prev_dir is the directory we are changing out of
|
|
String prev_dir;
|
|
char real_current_dir_name[2048];
|
|
ERR_FAIL_COND_V(getcwd(real_current_dir_name, 2048) == nullptr, ERR_BUG);
|
|
if (prev_dir.parse_utf8(real_current_dir_name)) {
|
|
prev_dir = real_current_dir_name; //no utf8, maybe latin?
|
|
}
|
|
|
|
// try_dir is the directory we are trying to change into
|
|
String try_dir = "";
|
|
if (p_dir.is_rel_path()) {
|
|
String next_dir = current_dir.plus_file(p_dir);
|
|
next_dir = next_dir.simplify_path();
|
|
try_dir = next_dir;
|
|
} else {
|
|
try_dir = p_dir;
|
|
}
|
|
|
|
bool worked = (chdir(try_dir.utf8().get_data()) == 0); // we can only give this utf8
|
|
if (!worked) {
|
|
return ERR_INVALID_PARAMETER;
|
|
}
|
|
|
|
String base;
|
|
if (base != String() && !try_dir.begins_with(base)) {
|
|
ERR_FAIL_COND_V(getcwd(real_current_dir_name, 2048) == nullptr, ERR_BUG);
|
|
String new_dir;
|
|
new_dir.parse_utf8(real_current_dir_name);
|
|
|
|
if (!new_dir.begins_with(base)) {
|
|
try_dir = current_dir; //revert
|
|
}
|
|
}
|
|
|
|
// the directory exists, so set current_dir to try_dir
|
|
current_dir = try_dir;
|
|
ERR_FAIL_COND_V(chdir(prev_dir.utf8().get_data()) != 0, ERR_BUG);
|
|
return OK;
|
|
}
|
|
|
|
String DirAccess::get_current_dir() {
|
|
String base;
|
|
if (base != "") {
|
|
String bd = current_dir.replace_first(base, "");
|
|
if (bd.begins_with("/")) {
|
|
return bd.substr(1, bd.length());
|
|
} else {
|
|
return bd;
|
|
}
|
|
}
|
|
return current_dir;
|
|
}
|
|
|
|
Error DirAccess::rename(String p_path, String p_new_path) {
|
|
if (p_path.is_rel_path()) {
|
|
p_path = get_current_dir().plus_file(p_path);
|
|
}
|
|
|
|
if (p_new_path.is_rel_path()) {
|
|
p_new_path = get_current_dir().plus_file(p_new_path);
|
|
}
|
|
|
|
return ::rename(p_path.utf8().get_data(), p_new_path.utf8().get_data()) == 0 ? OK : FAILED;
|
|
}
|
|
|
|
Error DirAccess::remove(String p_path) {
|
|
if (p_path.is_rel_path()) {
|
|
p_path = get_current_dir().plus_file(p_path);
|
|
}
|
|
|
|
struct stat flags;
|
|
if ((stat(p_path.utf8().get_data(), &flags) != 0)) {
|
|
return FAILED;
|
|
}
|
|
|
|
if (S_ISDIR(flags.st_mode)) {
|
|
return ::rmdir(p_path.utf8().get_data()) == 0 ? OK : FAILED;
|
|
} else {
|
|
return ::unlink(p_path.utf8().get_data()) == 0 ? OK : FAILED;
|
|
}
|
|
}
|
|
|
|
bool DirAccess::is_link(String p_file) {
|
|
if (p_file.is_rel_path()) {
|
|
p_file = get_current_dir().plus_file(p_file);
|
|
}
|
|
|
|
struct stat flags;
|
|
if ((lstat(p_file.utf8().get_data(), &flags) != 0)) {
|
|
return FAILED;
|
|
}
|
|
|
|
return S_ISLNK(flags.st_mode);
|
|
}
|
|
|
|
String DirAccess::read_link(String p_file) {
|
|
if (p_file.is_rel_path()) {
|
|
p_file = get_current_dir().plus_file(p_file);
|
|
}
|
|
|
|
char buf[256];
|
|
memset(buf, 0, 256);
|
|
ssize_t len = readlink(p_file.utf8().get_data(), buf, sizeof(buf));
|
|
String link;
|
|
if (len > 0) {
|
|
link.parse_utf8(buf, len);
|
|
}
|
|
return link;
|
|
}
|
|
|
|
Error DirAccess::create_link(String p_source, String p_target) {
|
|
if (p_target.is_rel_path()) {
|
|
p_target = get_current_dir().plus_file(p_target);
|
|
}
|
|
|
|
if (symlink(p_source.utf8().get_data(), p_target.utf8().get_data()) == 0) {
|
|
return OK;
|
|
} else {
|
|
return FAILED;
|
|
}
|
|
}
|
|
|
|
uint64_t DirAccess::get_space_left() {
|
|
#ifndef NO_STATVFS
|
|
struct statvfs vfs;
|
|
if (statvfs(current_dir.utf8().get_data(), &vfs) != 0) {
|
|
return 0;
|
|
};
|
|
|
|
return (uint64_t)vfs.f_bavail * (uint64_t)vfs.f_frsize;
|
|
#else
|
|
// FIXME: Implement this.
|
|
return 0;
|
|
#endif
|
|
};
|
|
|
|
String DirAccess::get_filesystem_type() const {
|
|
return ""; //TODO this should be implemented
|
|
}
|
|
|
|
bool DirAccess::is_hidden(const String &p_name) {
|
|
return p_name != "." && p_name != ".." && p_name.begins_with(".");
|
|
}
|
|
|
|
String DirAccess::get_current_dir_without_drive() {
|
|
return get_current_dir();
|
|
}
|
|
|
|
DirAccess::DirAccess() {
|
|
dir_stream = NULL;
|
|
_cisdir = false;
|
|
|
|
next_is_dir = false;
|
|
_skip_specials = false;
|
|
|
|
_cishidden = false;
|
|
_cisspecial = false;
|
|
|
|
/* determine drive count */
|
|
|
|
// set current directory to an absolute path of the current directory
|
|
char real_current_dir_name[2048];
|
|
ERR_FAIL_COND(getcwd(real_current_dir_name, 2048) == nullptr);
|
|
if (current_dir.parse_utf8(real_current_dir_name)) {
|
|
current_dir = real_current_dir_name;
|
|
}
|
|
|
|
change_dir(current_dir);
|
|
}
|
|
|
|
DirAccess::~DirAccess() {
|
|
list_dir_end();
|
|
}
|
|
|
|
#endif
|
|
|
|
static Error _erase_recursive(DirAccess *da) {
|
|
List<String> dirs;
|
|
List<String> files;
|
|
|
|
da->list_dir_begin();
|
|
String n = da->get_next();
|
|
while (n != String()) {
|
|
if (n != "." && n != "..") {
|
|
if (da->current_is_dir()) {
|
|
dirs.push_back(n);
|
|
} else {
|
|
files.push_back(n);
|
|
}
|
|
}
|
|
|
|
n = da->get_next();
|
|
}
|
|
|
|
da->list_dir_end();
|
|
|
|
for (List<String>::Element *E = dirs.front(); E; E = E->next()) {
|
|
Error err = da->change_dir(E->get());
|
|
if (err == OK) {
|
|
err = _erase_recursive(da);
|
|
if (err) {
|
|
da->change_dir("..");
|
|
return err;
|
|
}
|
|
err = da->change_dir("..");
|
|
if (err) {
|
|
return err;
|
|
}
|
|
err = da->remove(da->get_current_dir().plus_file(E->get()));
|
|
if (err) {
|
|
return err;
|
|
}
|
|
} else {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
for (List<String>::Element *E = files.front(); E; E = E->next()) {
|
|
Error err = da->remove(da->get_current_dir().plus_file(E->get()));
|
|
if (err) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
Error DirAccess::erase_contents_recursive() {
|
|
return _erase_recursive(this);
|
|
}
|
|
|
|
Error DirAccess::make_dir_recursive(String p_dir) {
|
|
if (p_dir.length() < 1) {
|
|
return OK;
|
|
};
|
|
|
|
String full_dir;
|
|
|
|
if (p_dir.is_rel_path()) {
|
|
//append current
|
|
full_dir = get_current_dir().plus_file(p_dir);
|
|
|
|
} else {
|
|
full_dir = p_dir;
|
|
}
|
|
|
|
full_dir = full_dir.replace("\\", "/");
|
|
|
|
String base;
|
|
|
|
if (full_dir.is_network_share_path()) {
|
|
int pos = full_dir.find("/", 2);
|
|
ERR_FAIL_COND_V(pos < 0, ERR_INVALID_PARAMETER);
|
|
pos = full_dir.find("/", pos + 1);
|
|
ERR_FAIL_COND_V(pos < 0, ERR_INVALID_PARAMETER);
|
|
base = full_dir.substr(0, pos + 1);
|
|
} else if (full_dir.begins_with("/")) {
|
|
base = "/";
|
|
} else if (full_dir.find(":/") != -1) {
|
|
base = full_dir.substr(0, full_dir.find(":/") + 2);
|
|
} else {
|
|
ERR_FAIL_V(ERR_INVALID_PARAMETER);
|
|
}
|
|
|
|
full_dir = full_dir.replace_first(base, "").simplify_path();
|
|
|
|
Vector<String> subdirs = full_dir.split("/");
|
|
|
|
String curpath = base;
|
|
for (int i = 0; i < subdirs.size(); i++) {
|
|
curpath = curpath.plus_file(subdirs[i]);
|
|
Error err = make_dir(curpath);
|
|
if (err != OK && err != ERR_ALREADY_EXISTS) {
|
|
ERR_FAIL_V_MSG(err, "Could not create directory: " + curpath);
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
DirAccess *DirAccess::create_for_path(const String &p_path) {
|
|
DirAccess *d = memnew(DirAccess());
|
|
d->open(p_path);
|
|
return d;
|
|
}
|
|
DirAccess *DirAccess::create() {
|
|
return memnew(DirAccess());
|
|
}
|
|
|
|
Error DirAccess::open(const String &p_path) {
|
|
return change_dir(p_path);
|
|
}
|
|
|
|
String DirAccess::get_full_path(const String &p_path) {
|
|
DirAccess d;
|
|
|
|
d.change_dir(p_path);
|
|
String full = d.get_current_dir();
|
|
|
|
return full;
|
|
}
|
|
|
|
Error DirAccess::copy(String p_from, String p_to, int p_chmod_flags) {
|
|
//printf("copy %s -> %s\n",p_from.ascii().get_data(),p_to.ascii().get_data());
|
|
Error err;
|
|
FileAccess *fsrc = FileAccess::create_and_open(p_from, FileAccess::READ, &err);
|
|
|
|
if (err) {
|
|
ERR_PRINT("Failed to open " + p_from);
|
|
return err;
|
|
}
|
|
|
|
FileAccess *fdst = FileAccess::create_and_open(p_to, FileAccess::WRITE, &err);
|
|
if (err) {
|
|
fsrc->close();
|
|
memdelete(fsrc);
|
|
ERR_PRINT("Failed to open " + p_to);
|
|
return err;
|
|
}
|
|
|
|
const size_t copy_buffer_limit = 65536; // 64 KB
|
|
|
|
fsrc->seek_end(0);
|
|
uint64_t size = fsrc->get_position();
|
|
fsrc->seek(0);
|
|
err = OK;
|
|
size_t buffer_size = MIN(size * sizeof(uint8_t), copy_buffer_limit);
|
|
LocalVector<uint8_t> buffer;
|
|
buffer.resize(buffer_size);
|
|
while (size > 0) {
|
|
if (fsrc->get_error() != OK) {
|
|
err = fsrc->get_error();
|
|
break;
|
|
}
|
|
if (fdst->get_error() != OK) {
|
|
err = fdst->get_error();
|
|
break;
|
|
}
|
|
|
|
int bytes_read = fsrc->get_buffer(buffer.ptr(), buffer_size);
|
|
if (bytes_read <= 0) {
|
|
err = FAILED;
|
|
break;
|
|
}
|
|
fdst->store_buffer(buffer.ptr(), bytes_read);
|
|
|
|
size -= bytes_read;
|
|
}
|
|
|
|
if (err == OK && p_chmod_flags != -1) {
|
|
fdst->close();
|
|
err = FileAccess::set_unix_permissions(p_to, p_chmod_flags);
|
|
// If running on a platform with no chmod support (i.e., Windows), don't fail
|
|
if (err == ERR_UNAVAILABLE) {
|
|
err = OK;
|
|
}
|
|
}
|
|
|
|
memdelete(fsrc);
|
|
memdelete(fdst);
|
|
|
|
return err;
|
|
}
|
|
|
|
// Changes dir for the current scope, returning back to the original dir
|
|
// when scope exits
|
|
class DirChanger {
|
|
DirAccess *da;
|
|
String original_dir;
|
|
|
|
public:
|
|
DirChanger(DirAccess *p_da, String p_dir) :
|
|
da(p_da),
|
|
original_dir(p_da->get_current_dir()) {
|
|
p_da->change_dir(p_dir);
|
|
}
|
|
|
|
~DirChanger() {
|
|
da->change_dir(original_dir);
|
|
}
|
|
};
|
|
|
|
Error DirAccess::_copy_dir(DirAccess *p_target_da, String p_to, int p_chmod_flags, bool p_copy_links) {
|
|
List<String> dirs;
|
|
|
|
String curdir = get_current_dir();
|
|
list_dir_begin();
|
|
String n = get_next();
|
|
while (n != String()) {
|
|
if (n != "." && n != "..") {
|
|
if (p_copy_links && is_link(get_current_dir().plus_file(n))) {
|
|
create_link(read_link(get_current_dir().plus_file(n)), p_to + n);
|
|
} else if (current_is_dir()) {
|
|
dirs.push_back(n);
|
|
} else {
|
|
const String &rel_path = n;
|
|
if (!n.is_rel_path()) {
|
|
list_dir_end();
|
|
return ERR_BUG;
|
|
}
|
|
Error err = copy(get_current_dir().plus_file(n), p_to + rel_path, p_chmod_flags);
|
|
if (err) {
|
|
list_dir_end();
|
|
return err;
|
|
}
|
|
}
|
|
}
|
|
|
|
n = get_next();
|
|
}
|
|
|
|
list_dir_end();
|
|
|
|
for (List<String>::Element *E = dirs.front(); E; E = E->next()) {
|
|
String rel_path = E->get();
|
|
String target_dir = p_to + rel_path;
|
|
if (!p_target_da->dir_exists(target_dir)) {
|
|
Error err = p_target_da->make_dir(target_dir);
|
|
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot create directory '" + target_dir + "'.");
|
|
}
|
|
|
|
Error err = change_dir(E->get());
|
|
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot change current directory to '" + E->get() + "'.");
|
|
|
|
err = _copy_dir(p_target_da, p_to + rel_path + "/", p_chmod_flags, p_copy_links);
|
|
if (err) {
|
|
change_dir("..");
|
|
ERR_FAIL_V_MSG(err, "Failed to copy recursively.");
|
|
}
|
|
err = change_dir("..");
|
|
ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to go back.");
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
Error DirAccess::copy_dir(String p_from, String p_to, int p_chmod_flags, bool p_copy_links) {
|
|
ERR_FAIL_COND_V_MSG(!dir_exists(p_from), ERR_FILE_NOT_FOUND, "Source directory doesn't exist.");
|
|
|
|
DirAccess *target_da = DirAccess::create_for_path(p_to);
|
|
ERR_FAIL_COND_V_MSG(!target_da, ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_to + "'.");
|
|
|
|
if (!target_da->dir_exists(p_to)) {
|
|
Error err = target_da->make_dir_recursive(p_to);
|
|
if (err) {
|
|
memdelete(target_da);
|
|
}
|
|
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot create directory '" + p_to + "'.");
|
|
}
|
|
|
|
if (!p_to.ends_with("/")) {
|
|
p_to = p_to + "/";
|
|
}
|
|
|
|
DirChanger dir_changer(this, p_from);
|
|
Error err = _copy_dir(target_da, p_to, p_chmod_flags, p_copy_links);
|
|
memdelete(target_da);
|
|
|
|
return err;
|
|
}
|
|
|
|
bool DirAccess::exists(String p_dir) {
|
|
DirAccess *da = DirAccess::create_for_path(p_dir);
|
|
bool valid = da->change_dir(p_dir) == OK;
|
|
memdelete(da);
|
|
return valid;
|
|
}
|
|
|
|
String DirAccess::get_filesystem_abspath_for(String p_path) {
|
|
DirAccess d;
|
|
|
|
d.change_dir(p_path);
|
|
String full = d.get_current_dir();
|
|
|
|
return full;
|
|
}
|
|
|
|
bool DirAccess::is_special(const String &p_path) {
|
|
if (p_path.size() > 2) {
|
|
return false;
|
|
}
|
|
|
|
return p_path == "." || p_path == "..";
|
|
}
|