Change the maximum data-block name from 64 to 256 bytes by increasing MAX_ID_NAME value. Also increase a few related non-ID data name max size, essentially the action slots identifiers, as these are the primary key used to match an Action's slot to an ID by name. Other sub-data (bones, modifiers, etc.) lengths are not modified here, as these can be made actual dynamic strings in the future, while keeping (a reasonable level of) forward compatibility, during the course of Blender 5 release cycles. Implements #137608. Co-authored-by: Bastien Montagne <bastien@blender.org> Pull Request: https://projects.blender.org/blender/blender/pulls/137196
2060 lines
56 KiB
C++
2060 lines
56 KiB
C++
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup bli
|
|
* Various string, file, list operations.
|
|
*/
|
|
|
|
#include <algorithm> /* For `min/max`. */
|
|
#include <cctype>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
#include "BLI_fileops.h"
|
|
#include "BLI_fnmatch.h"
|
|
#include "BLI_path_utils.hh"
|
|
#include "BLI_string.h"
|
|
#include "BLI_string_utils.hh"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#ifdef WIN32
|
|
# include "utf_winfunc.hh"
|
|
# include "utfconv.hh"
|
|
# include <io.h>
|
|
# ifdef _WIN32_IE
|
|
# undef _WIN32_IE
|
|
# endif
|
|
# define _WIN32_IE 0x0501
|
|
# include "BLI_alloca.h"
|
|
# include "BLI_winstuff.h"
|
|
# include <shlobj.h>
|
|
# include <windows.h>
|
|
#else
|
|
# include <unistd.h>
|
|
#endif /* WIN32 */
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
/* Declarations. */
|
|
|
|
static int BLI_path_unc_prefix_len(const char *path);
|
|
|
|
#ifdef WIN32
|
|
static bool BLI_path_is_abs_win32(const char *path);
|
|
static int BLI_path_win32_prefix_len(const char *path);
|
|
#endif /* WIN32 */
|
|
|
|
/**
|
|
* The maximum number of `#` characters expanded for #BLI_path_frame & #BLI_path_frame_range
|
|
* Typically 12 is enough and even 16 is very large.
|
|
* Use a much larger value so hitting the upper limit is not an issue.
|
|
* Exceeding this limit won't fail either, it will just not insert so many leading zeros.
|
|
*/
|
|
#define FILENAME_FRAME_CHARS_MAX FILE_MAX
|
|
|
|
int BLI_path_sequence_decode(const char *path,
|
|
char *head,
|
|
const size_t head_maxncpy,
|
|
char *tail,
|
|
const size_t tail_maxncpy,
|
|
ushort *r_digits_len)
|
|
{
|
|
if (head) {
|
|
BLI_string_debug_size(head, head_maxncpy);
|
|
}
|
|
if (tail) {
|
|
BLI_string_debug_size(tail, tail_maxncpy);
|
|
}
|
|
|
|
uint nums = 0, nume = 0;
|
|
int i;
|
|
bool found_digit = false;
|
|
const char *const lslash = BLI_path_slash_rfind(path);
|
|
const char *const extension = BLI_path_extension_or_end(lslash ? lslash : path);
|
|
const uint lslash_len = lslash != nullptr ? int(lslash - path) : 0;
|
|
const uint name_end = uint(extension - path);
|
|
|
|
for (i = name_end - 1; i >= int(lslash_len); i--) {
|
|
if (isdigit(path[i])) {
|
|
if (found_digit) {
|
|
nums = i;
|
|
}
|
|
else {
|
|
nume = i;
|
|
nums = i;
|
|
found_digit = true;
|
|
}
|
|
}
|
|
else {
|
|
if (found_digit) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (found_digit) {
|
|
const long long int ret = strtoll(&(path[nums]), nullptr, 10);
|
|
if (ret >= INT_MIN && ret <= INT_MAX) {
|
|
if (tail) {
|
|
BLI_strncpy(tail, &path[nume + 1], tail_maxncpy);
|
|
}
|
|
if (head) {
|
|
BLI_strncpy(head, path, std::min<size_t>(head_maxncpy, nums + 1));
|
|
}
|
|
if (r_digits_len) {
|
|
*r_digits_len = nume - nums + 1;
|
|
}
|
|
return int(ret);
|
|
}
|
|
}
|
|
|
|
if (tail) {
|
|
BLI_strncpy(tail, path + name_end, tail_maxncpy);
|
|
}
|
|
if (head) {
|
|
/* Name_end points to last character of head,
|
|
* make it +1 so null-terminator is nicely placed. */
|
|
BLI_strncpy(head, path, std::min<size_t>(head_maxncpy, name_end + 1));
|
|
}
|
|
if (r_digits_len) {
|
|
*r_digits_len = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void BLI_path_sequence_encode(char *path,
|
|
const size_t path_maxncpy,
|
|
const char *head,
|
|
const char *tail,
|
|
ushort numlen,
|
|
int pic)
|
|
{
|
|
BLI_string_debug_size(path, path_maxncpy);
|
|
|
|
/* FIXME: MAX_ID_NAME & FILE_MAXFILE
|
|
*
|
|
* As this function directly works on a full file path (typically a FILE_MAX long char buffer),
|
|
* and does not perform any check on the filename part of the path, it can easily generate final
|
|
* paths containing a filename longer than the max supported length (FILE_MAXFILE).
|
|
*/
|
|
BLI_snprintf(path, path_maxncpy, "%s%.*d%s", head, numlen, std::max(0, pic), tail);
|
|
}
|
|
|
|
/**
|
|
* Implementation for #BLI_path_normalize & #BLI_path_normalize_native.
|
|
* \return The path length.
|
|
*/
|
|
static int path_normalize_impl(char *path, bool check_blend_relative_prefix)
|
|
{
|
|
const char *path_orig = path;
|
|
int path_len = strlen(path);
|
|
|
|
/*
|
|
* Skip absolute prefix.
|
|
* ---------------------
|
|
*/
|
|
if (check_blend_relative_prefix && (path[0] == '/' && path[1] == '/')) {
|
|
path = path + 2; /* Leave the initial `//` untouched. */
|
|
path_len -= 2;
|
|
|
|
/* Strip leading slashes, as they will interfere with the absolute/relative check
|
|
* (besides being redundant). */
|
|
int i = 0;
|
|
while (path[i] == SEP) {
|
|
i++;
|
|
}
|
|
|
|
if (i != 0) {
|
|
memmove(path, path + i, (path_len - i) + 1);
|
|
path_len -= i;
|
|
}
|
|
BLI_assert(path_len == strlen(path));
|
|
}
|
|
|
|
#ifdef WIN32
|
|
/* Skip to the first slash of the drive or UNC path,
|
|
* so additional slashes are treated as doubles. */
|
|
if (path_orig == path) {
|
|
int path_unc_len = BLI_path_unc_prefix_len(path);
|
|
if (path_unc_len) {
|
|
path_unc_len -= 1;
|
|
BLI_assert(path_unc_len > 0 && path[path_unc_len] == SEP);
|
|
path += path_unc_len;
|
|
path_len -= path_unc_len;
|
|
}
|
|
else if (BLI_path_is_win32_drive(path)) { /* Check for `C:` (2 characters only). */
|
|
path += 2;
|
|
path_len -= 2;
|
|
}
|
|
}
|
|
#endif /* WIN32 */
|
|
/* Works on WIN32 as well, because the drive component is skipped. */
|
|
const bool is_relative = path[0] && (path[0] != SEP);
|
|
|
|
/*
|
|
* Strip redundant path components.
|
|
* --------------------------------
|
|
*/
|
|
|
|
/* NOTE(@ideasman42):
|
|
* `memmove(start, eind, strlen(eind) + 1);`
|
|
* is the same as
|
|
* `BLI_strncpy(start, eind, ...);`
|
|
* except string-copy should not be used because there is overlap,
|
|
* so use `memmove` 's slightly more obscure syntax. */
|
|
|
|
/* Inline replacement:
|
|
* - `/./` -> `/`.
|
|
* - `//` -> `/`.
|
|
* Performed until no more replacements can be made. */
|
|
if (path_len > 1) {
|
|
for (int i = path_len - 1; i > 0; i--) {
|
|
/* Calculate the redundant slash span (if any). */
|
|
if (path[i] == SEP) {
|
|
const int i_end = i;
|
|
do {
|
|
/* Stepping over elements assumes 'i' references a separator. */
|
|
BLI_assert(path[i] == SEP);
|
|
if (path[i - 1] == SEP) {
|
|
i -= 1; /* Found `//`, replace with `/`. */
|
|
}
|
|
else if (i >= 2 && path[i - 1] == '.' && path[i - 2] == SEP) {
|
|
i -= 2; /* Found `/./`, replace with `/`. */
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
} while (i > 0);
|
|
|
|
if (i < i_end) {
|
|
memmove(path + i, path + i_end, (path_len - i_end) + 1);
|
|
path_len -= i_end - i;
|
|
BLI_assert(strlen(path) == path_len);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* Remove redundant `./` prefix as it's redundant & complicates collapsing directories. */
|
|
if (is_relative) {
|
|
if ((path_len > 2) && (path[0] == '.') && (path[1] == SEP)) {
|
|
memmove(path, path + 2, (path_len - 2) + 1);
|
|
path_len -= 2;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Collapse Parent Directories.
|
|
* ----------------------------
|
|
*
|
|
* Example: `<parent>/<child>/../` -> `<parent>/`
|
|
*
|
|
* Notes:
|
|
* - Leading `../` are skipped as they cannot be collapsed (see `start_base`).
|
|
* - Multiple parent directories are handled at once to reduce number of `memmove` calls.
|
|
*/
|
|
|
|
#define IS_PARENT_DIR(p) ((p)[0] == '.' && (p)[1] == '.' && ELEM((p)[2], SEP, '\0'))
|
|
|
|
/* First non prefix path component. */
|
|
char *path_first_non_slash_part = path;
|
|
while (*path_first_non_slash_part && *path_first_non_slash_part == SEP) {
|
|
path_first_non_slash_part++;
|
|
}
|
|
|
|
/* Maintain a pointer to the end of leading `..` component.
|
|
* Skip leading parent directories because logically they cannot be collapsed. */
|
|
char *start_base = path_first_non_slash_part;
|
|
while (IS_PARENT_DIR(start_base)) {
|
|
start_base += 3;
|
|
}
|
|
|
|
/* It's possible the entire path is made of up `../`,
|
|
* in this case there is nothing to do. */
|
|
if (start_base < path + path_len) {
|
|
/* Step over directories, always starting out on the character after the slash. */
|
|
char *start = start_base;
|
|
char *start_temp;
|
|
while ((start_temp = strstr(start, SEP_STR ".." SEP_STR)) ||
|
|
/* Check if the string ends with `/..` & assign when found, else nullptr. */
|
|
(start_temp = ((start <= &path[path_len - 3]) &&
|
|
STREQ(&path[path_len - 3], SEP_STR "..")) ?
|
|
&path[path_len - 3] :
|
|
nullptr))
|
|
{
|
|
start = start_temp + 1; /* Skip the `/`. */
|
|
BLI_assert(start_base != start);
|
|
|
|
/* Step `end_all` forwards (over all `..`). */
|
|
char *end_all = start;
|
|
do {
|
|
BLI_assert(IS_PARENT_DIR(end_all));
|
|
end_all += 3;
|
|
BLI_assert(end_all <= path + path_len + 1);
|
|
} while (IS_PARENT_DIR(end_all));
|
|
|
|
/* Step `start` backwards (until `end` meets `end_all` or `start` meets `start_base`). */
|
|
char *end = start;
|
|
do {
|
|
BLI_assert(start_base < start);
|
|
BLI_assert(*(start - 1) == SEP);
|
|
/* Step `start` backwards one. */
|
|
do {
|
|
start--;
|
|
} while (start_base < start && *(start - 1) != SEP);
|
|
BLI_assert(*start != SEP); /* Ensure the loop ran at least once. */
|
|
BLI_assert(!IS_PARENT_DIR(start)); /* Clamping by `start_base` prevents this. */
|
|
end += 3;
|
|
} while ((start != start_base) && (end < end_all));
|
|
|
|
if (end > path + path_len) {
|
|
BLI_assert(*(end - 1) == '\0');
|
|
end--;
|
|
end_all--;
|
|
}
|
|
BLI_assert(start < end && start >= start_base);
|
|
const size_t start_len = path_len - (end - path);
|
|
memmove(start, end, start_len + 1);
|
|
path_len -= end - start;
|
|
BLI_assert(strlen(path) == path_len);
|
|
/* Other `..` directories may have been moved to the front, step `start_base` past them. */
|
|
if (UNLIKELY(start == start_base && (end != end_all))) {
|
|
start_base += (end_all - end);
|
|
start = (start_base < path + path_len) ? start_base : start_base - 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
BLI_assert(strlen(path) == path_len);
|
|
/* Characters before the `start_base` must *only* be `../../../` (multiples of 3). */
|
|
BLI_assert((start_base - path_first_non_slash_part) % 3 == 0);
|
|
/* All `..` ahead of `start_base` were collapsed (including trailing `/..`). */
|
|
BLI_assert(!(start_base < path + path_len) ||
|
|
(!strstr(start_base, SEP_STR ".." SEP_STR) &&
|
|
!(path_len >= 3 && STREQ(&path[path_len - 3], SEP_STR ".."))));
|
|
|
|
/*
|
|
* Final Prefix Cleanup.
|
|
* ---------------------
|
|
*/
|
|
if (is_relative) {
|
|
if (path_len == 0 && (path == path_orig)) {
|
|
path[0] = '.';
|
|
path[1] = '\0';
|
|
path_len = 1;
|
|
}
|
|
}
|
|
else {
|
|
/* Support for odd paths: eg `/../home/me` --> `/home/me`
|
|
* this is a valid path in blender but we can't handle this the usual way below
|
|
* simply strip this prefix then evaluate the path as usual.
|
|
* Python's `os.path.normpath()` does this. */
|
|
if (start_base != path_first_non_slash_part) {
|
|
char *start = start_base > path + path_len ? start_base - 1 : start_base;
|
|
/* As long as `start` is set correctly, it should never begin with `../`
|
|
* as these directories are expected to be skipped. */
|
|
BLI_assert(!IS_PARENT_DIR(start));
|
|
const size_t start_len = path_len - (start - path);
|
|
BLI_assert(strlen(start) == start_len);
|
|
memmove(path_first_non_slash_part, start, start_len + 1);
|
|
path_len -= start - path_first_non_slash_part;
|
|
BLI_assert(strlen(path) == path_len);
|
|
}
|
|
}
|
|
|
|
BLI_assert(strlen(path) == path_len);
|
|
|
|
#undef IS_PARENT_DIR
|
|
|
|
return (path - path_orig) + path_len;
|
|
}
|
|
|
|
int BLI_path_normalize(char *path)
|
|
{
|
|
return path_normalize_impl(path, true);
|
|
}
|
|
|
|
int BLI_path_normalize_native(char *path)
|
|
{
|
|
return path_normalize_impl(path, false);
|
|
}
|
|
|
|
int BLI_path_normalize_dir(char *dir, size_t dir_maxncpy)
|
|
{
|
|
/* Would just create an unexpected "/" path, just early exit entirely. */
|
|
if (dir[0] == '\0') {
|
|
return 0;
|
|
}
|
|
|
|
int dir_len = BLI_path_normalize(dir);
|
|
return BLI_path_slash_ensure_ex(dir, dir_maxncpy, dir_len);
|
|
}
|
|
|
|
int BLI_path_canonicalize_native(char *path, int path_maxncpy)
|
|
{
|
|
BLI_path_abs_from_cwd(path, path_maxncpy);
|
|
/* As these are system level paths, only convert slashes
|
|
* if the alternate direction is accepted as a slash. */
|
|
if (BLI_path_slash_is_native_compat(ALTSEP)) {
|
|
BLI_path_slash_native(path);
|
|
}
|
|
int path_len = BLI_path_normalize_native(path);
|
|
/* Strip trailing slash but don't strip `/` away to nothing. */
|
|
if (path_len > 1 && path[path_len - 1] == SEP) {
|
|
#ifdef WIN32
|
|
/* Don't strip `C:\` -> `C:` as this is no longer a valid directory. */
|
|
if (BLI_path_win32_prefix_len(path) + 1 < path_len)
|
|
#endif
|
|
{
|
|
path_len -= 1;
|
|
path[path_len] = '\0';
|
|
}
|
|
}
|
|
return path_len;
|
|
}
|
|
|
|
bool BLI_path_make_safe_filename_ex(char *filename, bool allow_tokens)
|
|
{
|
|
#define INVALID_CHARS \
|
|
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" \
|
|
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" \
|
|
"/\\?*:|\""
|
|
#define INVALID_TOKENS "<>"
|
|
|
|
const char *invalid = allow_tokens ? INVALID_CHARS : INVALID_CHARS INVALID_TOKENS;
|
|
|
|
#undef INVALID_CHARS
|
|
#undef INVALID_TOKENS
|
|
|
|
char *fn;
|
|
bool changed = false;
|
|
|
|
if (*filename == '\0') {
|
|
return changed;
|
|
}
|
|
|
|
for (fn = filename; *fn && (fn = strpbrk(fn, invalid)); fn++) {
|
|
*fn = '_';
|
|
changed = true;
|
|
}
|
|
|
|
/* Forbid only dots. */
|
|
for (fn = filename; *fn == '.'; fn++) {
|
|
/* Pass. */
|
|
}
|
|
if (*fn == '\0') {
|
|
*filename = '_';
|
|
changed = true;
|
|
}
|
|
|
|
#ifdef WIN32
|
|
{
|
|
const char *invalid_names[] = {
|
|
"con", "prn", "aux", "null", "com1", "com2", "com3", "com4",
|
|
"com5", "com6", "com7", "com8", "com9", "lpt1", "lpt2", "lpt3",
|
|
"lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", nullptr,
|
|
};
|
|
const size_t len = strlen(filename);
|
|
char *filename_lower = BLI_strdupn(filename, len);
|
|
const char **iname;
|
|
|
|
/* Forbid trailing dot (trailing space has already been replaced above). */
|
|
if (filename[len - 1] == '.') {
|
|
filename[len - 1] = '_';
|
|
changed = true;
|
|
}
|
|
|
|
/* Check for forbidden names - not we have to check all combination
|
|
* of upper and lower cases, hence the usage of filename_lower
|
|
* (more efficient than using #BLI_strcasestr repeatedly). */
|
|
BLI_str_tolower_ascii(filename_lower, len);
|
|
for (iname = invalid_names; *iname; iname++) {
|
|
if (strstr(filename_lower, *iname) == filename_lower) {
|
|
const size_t iname_len = strlen(*iname);
|
|
/* Only invalid if the whole name is made of the invalid chunk, or it has an
|
|
* (assumed extension) dot just after. This means it will also catch *valid*
|
|
* names like `aux.foo.bar`, but should be good enough for us! */
|
|
if ((iname_len == len) || (filename_lower[iname_len] == '.')) {
|
|
*filename = '_';
|
|
changed = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
MEM_freeN(filename_lower);
|
|
}
|
|
#endif
|
|
|
|
return changed;
|
|
}
|
|
|
|
bool BLI_path_make_safe_filename(char *filename)
|
|
{
|
|
return BLI_path_make_safe_filename_ex(filename, false);
|
|
}
|
|
|
|
bool BLI_path_make_safe(char *path)
|
|
{
|
|
/* Simply apply #BLI_path_make_safe_filename() over each component of the path.
|
|
* Luckily enough, same *safe* rules applies to file & directory names. */
|
|
char *curr_slash, *curr_path = path;
|
|
bool changed = false;
|
|
bool skip_first = false;
|
|
|
|
#ifdef WIN32
|
|
if (BLI_path_is_abs_win32(path)) {
|
|
/* Do not make safe `C:` in `C:\foo\bar`. */
|
|
skip_first = true;
|
|
}
|
|
#endif
|
|
|
|
for (curr_slash = (char *)BLI_path_slash_find(curr_path); curr_slash;
|
|
curr_slash = (char *)BLI_path_slash_find(curr_path))
|
|
{
|
|
const char backup = *curr_slash;
|
|
*curr_slash = '\0';
|
|
if (!skip_first && (*curr_path != '\0') && BLI_path_make_safe_filename(curr_path)) {
|
|
changed = true;
|
|
}
|
|
skip_first = false;
|
|
curr_path = curr_slash + 1;
|
|
*curr_slash = backup;
|
|
}
|
|
if (BLI_path_make_safe_filename(curr_path)) {
|
|
changed = true;
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
bool BLI_path_is_rel(const char *path)
|
|
{
|
|
return path[0] == '/' && path[1] == '/';
|
|
}
|
|
|
|
bool BLI_path_is_unc(const char *path)
|
|
{
|
|
return path[0] == '\\' && path[1] == '\\';
|
|
}
|
|
|
|
/**
|
|
* Returns the length of the identifying prefix
|
|
* of a UNC path which can start with '\\' (short version)
|
|
* or '\\?\' (long version)
|
|
* If the path is not a UNC path, return 0
|
|
*/
|
|
static int BLI_path_unc_prefix_len(const char *path)
|
|
{
|
|
if (BLI_path_is_unc(path)) {
|
|
if ((path[2] == '?') && (path[3] == '\\')) {
|
|
/* We assume long UNC path like `\\?\server\share\folder` etc. */
|
|
return 4;
|
|
}
|
|
|
|
return 2;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef WIN32
|
|
static int BLI_path_win32_prefix_len(const char *path)
|
|
{
|
|
if (BLI_path_is_win32_drive(path)) {
|
|
return 2;
|
|
}
|
|
return BLI_path_unc_prefix_len(path);
|
|
}
|
|
#endif
|
|
|
|
bool BLI_path_is_win32_drive(const char *path)
|
|
{
|
|
return isalpha(path[0]) && (path[1] == ':');
|
|
}
|
|
|
|
bool BLI_path_is_win32_drive_only(const char *path)
|
|
{
|
|
return isalpha(path[0]) && (path[1] == ':') && (path[2] == '\0');
|
|
}
|
|
|
|
bool BLI_path_is_win32_drive_with_slash(const char *path)
|
|
{
|
|
return isalpha(path[0]) && (path[1] == ':') && ELEM(path[2], '\\', '/');
|
|
}
|
|
|
|
#if defined(WIN32)
|
|
|
|
/**
|
|
* Return true if the path is an absolute path on a WIN32 file-system, it either:
|
|
* - Starts with a drive specifier* (eg `A:\`).
|
|
* - Is a UNC path.
|
|
*
|
|
* \note Not to be confused with the opposite of #BLI_path_is_rel which checks for the
|
|
* Blender specific convention of using `//` prefix for blend-file relative paths.
|
|
*/
|
|
static bool BLI_path_is_abs_win32(const char *path)
|
|
{
|
|
/* Don't use the `BLI_path_is_win32_drive_with_slash`
|
|
* since paths such as `C:` are valid on their own. */
|
|
return BLI_path_is_win32_drive(path) || BLI_path_is_unc(path);
|
|
}
|
|
|
|
static wchar_t *next_slash(wchar_t *path)
|
|
{
|
|
wchar_t *slash = path;
|
|
while (*slash && *slash != L'\\') {
|
|
slash++;
|
|
}
|
|
return slash;
|
|
}
|
|
|
|
/* Adds a slash if the UNC path points to a share. */
|
|
static void BLI_path_add_slash_to_share(wchar_t *uncpath)
|
|
{
|
|
wchar_t *slash_after_server = next_slash(uncpath + 2);
|
|
if (*slash_after_server) {
|
|
wchar_t *slash_after_share = next_slash(slash_after_server + 1);
|
|
if (!(*slash_after_share)) {
|
|
slash_after_share[0] = L'\\';
|
|
slash_after_share[1] = L'\0';
|
|
}
|
|
}
|
|
}
|
|
|
|
static void BLI_path_unc_to_short(wchar_t *unc)
|
|
{
|
|
wchar_t tmp[PATH_MAX];
|
|
|
|
int len = wcslen(unc);
|
|
/* Convert:
|
|
* - `\\?\UNC\server\share\folder\...` to `\\server\share\folder\...`
|
|
* - `\\?\C:\` to `C:\`
|
|
* - `\\?\C:\folder\...` to `C:\folder\...`
|
|
*/
|
|
if ((len > 3) && (unc[0] == L'\\') && (unc[1] == L'\\') && (unc[2] == L'?') &&
|
|
ELEM(unc[3], L'\\', L'/'))
|
|
{
|
|
if ((len > 5) && (unc[5] == L':')) {
|
|
wcsncpy(tmp, unc + 4, len - 4);
|
|
tmp[len - 4] = L'\0';
|
|
wcscpy(unc, tmp);
|
|
}
|
|
else if ((len > 7) && (wcsncmp(&unc[4], L"UNC", 3) == 0) && ELEM(unc[7], L'\\', L'/')) {
|
|
tmp[0] = L'\\';
|
|
tmp[1] = L'\\';
|
|
wcsncpy(tmp + 2, unc + 8, len - 8);
|
|
tmp[len - 6] = L'\0';
|
|
wcscpy(unc, tmp);
|
|
}
|
|
}
|
|
}
|
|
|
|
void BLI_path_normalize_unc(char *path, int path_maxncpy)
|
|
{
|
|
wchar_t *tmp_16 = alloc_utf16_from_8(path, 1);
|
|
BLI_path_normalize_unc_16(tmp_16);
|
|
conv_utf_16_to_8(tmp_16, path, path_maxncpy);
|
|
}
|
|
|
|
void BLI_path_normalize_unc_16(wchar_t *path_16)
|
|
{
|
|
BLI_path_unc_to_short(path_16);
|
|
BLI_path_add_slash_to_share(path_16);
|
|
}
|
|
#endif
|
|
|
|
void BLI_path_rel(char path[FILE_MAX], const char *basepath)
|
|
{
|
|
BLI_string_debug_size_after_nil(path, FILE_MAX);
|
|
/* A `basepath` starting with `//` will be made relative multiple times. */
|
|
BLI_assert_msg(!BLI_path_is_rel(basepath), "The 'basepath' cannot start with '//'!");
|
|
|
|
const char *lslash;
|
|
char temp[FILE_MAX];
|
|
|
|
/* If path is already relative, bail out. */
|
|
if (BLI_path_is_rel(path)) {
|
|
return;
|
|
}
|
|
|
|
/* Also bail out if relative path is not set. */
|
|
if (basepath[0] == '\0') {
|
|
return;
|
|
}
|
|
|
|
#ifdef WIN32
|
|
if (BLI_strnlen(basepath, 3) > 2 && !BLI_path_is_abs_win32(basepath)) {
|
|
char *ptemp;
|
|
/* Fix missing volume name in relative base,
|
|
* can happen with old `recent-files.txt` files. */
|
|
BLI_windows_get_default_root_dir(temp);
|
|
ptemp = &temp[2];
|
|
if (!ELEM(basepath[0], '\\', '/')) {
|
|
ptemp++;
|
|
}
|
|
BLI_strncpy(ptemp, basepath, FILE_MAX - 3);
|
|
}
|
|
else {
|
|
BLI_strncpy(temp, basepath, FILE_MAX);
|
|
}
|
|
|
|
if (BLI_strnlen(path, 3) > 2) {
|
|
bool is_unc = BLI_path_is_unc(path);
|
|
|
|
/* Ensure paths are both UNC paths or are both drives. */
|
|
if (BLI_path_is_unc(temp) != is_unc) {
|
|
return;
|
|
}
|
|
|
|
/* Ensure both UNC paths are on the same share. */
|
|
if (is_unc) {
|
|
int off;
|
|
int slash = 0;
|
|
for (off = 0; temp[off] && slash < 4; off++) {
|
|
if (temp[off] != path[off]) {
|
|
return;
|
|
}
|
|
|
|
if (temp[off] == '\\') {
|
|
slash++;
|
|
}
|
|
}
|
|
}
|
|
else if ((temp[1] == ':' && path[1] == ':') && (tolower(temp[0]) != tolower(path[0]))) {
|
|
return;
|
|
}
|
|
}
|
|
#else
|
|
STRNCPY(temp, basepath);
|
|
#endif
|
|
|
|
BLI_string_replace_char(temp + BLI_path_unc_prefix_len(temp), '\\', '/');
|
|
BLI_string_replace_char(path + BLI_path_unc_prefix_len(path), '\\', '/');
|
|
|
|
/* Remove `/./` which confuse the following slash counting. */
|
|
BLI_path_normalize(path);
|
|
BLI_path_normalize(temp);
|
|
|
|
/* The last slash in the path indicates where the path part ends. */
|
|
lslash = BLI_path_slash_rfind(temp);
|
|
|
|
if (lslash) {
|
|
/* Find the prefix of the filename that is equal for both filenames.
|
|
* This is replaced by the two slashes at the beginning. */
|
|
const char *p = temp;
|
|
const char *q = path;
|
|
|
|
#ifdef WIN32
|
|
while (tolower(*p) == tolower(*q))
|
|
#else
|
|
while (*p == *q)
|
|
#endif
|
|
{
|
|
p++;
|
|
q++;
|
|
|
|
/* Don't search beyond the end of the string in the rare case they match. */
|
|
if ((*p == '\0') || (*q == '\0')) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* We might have passed the slash when the beginning of a dir matches
|
|
* so we rewind. Only check on the actual filename. */
|
|
if (*q != '/') {
|
|
while ((q >= path) && (*q != '/')) {
|
|
q--;
|
|
p--;
|
|
}
|
|
}
|
|
else if (*p != '/') {
|
|
while ((p >= temp) && (*p != '/')) {
|
|
p--;
|
|
q--;
|
|
}
|
|
}
|
|
|
|
char res[FILE_MAX] = "//";
|
|
char *r = res + 2;
|
|
|
|
/* `p` now points to the slash that is at the beginning of the part
|
|
* where the path is different from the relative path.
|
|
* We count the number of directories we need to go up in the
|
|
* hierarchy to arrive at the common prefix of the path. */
|
|
if (p < temp) {
|
|
p = temp;
|
|
}
|
|
while (p && p < lslash) {
|
|
if (*p == '/') {
|
|
r += BLI_strncpy_rlen(r, "../", sizeof(res) - (r - res));
|
|
}
|
|
p++;
|
|
}
|
|
|
|
/* Don't copy the slash at the beginning. */
|
|
r += BLI_strncpy_rlen(r, q + 1, sizeof(res) - (r - res));
|
|
UNUSED_VARS(r);
|
|
|
|
#ifdef WIN32
|
|
BLI_string_replace_char(res + 2, '/', '\\');
|
|
#endif
|
|
BLI_strncpy(path, res, FILE_MAX);
|
|
}
|
|
}
|
|
|
|
bool BLI_path_suffix(char *path, size_t path_maxncpy, const char *suffix, const char *sep)
|
|
{
|
|
BLI_string_debug_size_after_nil(path, path_maxncpy);
|
|
|
|
const size_t suffix_len = strlen(suffix);
|
|
const size_t sep_len = strlen(sep);
|
|
char *extension = (char *)BLI_path_extension_or_end(path);
|
|
const size_t extension_len = strlen(extension);
|
|
const size_t path_end = extension - path;
|
|
const size_t path_len = path_end + extension_len;
|
|
if (path_len + sep_len + suffix_len >= path_maxncpy) {
|
|
return false;
|
|
}
|
|
|
|
if (extension_len) {
|
|
memmove(extension + (sep_len + suffix_len), extension, extension_len);
|
|
}
|
|
char *c = path + path_end;
|
|
if (sep_len) {
|
|
memcpy(c, sep, sep_len);
|
|
c += sep_len;
|
|
}
|
|
if (suffix_len) {
|
|
memcpy(c, suffix, suffix_len);
|
|
c += suffix_len;
|
|
}
|
|
c += extension_len;
|
|
*c = '\0';
|
|
return true;
|
|
}
|
|
|
|
const char *BLI_path_parent_dir_end(const char *path, size_t path_len)
|
|
{
|
|
const char *path_end = path + path_len - 1;
|
|
const char *p = path_end;
|
|
while (p >= path) {
|
|
if (BLI_path_slash_is_native_compat(*p)) {
|
|
break;
|
|
}
|
|
p--;
|
|
}
|
|
while (p > path) {
|
|
if (BLI_path_slash_is_native_compat(*(p - 1))) {
|
|
p -= 1; /* Skip `/`. */
|
|
}
|
|
else if ((p + 1 > path) && (*(p - 1) == '.') && BLI_path_slash_is_native_compat(*p - 2)) {
|
|
p -= 2; /* Skip `/.` (actually `/./` but the last slash was already skipped) */
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
if ((p > path) && (p != path_end)) {
|
|
return p;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool BLI_path_parent_dir(char *path)
|
|
{
|
|
/* Use #BLI_path_name_at_index instead of checking if the strings ends with `parent_dir`
|
|
* to ensure the logic isn't confused by:
|
|
* - Directory names that happen to end with `..`.
|
|
* - When `path` is empty, the contents will be `../`
|
|
* which would cause checking for a tailing `/../` fail.
|
|
* Extracting the span of the final directory avoids both these issues. */
|
|
int tail_ofs = 0, tail_len = 0;
|
|
if (!BLI_path_name_at_index(path, -1, &tail_ofs, &tail_len)) {
|
|
return false;
|
|
}
|
|
if (tail_len == 1) {
|
|
/* Last path is `.`, as normalize should remove this, it's safe to assume failure.
|
|
* This happens when the input a single period (possibly with slashes before or after). */
|
|
if (path[tail_ofs] == '.') {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* Input paths should already be normalized if `..` is part of the path. */
|
|
BLI_assert(!((tail_len == 2) && (path[tail_ofs] == '.') && (path[tail_ofs + 1] == '.')));
|
|
path[tail_ofs] = '\0';
|
|
return true;
|
|
}
|
|
|
|
bool BLI_path_parent_dir_until_exists(char *path)
|
|
{
|
|
bool valid_path = true;
|
|
|
|
/* Loop as long as cur path is not a dir, and we can get a parent path. */
|
|
while ((BLI_access(path, R_OK) != 0) && (valid_path = BLI_path_parent_dir(path))) {
|
|
/* Pass. */
|
|
}
|
|
return (valid_path && path[0]);
|
|
}
|
|
|
|
/**
|
|
* Looks for a sequence of "#" characters in the last slash-separated component of `path`,
|
|
* returning the indexes of the first and one past the last character in the sequence in
|
|
* `r_char_start` and `r_char_end` respectively.
|
|
*
|
|
* \param r_char_start: The first `#` character.
|
|
* \param r_char_end: The last `#` character +1.
|
|
*
|
|
* \return true if a frame sequence range was found.
|
|
*/
|
|
static bool path_frame_chars_find_range(const char *path, int *r_char_start, int *r_char_end)
|
|
{
|
|
uint ch_sta, ch_end, i;
|
|
/* Insert current frame: `file###` -> `file001`. */
|
|
ch_sta = ch_end = 0;
|
|
for (i = 0; path[i] != '\0'; i++) {
|
|
if (ELEM(path[i], '\\', '/')) {
|
|
ch_end = 0; /* This is a directory name, don't use any hashes we found. */
|
|
}
|
|
else if (path[i] == '#') {
|
|
ch_sta = i;
|
|
ch_end = ch_sta + 1;
|
|
while (path[ch_end] == '#') {
|
|
ch_end++;
|
|
}
|
|
i = ch_end - 1; /* Keep searching. */
|
|
|
|
/* Don't break, there may be a slash after this that invalidates the previous #'s. */
|
|
}
|
|
}
|
|
|
|
if (ch_end) {
|
|
*r_char_start = ch_sta;
|
|
*r_char_end = ch_end;
|
|
return true;
|
|
}
|
|
|
|
*r_char_start = -1;
|
|
*r_char_end = -1;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Ensure `path` contains at least one "#" character in its last slash-separated
|
|
* component, appending one digits long if not.
|
|
*/
|
|
static void ensure_digits(char *path, int digits)
|
|
{
|
|
char *file = (char *)BLI_path_basename(path);
|
|
if (strrchr(file, '#') == nullptr) {
|
|
int len = strlen(file);
|
|
|
|
while (digits--) {
|
|
file[len++] = '#';
|
|
}
|
|
file[len] = '\0';
|
|
}
|
|
}
|
|
|
|
bool BLI_path_frame(char *path, size_t path_maxncpy, int frame, int digits)
|
|
{
|
|
BLI_string_debug_size_after_nil(path, path_maxncpy);
|
|
|
|
int ch_sta, ch_end;
|
|
|
|
if (digits) {
|
|
ensure_digits(path, digits);
|
|
}
|
|
|
|
if (path_frame_chars_find_range(path, &ch_sta, &ch_end)) {
|
|
char frame_str[FILENAME_FRAME_CHARS_MAX + 1]; /* One for null. */
|
|
const int ch_span = std::min(ch_end - ch_sta, FILENAME_FRAME_CHARS_MAX);
|
|
SNPRINTF(frame_str, "%.*d", ch_span, frame);
|
|
BLI_string_replace_range(path, path_maxncpy, ch_sta, ch_end, frame_str);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool BLI_path_frame_range(char *path, size_t path_maxncpy, int sta, int end, int digits)
|
|
{
|
|
BLI_string_debug_size_after_nil(path, path_maxncpy);
|
|
|
|
int ch_sta, ch_end;
|
|
|
|
if (digits) {
|
|
ensure_digits(path, digits);
|
|
}
|
|
|
|
if (path_frame_chars_find_range(path, &ch_sta, &ch_end)) {
|
|
char frame_str[(FILENAME_FRAME_CHARS_MAX * 2) + 1 + 1]; /* One for null, one for the '-' */
|
|
const int ch_span = std::min(ch_end - ch_sta, FILENAME_FRAME_CHARS_MAX);
|
|
SNPRINTF(frame_str, "%.*d-%.*d", ch_span, sta, ch_span, end);
|
|
BLI_string_replace_range(path, path_maxncpy, ch_sta, ch_end, frame_str);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool BLI_path_frame_get(const char *path, int *r_frame, int *r_digits_len)
|
|
{
|
|
if (*path == '\0') {
|
|
return false;
|
|
}
|
|
|
|
*r_digits_len = 0;
|
|
|
|
const char *file = BLI_path_basename(path);
|
|
const char *file_ext = BLI_path_extension_or_end(file);
|
|
const char *c = file_ext;
|
|
|
|
/* Find start of number (if there is one). */
|
|
int digits_len = 0;
|
|
while (c-- != file && isdigit(*c)) {
|
|
digits_len++;
|
|
}
|
|
c++;
|
|
|
|
if (digits_len == 0) {
|
|
return false;
|
|
}
|
|
|
|
/* No need to trim the string, `atio` ignores non-digits. */
|
|
*r_frame = atoi(c);
|
|
*r_digits_len = digits_len;
|
|
return true;
|
|
}
|
|
|
|
void BLI_path_frame_strip(char *path, char *r_ext, const size_t ext_maxncpy)
|
|
{
|
|
BLI_string_debug_size(r_ext, ext_maxncpy);
|
|
*r_ext = '\0';
|
|
if (*path == '\0') {
|
|
return;
|
|
}
|
|
|
|
char *file = (char *)BLI_path_basename(path);
|
|
char *file_ext = (char *)BLI_path_extension_or_end(file);
|
|
char *c = file_ext;
|
|
|
|
/* Find start of number (if there is one). */
|
|
int digits_len = 0;
|
|
while (c-- != file && isdigit(*c)) {
|
|
digits_len++;
|
|
}
|
|
c++;
|
|
|
|
BLI_strncpy(r_ext, file_ext, ext_maxncpy);
|
|
|
|
/* Replace the number with the suffix and terminate the string. */
|
|
while (digits_len--) {
|
|
*c++ = '#';
|
|
}
|
|
*c = '\0';
|
|
}
|
|
|
|
bool BLI_path_frame_check_chars(const char *path)
|
|
{
|
|
int ch_sta_dummy, ch_end_dummy;
|
|
return path_frame_chars_find_range(path, &ch_sta_dummy, &ch_end_dummy);
|
|
}
|
|
|
|
void BLI_path_to_display_name(char *display_name, int display_name_maxncpy, const char *name)
|
|
{
|
|
BLI_string_debug_size(display_name, display_name_maxncpy);
|
|
|
|
/* Strip leading underscores and spaces. */
|
|
int strip_offset = 0;
|
|
while (ELEM(name[strip_offset], '_', ' ')) {
|
|
strip_offset++;
|
|
}
|
|
|
|
BLI_strncpy(display_name, name + strip_offset, display_name_maxncpy);
|
|
|
|
/* Replace underscores with spaces. */
|
|
BLI_string_replace_char(display_name, '_', ' ');
|
|
|
|
BLI_path_extension_strip(display_name);
|
|
|
|
/* Test if string has any upper case characters. */
|
|
bool all_lower = true;
|
|
for (int i = 0; display_name[i]; i++) {
|
|
if (isupper(display_name[i])) {
|
|
all_lower = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (all_lower) {
|
|
/* For full lowercase string, use title case. */
|
|
bool prevspace = true;
|
|
for (int i = 0; display_name[i]; i++) {
|
|
if (prevspace) {
|
|
display_name[i] = toupper(display_name[i]);
|
|
}
|
|
|
|
prevspace = isspace(display_name[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool BLI_path_abs(char path[FILE_MAX], const char *basepath)
|
|
{
|
|
BLI_string_debug_size_after_nil(path, FILE_MAX);
|
|
/* A `basepath` starting with `//` will be made absolute multiple times. */
|
|
BLI_assert_msg(!BLI_path_is_rel(basepath), "The 'basepath' cannot start with '//'!");
|
|
|
|
const bool wasrelative = BLI_path_is_rel(path);
|
|
char tmp[FILE_MAX];
|
|
char base[FILE_MAX];
|
|
#ifdef WIN32
|
|
|
|
/* Without this, an empty string converts to: `C:\` */
|
|
if (*path == '\0') {
|
|
return wasrelative;
|
|
}
|
|
|
|
/* We are checking here if we have an absolute path that is not in the current `.blend` file
|
|
* as a lib main - we are basically checking for the case that a UNIX root `/` is passed. */
|
|
if (!wasrelative && !BLI_path_is_abs_win32(path)) {
|
|
const size_t root_dir_len = 3;
|
|
char *p = path;
|
|
BLI_windows_get_default_root_dir(tmp);
|
|
BLI_assert(strlen(tmp) == root_dir_len);
|
|
|
|
/* Step over the slashes at the beginning of the path. */
|
|
p = (char *)BLI_path_slash_skip(p);
|
|
BLI_strncpy(tmp + root_dir_len, p, sizeof(tmp) - root_dir_len);
|
|
}
|
|
else {
|
|
STRNCPY(tmp, path);
|
|
}
|
|
#else
|
|
STRNCPY(tmp, path);
|
|
|
|
/* Check for loading a MS-Windows path on a POSIX system
|
|
* in this case, there is no use in trying `C:/` since it
|
|
* will never exist on a Unix system.
|
|
*
|
|
* Add a `/` prefix and lowercase the drive-letter, remove the `:`.
|
|
* `C:\foo.JPG` -> `/c/foo.JPG` */
|
|
|
|
if (BLI_path_is_win32_drive_with_slash(tmp)) {
|
|
tmp[1] = tolower(tmp[0]); /* Replace `:` with drive-letter. */
|
|
tmp[0] = '/';
|
|
/* `\` the slash will be converted later. */
|
|
}
|
|
|
|
#endif
|
|
|
|
/* NOTE(@jesterKing): push slashes into unix mode - strings entering this part are
|
|
* potentially messed up: having both back- and forward slashes.
|
|
* Here we push into one conform direction, and at the end we
|
|
* push them into the system specific dir. This ensures uniformity
|
|
* of paths and solving some problems (and prevent potential future ones).
|
|
*
|
|
* NOTE(@elubie): For UNC paths the first characters containing the UNC prefix
|
|
* shouldn't be switched as we need to distinguish them from
|
|
* paths relative to the `.blend` file. */
|
|
BLI_string_replace_char(tmp + BLI_path_unc_prefix_len(tmp), '\\', '/');
|
|
|
|
/* Paths starting with `//` will get the blend file as their base,
|
|
* this isn't standard in any OS but is used in blender all over the place. */
|
|
if (wasrelative) {
|
|
const char *lslash;
|
|
STRNCPY(base, basepath);
|
|
|
|
/* File component is ignored, so don't bother with the trailing slash. */
|
|
BLI_path_normalize(base);
|
|
lslash = BLI_path_slash_rfind(base);
|
|
BLI_string_replace_char(base + BLI_path_unc_prefix_len(base), '\\', '/');
|
|
|
|
if (lslash) {
|
|
/* Length up to and including last `/`. */
|
|
const int baselen = int(lslash - base) + 1;
|
|
/* Use path for temp storage here, we copy back over it right away. */
|
|
BLI_strncpy(path, tmp + 2, FILE_MAX); /* Strip `//` prefix. */
|
|
|
|
memcpy(tmp, base, baselen); /* Prefix with base up to last `/`. */
|
|
BLI_strncpy(tmp + baselen, path, sizeof(tmp) - baselen); /* Append path after `//`. */
|
|
BLI_strncpy(path, tmp, FILE_MAX); /* Return as result. */
|
|
}
|
|
else {
|
|
/* Base doesn't seem to be a directory, ignore it and just strip `//` prefix on path. */
|
|
BLI_strncpy(path, tmp + 2, FILE_MAX);
|
|
}
|
|
}
|
|
else {
|
|
/* Base ignored. */
|
|
BLI_strncpy(path, tmp, FILE_MAX);
|
|
}
|
|
|
|
#ifdef WIN32
|
|
/* NOTE(@jesterking): Skip first two chars, which in case of absolute path will
|
|
* be `drive:/blabla` and in case of `relpath` `//blabla/`.
|
|
* So `relpath` `//` will be retained, rest will be nice and shiny WIN32 backward slashes. */
|
|
BLI_string_replace_char(path + 2, '/', '\\');
|
|
#endif
|
|
|
|
/* Ensure this is after correcting for path switch. */
|
|
BLI_path_normalize(path);
|
|
|
|
return wasrelative;
|
|
}
|
|
|
|
bool BLI_path_is_abs_from_cwd(const char *path)
|
|
{
|
|
bool is_abs = false;
|
|
|
|
#ifdef WIN32
|
|
if (BLI_path_is_abs_win32(path)) {
|
|
is_abs = true;
|
|
}
|
|
#else
|
|
if (path[0] == '/') {
|
|
is_abs = true;
|
|
}
|
|
#endif
|
|
return is_abs;
|
|
}
|
|
|
|
bool BLI_path_abs_from_cwd(char *path, const size_t path_maxncpy)
|
|
{
|
|
BLI_string_debug_size_after_nil(path, path_maxncpy);
|
|
|
|
if (!BLI_path_is_abs_from_cwd(path)) {
|
|
char cwd[PATH_MAX];
|
|
/* In case the full path to the blend isn't used. */
|
|
if (BLI_current_working_dir(cwd, sizeof(cwd))) {
|
|
char origpath[PATH_MAX];
|
|
STRNCPY(origpath, path);
|
|
BLI_path_join(path, path_maxncpy, cwd, origpath);
|
|
}
|
|
else {
|
|
printf("Could not get the current working directory - $PWD for an unknown reason.\n");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
/**
|
|
* Tries appending each of the semicolon-separated extensions in the `PATHEXT`
|
|
* environment variable (Windows-only) onto `program_name` in turn until such a file is found.
|
|
* Returns success/failure.
|
|
*/
|
|
bool BLI_path_program_extensions_add_win32(char *program_name, const size_t program_name_maxncpy)
|
|
{
|
|
bool retval = false;
|
|
int type;
|
|
|
|
type = BLI_exists(program_name);
|
|
if ((type == 0) || S_ISDIR(type)) {
|
|
/* Typically 3-5, ".EXE", ".BAT"... etc. */
|
|
const int ext_max = 12;
|
|
const char *ext = BLI_getenv("PATHEXT");
|
|
if (ext) {
|
|
const int program_name_len = strlen(program_name);
|
|
char *filename = static_cast<char *>(alloca(program_name_len + ext_max));
|
|
char *filename_ext;
|
|
const char *ext_next;
|
|
|
|
/* Null terminated in the loop. */
|
|
memcpy(filename, program_name, program_name_len);
|
|
filename_ext = filename + program_name_len;
|
|
|
|
do {
|
|
int ext_len;
|
|
ext_next = strchr(ext, ';');
|
|
ext_len = ext_next ? ((ext_next++) - ext) : strlen(ext);
|
|
|
|
if (LIKELY(ext_len < ext_max)) {
|
|
memcpy(filename_ext, ext, ext_len);
|
|
filename_ext[ext_len] = '\0';
|
|
|
|
type = BLI_exists(filename);
|
|
if (type && (!S_ISDIR(type))) {
|
|
retval = true;
|
|
BLI_strncpy(program_name, filename, program_name_maxncpy);
|
|
break;
|
|
}
|
|
}
|
|
} while ((ext = ext_next));
|
|
}
|
|
}
|
|
else {
|
|
retval = true;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
#endif /* WIN32 */
|
|
|
|
bool BLI_path_program_search(char *program_filepath,
|
|
const size_t program_filepath_maxncpy,
|
|
const char *program_name)
|
|
{
|
|
BLI_string_debug_size(program_filepath, program_filepath_maxncpy);
|
|
|
|
const char *path;
|
|
bool retval = false;
|
|
|
|
#ifdef _WIN32
|
|
const char separator = ';';
|
|
#else
|
|
const char separator = ':';
|
|
#endif
|
|
|
|
path = BLI_getenv("PATH");
|
|
if (path) {
|
|
char filepath_test[PATH_MAX];
|
|
const char *temp;
|
|
|
|
do {
|
|
temp = strchr(path, separator);
|
|
if (temp) {
|
|
memcpy(filepath_test, path, temp - path);
|
|
filepath_test[temp - path] = 0;
|
|
path = temp + 1;
|
|
}
|
|
else {
|
|
STRNCPY(filepath_test, path);
|
|
}
|
|
|
|
BLI_path_append(filepath_test, program_filepath_maxncpy, program_name);
|
|
if (
|
|
#ifdef _WIN32
|
|
BLI_path_program_extensions_add_win32(filepath_test, sizeof(filepath_test))
|
|
#else
|
|
BLI_exists(filepath_test)
|
|
#endif
|
|
)
|
|
{
|
|
BLI_strncpy(program_filepath, filepath_test, program_filepath_maxncpy);
|
|
retval = true;
|
|
break;
|
|
}
|
|
} while (temp);
|
|
}
|
|
|
|
if (retval == false) {
|
|
*program_filepath = '\0';
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
void BLI_setenv(const char *env, const char *val)
|
|
{
|
|
#if (defined(_WIN32) || defined(_WIN64))
|
|
/* MS-Windows. */
|
|
uputenv(env, val);
|
|
#else
|
|
/* Linux/macOS/BSD */
|
|
if (val) {
|
|
setenv(env, val, 1);
|
|
}
|
|
else {
|
|
unsetenv(env);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void BLI_setenv_if_new(const char *env, const char *val)
|
|
{
|
|
if (BLI_getenv(env) == nullptr) {
|
|
BLI_setenv(env, val);
|
|
}
|
|
}
|
|
|
|
const char *BLI_getenv(const char *env)
|
|
{
|
|
#ifdef _MSC_VER
|
|
const char *result = nullptr;
|
|
/* 32767 is the maximum size of the environment variable on windows,
|
|
* reserve one more character for the zero terminator. */
|
|
static wchar_t buffer[32768];
|
|
wchar_t *env_16 = alloc_utf16_from_8(env, 0);
|
|
if (env_16) {
|
|
if (GetEnvironmentVariableW(env_16, buffer, ARRAY_SIZE(buffer))) {
|
|
char *res_utf8 = alloc_utf_8_from_16(buffer, 0);
|
|
/* Make sure the result is valid, and will fit into our temporary storage buffer. */
|
|
if (res_utf8) {
|
|
if (strlen(res_utf8) + 1 < sizeof(buffer)) {
|
|
/* We are re-using the utf16 buffer here, since allocating a second static buffer to
|
|
* contain the UTF8 version to return would be wasteful. */
|
|
memcpy(buffer, res_utf8, strlen(res_utf8) + 1);
|
|
result = (const char *)buffer;
|
|
}
|
|
free(res_utf8);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
#else
|
|
return getenv(env);
|
|
#endif
|
|
}
|
|
|
|
static bool path_extension_check_ex(const char *path,
|
|
const size_t path_len,
|
|
const char *ext,
|
|
const size_t ext_len)
|
|
{
|
|
BLI_assert(strlen(path) == path_len);
|
|
BLI_assert(strlen(ext) == ext_len);
|
|
|
|
return (((path_len == 0 || ext_len == 0 || ext_len >= path_len) == 0) &&
|
|
(BLI_strcasecmp(ext, path + path_len - ext_len) == 0));
|
|
}
|
|
|
|
bool BLI_path_extension_check(const char *path, const char *ext)
|
|
{
|
|
return path_extension_check_ex(path, strlen(path), ext, strlen(ext));
|
|
}
|
|
|
|
bool BLI_path_extension_check_n(const char *path, ...)
|
|
{
|
|
const size_t path_len = strlen(path);
|
|
|
|
va_list args;
|
|
const char *ext;
|
|
bool ret = false;
|
|
|
|
va_start(args, path);
|
|
|
|
while ((ext = (const char *)va_arg(args, void *))) {
|
|
if (path_extension_check_ex(path, path_len, ext, strlen(ext))) {
|
|
ret = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
va_end(args);
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool BLI_path_extension_check_array(const char *path, const char **ext_array)
|
|
{
|
|
const size_t path_len = strlen(path);
|
|
int i = 0;
|
|
|
|
while (ext_array[i]) {
|
|
if (path_extension_check_ex(path, path_len, ext_array[i], strlen(ext_array[i]))) {
|
|
return true;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool BLI_path_extension_check_glob(const char *path, const char *ext_fnmatch)
|
|
{
|
|
const char *ext_step = ext_fnmatch;
|
|
char pattern[16];
|
|
|
|
while (ext_step[0]) {
|
|
const char *ext_next;
|
|
size_t len_ext;
|
|
|
|
if ((ext_next = strchr(ext_step, ';'))) {
|
|
len_ext = ext_next - ext_step + 1;
|
|
BLI_strncpy(pattern, ext_step, (len_ext > sizeof(pattern)) ? sizeof(pattern) : len_ext);
|
|
}
|
|
else {
|
|
len_ext = STRNCPY_RLEN(pattern, ext_step);
|
|
}
|
|
|
|
if (fnmatch(pattern, path, FNM_CASEFOLD) == 0) {
|
|
return true;
|
|
}
|
|
ext_step += len_ext;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool BLI_path_extension_glob_validate(char *ext_fnmatch)
|
|
{
|
|
bool only_wildcards = false;
|
|
|
|
for (size_t i = strlen(ext_fnmatch); i-- > 0;) {
|
|
if (ext_fnmatch[i] == ';') {
|
|
/* Group separator, we truncate here if we only had wildcards so far.
|
|
* Otherwise, all is sound and fine. */
|
|
if (only_wildcards) {
|
|
ext_fnmatch[i] = '\0';
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
if (!ELEM(ext_fnmatch[i], '?', '*')) {
|
|
/* Non-wildcard char, we can break here and consider the pattern valid. */
|
|
return false;
|
|
}
|
|
/* So far, only wildcards in last group of the pattern. */
|
|
only_wildcards = true;
|
|
}
|
|
/* Only one group in the pattern, so even if its only made of wildcard(s),
|
|
* it is assumed valid. */
|
|
return false;
|
|
}
|
|
|
|
bool BLI_path_extension_replace(char *path, size_t path_maxncpy, const char *ext)
|
|
{
|
|
BLI_string_debug_size_after_nil(path, path_maxncpy);
|
|
|
|
char *path_ext = (char *)BLI_path_extension_or_end(path);
|
|
const size_t ext_len = strlen(ext);
|
|
if ((path_ext - path) + ext_len >= path_maxncpy) {
|
|
return false;
|
|
}
|
|
|
|
memcpy(path_ext, ext, ext_len + 1);
|
|
return true;
|
|
}
|
|
|
|
bool BLI_path_extension_strip(char *path)
|
|
{
|
|
char *path_ext = (char *)BLI_path_extension(path);
|
|
if (path_ext == nullptr) {
|
|
return false;
|
|
}
|
|
*path_ext = '\0';
|
|
return true;
|
|
}
|
|
|
|
bool BLI_path_extension_ensure(char *path, size_t path_maxncpy, const char *ext)
|
|
{
|
|
BLI_string_debug_size_after_nil(path, path_maxncpy);
|
|
|
|
/* First check the extension is already there.
|
|
* If `path_ext` is the end of the string this is simply checking if `ext` is also empty. */
|
|
const char *path_ext = BLI_path_extension_or_end(path);
|
|
if (STREQ(path_ext, ext)) {
|
|
return true;
|
|
}
|
|
|
|
const size_t path_len = strlen(path);
|
|
const size_t ext_len = strlen(ext);
|
|
int64_t a;
|
|
|
|
for (a = path_len - 1; a >= 0; a--) {
|
|
if (path[a] == '.') {
|
|
path[a] = '\0';
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
a++;
|
|
|
|
if (a + ext_len >= path_maxncpy) {
|
|
return false;
|
|
}
|
|
|
|
memcpy(path + a, ext, ext_len + 1);
|
|
return true;
|
|
}
|
|
|
|
bool BLI_path_filename_ensure(char *filepath, size_t filepath_maxncpy, const char *filename)
|
|
{
|
|
BLI_string_debug_size_after_nil(filepath, filepath_maxncpy);
|
|
char *c = (char *)BLI_path_basename(filepath);
|
|
const size_t filename_size = strlen(filename) + 1;
|
|
if (filename_size <= filepath_maxncpy - (c - filepath)) {
|
|
memcpy(c, filename, filename_size);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void BLI_path_split_dir_file(const char *filepath,
|
|
char *dir,
|
|
const size_t dir_maxncpy,
|
|
char *file,
|
|
const size_t file_maxncpy)
|
|
{
|
|
BLI_string_debug_size(dir, dir_maxncpy);
|
|
BLI_string_debug_size(file, file_maxncpy);
|
|
|
|
const char *basename = BLI_path_basename(filepath);
|
|
if (basename != filepath) {
|
|
const size_t dir_size = (basename - filepath) + 1;
|
|
BLI_strncpy(dir, filepath, std::min(dir_maxncpy, dir_size));
|
|
}
|
|
else {
|
|
dir[0] = '\0';
|
|
}
|
|
BLI_strncpy(file, basename, file_maxncpy);
|
|
}
|
|
|
|
void BLI_path_split_dir_part(const char *filepath, char *dir, const size_t dir_maxncpy)
|
|
{
|
|
BLI_string_debug_size(dir, dir_maxncpy);
|
|
const char *basename = BLI_path_basename(filepath);
|
|
if (basename != filepath) {
|
|
const size_t dir_size = (basename - filepath) + 1;
|
|
BLI_strncpy(dir, filepath, std::min(dir_maxncpy, dir_size));
|
|
}
|
|
else {
|
|
dir[0] = '\0';
|
|
}
|
|
}
|
|
|
|
void BLI_path_split_file_part(const char *filepath, char *file, const size_t file_maxncpy)
|
|
{
|
|
BLI_string_debug_size(file, file_maxncpy);
|
|
const char *basename = BLI_path_basename(filepath);
|
|
BLI_strncpy(file, basename, file_maxncpy);
|
|
}
|
|
|
|
const char *BLI_path_extension_or_end(const char *filepath)
|
|
{
|
|
/* NOTE(@ideasman42): Skip the extension when there are no preceding non-extension characters in
|
|
* the file name. This ignores extensions at the beginning of a string or directly after a slash.
|
|
* Only using trailing extension characters has the advantage that stripping the extension
|
|
* never leads to a blank string (which can't be used as a file path).
|
|
* Matches Python's `os.path.splitext`. */
|
|
const char *ext = nullptr;
|
|
bool has_non_ext = false;
|
|
const char *c = filepath;
|
|
for (; *c; c++) {
|
|
switch (*c) {
|
|
case '.': {
|
|
if (has_non_ext) {
|
|
ext = c;
|
|
}
|
|
break;
|
|
}
|
|
case SEP:
|
|
case ALTSEP: {
|
|
ext = nullptr;
|
|
has_non_ext = false;
|
|
break;
|
|
}
|
|
default: {
|
|
has_non_ext = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (ext) {
|
|
return ext;
|
|
}
|
|
BLI_assert(*c == '\0');
|
|
return c;
|
|
}
|
|
|
|
const char *BLI_path_extension(const char *filepath)
|
|
{
|
|
const char *ext = BLI_path_extension_or_end(filepath);
|
|
return *ext ? ext : nullptr;
|
|
}
|
|
|
|
size_t BLI_path_append(char *__restrict dst, const size_t dst_maxncpy, const char *__restrict file)
|
|
{
|
|
/* Slash ensure uses #BLI_string_debug_size */
|
|
int dst_len = BLI_path_slash_ensure(dst, dst_maxncpy);
|
|
if (dst_len + 1 < dst_maxncpy) {
|
|
dst_len += BLI_strncpy_rlen(dst + dst_len, file, dst_maxncpy - dst_len);
|
|
}
|
|
return dst_len;
|
|
}
|
|
|
|
size_t BLI_path_append_dir(char *__restrict dst,
|
|
const size_t dst_maxncpy,
|
|
const char *__restrict dir)
|
|
{
|
|
size_t dst_len = BLI_path_append(dst, dst_maxncpy, dir);
|
|
return BLI_path_slash_ensure_ex(dst, dst_maxncpy, dst_len);
|
|
}
|
|
|
|
size_t BLI_path_join_array(char *__restrict dst,
|
|
const size_t dst_maxncpy,
|
|
const char *path_array[],
|
|
const int path_array_num)
|
|
{
|
|
BLI_assert(path_array_num > 0);
|
|
BLI_string_debug_size(dst, dst_maxncpy);
|
|
|
|
if (UNLIKELY(dst_maxncpy == 0)) {
|
|
return 0;
|
|
}
|
|
const char *path = path_array[0];
|
|
|
|
const size_t dst_last = dst_maxncpy - 1;
|
|
size_t ofs = BLI_strncpy_rlen(dst, path, dst_maxncpy);
|
|
|
|
if (ofs == dst_last) {
|
|
return ofs;
|
|
}
|
|
|
|
#ifdef WIN32
|
|
/* Special case `//` for relative paths, don't use separator #SEP
|
|
* as this has a special meaning on both WIN32 & UNIX.
|
|
* Without this check joining `"//", "path"`. results in `"//\path"`. */
|
|
if (ofs != 0) {
|
|
size_t i;
|
|
for (i = 0; i < ofs; i++) {
|
|
if (dst[i] != '/') {
|
|
break;
|
|
}
|
|
}
|
|
if (i == ofs) {
|
|
/* All slashes, keep them as-is, and join the remaining path array. */
|
|
return path_array_num > 1 ?
|
|
BLI_path_join_array(
|
|
dst + ofs, dst_maxncpy - ofs, &path_array[1], path_array_num - 1) :
|
|
ofs;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Remove trailing slashes, unless there are *only* trailing slashes
|
|
* (allow `//` or `//some_path` as the first argument). */
|
|
bool has_trailing_slash = false;
|
|
if (ofs != 0) {
|
|
size_t len = ofs;
|
|
while ((len != 0) && BLI_path_slash_is_native_compat(path[len - 1])) {
|
|
len -= 1;
|
|
}
|
|
|
|
if (len != 0) {
|
|
ofs = len;
|
|
}
|
|
has_trailing_slash = (path[len] != '\0');
|
|
}
|
|
|
|
for (int path_index = 1; path_index < path_array_num; path_index++) {
|
|
path = path_array[path_index];
|
|
has_trailing_slash = false;
|
|
const char *path_init = path;
|
|
while (BLI_path_slash_is_native_compat(path[0])) {
|
|
path++;
|
|
}
|
|
size_t len = strlen(path);
|
|
if (len != 0) {
|
|
while ((len != 0) && BLI_path_slash_is_native_compat(path[len - 1])) {
|
|
len -= 1;
|
|
}
|
|
|
|
if (len != 0) {
|
|
/* The very first path may have a slash at the end. */
|
|
if (ofs && !BLI_path_slash_is_native_compat(dst[ofs - 1])) {
|
|
dst[ofs++] = SEP;
|
|
if (ofs == dst_last) {
|
|
break;
|
|
}
|
|
}
|
|
has_trailing_slash = (path[len] != '\0');
|
|
if (ofs + len >= dst_last) {
|
|
len = dst_last - ofs;
|
|
}
|
|
memcpy(&dst[ofs], path, len);
|
|
ofs += len;
|
|
if (ofs == dst_last) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
has_trailing_slash = (path_init != path);
|
|
}
|
|
}
|
|
|
|
if (has_trailing_slash) {
|
|
if ((ofs != dst_last) && (ofs != 0) && !BLI_path_slash_is_native_compat(dst[ofs - 1])) {
|
|
dst[ofs++] = SEP;
|
|
}
|
|
}
|
|
|
|
BLI_assert(ofs <= dst_last);
|
|
dst[ofs] = '\0';
|
|
|
|
return ofs;
|
|
}
|
|
|
|
const char *BLI_path_basename(const char *path)
|
|
{
|
|
const char *const filename = BLI_path_slash_rfind(path);
|
|
return filename ? filename + 1 : path;
|
|
}
|
|
|
|
static bool path_name_at_index_forward(const char *__restrict path,
|
|
const int index,
|
|
int *__restrict r_offset,
|
|
int *__restrict r_len)
|
|
{
|
|
BLI_assert(index >= 0);
|
|
int index_step = 0;
|
|
int prev = -1;
|
|
int i = 0;
|
|
while (true) {
|
|
const char c = path[i];
|
|
if ((c == '\0') || BLI_path_slash_is_native_compat(c)) {
|
|
if (prev + 1 != i) {
|
|
prev += 1;
|
|
/* Skip `/./` (behave as if they don't exist). */
|
|
if (!((i - prev == 1) && (prev != 0) && (path[prev] == '.'))) {
|
|
if (index_step == index) {
|
|
*r_offset = prev;
|
|
*r_len = i - prev;
|
|
return true;
|
|
}
|
|
index_step += 1;
|
|
}
|
|
}
|
|
if (c == '\0') {
|
|
break;
|
|
}
|
|
prev = i;
|
|
}
|
|
i += 1;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool path_name_at_index_backward(const char *__restrict path,
|
|
const int index,
|
|
int *__restrict r_offset,
|
|
int *__restrict r_len)
|
|
{
|
|
/* Negative number, reverse where -1 is the last element. */
|
|
BLI_assert(index < 0);
|
|
int index_step = -1;
|
|
int prev = strlen(path);
|
|
int i = prev - 1;
|
|
while (true) {
|
|
const char c = i >= 0 ? path[i] : '\0';
|
|
if ((c == '\0') || BLI_path_slash_is_native_compat(c)) {
|
|
if (prev - 1 != i) {
|
|
i += 1;
|
|
/* Skip `/./` (behave as if they don't exist). */
|
|
if (!((prev - i == 1) && (i != 0) && (path[i] == '.'))) {
|
|
if (index_step == index) {
|
|
*r_offset = i;
|
|
*r_len = prev - i;
|
|
return true;
|
|
}
|
|
index_step -= 1;
|
|
}
|
|
}
|
|
if (c == '\0') {
|
|
break;
|
|
}
|
|
prev = i;
|
|
}
|
|
i -= 1;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool BLI_path_name_at_index(const char *__restrict path,
|
|
const int index,
|
|
int *__restrict r_offset,
|
|
int *__restrict r_len)
|
|
{
|
|
return (index >= 0) ? path_name_at_index_forward(path, index, r_offset, r_len) :
|
|
path_name_at_index_backward(path, index, r_offset, r_len);
|
|
}
|
|
|
|
bool BLI_path_contains(const char *container_path, const char *containee_path)
|
|
{
|
|
char container_native[PATH_MAX];
|
|
char containee_native[PATH_MAX];
|
|
|
|
/* Keep space for a trailing slash. If the path is truncated by this, the containee path is
|
|
* longer than #PATH_MAX and the result is ill-defined. */
|
|
BLI_strncpy(container_native, container_path, PATH_MAX - 1);
|
|
STRNCPY(containee_native, containee_path);
|
|
|
|
BLI_path_slash_native(container_native);
|
|
BLI_path_slash_native(containee_native);
|
|
|
|
BLI_path_normalize(container_native);
|
|
BLI_path_normalize(containee_native);
|
|
|
|
#ifdef WIN32
|
|
BLI_str_tolower_ascii(container_native, PATH_MAX);
|
|
BLI_str_tolower_ascii(containee_native, PATH_MAX);
|
|
#endif
|
|
|
|
if (STREQ(container_native, containee_native)) {
|
|
/* The paths are equal, they contain each other. */
|
|
return true;
|
|
}
|
|
|
|
/* Add a trailing slash to prevent same-prefix directories from matching.
|
|
* e.g. "/some/path" doesn't contain "/some/path_lib". */
|
|
BLI_path_slash_ensure(container_native, sizeof(container_native));
|
|
|
|
return BLI_str_startswith(containee_native, container_native);
|
|
}
|
|
|
|
const char *BLI_path_slash_find(const char *path)
|
|
{
|
|
const char *const ffslash = strchr(path, '/');
|
|
const char *const fbslash = strchr(path, '\\');
|
|
|
|
if (!ffslash) {
|
|
return fbslash;
|
|
}
|
|
if (!fbslash) {
|
|
return ffslash;
|
|
}
|
|
|
|
return (ffslash < fbslash) ? ffslash : fbslash;
|
|
}
|
|
|
|
const char *BLI_path_slash_rfind(const char *path)
|
|
{
|
|
const char *const lfslash = strrchr(path, '/');
|
|
const char *const lbslash = strrchr(path, '\\');
|
|
|
|
if (!lfslash) {
|
|
return lbslash;
|
|
}
|
|
if (!lbslash) {
|
|
return lfslash;
|
|
}
|
|
|
|
return (lfslash > lbslash) ? lfslash : lbslash;
|
|
}
|
|
|
|
int BLI_path_slash_ensure_ex(char *path, size_t path_maxncpy, size_t path_len)
|
|
{
|
|
BLI_string_debug_size_after_nil(path, path_maxncpy);
|
|
BLI_assert(strlen(path) == path_len);
|
|
BLI_assert(path_len < path_maxncpy);
|
|
if (path_len == 0 || !BLI_path_slash_is_native_compat(path[path_len - 1])) {
|
|
/* Avoid unlikely buffer overflow. */
|
|
if (path_len + 1 < path_maxncpy) {
|
|
path[path_len++] = SEP;
|
|
path[path_len] = '\0';
|
|
}
|
|
}
|
|
return path_len;
|
|
}
|
|
|
|
int BLI_path_slash_ensure(char *path, size_t path_maxncpy)
|
|
{
|
|
return BLI_path_slash_ensure_ex(path, path_maxncpy, strlen(path));
|
|
}
|
|
|
|
void BLI_path_slash_rstrip(char *path)
|
|
{
|
|
int len = strlen(path);
|
|
while (len) {
|
|
if (BLI_path_slash_is_native_compat(path[len - 1])) {
|
|
path[len - 1] = '\0';
|
|
len--;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const char *BLI_path_slash_skip(const char *path)
|
|
{
|
|
/* This accounts for a null byte too. */
|
|
while (BLI_path_slash_is_native_compat(*path)) {
|
|
path++;
|
|
}
|
|
return path;
|
|
}
|
|
|
|
void BLI_path_slash_native(char *path)
|
|
{
|
|
#ifdef WIN32
|
|
if (path && BLI_strnlen(path, 3) > 2) {
|
|
BLI_string_replace_char(path + 2, ALTSEP, SEP);
|
|
}
|
|
#else
|
|
BLI_string_replace_char(path + BLI_path_unc_prefix_len(path), ALTSEP, SEP);
|
|
#endif
|
|
}
|
|
|
|
int BLI_path_cmp_normalized(const char *p1, const char *p2)
|
|
{
|
|
BLI_assert_msg(!BLI_path_is_rel(p1) && !BLI_path_is_rel(p2), "Paths arguments must be absolute");
|
|
|
|
/* Normalize the paths so we can compare them. */
|
|
char norm_p1_buf[256];
|
|
char norm_p2_buf[256];
|
|
|
|
const size_t p1_size = strlen(p1) + 1;
|
|
const size_t p2_size = strlen(p2) + 1;
|
|
|
|
char *norm_p1 = (p1_size <= sizeof(norm_p1_buf)) ? norm_p1_buf :
|
|
MEM_calloc_arrayN<char>(p1_size, __func__);
|
|
char *norm_p2 = (p2_size <= sizeof(norm_p2_buf)) ? norm_p2_buf :
|
|
MEM_calloc_arrayN<char>(p2_size, __func__);
|
|
|
|
memcpy(norm_p1, p1, p1_size);
|
|
memcpy(norm_p2, p2, p2_size);
|
|
|
|
BLI_path_slash_native(norm_p1);
|
|
BLI_path_slash_native(norm_p2);
|
|
|
|
/* One of the paths ending with a slash does not make them different, strip both. */
|
|
BLI_path_slash_rstrip(norm_p1);
|
|
BLI_path_slash_rstrip(norm_p2);
|
|
|
|
BLI_path_normalize(norm_p1);
|
|
BLI_path_normalize(norm_p2);
|
|
|
|
const int result = BLI_path_cmp(norm_p1, norm_p2);
|
|
|
|
if (norm_p1 != norm_p1_buf) {
|
|
MEM_freeN(norm_p1);
|
|
}
|
|
if (norm_p2 != norm_p2_buf) {
|
|
MEM_freeN(norm_p2);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool BLI_path_has_hidden_component(const char *path)
|
|
{
|
|
bool component_start = true;
|
|
char cur_char = path[0];
|
|
char prev_char = '\0';
|
|
|
|
while (cur_char != '\0') {
|
|
char next_char = path[1];
|
|
/* If we're at a start of path component, current is '.'
|
|
* and next one is not '.', end or separator: hidden. */
|
|
if (component_start && cur_char == '.') {
|
|
if (!ELEM(path[1], '.', '\0', '/', '\\')) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
component_start = ELEM(cur_char, '/', '\\');
|
|
/* Separator, and previous was tilde: hidden. */
|
|
if (component_start && prev_char == '~') {
|
|
return true;
|
|
}
|
|
|
|
path++;
|
|
prev_char = cur_char;
|
|
cur_char = next_char;
|
|
}
|
|
|
|
/* Was a tilde right at end of path: hidden. */
|
|
if (prev_char == '~') {
|
|
return true;
|
|
}
|
|
|
|
/* Nothing was hidden. */
|
|
return false;
|
|
}
|