Files
create/src/Base/FileInfo.cpp
Kacper Donat 12a69fe296 Base: Add isNullOrEmpty string helper
This adds isNullOrEmpty string helper that cheks if string is... well
null or empty. It is done to improve readability of the code and better
express intent.
2025-02-21 15:04:43 +01:00

687 lines
19 KiB
C++

/***************************************************************************
* Copyright (c) 2005 Jürgen Riegel <juergen.riegel@web.de> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Library General Public License (LGPL) *
* as published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* for detail see the LICENCE text file. *
* *
* FreeCAD 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 Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with FreeCAD; if not, write to the Free Software *
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
* USA *
* *
***************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
#include <algorithm>
#include <codecvt>
#include <cstring>
#include <iostream>
#if defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_MACOSX) || defined(FC_OS_BSD)
#include <dirent.h>
#include <unistd.h>
#include <limits.h>
#elif defined(FC_OS_WIN32)
#include <io.h>
#include <Windows.h>
#endif
#include <sys/stat.h>
#include <sys/types.h>
#endif
#include "FileInfo.h"
#include "Exception.h"
#include "Stream.h"
#include "TimeInfo.h"
#include "Tools.h"
using namespace Base;
#ifndef R_OK
#define R_OK 4 /* Test for read permission */
#endif
#ifndef W_OK
#define W_OK 2 /* Test for write permission */
#endif
#ifndef X_OK
#define X_OK 1 /* Test for execute permission */
#endif
#ifndef F_OK
#define F_OK 0 /* Test for existence */
#endif
//**********************************************************************************
// helper
#ifdef FC_OS_WIN32
std::string ConvertFromWideString(const std::wstring& string)
{
int neededSize = WideCharToMultiByte(CP_UTF8, 0, string.c_str(), -1, 0, 0, 0, 0);
char* CharString = new char[static_cast<size_t>(neededSize)];
WideCharToMultiByte(CP_UTF8, 0, string.c_str(), -1, CharString, neededSize, 0, 0);
std::string String(CharString);
delete[] CharString;
CharString = NULL;
return String;
}
std::wstring ConvertToWideString(const std::string& string)
{
int neededSize = MultiByteToWideChar(CP_UTF8, 0, string.c_str(), -1, 0, 0);
wchar_t* wideCharString = new wchar_t[static_cast<size_t>(neededSize)];
MultiByteToWideChar(CP_UTF8, 0, string.c_str(), -1, wideCharString, neededSize);
std::wstring wideString(wideCharString);
delete[] wideCharString;
wideCharString = NULL;
return wideString;
}
#endif
//**********************************************************************************
// FileInfo
FileInfo::FileInfo(const char* fileName)
{
setFile(fileName);
}
FileInfo::FileInfo(const std::string& fileName)
{
setFile(fileName.c_str());
}
const std::string& FileInfo::getTempPath()
{
static std::string tempPath;
if (tempPath.empty()) {
#ifdef FC_OS_WIN32
wchar_t buf[MAX_PATH + 2];
GetTempPathW(MAX_PATH + 1, buf);
int neededSize = WideCharToMultiByte(CP_UTF8, 0, buf, -1, 0, 0, 0, 0);
char* dest = new char[static_cast<size_t>(neededSize)];
WideCharToMultiByte(CP_UTF8, 0, buf, -1, dest, neededSize, 0, 0);
tempPath = dest;
delete[] dest;
#else
const char* tmp = getenv("TMPDIR");
if (!Base::Tools::isNullOrEmpty(tmp)) {
tempPath = tmp;
FileInfo fi(tempPath);
if (tempPath.empty() || !fi.isDir()) { // still empty or non-existent
tempPath = "/tmp/";
}
else if (tempPath.at(tempPath.size() - 1) != '/') {
tempPath.append("/");
}
}
else {
tempPath = "/tmp/";
}
#endif
}
return tempPath;
}
std::string FileInfo::getTempFileName(const char* FileName, const char* Path)
{
// FIXME: To avoid race conditions we should rather return a file pointer
// than a file name.
#ifdef FC_OS_WIN32
wchar_t buf[MAX_PATH + 2];
// Path where the file is located
std::wstring path;
if (Path) {
path = ConvertToWideString(std::string(Path));
}
else {
path = ConvertToWideString(getTempPath());
}
// File name in the path
std::wstring file;
if (FileName) {
file = ConvertToWideString(std::string(FileName));
}
else {
file = L"FCTempFile";
}
// this already creates the file
GetTempFileNameW(path.c_str(), file.c_str(), 0, buf);
DeleteFileW(buf);
return std::string(ConvertFromWideString(std::wstring(buf)));
#else
std::string buf;
// Path where the file is located
if (Path) {
buf = Path;
}
else {
buf = getTempPath();
}
// File name in the path
if (FileName) {
buf += "/";
buf += FileName;
buf += "XXXXXX";
}
else {
buf += "/fileXXXXXX";
}
std::vector<char> vec;
std::copy(buf.begin(), buf.end(), std::back_inserter(vec));
vec.push_back('\0');
/* coverity[secure_temp] mkstemp uses 0600 as the mode and is safe */
int id = mkstemp(vec.data());
if (id > -1) {
FILE* file = fdopen(id, "w");
fclose(file);
vec.pop_back(); // remove '\0'
std::string str(vec.begin(), vec.end());
buf.swap(str);
unlink(buf.c_str());
}
return buf;
#endif
}
std::filesystem::path FileInfo::stringToPath(const std::string& str)
{
#if defined(FC_OS_WIN32)
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
std::filesystem::path path(converter.from_bytes(str));
#else
std::filesystem::path path(str);
#endif
return path;
}
std::string FileInfo::pathToString(const std::filesystem::path& path)
{
#if defined(FC_OS_WIN32)
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
return converter.to_bytes(path.wstring());
#else
return path.string();
#endif
}
void FileInfo::setFile(const char* name)
{
if (!name) {
FileName.clear();
return;
}
FileName = name;
// keep the UNC paths intact
if (FileName.substr(0, 2) == std::string("\\\\")) {
std::replace(FileName.begin() + 2, FileName.end(), '\\', '/');
}
else {
std::replace(FileName.begin(), FileName.end(), '\\', '/');
}
}
std::string FileInfo::filePath() const
{
return FileName;
}
std::string FileInfo::fileName() const
{
return FileName.substr(FileName.find_last_of('/') + 1);
}
std::string FileInfo::dirPath() const
{
std::size_t last_pos {};
std::string retval;
last_pos = FileName.find_last_of('/');
if (last_pos != std::string::npos) {
retval = FileName.substr(0, last_pos);
}
else {
#ifdef FC_OS_WIN32
wchar_t buf[MAX_PATH + 1];
GetCurrentDirectoryW(MAX_PATH, buf);
retval = std::string(ConvertFromWideString(std::wstring(buf)));
#else
char buf[PATH_MAX + 1];
const char* cwd = getcwd(buf, PATH_MAX);
retval = std::string(cwd ? cwd : ".");
#endif
}
return retval;
}
std::string FileInfo::fileNamePure() const
{
std::string temp = fileName();
std::string::size_type pos = temp.find_last_of('.');
if (pos != std::string::npos) {
return temp.substr(0, pos);
}
return temp;
}
std::wstring FileInfo::toStdWString() const
{
// As FileName is UTF-8 is encoded we have to convert it
// for Windows because the path names are UTF-16 encoded.
#ifdef FC_OS_WIN32
return ConvertToWideString(FileName);
#else
// On other platforms it's discouraged to use wchar_t for file names
throw Base::FileException("Cannot use FileInfo::toStdWString() on this platform");
#endif
}
std::string FileInfo::extension() const
{
std::string::size_type pos = FileName.find_last_of('.');
if (pos == std::string::npos) {
return {};
}
return FileName.substr(pos + 1);
}
std::string FileInfo::completeExtension() const
{
std::string::size_type pos = FileName.find_first_of('.');
if (pos == std::string::npos) {
return {};
}
return FileName.substr(pos + 1);
}
bool FileInfo::hasExtension(const char* Ext) const
{
#if defined(FC_OS_WIN32)
return _stricmp(Ext, extension().c_str()) == 0;
#elif defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_MACOSX) || defined(FC_OS_BSD)
return strcasecmp(Ext, extension().c_str()) == 0;
#endif
}
bool FileInfo::hasExtension(std::initializer_list<const char*> Exts) const
{
return std::any_of(Exts.begin(), Exts.end(), [this](const char* ext) {
return hasExtension(ext);
});
}
bool FileInfo::exists() const
{
#if defined(FC_OS_WIN32)
std::wstring wstr = toStdWString();
return _waccess(wstr.c_str(), F_OK) == 0;
#elif defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_MACOSX) || defined(FC_OS_BSD)
return access(FileName.c_str(), F_OK) == 0;
#endif
}
bool FileInfo::isReadable() const
{
#if defined(FC_OS_WIN32)
std::wstring wstr = toStdWString();
return _waccess(wstr.c_str(), R_OK) == 0;
#elif defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_MACOSX) || defined(FC_OS_BSD)
return access(FileName.c_str(), R_OK) == 0;
#endif
}
bool FileInfo::isWritable() const
{
#if defined(FC_OS_WIN32)
std::wstring wstr = toStdWString();
return _waccess(wstr.c_str(), W_OK) == 0;
#elif defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_MACOSX) || defined(FC_OS_BSD)
return access(FileName.c_str(), W_OK) == 0;
#endif
}
bool FileInfo::setPermissions(Permissions perms)
{
int mode = 0;
if (perms & FileInfo::ReadOnly) {
mode |= S_IREAD;
}
if (perms & FileInfo::WriteOnly) {
mode |= S_IWRITE;
}
if (mode == 0) { // bad argument
return false;
}
#if defined(FC_OS_WIN32)
std::wstring wstr = toStdWString();
return _wchmod(wstr.c_str(), mode) == 0;
#elif defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_MACOSX) || defined(FC_OS_BSD)
return chmod(FileName.c_str(), mode) == 0;
#endif
}
bool FileInfo::isFile() const
{
if (exists()) {
#ifdef FC_OS_WIN32
std::wstring wstr = toStdWString();
FILE* fd = _wfopen(wstr.c_str(), L"rb");
bool ok = (fd != 0);
if (fd) {
fclose(fd);
}
return ok;
#else
// clang-format off
struct stat st {};
// clang-format on
if (stat(FileName.c_str(), &st) != 0) {
return false;
}
return S_ISREG(st.st_mode);
#endif
}
// TODO: Check for valid file name
return true;
}
bool FileInfo::isDir() const
{
if (exists()) {
// if we can chdir then it must be a directory, otherwise we assume it
// is a file (which doesn't need to be true for any cases)
#if defined(FC_OS_WIN32)
std::wstring wstr = toStdWString();
struct _stat st;
if (_wstat(wstr.c_str(), &st) != 0) {
return false;
}
return ((st.st_mode & _S_IFDIR) != 0);
#elif defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_MACOSX) || defined(FC_OS_BSD)
struct stat st {};
if (stat(FileName.c_str(), &st) != 0) {
return false;
}
return S_ISDIR(st.st_mode);
#else
return false;
#endif
}
else {
return false;
}
// TODO: Check for valid path name
// return true;
}
unsigned int FileInfo::size() const
{
unsigned int bytes {};
if (exists()) {
#if defined(FC_OS_WIN32)
std::wstring wstr = toStdWString();
struct _stat st;
if (_wstat(wstr.c_str(), &st) == 0) {
bytes = st.st_size;
}
#elif defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_MACOSX) || defined(FC_OS_BSD)
struct stat st {};
if (stat(FileName.c_str(), &st) == 0) {
bytes = st.st_size;
}
#endif
}
return bytes;
}
TimeInfo FileInfo::lastModified() const
{
TimeInfo ti = TimeInfo::null();
if (exists()) {
#if defined(FC_OS_WIN32)
std::wstring wstr = toStdWString();
struct _stat st;
if (_wstat(wstr.c_str(), &st) == 0) {
ti.setTime_t(st.st_mtime);
}
#elif defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_MACOSX) || defined(FC_OS_BSD)
struct stat st {};
if (stat(FileName.c_str(), &st) == 0) {
ti.setTime_t(st.st_mtime);
}
#endif
}
return ti;
}
TimeInfo FileInfo::lastRead() const
{
TimeInfo ti = TimeInfo::null();
if (exists()) {
#if defined(FC_OS_WIN32)
std::wstring wstr = toStdWString();
struct _stat st;
if (_wstat(wstr.c_str(), &st) == 0) {
ti.setTime_t(st.st_atime);
}
#elif defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_MACOSX) || defined(FC_OS_BSD)
struct stat st {};
if (stat(FileName.c_str(), &st) == 0) {
ti.setTime_t(st.st_atime);
}
#endif
}
return ti;
}
bool FileInfo::deleteFile() const
{
#if defined(FC_OS_WIN32)
std::wstring wstr = toStdWString();
return ::_wremove(wstr.c_str()) == 0;
#elif defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_MACOSX) || defined(FC_OS_BSD)
return (::remove(FileName.c_str()) == 0);
#else
#error "FileInfo::deleteFile() not implemented for this platform!"
#endif
}
bool FileInfo::renameFile(const char* NewName)
{
bool res {};
#if defined(FC_OS_WIN32)
std::wstring oldname = toStdWString();
std::wstring newname = ConvertToWideString(NewName);
res = ::_wrename(oldname.c_str(), newname.c_str()) == 0;
#elif defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_MACOSX) || defined(FC_OS_BSD)
res = ::rename(FileName.c_str(), NewName) == 0;
#else
#error "FileInfo::renameFile() not implemented for this platform!"
#endif
if (!res) {
int code = errno;
std::clog << "Error in renameFile: " << strerror(code) << " (" << code << ")" << std::endl;
}
else {
FileName = NewName;
}
return res;
}
bool FileInfo::copyTo(const char* NewName) const
{
#if defined(FC_OS_WIN32)
std::wstring oldname = toStdWString();
std::wstring newname = ConvertToWideString(NewName);
return CopyFileW(oldname.c_str(), newname.c_str(), true) != 0;
#elif defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_MACOSX) || defined(FC_OS_BSD)
FileInfo fi1(FileName);
FileInfo fi2(NewName);
Base::ifstream file(fi1, std::ios::in | std::ios::binary);
file.unsetf(std::ios_base::skipws);
Base::ofstream copy(fi2, std::ios::out | std::ios::binary);
file >> copy.rdbuf();
return file.is_open() && copy.is_open();
#else
#error "FileInfo::copyTo() not implemented for this platform!"
#endif
}
bool FileInfo::createDirectory() const
{
#if defined(FC_OS_WIN32)
std::wstring wstr = toStdWString();
return _wmkdir(wstr.c_str()) == 0;
#elif defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_MACOSX) || defined(FC_OS_BSD)
return mkdir(FileName.c_str(), 0777) == 0;
#else
#error "FileInfo::createDirectory() not implemented for this platform!"
#endif
}
bool FileInfo::createDirectories() const
{
try {
std::filesystem::path path(stringToPath(FileName));
if (std::filesystem::exists(path)) {
return true;
}
std::filesystem::create_directories(path);
return true;
}
catch (const std::filesystem::filesystem_error&) {
return false;
}
}
bool FileInfo::deleteDirectory() const
{
if (!isDir()) {
return false;
}
#if defined(FC_OS_WIN32)
std::wstring wstr = toStdWString();
return _wrmdir(wstr.c_str()) == 0;
#elif defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_MACOSX) || defined(FC_OS_BSD)
return rmdir(FileName.c_str()) == 0;
#else
#error "FileInfo::rmdir() not implemented for this platform!"
#endif
}
bool FileInfo::deleteDirectoryRecursive() const
{
if (!isDir()) {
return false;
}
std::vector<Base::FileInfo> List = getDirectoryContent();
for (Base::FileInfo& fi : List) {
if (fi.isDir()) {
// At least on Linux, directory needs execute permission to be
// deleted. We don't really need to set permission for directory
// anyway, since FC code does not touch directory permission.
//
// It->setPermissions(FileInfo::ReadWrite);
fi.deleteDirectoryRecursive();
}
else if (fi.isFile()) {
fi.setPermissions(FileInfo::ReadWrite);
fi.deleteFile();
}
else {
throw Base::FileException(
"FileInfo::deleteDirectoryRecursive(): Unknown object Type in directory!");
}
}
return deleteDirectory();
}
std::vector<Base::FileInfo> FileInfo::getDirectoryContent() const
{
std::vector<Base::FileInfo> List;
#if defined(FC_OS_WIN32)
struct _wfinddata_t dentry;
intptr_t hFile;
// Find first directory entry
std::wstring wstr = toStdWString();
wstr += L"/*";
if ((hFile = _wfindfirst(wstr.c_str(), &dentry)) == -1L) {
return List;
}
while (_wfindnext(hFile, &dentry) == 0) {
if (wcscmp(dentry.name, L"..") != 0) {
List.push_back(
FileInfo(FileName + "/" + ConvertFromWideString(std::wstring(dentry.name))));
}
}
_findclose(hFile);
#elif defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_MACOSX) || defined(FC_OS_BSD)
DIR* dp(nullptr);
struct dirent* dentry(nullptr);
if (!(dp = opendir(FileName.c_str()))) {
return List;
}
while ((dentry = readdir(dp))) {
std::string dir = dentry->d_name;
if (dir != "." && dir != "..") {
List.emplace_back(FileName + "/" + dir);
}
}
closedir(dp);
#else
#error "FileInfo::getDirectoryContent() not implemented for this platform!"
#endif
return List;
}