mirror of
https://github.com/lifegpc/c-utils.git
synced 2026-06-06 05:08:45 +08:00
746 lines
20 KiB
C++
746 lines
20 KiB
C++
#if HAVE_UTILS_CONFIG_H
|
|
#include "utils_config.h"
|
|
#endif
|
|
|
|
#include "fileop.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#ifdef _WIN32
|
|
#include <Windows.h>
|
|
#include <io.h>
|
|
#include <direct.h>
|
|
#include <fileapi.h>
|
|
#else
|
|
#include <dirent.h>
|
|
#include <unistd.h>
|
|
#include <utime.h>
|
|
#endif
|
|
#include <fcntl.h>
|
|
#include <ctype.h>
|
|
#include "err.h"
|
|
#include "str_util.h"
|
|
#include "wchar_util.h"
|
|
#include "time_util.h"
|
|
#include <regex>
|
|
#include <list>
|
|
#include <malloc.h>
|
|
|
|
#ifdef _WIN32
|
|
#if HAVE__ACCESS_S
|
|
#define access _access_s
|
|
#endif
|
|
#if HAVE__WACCESS_S
|
|
#define _waccess _waccess_s
|
|
#endif
|
|
#if HAVE_PRINTF_S
|
|
#define printf printf_s
|
|
#endif
|
|
#if HAVE_SSCANF_S
|
|
#define sscanf sscanf_s
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
bool exists_internal(wchar_t* fn) {
|
|
return !_waccess(fn, 0);
|
|
}
|
|
|
|
FILE* fopen_internal(wchar_t* fn, const wchar_t* mode) {
|
|
return _wfopen(fn, mode);
|
|
}
|
|
|
|
bool remove_internal(wchar_t* fn, bool print_error) {
|
|
int ret = _wremove(fn);
|
|
if (ret && print_error && errno != ENOENT) {
|
|
std::string o;
|
|
std::string tfn;
|
|
if (err::get_errno_message(o, errno) && wchar_util::wstr_to_str(tfn, fn, CP_UTF8)) {
|
|
printf("Can not remove file \"%s\": %s.\n", tfn.c_str(), o.c_str());
|
|
}
|
|
}
|
|
return !ret;
|
|
}
|
|
|
|
int open_internal(wchar_t* fn, int* fd, int oflag, int shflag, int pmode) {
|
|
return _wsopen_s(fd, fn, oflag, shflag, pmode);
|
|
}
|
|
|
|
bool isdir_internal(wchar_t* fn, bool& result) {
|
|
#if __MINGW32__
|
|
struct _stat stats;
|
|
if (_wstat(fn, &stats)) {
|
|
#else
|
|
struct __stat64 stats;
|
|
if (_wstat64(fn, &stats)) {
|
|
#endif
|
|
return false;
|
|
}
|
|
result = stats.st_mode & S_IFDIR;
|
|
return true;
|
|
}
|
|
|
|
bool mkdir_internal(wchar_t* fn) {
|
|
return !_wmkdir(fn);
|
|
}
|
|
|
|
HANDLE set_file_time_internal(wchar_t* fn) {
|
|
return CreateFileW(fn, FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
|
}
|
|
|
|
HANDLE get_file_size_internal(wchar_t* fn) {
|
|
return CreateFileW(fn, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
}
|
|
|
|
bool listdir_internal(wchar_t* fn, std::list<std::string>& list, bool ignore_hidden_file) {
|
|
std::list<std::string> li;
|
|
WIN32_FIND_DATAW data;
|
|
HANDLE re = FindFirstFileW(fn, &data);
|
|
if (re == INVALID_HANDLE_VALUE) return false;
|
|
std::string tfn;
|
|
do {
|
|
if (!wchar_util::wstr_to_str(tfn, data.cFileName, CP_UTF8)) {
|
|
FindClose(re);
|
|
return false;
|
|
}
|
|
if ((ignore_hidden_file && tfn.find(".") != 0) || (!ignore_hidden_file && tfn != "." && tfn != "..")) li.push_back(tfn);
|
|
BOOL r = FindNextFileW(re, &data);
|
|
if (!r) {
|
|
FindClose(re);
|
|
if (GetLastError() == ERROR_NO_MORE_FILES) break;
|
|
return false;
|
|
}
|
|
} while (1);
|
|
list = li;
|
|
return true;
|
|
}
|
|
|
|
std::string realpath_internal(wchar_t* fn) {
|
|
DWORD len = GetFullPathNameW(fn, 0, nullptr, nullptr);
|
|
wchar_t* buf = (wchar_t*)malloc(sizeof(wchar_t) * len);
|
|
GetFullPathNameW(fn, len, buf, nullptr);
|
|
std::string re;
|
|
wchar_util::wstr_to_str(re, buf, CP_UTF8);
|
|
free(buf);
|
|
return re;
|
|
}
|
|
|
|
template <typename T, typename ... Args>
|
|
T fileop_internal(const char* fname, UINT codePage, T(*callback)(wchar_t* fn, Args... args), T failed, Args... args) {
|
|
int wlen;
|
|
wchar_t* fn;
|
|
DWORD opt = wchar_util::getMultiByteToWideCharOptions(MB_ERR_INVALID_CHARS, codePage);
|
|
wlen = MultiByteToWideChar(codePage, opt, fname, -1, nullptr, 0);
|
|
if (!wlen) return failed;
|
|
fn = (wchar_t*)malloc(sizeof(wchar_t) * wlen);
|
|
if (!MultiByteToWideChar(codePage, opt, fname, -1, fn, wlen)) {
|
|
free(fn);
|
|
return failed;
|
|
}
|
|
T re = callback(fn, args...);
|
|
free(fn);
|
|
return re;
|
|
}
|
|
#endif
|
|
|
|
bool fileop::exists(std::string fn) {
|
|
#if _WIN32
|
|
UINT cp[] = { CP_UTF8, CP_OEMCP, CP_ACP };
|
|
int i;
|
|
for (i = 0; i < 3; i++) {
|
|
if (fileop_internal(fn.c_str(), cp[i], &exists_internal, false)) return true;
|
|
}
|
|
return !access(fn.c_str(), 0);
|
|
#else
|
|
return !access(fn.c_str(), 0);
|
|
#endif
|
|
}
|
|
|
|
bool fileop::remove(std::string fn, bool print_error) {
|
|
#if _WIN32
|
|
UINT cp[] = { CP_UTF8, CP_OEMCP, CP_ACP };
|
|
int i;
|
|
for (i = 0; i < 3; i++) {
|
|
if (fileop_internal(fn.c_str(), cp[i], &remove_internal, false, print_error)) return true;
|
|
}
|
|
#endif
|
|
int ret = ::remove(fn.c_str());
|
|
if (ret && print_error) {
|
|
std::string o;
|
|
if (err::get_errno_message(o, errno)) {
|
|
printf("Can not remove file \"%s\": %s.\n", fn.c_str(), o.c_str());
|
|
}
|
|
}
|
|
return !ret;
|
|
}
|
|
|
|
std::string fileop::dirname(std::string fn) {
|
|
auto i = fn.find_last_of('/');
|
|
auto i2 = fn.find_last_of('\\');
|
|
i = (i == std::string::npos || (i2 != std::string::npos && i2 > i)) ? i2 : i;
|
|
return i == std::string::npos ? "" : fn.substr(0, i);
|
|
}
|
|
|
|
bool fileop::is_url(std::string fn) {
|
|
return fn.find("://") != std::string::npos;
|
|
}
|
|
|
|
std::string fileop::basename(std::string fn) {
|
|
if (!is_url(fn)) {
|
|
auto i = fn.find_last_of('/');
|
|
auto i2 = fn.find_last_of('\\');
|
|
i = (i == std::string::npos || (i2 != std::string::npos && i2 > i)) ? i2 : i;
|
|
return i == std::string::npos ? fn : fn.substr(i + 1, fn.length() - i - 1);
|
|
} else {
|
|
auto iq = fn.find_first_of('?');
|
|
auto iq2 = fn.find_first_of('#');
|
|
iq = (iq == std::string::npos || (iq2 != std::string::npos && iq2 < iq)) ? iq2 : iq;
|
|
auto i = fn.find_last_of('/', iq);
|
|
auto i2 = fn.find_last_of('\\', iq);
|
|
i = (i == std::string::npos || (i2 != std::string::npos && i2 > i)) ? i2 : i;
|
|
if (i == std::string::npos && iq == std::string::npos) {
|
|
return fn;
|
|
} else if (i == std::string::npos) {
|
|
return fn.substr(0, iq);
|
|
} else if (iq == std::string::npos) {
|
|
return fn.substr(i + 1, fn.length() - i - 1);
|
|
} else {
|
|
return fn.substr(i + 1, iq - i - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool fileop::parse_size(std::string size, size_t& fs, bool is_byte) {
|
|
const std::regex REG(R"(^(\d+)([kmgtpezy])?(i)?(b)?$)", std::regex::icase);
|
|
std::smatch m;
|
|
if (!std::regex_search(size, m, REG)) {
|
|
return false;
|
|
}
|
|
auto bs = m[1].str();
|
|
size_t base;
|
|
if (sscanf(bs.c_str(), "%zu", &base) != 1) {
|
|
return false;
|
|
}
|
|
size_t pow = 0;
|
|
if (m[2].matched) {
|
|
auto sp = std::tolower(m[2].str()[0]);
|
|
if (sp == 'k') pow = 1;
|
|
else if (sp == 'm') pow = 2;
|
|
else if (sp == 'g') pow = 3;
|
|
else if (sp == 't') pow = 4;
|
|
else if (sp == 'p') pow = 5;
|
|
else if (sp == 'e') pow = 6;
|
|
else if (sp == 'z') pow = 7;
|
|
else if (sp == 'y') pow = 8;
|
|
}
|
|
if (m[3].matched) {
|
|
pow = 1ull << (pow * 10);
|
|
} else {
|
|
size_t tmp = 1;
|
|
while (pow > 0) {
|
|
tmp *= 1000ull;
|
|
pow--;
|
|
}
|
|
pow = tmp;
|
|
}
|
|
if (m[4].matched) {
|
|
auto b = m[4].str();
|
|
if (b == "b") {
|
|
fs = is_byte ? base * pow / 8ull : base * pow;
|
|
} else {
|
|
fs = is_byte ? base * pow : base * pow * 8ull;
|
|
}
|
|
} else {
|
|
fs = base * pow;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int fileop::open(std::string fn, int& fd, int oflag, int shflag, int pmode) {
|
|
#if _WIN32
|
|
UINT cp[] = { CP_UTF8, CP_OEMCP, CP_ACP };
|
|
int i;
|
|
int tmfd;
|
|
int err;
|
|
for (i = 0; i < 3; i++) {
|
|
if (!fileop_internal(fn.c_str(), cp[i], &open_internal, EINVAL, &tmfd, oflag, shflag, pmode)) {
|
|
fd = tmfd;
|
|
return 0;
|
|
}
|
|
}
|
|
err = ::_sopen_s(&tmfd, fn.c_str(), oflag, shflag, pmode);
|
|
fd = tmfd;
|
|
return fd == -1 ? err : 0;
|
|
#else
|
|
fd = ::open(fn.c_str(), oflag, pmode);
|
|
return fd == -1 ? errno : 0;
|
|
#endif
|
|
}
|
|
|
|
bool fileop::isabs(std::string path) {
|
|
if (!path.length()) return false;
|
|
#if _WIN32
|
|
if (path.length() <= 2) return false;
|
|
if (isalpha(path[0]) && path[1] == ':' && (path[2] == '/' || path[2] == '\\')) return true; else return false;
|
|
#else
|
|
return path[0] == '/' ? true : false;
|
|
#endif
|
|
}
|
|
|
|
std::string fileop::join(std::string path, std::string path2) {
|
|
auto l1 = path.length(), l2 = path2.length();
|
|
if (!l1) return path2;
|
|
if (!l2) return path;
|
|
if (isabs(path2)) return path2;
|
|
#if _WIN32
|
|
if (l2 >= 2 && isalpha(path2[0]) && path2[1] == ':') return path2;
|
|
if (l1 >= 2 && isalpha(path[0]) && path[1] == ':') {
|
|
if (path2[0] == '/' || path2[0] == '\\') return path.substr(0, 2) + path2;
|
|
return (path[l1 - 1] == '/' || path[l1 - 1] == '\\') ? path + path2 : path + "\\" + path2;
|
|
}
|
|
if (path2[0] == '/' || path2[0] == '\\') return path2;
|
|
return (path[l1 - 1] == '/' || path[l1 - 1] == '\\') ? path + path2 : path + "\\" + path2;
|
|
#else
|
|
return path[l1 - 1] == '/' ? path + path2 : path + "/" + path2;
|
|
#endif
|
|
}
|
|
|
|
std::string fileop::join(std::initializer_list<std::string> paths) {
|
|
std::string path;
|
|
for (auto it = paths.begin(); it != paths.end(); it++) {
|
|
if (it == paths.begin()) {
|
|
path = *it;
|
|
} else {
|
|
path = join(path, *it);
|
|
}
|
|
}
|
|
return path;
|
|
}
|
|
|
|
bool fileop::isdir(std::string path, bool& result) {
|
|
if (!exists(path)) {
|
|
result = false;
|
|
return true;
|
|
}
|
|
#if _WIN32
|
|
UINT cp[] = { CP_UTF8, CP_OEMCP, CP_ACP };
|
|
int i;
|
|
for (i = 0; i < 3; i++) {
|
|
if (fileop_internal<bool, bool&>(path.c_str(), cp[i], &isdir_internal, false, result)) return true;
|
|
}
|
|
struct __stat64 stats;
|
|
if (_stat64(path.c_str(), &stats)) {
|
|
#else
|
|
struct stat stats;
|
|
if (stat(path.c_str(), &stats)) {
|
|
#endif
|
|
return false;
|
|
}
|
|
result = stats.st_mode & S_IFDIR;
|
|
return true;
|
|
}
|
|
|
|
#if _WIN32
|
|
bool fileop::isdrive(std::string path) {
|
|
auto l = path.length();
|
|
if (l == 2 && isalpha(path[0]) && path[1] == ':') return true;
|
|
if (l == 3 && isalpha(path[0]) && path[1] == ':' && (path[2] == '/' || path[2] == '\\')) return true;
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
bool fileop::mkdir(std::string path, int mode) {
|
|
#if _WIN32
|
|
UINT cp[] = { CP_UTF8, CP_OEMCP, CP_ACP };
|
|
int i;
|
|
for (i = 0; i < 3; i++) {
|
|
if (fileop_internal(path.c_str(), cp[i], &mkdir_internal, false)) return true;
|
|
}
|
|
return !::_mkdir(path.c_str());
|
|
#else
|
|
return !::mkdir(path.c_str(), mode);
|
|
#endif
|
|
}
|
|
|
|
bool fileop::set_file_time(std::string path, time_t ctime, time_t actime, time_t modtime) {
|
|
#if _WIN32
|
|
UINT cp[] = { CP_UTF8, CP_OEMCP, CP_ACP };
|
|
int i;
|
|
HANDLE file;
|
|
for (i = 0; i < 3; i++) {
|
|
if ((file = fileop_internal(path.c_str(), cp[i], &set_file_time_internal, INVALID_HANDLE_VALUE)) != INVALID_HANDLE_VALUE) {
|
|
break;
|
|
}
|
|
}
|
|
if (file == INVALID_HANDLE_VALUE) {
|
|
file = CreateFileA(path.c_str(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
|
if (file == INVALID_HANDLE_VALUE) return false;
|
|
}
|
|
FILETIME c, ac, mod;
|
|
time_util::time_t_to_file_time(ctime, &c);
|
|
time_util::time_t_to_file_time(actime, &ac);
|
|
time_util::time_t_to_file_time(modtime, &mod);
|
|
auto re = SetFileTime(file, &c, &ac, &mod);
|
|
CloseHandle(file);
|
|
return re ? true : false;
|
|
#else
|
|
struct utimbuf t = { actime, modtime };
|
|
return !utime(path.c_str(), &t);
|
|
#endif
|
|
}
|
|
|
|
FILE* fileop::fdopen(int fd, std::string mode) {
|
|
#if _WIN32
|
|
return ::_fdopen(fd, mode.c_str());
|
|
#else
|
|
return ::fdopen(fd, mode.c_str());
|
|
#endif
|
|
}
|
|
|
|
bool fileop::close(int fd) {
|
|
#if _WIN32
|
|
return !::_close(fd);
|
|
#else
|
|
return !::close(fd);
|
|
#endif
|
|
}
|
|
|
|
bool fileop::fclose(FILE* f) {
|
|
if (!f) return false;
|
|
return !::fclose(f);
|
|
}
|
|
|
|
bool fileop::mkdirs(std::string path, int mode, bool allow_exists) {
|
|
bool exists;
|
|
if (!isdir(path, exists)) return false;
|
|
if (exists) return allow_exists ? true : false;
|
|
std::string dn;
|
|
#if _WIN32
|
|
std::string sp = "\\";
|
|
#else
|
|
std::string sp = "/";
|
|
#endif
|
|
dn = path;
|
|
if (!dn.length() || dn == ".") dn = "." + sp;
|
|
if (!isdir(dn, exists)) return false;
|
|
if (exists) return allow_exists ? true : false;
|
|
std::list<std::string> li;
|
|
li.push_back(dn);
|
|
do {
|
|
dn = dirname(dn);
|
|
if (dn.length() == 0) {
|
|
if (li.size() > 0) {
|
|
auto en = *(li.rbegin());
|
|
if (en == ("." + sp)) return false;
|
|
}
|
|
dn = ".";
|
|
}
|
|
if (!isdir(dn + sp, exists)) return false;
|
|
if (!exists) li.push_back(dn + sp);
|
|
} while (!exists);
|
|
auto it = li.rbegin();
|
|
for (; it != li.rend(); it++) {
|
|
if (!mkdir(*it, mode)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool fileop::get_file_size(std::string path, size_t& size) {
|
|
#if _WIN32
|
|
UINT cp[] = { CP_UTF8, CP_OEMCP, CP_ACP };
|
|
int i;
|
|
HANDLE file;
|
|
for (i = 0; i < 3; i++) {
|
|
if ((file = fileop_internal(path.c_str(), cp[i], &get_file_size_internal, INVALID_HANDLE_VALUE)) != INVALID_HANDLE_VALUE) {
|
|
break;
|
|
}
|
|
}
|
|
if (file == INVALID_HANDLE_VALUE) {
|
|
file = CreateFileA(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
if (file == INVALID_HANDLE_VALUE) return false;
|
|
}
|
|
LARGE_INTEGER si;
|
|
auto re = GetFileSizeEx(file, &si);
|
|
if (re) size = si.QuadPart;
|
|
CloseHandle(file);
|
|
return re ? true : false;
|
|
#else
|
|
struct stat stats;
|
|
if (stat(path.c_str(), &stats)) {
|
|
return false;
|
|
}
|
|
size = stats.st_size;
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
int fileop::fseek(FILE* f, int64_t offset, int origin) {
|
|
#if _WIN32
|
|
return ::_fseeki64(f, offset, origin);
|
|
#else
|
|
#if HAVE_FSEEKO64
|
|
return ::fseeko64(f, offset, origin);
|
|
#elif HAVE_FSEEKO
|
|
return ::fseeko(f, offset, origin);
|
|
#else
|
|
return ::fseek(f, offset, origin);
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
bool fileop::mkdir_for_file(std::string path, int mode) {
|
|
return mkdirs(dirname(path), mode, true);
|
|
}
|
|
|
|
int64_t fileop::ftell(FILE* f) {
|
|
#if _WIN32
|
|
return ::_ftelli64(f);
|
|
#else
|
|
#if HAVE_FTELLO64
|
|
return ::ftello64(f);
|
|
#elif HAVE_FTELLO
|
|
return ::ftello(f);
|
|
#else
|
|
return ::ftell(f);
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
bool fileop::listdir(std::string path, std::list<std::string>& filelist, bool ignore_hidden_file) {
|
|
#if _WIN32
|
|
if (!path.length()) path = ".";
|
|
path = str_util::str_replace(path, "/", "\\");
|
|
path = join(path, "*");
|
|
UINT cp[] = { CP_UTF8, CP_OEMCP, CP_ACP };
|
|
int i;
|
|
std::list<std::string> li;
|
|
for (i = 0; i < 3; i++) {
|
|
if (fileop_internal<bool, std::list<std::string>&, bool>(path.c_str(), cp[i], &listdir_internal, false, li, ignore_hidden_file)) {
|
|
filelist = li;
|
|
return true;
|
|
}
|
|
}
|
|
WIN32_FIND_DATAA data;
|
|
HANDLE re = FindFirstFileA(path.c_str(), &data);
|
|
if (re == INVALID_HANDLE_VALUE) return false;
|
|
std::string tfn;
|
|
do {
|
|
tfn = data.cFileName;
|
|
if ((ignore_hidden_file && tfn.find(".") != 0) || (!ignore_hidden_file && tfn != "." && tfn != "..")) li.push_back(tfn);
|
|
BOOL r = FindNextFileA(re, &data);
|
|
if (!r) {
|
|
FindClose(re);
|
|
if (GetLastError() == ERROR_NO_MORE_FILES) break;
|
|
return false;
|
|
}
|
|
} while (1);
|
|
filelist = li;
|
|
return true;
|
|
#else
|
|
DIR* dir = opendir(path.c_str());
|
|
if (!dir) return false;
|
|
struct dirent* d = readdir(dir);
|
|
std::list<std::string> li;
|
|
std::string tfn;
|
|
while (d) {
|
|
tfn = d->d_name;
|
|
if ((ignore_hidden_file && tfn.find(".") != 0) || (!ignore_hidden_file && tfn != "." && tfn != "..")) li.push_back(tfn);
|
|
d = readdir(dir);
|
|
}
|
|
if (errno) {
|
|
closedir(dir);
|
|
return false;
|
|
}
|
|
closedir(dir);
|
|
filelist = li;
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
std::string fileop::filename(std::string path) {
|
|
auto loc = path.find_last_of('.');
|
|
return loc == -1 ? path : path.substr(0, loc);
|
|
}
|
|
|
|
int fileop::fcloseall() {
|
|
#if _WIN32
|
|
return ::_fcloseall();
|
|
#elif HAVE_FCLOSEALL
|
|
return ::fcloseall();
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
std::string fileop::relpath(std::string path, std::string start) {
|
|
// 如果 start 为空,使用当前目录
|
|
if (start.empty()) {
|
|
start = ".";
|
|
}
|
|
|
|
// 将 Windows 风格的路径分隔符统一转换为 '/'
|
|
auto normalize_path = [](std::string& p) {
|
|
std::string result = p;
|
|
for (auto& c : result) {
|
|
if (c == '\\') c = '/';
|
|
}
|
|
// 确保路径末尾没有斜杠
|
|
if (!result.empty() && result.back() == '/') {
|
|
result.pop_back();
|
|
}
|
|
return result;
|
|
};
|
|
|
|
std::string norm_path = normalize_path(path);
|
|
std::string norm_start = normalize_path(start);
|
|
|
|
// 如果两个路径相同,返回当前目录
|
|
if (norm_path == norm_start) {
|
|
return ".";
|
|
}
|
|
|
|
// 如果 path 是绝对路径但 start 不是,或者反之,无法计算相对路径
|
|
#ifdef _WIN32
|
|
bool path_is_abs = isabs(path);
|
|
bool start_is_abs = isabs(start);
|
|
|
|
// 在 Windows 上,还要检查驱动器号是否相同
|
|
if (path_is_abs && start_is_abs) {
|
|
if (norm_path.length() >= 2 && norm_start.length() >= 2) {
|
|
// 如果驱动器号不同,无法计算相对路径
|
|
if (tolower(norm_path[0]) != tolower(norm_start[0]) || norm_path[1] != ':') {
|
|
return path; // 返回原路径
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
bool path_is_abs = !norm_path.empty() && norm_path[0] == '/';
|
|
bool start_is_abs = !norm_start.empty() && norm_start[0] == '/';
|
|
#endif
|
|
|
|
if (path_is_abs != start_is_abs) {
|
|
return path; // 无法计算相对路径,返回原路径
|
|
}
|
|
|
|
// 分割路径为组件
|
|
auto split_path = [](const std::string& p) {
|
|
std::vector<std::string> components;
|
|
std::string::size_type start = 0;
|
|
std::string::size_type end = 0;
|
|
|
|
while ((end = p.find('/', start)) != std::string::npos) {
|
|
if (end != start) {
|
|
components.push_back(p.substr(start, end - start));
|
|
}
|
|
start = end + 1;
|
|
}
|
|
|
|
if (start < p.length()) {
|
|
components.push_back(p.substr(start));
|
|
}
|
|
|
|
return components;
|
|
};
|
|
|
|
auto path_components = split_path(norm_path);
|
|
auto start_components = split_path(norm_start);
|
|
|
|
// 找到公共前缀
|
|
size_t i = 0;
|
|
while (i < path_components.size() && i < start_components.size()) {
|
|
#ifdef _WIN32
|
|
// Windows 路径不区分大小写
|
|
if (str_util::tolower(path_components[i]) != str_util::tolower(start_components[i])) {
|
|
break;
|
|
}
|
|
#else
|
|
if (path_components[i] != start_components[i]) {
|
|
break;
|
|
}
|
|
#endif
|
|
i++;
|
|
}
|
|
|
|
// 构建相对路径
|
|
std::string result;
|
|
|
|
// 添加 ".." 以表示上级目录
|
|
for (size_t j = i; j < start_components.size(); j++) {
|
|
if (!result.empty()) {
|
|
result += "/";
|
|
}
|
|
result += "..";
|
|
}
|
|
|
|
// 添加目标路径的非共享部分
|
|
for (size_t j = i; j < path_components.size(); j++) {
|
|
if (!result.empty()) {
|
|
result += "/";
|
|
}
|
|
result += path_components[j];
|
|
}
|
|
|
|
// 如果结果为空,表示当前目录
|
|
if (result.empty()) {
|
|
return ".";
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
// 在 Windows 上,将斜杠转换回反斜杠
|
|
for (auto& c : result) {
|
|
if (c == '/') c = '\\';
|
|
}
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
FILE* fileop::fopen(std::string path, std::string mode) {
|
|
#if _WIN32
|
|
std::wstring wMode;
|
|
if (wchar_util::str_to_wstr(wMode, mode, CP_UTF8)) {
|
|
UINT cp[] = { CP_UTF8, CP_OEMCP, CP_ACP };
|
|
int i;
|
|
for (i = 0; i < 3; i++) {
|
|
auto f = fileop_internal<FILE*>(path.c_str(), cp[i], &fopen_internal, nullptr, wMode.c_str());
|
|
if (f) return f;
|
|
}
|
|
}
|
|
return ::fopen(path.c_str(), mode.c_str());
|
|
#else
|
|
return ::fopen(path.c_str(), mode.c_str());
|
|
#endif
|
|
}
|
|
|
|
std::string fileop::extname(std::string path) {
|
|
auto loc = path.find_last_of('.');
|
|
if (loc == std::string::npos) {
|
|
return "";
|
|
}
|
|
return path.substr(loc + 1, path.length() - loc - 1);
|
|
}
|
|
|
|
std::string fileop::abspath(std::string path) {
|
|
#if _WIN32
|
|
UINT cp[] = { CP_UTF8, CP_OEMCP, CP_ACP };
|
|
int i;
|
|
std::string re;
|
|
for (i = 0; i < 3; i++) {
|
|
re = fileop_internal<std::string>(path.c_str(), cp[i], &realpath_internal, "");
|
|
if (!re.empty()) return re;
|
|
}
|
|
DWORD len = GetFullPathNameA(path.c_str(), 0, nullptr, nullptr);
|
|
char* buf = (char*)malloc(sizeof(char) * len);
|
|
GetFullPathNameA(path.c_str(), len, buf, nullptr);
|
|
auto re2 = std::string(buf);
|
|
free(buf);
|
|
return re2;
|
|
#else
|
|
char* rp = realpath(path.c_str(), nullptr);
|
|
if (!rp) return path;
|
|
std::string re(rp);
|
|
free(rp);
|
|
return re;
|
|
#endif
|
|
}
|