Add initial bits for Qt6 support
[carla.git] / source / modules / ysfx / sources / ysfx_utils.cpp
bloba78e7a176702c48c3023e43830a5d6527d2d4bfa
1 // Copyright 2021 Jean Pierre Cimalando
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 // SPDX-License-Identifier: Apache-2.0
18 #include "ysfx_utils.hpp"
19 #include "base64/Base64.hpp"
20 #include <system_error>
21 #include <algorithm>
22 #include <deque>
23 #include <clocale>
24 #include <cstring>
25 #include <cassert>
26 #if !defined(_WIN32)
27 # include <sys/stat.h>
28 # include <sys/types.h>
29 # include <unistd.h>
30 # include <dirent.h>
31 # include <fcntl.h>
32 #else
33 # include <windows.h>
34 # include <io.h>
35 #endif
37 namespace ysfx {
39 #if !defined(_WIN32)
40 static_assert(sizeof(off_t) == 8, "64-bit large file support is not enabled");
41 #endif
43 FILE *fopen_utf8(const char *path, const char *mode)
45 #if defined(_WIN32)
46 return _wfopen(widen(path).c_str(), widen(mode).c_str());
47 #else
48 return fopen(path, mode);
49 #endif
52 int64_t fseek_lfs(FILE *stream, int64_t off, int whence)
54 #if defined(_WIN32)
55 return _fseeki64(stream, off, whence);
56 #else
57 return fseeko(stream, off, whence);
58 #endif
61 int64_t ftell_lfs(FILE *stream)
63 #if defined(_WIN32)
64 return _ftelli64(stream);
65 #else
66 return ftello(stream);
67 #endif
70 //------------------------------------------------------------------------------
72 namespace {
74 struct scoped_c_locale
76 scoped_c_locale(int lc, const char *name);
77 ~scoped_c_locale();
78 c_locale_t m_loc{};
80 scoped_c_locale(const scoped_c_locale &) = delete;
81 scoped_c_locale &operator=(const scoped_c_locale &) = delete;
84 scoped_c_locale::scoped_c_locale(int lc, const char *name)
86 #if defined(_WIN32)
87 m_loc = _create_locale(lc, name);
88 #else
89 switch (lc) {
90 case LC_ALL:
91 m_loc = newlocale(LC_ALL_MASK, name, c_locale_t{});
92 break;
93 case LC_CTYPE:
94 m_loc = newlocale(LC_CTYPE_MASK, name, c_locale_t{});
95 break;
96 case LC_COLLATE:
97 m_loc = newlocale(LC_COLLATE_MASK, name, c_locale_t{});
98 break;
99 case LC_MONETARY:
100 m_loc = newlocale(LC_MONETARY_MASK, name, c_locale_t{});
101 break;
102 case LC_NUMERIC:
103 m_loc = newlocale(LC_NUMERIC_MASK, name, c_locale_t{});
104 break;
105 case LC_TIME:
106 m_loc = newlocale(LC_TIME_MASK, name, c_locale_t{});
107 break;
108 case LC_MESSAGES:
109 m_loc = newlocale(LC_MESSAGES_MASK, name, c_locale_t{});
110 break;
111 default:
112 errno = EINVAL;
113 break;
115 #endif
116 if (!m_loc)
117 throw std::system_error(errno, std::generic_category());
120 scoped_c_locale::~scoped_c_locale()
122 if (!m_loc) return;
123 #if !defined(_WIN32)
124 freelocale(m_loc);
125 #else
126 _free_locale(m_loc);
127 #endif
130 #if !defined(_WIN32)
131 struct scoped_posix_uselocale {
132 explicit scoped_posix_uselocale(c_locale_t loc);
133 ~scoped_posix_uselocale();
135 c_locale_t m_loc{};
136 c_locale_t m_old{};
138 scoped_posix_uselocale(const scoped_posix_uselocale &) = delete;
139 scoped_posix_uselocale &operator=(const scoped_posix_uselocale &) = delete;
142 scoped_posix_uselocale::scoped_posix_uselocale(c_locale_t loc)
144 if (loc)
146 m_loc = loc;
147 m_old = uselocale(loc);
151 scoped_posix_uselocale::~scoped_posix_uselocale()
153 if (m_loc)
154 uselocale(m_old);
156 #endif
158 } // namespace
160 //------------------------------------------------------------------------------
162 c_locale_t c_numeric_locale()
164 static scoped_c_locale loc(LC_NUMERIC, "C");
165 return loc.m_loc;
168 //------------------------------------------------------------------------------
170 double c_atof(const char *text, c_locale_t loc)
172 #if defined(_WIN32)
173 return _atof_l(text, loc);
174 #else
175 scoped_posix_uselocale use(loc);
176 return atof(text);
177 #endif
180 double c_strtod(const char *text, char **endp, c_locale_t loc)
182 #if defined(_WIN32)
183 return _strtod_l(text, endp, loc);
184 #else
185 scoped_posix_uselocale use(loc);
186 return strtod(text, endp);
187 #endif
190 double dot_atof(const char *text)
192 return c_atof(text, c_numeric_locale());
195 double dot_strtod(const char *text, char **endp)
197 return c_strtod(text, endp, c_numeric_locale());
200 bool ascii_isspace(char c)
202 switch (c) {
203 case ' ': case '\f': case '\n': case '\r': case '\t': case '\v':
204 return true;
205 default:
206 return false;
210 bool ascii_isalpha(char c)
212 return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
215 char ascii_tolower(char c)
217 return (c >= 'A' && c <= 'Z') ? (c - 'A' + 'a') : c;
220 char ascii_toupper(char c)
222 return (c >= 'a' && c <= 'z') ? (c - 'a' + 'A') : c;
225 int ascii_casecmp(const char *a, const char *b)
227 for (char ca, cb; (ca = *a++) | (cb = *b++); ) {
228 ca = ascii_tolower(ca);
229 cb = ascii_tolower(cb);
230 if (ca < cb) return -1;
231 if (ca > cb) return +1;
233 return 0;
236 uint32_t latin1_toupper(uint32_t c)
238 if (c >= 'a' && c <= 'z')
239 return c - 'a' + 'A';
240 if ((c >= 0xe0 && c <= 0xf6) || (c >= 0xf8 && c <= 0xfe))
241 return c - 0x20;
242 return c;
245 uint32_t latin1_tolower(uint32_t c)
247 if (c >= 'A' && c <= 'Z')
248 return c - 'A' + 'a';
249 if ((c >= 0xc0 && c <= 0xd6) || (c >= 0xd8 && c <= 0xde))
250 return c + 0x20;
251 return c;
254 char *strdup_using_new(const char *src)
256 size_t len = strlen(src);
257 char *dst = new char[len + 1];
258 memcpy(dst, src, len + 1);
259 return dst;
262 string_list split_strings_noempty(const char *input, bool(*pred)(char))
264 string_list list;
266 if (input) {
267 std::string acc;
268 acc.reserve(256);
270 for (char c; (c = *input++) != '\0'; ) {
271 if (!pred(c))
272 acc.push_back(c);
273 else {
274 if (!acc.empty()) {
275 list.push_back(acc);
276 acc.clear();
281 if (!acc.empty())
282 list.push_back(acc);
285 return list;
288 std::string trim(const char *input, bool(*pred)(char))
290 const char *start = input;
291 while (*start != '\0' && pred(*start))
292 ++start;
294 const char *end = start + strlen(start);
295 while (end > start && pred(*(end - 1)))
296 --end;
298 return std::string(start, end);
301 //------------------------------------------------------------------------------
303 void pack_u32le(uint32_t value, uint8_t data[4])
305 data[0] = value & 0xff;
306 data[1] = (value >> 8) & 0xff;
307 data[2] = (value >> 16) & 0xff;
308 data[3] = value >> 24;
311 void pack_f32le(float value, uint8_t data[4])
313 uint32_t u;
314 memcpy(&u, &value, 4);
315 pack_u32le(u, data);
318 uint32_t unpack_u32le(const uint8_t data[4])
320 return data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
323 float unpack_f32le(const uint8_t data[4])
325 float value;
326 uint32_t u = unpack_u32le(data);
327 memcpy(&value, &u, 4);
328 return value;
331 //------------------------------------------------------------------------------
333 std::vector<uint8_t> decode_base64(const char *text, size_t len)
335 return d_getChunkFromBase64String(text, len);
338 std::string encode_base64(const uint8_t *data, size_t len)
340 return d_getBase64StringFromChunk(data, len);
343 //------------------------------------------------------------------------------
345 bool get_file_uid(const char *path, file_uid &uid)
347 #ifdef _WIN32
348 HANDLE handle = CreateFileW(widen(path).c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
349 if (handle == INVALID_HANDLE_VALUE)
350 return false;
351 bool success = get_handle_file_uid((void *)handle, uid);
352 CloseHandle(handle);
353 return success;
354 #else
355 int fd = open(path, O_RDONLY);
356 if (fd == -1)
357 return false;
358 bool success = get_descriptor_file_uid(fd, uid);
359 close(fd);
360 return success;
361 #endif
364 bool get_stream_file_uid(FILE *stream, file_uid &uid)
366 #if !defined(_WIN32)
367 int fd = fileno(stream);
368 if (fd == -1)
369 return false;
370 #else
371 int fd = _fileno(stream);
372 if (fd == -1)
373 return false;
374 #endif
375 return get_descriptor_file_uid(fd, uid);
378 bool get_descriptor_file_uid(int fd, file_uid &uid)
380 #if !defined(_WIN32)
381 struct stat st;
382 if (fstat(fd, &st) != 0)
383 return false;
384 uid.first = (uint64_t)st.st_dev;
385 uid.second = (uint64_t)st.st_ino;
386 return true;
387 #else
388 HANDLE handle = (HANDLE)_get_osfhandle(fd);
389 if (handle == INVALID_HANDLE_VALUE)
390 return false;
391 return get_handle_file_uid((void *)handle, uid);
392 #endif
395 #if defined(_WIN32)
396 bool get_handle_file_uid(void *handle, file_uid &uid)
398 BY_HANDLE_FILE_INFORMATION info;
399 if (!GetFileInformationByHandle((HANDLE)handle, &info))
400 return false;
401 uid.first = info.dwVolumeSerialNumber;
402 uid.second = (uint64_t)info.nFileIndexLow | ((uint64_t)info.nFileIndexHigh << 32);
403 return true;
405 #endif
407 //------------------------------------------------------------------------------
409 bool is_path_separator(char ch)
411 #if !defined(_WIN32)
412 return ch == '/';
413 #else
414 return ch == '/' || ch == '\\';
415 #endif
418 split_path_t split_path(const char *path)
420 split_path_t sp;
421 #if !defined(_WIN32)
422 size_t npos = ~(size_t)0;
423 size_t pos = npos;
424 for (size_t i = 0; path[i] != '\0'; ++i) {
425 if (is_path_separator(path[i]))
426 pos = i;
428 if (pos == npos)
429 sp.file.assign(path);
430 else {
431 sp.dir.assign(path, pos + 1);
432 sp.file.assign(path + pos + 1);
434 #else
435 std::wstring wpath = widen(path);
436 std::unique_ptr<wchar_t[]> drive{new wchar_t[wpath.size() + 1]{}};
437 std::unique_ptr<wchar_t[]> dir{new wchar_t[wpath.size() + 1]{}};
438 std::unique_ptr<wchar_t[]> fname{new wchar_t[wpath.size() + 1]{}};
439 std::unique_ptr<wchar_t[]> ext{new wchar_t[wpath.size() + 1]{}};
440 _wsplitpath(wpath.c_str(), drive.get(), dir.get(), fname.get(), ext.get());
441 sp.drive = narrow(drive.get());
442 if (!drive[0] || dir[0] == L'/' || dir[0] == L'\\')
443 sp.dir = narrow(dir.get());
444 else
445 sp.dir = narrow(L'\\' + std::wstring(dir.get()));
446 sp.file = narrow(std::wstring(fname.get()) + std::wstring(ext.get()));
447 #endif
448 return sp;
451 std::string path_file_name(const char *path)
453 return split_path(path).file;
456 std::string path_directory(const char *path)
458 split_path_t sp = split_path(path);
459 return sp.dir.empty() ? std::string("./") : (sp.drive + sp.dir);
462 std::string path_ensure_final_separator(const char *path)
464 std::string result(path);
466 if (!result.empty() && !is_path_separator(result.back()))
467 result.push_back('/');
469 return result;
472 bool path_has_suffix(const char *path, const char *suffix)
474 if (*suffix == '.')
475 ++suffix;
477 size_t plen = strlen(path);
478 size_t slen = strlen(suffix);
479 if (plen < slen + 2)
480 return false;
482 return path[plen - slen - 1] == '.' &&
483 ascii_casecmp(suffix, &path[plen - slen]) == 0;
486 bool path_is_relative(const char *path)
488 #if !defined(_WIN32)
489 return !is_path_separator(path[0]);
490 #else
491 return !is_path_separator(split_path(path).dir.c_str()[0]);
492 #endif
495 //------------------------------------------------------------------------------
497 #if !defined(_WIN32)
498 bool exists(const char *path)
500 return access(path, F_OK) == 0;
503 string_list list_directory(const char *path)
505 string_list list;
507 DIR *dir = opendir(path);
508 if (!dir)
509 return list;
510 auto dir_cleanup = defer([dir]() { closedir(dir); });
512 list.reserve(256);
514 std::string pathbuf;
515 pathbuf.reserve(1024);
517 while (dirent *ent = readdir(dir)) {
518 const char *name = ent->d_name;
519 if (!strcmp(name, ".") || !strcmp(name, ".."))
520 continue;
522 pathbuf.assign(name);
523 if (ent->d_type == DT_DIR)
524 pathbuf.push_back('/');
526 list.push_back(pathbuf);
529 std::sort(list.begin(), list.end());
530 return list;
533 // void visit_directories(const char *rootpath, bool (*visit)(const std::string &, void *), void *data);
534 // NOTE: implemented in separate file `ysfx_utils_fts.cpp`
535 #else
536 bool exists(const char *path)
538 return _waccess(widen(path).c_str(), 0) == 0;
541 string_list list_directory(const char *path)
543 string_list list;
545 std::wstring pattern = widen(path) + L"\\*";
547 WIN32_FIND_DATAW fd;
548 HANDLE handle = FindFirstFileW(pattern.c_str(), &fd);
549 if (handle == INVALID_HANDLE_VALUE)
550 return list;
551 auto handle_cleanup = defer([handle]() { FindClose(handle); });
553 list.reserve(256);
555 do {
556 const wchar_t *name = fd.cFileName;
557 if (!wcscmp(name, L".") || !wcscmp(name, L".."))
558 continue;
560 std::string entry = narrow(name);
561 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && !(fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
562 entry.push_back('/');
564 list.push_back(std::move(entry));
565 } while (FindNextFileW(handle, &fd));
567 std::sort(list.begin(), list.end());
568 return list;
571 void visit_directories(const char *rootpath, bool (*visit)(const std::string &, void *), void *data)
573 std::deque<std::wstring> dirs;
574 dirs.push_back(widen(path_ensure_final_separator(rootpath)));
576 std::wstring pathbuf;
577 pathbuf.reserve(1024);
579 std::vector<std::wstring> entries;
580 entries.reserve(256);
582 while (!dirs.empty()) {
583 std::wstring dir = std::move(dirs.front());
584 dirs.pop_front();
586 if (!visit(narrow(dir), data))
587 return;
589 pathbuf.assign(dir);
590 pathbuf.append(L"\\*");
592 WIN32_FIND_DATAW fd;
593 HANDLE handle = FindFirstFileW(pathbuf.c_str(), &fd);
594 if (handle == INVALID_HANDLE_VALUE)
595 continue;
596 auto handle_cleanup = defer([handle]() { FindClose(handle); });
598 entries.clear();
599 do {
600 if (fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
601 continue;
602 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
603 const wchar_t *name = fd.cFileName;
604 if (!wcscmp(name, L".") || !wcscmp(name, L".."))
605 continue;
606 pathbuf.assign(dir);
607 pathbuf.append(name);
608 pathbuf.push_back(L'\\');
609 entries.push_back(pathbuf);
611 } while (FindNextFileW(handle, &fd));
613 std::sort(entries.begin(), entries.end());
614 for (size_t n = entries.size(); n-- > 0; )
615 dirs.push_front(std::move(entries[n]));
618 #endif
620 int case_resolve(const char *root_, const char *fragment, std::string &result)
622 if (fragment[0] == '\0')
623 return 0;
625 std::string root = path_ensure_final_separator(root_);
627 std::string pathbuf;
628 pathbuf.reserve(1024);
630 pathbuf.assign(root);
631 pathbuf.append(fragment);
632 if (exists(pathbuf.c_str())) {
633 result = std::move(pathbuf);
634 return 1;
637 struct Item {
638 std::string root;
639 string_list components;
642 std::deque<Item> worklist;
645 Item item;
646 item.root = root;
647 item.components = split_strings_noempty(fragment, &is_path_separator);
648 if (item.components.empty())
649 return 0;
650 for (size_t i = 0; i + 1 < item.components.size(); ++i)
651 item.components[i].push_back('/');
652 if (is_path_separator(fragment[strlen(fragment) - 1]))
653 item.components.back().push_back('/');
654 worklist.push_back(std::move(item));
657 while (!worklist.empty()) {
658 Item item = std::move(worklist.front());
659 worklist.pop_front();
661 for (const std::string &entry : list_directory(item.root.c_str())) {
662 if (ascii_casecmp(entry.c_str(), item.components[0].c_str()) != 0)
663 continue;
665 if (item.components.size() == 1) {
666 pathbuf.assign(item.root);
667 pathbuf.append(entry);
668 if (exists(pathbuf.c_str())) {
669 result = std::move(pathbuf);
670 return 2;
673 else {
674 assert(item.components.size() > 1);
675 Item newitem;
676 newitem.root = item.root + entry;
677 newitem.components.assign(item.components.begin() + 1, item.components.end());
678 worklist.push_front(std::move(newitem));
683 return 0;
686 //------------------------------------------------------------------------------
688 #if defined(_WIN32)
689 std::wstring widen(const std::string &u8str)
691 return widen(u8str.data(), u8str.size());
694 std::wstring widen(const char *u8data, size_t u8len)
696 if (u8len == ~(size_t)0)
697 u8len = strlen(u8data);
698 std::wstring wstr;
699 int wch = MultiByteToWideChar(CP_UTF8, 0, u8data, (int)u8len, nullptr, 0);
700 if (wch != 0) {
701 wstr.resize((size_t)wch);
702 MultiByteToWideChar(CP_UTF8, 0, u8data, (int)u8len, &wstr[0], wch);
704 return wstr;
707 std::string narrow(const std::wstring &wstr)
709 return narrow(wstr.data(), wstr.size());
712 std::string narrow(const wchar_t *wdata, size_t wlen)
714 if (wlen == ~(size_t)0)
715 wlen = wcslen(wdata);
716 std::string u8str;
717 int u8ch = WideCharToMultiByte(CP_UTF8, 0, wdata, (int)wlen, nullptr, 0, nullptr, nullptr);
718 if (u8ch != 0) {
719 u8str.resize((size_t)u8ch);
720 WideCharToMultiByte(CP_UTF8, 0, wdata, (int)wlen, &u8str[0], u8ch, nullptr, nullptr);
722 return u8str;
724 #endif
726 } // namespace ysfx
728 //------------------------------------------------------------------------------
729 // WDL helpers
731 // our replacement `atof` for WDL, which is unaffected by current locale
732 extern "C" double ysfx_wdl_atof(const char *text)
734 return ysfx::dot_atof(text);