563 lines
14 KiB
C++
Raw Normal View History

2022-08-05 02:06:42 -04:00
/*
* Copyright (C) Nemirtingas
* This file is part of System.
*
* System is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* System is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with System; if not, see
* <http://www.gnu.org/licenses/>.
*/
#include "Filesystem.h"
#include "Encoding.hpp"
#include "System_internals.h"
#if defined(SYSTEM_OS_WINDOWS)
#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN
#define NOMINMAX
#include <Windows.h>
#ifdef CreateDirectory
#undef CreateDirectory
#endif
#ifdef DeleteFile
#undef DeleteFile
#endif
#elif defined(SYSTEM_OS_LINUX) || defined(SYSTEM_OS_APPLE)
#include <sys/types.h>
#include <sys/ioctl.h> // get iface broadcast
#include <sys/stat.h> // stats on a file (is directory, size, mtime)
#include <dirent.h> // to open directories
#include <dlfcn.h> // dlopen (like dll for linux)
#include <string.h>
#include <limits.h> // PATH_MAX
#include <unistd.h>
#else
#error "unknown arch"
#endif
#include <fstream>
#include <algorithm>
#include <iterator>
#include <ctime>
namespace System {
namespace Filesystem {
static void _CleanSlashes(std::string& str);
std::string Filename(std::string const& path)
{
size_t pos = path.find_last_of("/\\");
if (pos != std::string::npos)
return path.substr(pos+1);
return path;
}
std::string Dirname(std::string const& path)
{
std::string r(path);
_CleanSlashes(r);
size_t pos = r.find_last_of("/\\");
if (pos == std::string::npos || (pos == 0 && r.length() == 1))
return std::string();
if (pos == 0)
++pos;
return r.substr(0, pos);
}
std::string Join(StringView r, StringView l)
{
std::string result(r.to_string());
result += Separator;
result += l.to_string();
_CleanSlashes(result);
return result;
}
std::string CanonicalPath(std::string const& path)
{
if (IsAbsolute(path))
return CleanPath(path);
return CleanPath(Join(GetCwd(),path));
}
size_t FileSize(std::string const& path)
{
std::ifstream in_file(path, std::ios::in | std::ios::binary | std::ios::ate);
if (in_file)
{
return in_file.tellg();
}
return 0;
}
std::chrono::system_clock::time_point FileATime(std::string const& path)
{
struct stat file_stat = {};
if (stat(path.c_str(), &file_stat) != 0)
return std::chrono::system_clock::time_point{};
return std::chrono::system_clock::from_time_t(file_stat.st_atime);
}
std::chrono::system_clock::time_point FileMTime(std::string const& path)
{
struct stat file_stat = {};
if (stat(path.c_str(), &file_stat) != 0)
return std::chrono::system_clock::time_point{};
return std::chrono::system_clock::from_time_t(file_stat.st_mtime);
}
std::chrono::system_clock::time_point FileCTime(std::string const& path)
{
struct stat file_stat = {};
if (stat(path.c_str(), &file_stat) != 0)
return std::chrono::system_clock::time_point{};
return std::chrono::system_clock::from_time_t(file_stat.st_ctime);
}
#ifdef SYSTEM_OS_WINDOWS
static void _CleanSlashes(std::string& str)
{
size_t pos;
std::replace(str.begin(), str.end(), '/', '\\');
while ((pos = str.find("\\\\")) != std::string::npos)
str.replace(pos, 2, "\\");
pos = 0;
while ((pos = str.find("\\.", pos)) != std::string::npos)
{
if (str[pos + 2] == '\\' || (pos + 2) >= str.length())
{
str.replace(pos, 3, "\\");
}
else
{
++pos;
}
}
}
std::string GetCwd()
{
DWORD size = GetCurrentDirectoryW(0, nullptr);
if (size == 0)
return ".";
std::wstring wdirectory;
++size;
wdirectory.resize(size);
wdirectory.resize(GetCurrentDirectoryW(size, &wdirectory[0]));
wdirectory += L'\\';
return System::Encoding::WCharToUtf8(wdirectory);
}
bool IsAbsolute(std::string const& path)
{
return path.length() >= 2 && (((path[0] >= 'a' && path[0] <= 'z') || (path[0] >= 'A' && path[0] <= 'Z')) && path[1] == ':');
}
std::string CleanPath(std::string const& path)
{
std::string cleaned_path(path);
size_t pos;
size_t size;
_CleanSlashes(cleaned_path);
pos = 0;
while ((pos = cleaned_path.find("\\..", pos)) != std::string::npos )
{
if (cleaned_path[pos + 3] == '\\' || (pos+3) >= cleaned_path.length())
{
if (pos == 0)
size = 3;
else
{
size_t parent_pos = cleaned_path.rfind("\\", pos - 1);
if (parent_pos == std::string::npos)
{
size = pos + 3;
pos = 0;
}
else
{
size = 3 + pos - parent_pos;
pos = parent_pos;
}
}
cleaned_path.replace(pos, size, "");
}
else
{
++pos;
}
}
return cleaned_path;
}
bool IsDir(std::string const& path)
{
std::wstring wpath(System::Encoding::Utf8ToWChar(path));
DWORD attrs = GetFileAttributesW(wpath.c_str());
return attrs != INVALID_FILE_ATTRIBUTES && attrs & FILE_ATTRIBUTE_DIRECTORY;
}
bool IsFile(std::string const& path)
{
std::wstring wpath(System::Encoding::Utf8ToWChar(path));
DWORD attrs = GetFileAttributesW(wpath.c_str());
return attrs != INVALID_FILE_ATTRIBUTES && ((attrs & FILE_ATTRIBUTE_DIRECTORY) != FILE_ATTRIBUTE_DIRECTORY);
}
bool Exists(std::string const& path)
{
std::wstring wpath(System::Encoding::Utf8ToWChar(path));
DWORD attrs = GetFileAttributesW(wpath.c_str());
return attrs != INVALID_FILE_ATTRIBUTES;
}
bool CreateDirectory(std::string const& directory, bool recursive)
{
size_t pos = 0;
struct _stat sb;
std::wstring sub_dir;
std::wstring wdirectory(System::Encoding::Utf8ToWChar(directory));
if (wdirectory.empty())
return false;
if (recursive)
{
pos = 3;
do
{
pos = wdirectory.find_first_of(L"\\/", pos + 1);
sub_dir = std::move(wdirectory.substr(0, pos));
if (_wstat(sub_dir.c_str(), &sb) == 0)
{
if (!(sb.st_mode & _S_IFDIR))
{// A subpath in the target is not a directory
return false;
}
// Folder Exists
}
else if (CreateDirectoryW(wdirectory.substr(0, pos).c_str(), NULL) == FALSE && GetLastError() != ERROR_ALREADY_EXISTS)
{// Failed to create directory
return false;
}
}
while (pos != std::string::npos);
return true;
}
return (CreateDirectoryW(wdirectory.c_str(), NULL) != FALSE || GetLastError() == ERROR_ALREADY_EXISTS);
}
bool DeleteFile(std::string const& path)
{
std::wstring wpath(System::Encoding::Utf8ToWChar(path));
return DeleteFileW(wpath.c_str()) == TRUE || GetLastError() == ERROR_FILE_NOT_FOUND;
}
static std::vector<std::wstring> ListFiles(std::wstring const& path, bool files_only, bool recursive)
{
std::vector<std::wstring> files;
WIN32_FIND_DATAW hfind_data;
HANDLE hfind = INVALID_HANDLE_VALUE;
std::wstring search_path = path;
if (*path.rbegin() != L'\\')
search_path += L'\\';
search_path += L'*';
// Start iterating over the files in the path directory.
hfind = FindFirstFileW(search_path.c_str(), &hfind_data);
if (hfind != INVALID_HANDLE_VALUE)
{
search_path.pop_back();
do // Managed to locate and create an handle to that folder.
{
if (wcscmp(L".", hfind_data.cFileName) == 0
|| wcscmp(L"..", hfind_data.cFileName) == 0)
continue;
if (hfind_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
if (recursive)
{
std::wstring dir_name = hfind_data.cFileName;
std::vector<std::wstring> sub_files = std::move(ListFiles(search_path + dir_name, files_only, true));
std::transform(sub_files.begin(), sub_files.end(), std::back_inserter(files), [&dir_name](std::wstring& Filename)
{
return dir_name + L'\\' + Filename;
});
}
if (!files_only)
{
files.emplace_back(hfind_data.cFileName);
}
}
else
{
files.emplace_back(hfind_data.cFileName);
}
} while (FindNextFileW(hfind, &hfind_data) == TRUE);
FindClose(hfind);
}
return files;
}
std::vector<std::string> ListFiles(std::string const& path, bool files_only, bool recursive)
{
std::vector<std::string> files;
std::wstring wpath(System::Encoding::Utf8ToWChar(path));
std::vector<std::wstring> wfiles(std::move(ListFiles(wpath, files_only, recursive)));
files.reserve(wfiles.size());
std::transform(wfiles.begin(), wfiles.end(), std::back_inserter(files), [](std::wstring const& wFilename)
{
return System::Encoding::WCharToUtf8(wFilename);
});
return files;
}
#else
static void _CleanSlashes(std::string& str)
{
size_t pos;
std::replace(str.begin(), str.end(), '\\', '/');
while ((pos = str.find("//")) != std::string::npos)
str.replace(pos, 2, "/");
pos = 0;
while ((pos = str.find("/.", pos)) != std::string::npos)
{
if (str[pos + 2] == '/' || (pos + 2) >= str.length())
{
str.replace(pos, 3, "/");
}
else
{
++pos;
}
}
}
std::string GetCwd()
{
char buff[4096];
std::string tmp(getcwd(buff, 4096) == nullptr ? "." : buff);
tmp += '/';
return tmp;
}
bool IsAbsolute(std::string const& path)
{
return path[0] == '/';
}
std::string CleanPath(std::string const& path)
{
std::string cleaned_path(path);
size_t pos;
size_t size;
std::replace(cleaned_path.begin(), cleaned_path.end(), '\\', '/');
_CleanSlashes(cleaned_path);
pos = 0;
while ((pos = cleaned_path.find("/..", pos)) != std::string::npos)
{
if (cleaned_path[pos + 3] == '/' || (pos + 3) >= cleaned_path.length())
{
if (pos == 0)
size = 3;
else
{
size_t parent_pos = cleaned_path.rfind("/", pos - 1);
if (parent_pos == std::string::npos)
{
size = pos + 3;
pos = 0;
}
else
{
size = 3 + pos - parent_pos;
pos = parent_pos;
}
}
cleaned_path.replace(pos, size, "");
}
else
{
++pos;
}
}
return cleaned_path;
}
bool IsDir(std::string const& path)
{
struct stat sb;
if (stat(path.c_str(), &sb) == 0)
{
return S_ISDIR(sb.st_mode);
}
return false;
}
bool IsFile(std::string const& path)
{
struct stat sb;
if (stat(path.c_str(), &sb) == 0)
{
return S_ISREG(sb.st_mode);
}
return false;
}
bool Exists(std::string const& path)
{
struct stat sb;
return stat(path.c_str(), &sb) == 0;
}
bool CreateDirectory(std::string const& directory, bool recursive)
{
size_t pos = 0;
struct stat sb;
std::string sub_dir;
do
{
pos = directory.find("/", pos + 1);
sub_dir = std::move(directory.substr(0, pos));
if (stat(sub_dir.c_str(), &sb) == 0)
{
if (!S_ISDIR(sb.st_mode))
{// A subpath in the target is not a directory
return false;
}
// Folder Exists
}
else if (mkdir(sub_dir.c_str(), 0755) < 0 && errno != EEXIST)
{// Failed to create directory (no permission?)
return false;
}
}
while (pos != std::string::npos);
return true;
}
bool DeleteFile(std::string const& path)
{
return unlink(path.c_str()) == 0;
}
std::vector<std::string> ListFiles(std::string const& path, bool files_only, bool recursive)
{
std::vector<std::string> files;
std::string search_path = path;
if (*path.rbegin() != Separator)
search_path += Separator;
DIR* dir = opendir(search_path.c_str());
struct dirent* entry;
if (dir == nullptr)
return files;
while ((entry = readdir(dir)) != nullptr)
{
if (strcmp(entry->d_name, ".") == 0
|| strcmp(entry->d_name, "..") == 0)
continue;
if (entry->d_type == DT_DIR)
{
if (recursive)
{
std::string dir_name = entry->d_name;
std::vector<std::string> sub_files = std::move(ListFiles(search_path + dir_name, true));
std::transform(sub_files.begin(), sub_files.end(), std::back_inserter(files), [&dir_name](std::string& Filename)
{
return dir_name + Separator + Filename;
});
}
if (!files_only)
{
files.emplace_back(entry->d_name);
}
}
else if (entry->d_type == DT_REG)
{
files.emplace_back(entry->d_name);
}
}
closedir(dir);
return files;
}
#endif
}
}