harfang3d/harfang/foundation/file.cpp
2022-12-07 09:51:01 +01:00

351 lines
7.5 KiB
C++

// HARFANG(R) Copyright (C) 2022 NWNC. Released under GPL/LGPL/Commercial Licence, see licence.txt for details.
#if _WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#else
#include <sys/types.h>
#include <unistd.h>
#endif
#include <sys/stat.h>
#include "foundation/cext.h"
#include "foundation/dir.h"
#include "foundation/file.h"
#include "foundation/log.h"
#include "foundation/rand.h"
#include "foundation/string.h"
#include "foundation/format.h"
#include <cstdio>
#include <mutex>
#undef CopyFile
namespace hg {
static generational_vector_list<FILE *> files;
static std::mutex files_mutex;
static FILE *_Open(const char *path, const char *mode, bool silent = false) {
FILE *file = nullptr;
#if _WIN32
const std::wstring wpath = utf8_to_wchar(path);
const std::wstring wmode = utf8_to_wchar(mode);
const errno_t err = _wfopen_s(&file, wpath.data(), wmode.data());
if (!silent && err != 0) {
char errmsg[256];
strerror_s(errmsg, 255, err);
warn(format("Failed to open file '%1' mode '%2', error code %3 (%4)").arg(path).arg(mode).arg(err).arg(errmsg));
}
#else
file = fopen(path, mode);
#endif
return file;
}
File Open(const char *path, bool silent) {
auto *f = _Open(path, "rb", silent);
if (f) {
std::lock_guard<std::mutex> lock(files_mutex);
return {files.add_ref(f)};
}
return {invalid_gen_ref};
}
File OpenText(const char *path, bool silent) {
auto *f = _Open(path, "r", silent);
if (f) {
std::lock_guard<std::mutex> lock(files_mutex);
return {files.add_ref(f)};
}
return {invalid_gen_ref};
}
File OpenWrite(const char *path) {
auto *f = _Open(path, "wb");
if (f) {
std::lock_guard<std::mutex> lock(files_mutex);
return {files.add_ref(f)};
}
return {invalid_gen_ref};
}
File OpenWriteText(const char *path) {
auto *f = _Open(path, "w");
if (f) {
std::lock_guard<std::mutex> lock(files_mutex);
return {files.add_ref(f)};
}
return {invalid_gen_ref};
}
File OpenAppendText(const char *path) {
auto *f = _Open(path, "a");
if (f) {
std::lock_guard<std::mutex> lock(files_mutex);
return {files.add_ref(f)};
}
return {invalid_gen_ref};
}
File OpenTemp(const char *tmplt) {
size_t len = strlen(tmplt);
File out{invalid_gen_ref};
if ((len < 6) || !ends_with(tmplt, "XXXXXX"))
return out;
char *path = strdup(tmplt);
char *suffix = path + len - 6;
#ifndef TMP_MAX
#define TMP_MAX 238328
#endif
int attempts;
for (attempts = 0; attempts < TMP_MAX; attempts++) {
for (int j = 0; j < 6; j++) {
uint32_t r = Rand(26 * 2 + 10 + 2);
char c;
if (r < 26) {
c = 'a' + r;
} else if (r < 52) {
c = 'A' + r - 26;
} else if (r < 62) {
c = '0' + r - 52;
} else {
c = (r & 1) ? '_' : '-';
}
suffix[j] = c;
}
if (!Exists(path)) {
out = OpenWrite(path);
if (IsValid(out))
break;
}
}
free(path);
return out;
}
bool Close(File file) {
std::lock_guard<std::mutex> lock(files_mutex);
if (!files.is_valid(file.ref))
return false;
fclose(files[file.ref.idx]);
files.remove_ref(file.ref);
return true;
}
bool IsValid(File file) {
std::lock_guard<std::mutex> lock(files_mutex);
return files.is_valid(file.ref);
}
bool IsEOF(File file) {
std::lock_guard<std::mutex> lock(files_mutex);
return files.is_valid(file.ref) ? feof(files[file.ref.idx]) != 0 : true;
}
size_t GetSize(File file) {
std::lock_guard<std::mutex> lock(files_mutex);
if (!files.is_valid(file.ref))
return 0;
FILE *s = files[file.ref.idx];
const long t = ftell(s);
fseek(s, 0, SEEK_END);
const size_t size = ftell(s);
fseek(s, t, SEEK_SET);
return size;
}
size_t Read(File file, void *data, size_t size) {
std::lock_guard<std::mutex> lock(files_mutex);
return files.is_valid(file.ref) ? fread(data, 1, size, files[file.ref.idx]) : 0;
}
size_t Write(File file, const void *data, size_t size) {
std::lock_guard<std::mutex> lock(files_mutex);
return files.is_valid(file.ref) ? fwrite(data, 1, size, files[file.ref.idx]) : 0;
}
bool Seek(File file, ptrdiff_t offset, SeekMode mode) {
std::lock_guard<std::mutex> lock(files_mutex);
int _mode;
if (mode == SM_Start)
_mode = SEEK_SET;
else if (mode == SM_Current)
_mode = SEEK_CUR;
else
_mode = SEEK_END;
return files.is_valid(file.ref) ? (fseek(files[file.ref.idx], long(offset), _mode) == 0) : false;
}
size_t Tell(File file) {
std::lock_guard<std::mutex> lock(files_mutex);
return files.is_valid(file.ref) ? ftell(files[file.ref.idx]) : 0;
}
void Rewind(File file) {
std::lock_guard<std::mutex> lock(files_mutex);
if (files.is_valid(file.ref))
fseek(files[file.ref.idx], 0, SEEK_SET);
}
//
FileInfo GetFileInfo(const char *path) {
#if defined WIN32 && !defined __CYGWIN__
struct _stat info;
const auto wpath = utf8_to_wchar(path);
if (_wstat(wpath.c_str(), &info) != 0)
return {false, 0, 0, 0};
#else
struct stat info;
if (stat(path, &info) != 0)
return {false, 0, 0, 0};
#endif
#if defined __CYGWIN__
return {S_ISREG(info.st_mode) ? true : false, size_t(info.st_size), time_ns(info.st_ctime), time_ns(info.st_mtime)};
#else
return {info.st_mode & S_IFREG ? true : false, size_t(info.st_size), time_ns(info.st_ctime), time_ns(info.st_mtime)};
#endif
}
//
bool IsFile(const char *path) {
#if defined WIN32 && !defined __CYGWIN__
struct _stat info;
if (_wstat(utf8_to_wchar(path).c_str(), &info) != 0)
return false;
#else
struct stat info;
if (stat(path, &info) != 0)
return false;
#endif
#if defined __CYGWIN__
if (S_ISREG(info.st_mode))
#else
if (info.st_mode & S_IFREG)
#endif
return true;
return false;
}
//
bool IsDirectory(const char *path) {
bool res;
#if _WIN32
struct _stat info;
if (_wstat(utf8_to_wchar(path).c_str(), &info) == 0) {
res = (info.st_mode & S_IFDIR) != 0;
} else {
res = false;
}
#else
struct stat info;
if (stat(path, &info) == 0) {
res = (info.st_mode & S_IFDIR) != 0;
} else {
res = false;
}
#endif
return res;
}
bool Unlink(const char *path) {
#if _WIN32
const std::wstring wpath = utf8_to_wchar(path);
return DeleteFileW(wpath.c_str()) == TRUE;
#else
return unlink(path) == 0;
#endif
}
//
bool CopyFile(const char *src, const char *dst) {
#if _WIN32
const std::wstring wsrc = utf8_to_wchar(src);
const std::wstring wdst = utf8_to_wchar(dst);
return ::CopyFileW(wsrc.c_str(), wdst.c_str(), FALSE) ? true : false;
#else
ScopedFile in(Open(src));
if (!in)
return false;
ScopedFile out(OpenWrite(dst));
std::vector<char> data(65536); // 64KB copy
while (!IsEOF(in)) {
const auto size = Read(in, data.data(), data.size());
if (size == 0)
break;
if (Write(out, data.data(), size) != size)
return false;
}
return true;
#endif
}
//
bool FileToData(const char *path, Data &data, bool silent) {
File file = Open(path, silent);
if (!IsValid(file)) {
return false;
}
data.Resize(GetSize(file));
Read(file, data.GetData(), data.GetSize());
Close(file);
return true;
}
//
std::string FileToString(const char *path, bool silent) {
ScopedFile in(Open(path, silent));
const size_t size = GetSize(in);
std::string str(size, 0);
Read(in, &str[0], size);
return str;
}
bool StringToFile(const char *path, const char *str) {
ScopedFile out(OpenWrite(path));
const size_t size = str ? strlen(str) : 0;
return size ? Write(out, str, size) == size : true;
}
//
std::string ReadString(File file) {
const size_t len = Read<uint32_t>(file);
std::string s(len, 0);
Read(file, &s[0], len);
return s;
}
bool WriteString(File file, const std::string &v) {
const size_t len = v.length();
return Write(file, numeric_cast<uint32_t>(len)) && Write(file, v.data(), len) == len;
}
bool WriteStringAsText(File file, const std::string &v) { return Write(file, v.data(), v.length()) == v.length(); }
} // namespace hg