The Issue ======= For a long time now MinGW has been unsupported and unmaintained and at this point, it looks like something that we should just leave behind and move on. Why Remove ========== One of the big motivations for MinGW back in the day is that it was free compared to MSVC which was licensed based. However, now that this is no longer true we have basically stopped updating the need CMake files. Along with the CMake files, there are several patches to the extern libs needed to make this work. For example, see: https://developer.blender.org/diffusion/B/browse/master/extern/carve/patches/mingw_w64.patch If we wanted to keep MinGW then we would need to make more custom patches to the external libs and this is not something our platform maintainers are willing to do. For example, here is the patches needed to build python: https://github.com/Alexpux/MINGW-packages/tree/master/mingw-w64-python3 Fixes T51301 Differential Revision: https://developer.blender.org/D2648
1948 lines
48 KiB
C
1948 lines
48 KiB
C
/*
|
|
* ***** BEGIN GPL LICENSE BLOCK *****
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
|
|
* All rights reserved.
|
|
*
|
|
* The Original Code is: all of this file.
|
|
*
|
|
* Contributor(s): none yet.
|
|
*
|
|
* ***** END GPL LICENSE BLOCK *****
|
|
*
|
|
* various string, file, list operations.
|
|
*/
|
|
|
|
/** \file blender/blenlib/intern/path_util.c
|
|
* \ingroup bli
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
|
|
#include "DNA_listBase.h"
|
|
|
|
#include "BLI_utildefines.h"
|
|
#include "BLI_fileops.h"
|
|
#include "BLI_path_util.h"
|
|
#include "BLI_string.h"
|
|
#include "BLI_string_utf8.h"
|
|
#include "BLI_fnmatch.h"
|
|
|
|
#ifdef WIN32
|
|
# include "utf_winfunc.h"
|
|
# include "utfconv.h"
|
|
# include <io.h>
|
|
# ifdef _WIN32_IE
|
|
# undef _WIN32_IE
|
|
# endif
|
|
# define _WIN32_IE 0x0501
|
|
# include <windows.h>
|
|
# include <shlobj.h>
|
|
# include "BLI_winstuff.h"
|
|
# include "BLI_alloca.h"
|
|
#else
|
|
# include "unistd.h"
|
|
#endif /* WIN32 */
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
/* Declarations */
|
|
|
|
#ifdef WIN32
|
|
|
|
/* return true if the path is absolute ie starts with a drive specifier (eg A:\) or is a UNC path */
|
|
static bool BLI_path_is_abs(const char *name);
|
|
|
|
#endif /* WIN32 */
|
|
|
|
/* implementation */
|
|
|
|
/**
|
|
* Looks for a sequence of decimal digits in string, preceding any filename extension,
|
|
* returning the integer value if found, or 0 if not.
|
|
*
|
|
* \param string String to scan.
|
|
* \param head Optional area to return copy of part of string prior to digits, or before dot if no digits.
|
|
* \param tail Optional area to return copy of part of string following digits, or from dot if no digits.
|
|
* \param numlen Optional to return number of digits found.
|
|
*/
|
|
int BLI_stringdec(const char *string, char *head, char *tail, unsigned short *numlen)
|
|
{
|
|
unsigned int nums = 0, nume = 0;
|
|
int i;
|
|
bool found_digit = false;
|
|
const char * const lslash = BLI_last_slash(string);
|
|
const unsigned int string_len = strlen(string);
|
|
const unsigned int lslash_len = lslash != NULL ? (int)(lslash - string) : 0;
|
|
unsigned int name_end = string_len;
|
|
|
|
while (name_end > lslash_len && string[--name_end] != '.') {} /* name ends at dot if present */
|
|
if (name_end == lslash_len && string[name_end] != '.') name_end = string_len;
|
|
|
|
for (i = name_end - 1; i >= (int)lslash_len; i--) {
|
|
if (isdigit(string[i])) {
|
|
if (found_digit) {
|
|
nums = i;
|
|
}
|
|
else {
|
|
nume = i;
|
|
nums = i;
|
|
found_digit = true;
|
|
}
|
|
}
|
|
else {
|
|
if (found_digit) break;
|
|
}
|
|
}
|
|
|
|
if (found_digit) {
|
|
if (tail) strcpy(tail, &string[nume + 1]);
|
|
if (head) {
|
|
strcpy(head, string);
|
|
head[nums] = 0;
|
|
}
|
|
if (numlen) *numlen = nume - nums + 1;
|
|
return ((int)atoi(&(string[nums])));
|
|
}
|
|
else {
|
|
if (tail) strcpy(tail, string + name_end);
|
|
if (head) {
|
|
/* name_end points to last character of head,
|
|
* make it +1 so null-terminator is nicely placed
|
|
*/
|
|
BLI_strncpy(head, string, name_end + 1);
|
|
}
|
|
if (numlen) *numlen = 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns in area pointed to by string a string of the form "<head><pic><tail>", where pic
|
|
* is formatted as numlen digits with leading zeroes.
|
|
*/
|
|
void BLI_stringenc(char *string, const char *head, const char *tail, unsigned short numlen, int pic)
|
|
{
|
|
sprintf(string, "%s%.*d%s", head, numlen, MAX2(0, pic), tail);
|
|
}
|
|
|
|
static int BLI_path_unc_prefix_len(const char *path); /* defined below in same file */
|
|
|
|
/* ******************** string encoding ***************** */
|
|
|
|
/**
|
|
* Remove redundant characters from \a path and optionally make absolute.
|
|
*
|
|
* \param relabase: The path this is relative to, or ignored when NULL.
|
|
* \param path: Can be any input, and this function converts it to a regular full path.
|
|
* Also removes garbage from directory paths, like `/../` or double slashes etc.
|
|
*
|
|
* \note \a path isn't protected for max string names...
|
|
*/
|
|
void BLI_cleanup_path(const char *relabase, char *path)
|
|
{
|
|
ptrdiff_t a;
|
|
char *start, *eind;
|
|
if (relabase) {
|
|
BLI_path_abs(path, relabase);
|
|
}
|
|
else {
|
|
if (path[0] == '/' && path[1] == '/') {
|
|
if (path[2] == '\0') {
|
|
return; /* path is "//" - cant clean it */
|
|
}
|
|
path = path + 2; /* leave the initial "//" untouched */
|
|
}
|
|
}
|
|
|
|
/* Note
|
|
* memmove(start, eind, strlen(eind) + 1);
|
|
* is the same as
|
|
* strcpy(start, eind);
|
|
* except strcpy should not be used because there is overlap,
|
|
* so use memmove's slightly more obscure syntax - Campbell
|
|
*/
|
|
|
|
#ifdef WIN32
|
|
while ( (start = strstr(path, "\\..\\")) ) {
|
|
eind = start + strlen("\\..\\") - 1;
|
|
a = start - path - 1;
|
|
while (a > 0) {
|
|
if (path[a] == '\\') break;
|
|
a--;
|
|
}
|
|
if (a < 0) {
|
|
break;
|
|
}
|
|
else {
|
|
memmove(path + a, eind, strlen(eind) + 1);
|
|
}
|
|
}
|
|
|
|
while ( (start = strstr(path, "\\.\\")) ) {
|
|
eind = start + strlen("\\.\\") - 1;
|
|
memmove(start, eind, strlen(eind) + 1);
|
|
}
|
|
|
|
/* remove two consecutive backslashes, but skip the UNC prefix,
|
|
* which needs to be preserved */
|
|
while ( (start = strstr(path + BLI_path_unc_prefix_len(path), "\\\\")) ) {
|
|
eind = start + strlen("\\\\") - 1;
|
|
memmove(start, eind, strlen(eind) + 1);
|
|
}
|
|
#else
|
|
while ( (start = strstr(path, "/../")) ) {
|
|
a = start - path - 1;
|
|
if (a > 0) {
|
|
/* <prefix>/<parent>/../<postfix> => <prefix>/<postfix> */
|
|
eind = start + (4 - 1) /* strlen("/../") - 1 */; /* strip "/.." and keep last "/" */
|
|
while (a > 0 && path[a] != '/') { /* find start of <parent> */
|
|
a--;
|
|
}
|
|
memmove(path + a, eind, strlen(eind) + 1);
|
|
}
|
|
else {
|
|
/* support for odd paths: eg /../home/me --> /home/me
|
|
* this is a valid path in blender but we cant handle this the usual way below
|
|
* simply strip this prefix then evaluate the path as usual.
|
|
* pythons os.path.normpath() does this */
|
|
|
|
/* Note: previous version of following call used an offset of 3 instead of 4,
|
|
* which meant that the "/../home/me" example actually became "home/me".
|
|
* Using offset of 3 gives behaviour consistent with the abovementioned
|
|
* Python routine. */
|
|
memmove(path, path + 3, strlen(path + 3) + 1);
|
|
}
|
|
}
|
|
|
|
while ( (start = strstr(path, "/./")) ) {
|
|
eind = start + (3 - 1) /* strlen("/./") - 1 */;
|
|
memmove(start, eind, strlen(eind) + 1);
|
|
}
|
|
|
|
while ( (start = strstr(path, "//")) ) {
|
|
eind = start + (2 - 1) /* strlen("//") - 1 */;
|
|
memmove(start, eind, strlen(eind) + 1);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Cleanup filepath ensuring a trailing slash.
|
|
*/
|
|
void BLI_cleanup_dir(const char *relabase, char *dir)
|
|
{
|
|
BLI_cleanup_path(relabase, dir);
|
|
BLI_add_slash(dir);
|
|
|
|
}
|
|
|
|
/**
|
|
* Cleanup filepath ensuring no trailing slash.
|
|
*/
|
|
void BLI_cleanup_file(const char *relabase, char *path)
|
|
{
|
|
BLI_cleanup_path(relabase, path);
|
|
BLI_del_slash(path);
|
|
}
|
|
|
|
|
|
/**
|
|
* Make given name safe to be used in paths.
|
|
*
|
|
* \return true if \a fname was changed, false otherwise.
|
|
*
|
|
* For now, simply replaces reserved chars (as listed in
|
|
* https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words )
|
|
* by underscores ('_').
|
|
*
|
|
* \note Space case ' ' is a bit of an edge case here - in theory it is allowed, but again can be an issue
|
|
* in some cases, so we simply replace it by an underscore too (good practice anyway).
|
|
* REMOVED based on popular demand (see T45900).
|
|
* Percent '%' char is a bit same case - not recommended to use it, but supported by all decent FS/OS around...
|
|
*
|
|
* \note On Windows, it also ensures there is no '.' (dot char) at the end of the file, this can lead to issues...
|
|
*
|
|
* \note On Windows, it also checks for forbidden names
|
|
* (see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx ).
|
|
*/
|
|
bool BLI_filename_make_safe(char *fname)
|
|
{
|
|
const char *invalid = "\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"
|
|
"/\\?*:|\"<>";
|
|
char *fn;
|
|
bool changed = false;
|
|
|
|
if (*fname == '\0') {
|
|
return changed;
|
|
}
|
|
|
|
for (fn = fname; *fn && (fn = strpbrk(fn, invalid)); fn++) {
|
|
*fn = '_';
|
|
changed = true;
|
|
}
|
|
|
|
/* Forbid only dots. */
|
|
for (fn = fname; *fn == '.'; fn++);
|
|
if (*fn == '\0') {
|
|
*fname = '_';
|
|
changed = true;
|
|
}
|
|
|
|
#ifdef WIN32
|
|
{
|
|
const size_t len = strlen(fname);
|
|
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",
|
|
NULL
|
|
};
|
|
char *lower_fname = BLI_strdup(fname);
|
|
const char **iname;
|
|
|
|
/* Forbid trailing dot (trailing space has already been replaced above). */
|
|
if (fname[len - 1] == '.') {
|
|
fname[len - 1] = '_';
|
|
changed = true;
|
|
}
|
|
|
|
/* Check for forbidden names - not we have to check all combination of upper and lower cases, hence the usage
|
|
* of lower_fname (more efficient than using BLI_strcasestr repeatedly). */
|
|
BLI_str_tolower_ascii(lower_fname, len);
|
|
for (iname = invalid_names; *iname; iname++) {
|
|
if (strstr(lower_fname, *iname) == lower_fname) {
|
|
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) || (lower_fname[iname_len] == '.')) {
|
|
*fname = '_';
|
|
changed = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
MEM_freeN(lower_fname);
|
|
}
|
|
#endif
|
|
|
|
return changed;
|
|
}
|
|
|
|
/**
|
|
* Make given path OS-safe.
|
|
*
|
|
* \return true if \a path was changed, false otherwise.
|
|
*/
|
|
bool BLI_path_make_safe(char *path)
|
|
{
|
|
/* Simply apply BLI_filename_make_safe() over each component of the path.
|
|
* Luckily enough, same 'safe' rules applies to filenames and dirnames. */
|
|
char *curr_slash, *curr_path = path;
|
|
bool changed = false;
|
|
bool skip_first = false;
|
|
|
|
#ifdef WIN32
|
|
if (BLI_path_is_abs(path)) {
|
|
/* Do not make safe 'C:' in 'C:\foo\bar'... */
|
|
skip_first = true;
|
|
}
|
|
#endif
|
|
|
|
for (curr_slash = (char *)BLI_first_slash(curr_path); curr_slash; curr_slash = (char *)BLI_first_slash(curr_path)) {
|
|
const char backup = *curr_slash;
|
|
*curr_slash = '\0';
|
|
if (!skip_first && (*curr_path != '\0') && BLI_filename_make_safe(curr_path)) {
|
|
changed = true;
|
|
}
|
|
skip_first = false;
|
|
curr_path = curr_slash + 1;
|
|
*curr_slash = backup;
|
|
}
|
|
if (BLI_filename_make_safe(curr_path)) {
|
|
changed = true;
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
/**
|
|
* Does path begin with the special "//" prefix that Blender uses to indicate
|
|
* a path relative to the .blend file.
|
|
*/
|
|
bool BLI_path_is_rel(const char *path)
|
|
{
|
|
return path[0] == '/' && path[1] == '/';
|
|
}
|
|
|
|
/* return true if the path is a UNC share */
|
|
bool BLI_path_is_unc(const char *name)
|
|
{
|
|
return name[0] == '\\' && name[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;
|
|
}
|
|
else {
|
|
return 2;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(WIN32)
|
|
|
|
/* return true if the path is absolute ie starts with a drive specifier (eg A:\) or is a UNC path */
|
|
static bool BLI_path_is_abs(const char *name)
|
|
{
|
|
return (name[1] == ':' && (name[2] == '\\' || name[2] == '/') ) || BLI_path_is_unc(name);
|
|
}
|
|
|
|
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 sto 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);
|
|
int copy_start = 0;
|
|
/* convert:
|
|
* \\?\UNC\server\share\folder\... to \\server\share\folder\...
|
|
* \\?\C:\ to C:\ and \\?\C:\folder\... to C:\folder\...
|
|
*/
|
|
if ((len > 3) &&
|
|
(unc[0] == L'\\') &&
|
|
(unc[1] == L'\\') &&
|
|
(unc[2] == L'?') &&
|
|
((unc[3] == L'\\') || (unc[3] == 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) &&
|
|
((unc[7] == L'\\') || (unc[7] == L'/')))
|
|
{
|
|
tmp[0] = L'\\';
|
|
tmp[1] = L'\\';
|
|
wcsncpy(tmp + 2, unc + 8, len - 8);
|
|
tmp[len - 6] = L'\0';
|
|
wcscpy(unc, tmp);
|
|
}
|
|
}
|
|
}
|
|
|
|
void BLI_cleanup_unc(char *path, int maxlen)
|
|
{
|
|
wchar_t *tmp_16 = alloc_utf16_from_8(path, 1);
|
|
BLI_cleanup_unc_16(tmp_16);
|
|
conv_utf_16_to_8(tmp_16, path, maxlen);
|
|
}
|
|
|
|
void BLI_cleanup_unc_16(wchar_t *path_16)
|
|
{
|
|
BLI_path_unc_to_short(path_16);
|
|
BLI_path_add_slash_to_share(path_16);
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Replaces *file with a relative version (prefixed by "//") such that BLI_path_abs, given
|
|
* the same *relfile, will convert it back to its original value.
|
|
*/
|
|
void BLI_path_rel(char *file, const char *relfile)
|
|
{
|
|
const char *lslash;
|
|
char temp[FILE_MAX];
|
|
char res[FILE_MAX];
|
|
|
|
/* if file is already relative, bail out */
|
|
if (BLI_path_is_rel(file)) {
|
|
return;
|
|
}
|
|
|
|
/* also bail out if relative path is not set */
|
|
if (relfile[0] == '\0') {
|
|
return;
|
|
}
|
|
|
|
#ifdef WIN32
|
|
if (BLI_strnlen(relfile, 3) > 2 && !BLI_path_is_abs(relfile)) {
|
|
char *ptemp;
|
|
/* fix missing volume name in relative base,
|
|
* can happen with old recent-files.txt files */
|
|
get_default_root(temp);
|
|
ptemp = &temp[2];
|
|
if (relfile[0] != '\\' && relfile[0] != '/') {
|
|
ptemp++;
|
|
}
|
|
BLI_strncpy(ptemp, relfile, FILE_MAX - 3);
|
|
}
|
|
else {
|
|
BLI_strncpy(temp, relfile, FILE_MAX);
|
|
}
|
|
|
|
if (BLI_strnlen(file, 3) > 2) {
|
|
bool is_unc = BLI_path_is_unc(file);
|
|
|
|
/* 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] != file[off])
|
|
return;
|
|
|
|
if (temp[off] == '\\')
|
|
slash++;
|
|
}
|
|
}
|
|
else if (temp[1] == ':' && file[1] == ':' && temp[0] != file[0]) {
|
|
return;
|
|
}
|
|
}
|
|
#else
|
|
BLI_strncpy(temp, relfile, FILE_MAX);
|
|
#endif
|
|
|
|
BLI_str_replace_char(temp + BLI_path_unc_prefix_len(temp), '\\', '/');
|
|
BLI_str_replace_char(file + BLI_path_unc_prefix_len(file), '\\', '/');
|
|
|
|
/* remove /./ which confuse the following slash counting... */
|
|
BLI_cleanup_path(NULL, file);
|
|
BLI_cleanup_path(NULL, temp);
|
|
|
|
/* the last slash in the file indicates where the path part ends */
|
|
lslash = BLI_last_slash(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 = file;
|
|
char *r = res;
|
|
|
|
#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 >= file) && (*q != '/') ) { --q; --p; }
|
|
}
|
|
else if (*p != '/') {
|
|
while ( (p >= temp) && (*p != '/') ) { --p; --q; }
|
|
}
|
|
|
|
r += BLI_strcpy_rlen(r, "//");
|
|
|
|
/* 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_strcpy_rlen(r, "../");
|
|
}
|
|
p++;
|
|
}
|
|
|
|
/* don't copy the slash at the beginning */
|
|
r += BLI_strcpy_rlen(r, q + 1);
|
|
|
|
#ifdef WIN32
|
|
BLI_str_replace_char(res + 2, '/', '\\');
|
|
#endif
|
|
strcpy(file, res);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Appends a suffix to the string, fitting it before the extension
|
|
*
|
|
* string = Foo.png, suffix = 123, separator = _
|
|
* Foo.png -> Foo_123.png
|
|
*
|
|
* \param string original (and final) string
|
|
* \param maxlen Maximum length of string
|
|
* \param suffix String to append to the original string
|
|
* \param sep Optional separator character
|
|
* \return true if succeeded
|
|
*/
|
|
bool BLI_path_suffix(char *string, size_t maxlen, const char *suffix, const char *sep)
|
|
{
|
|
const size_t string_len = strlen(string);
|
|
const size_t suffix_len = strlen(suffix);
|
|
const size_t sep_len = strlen(sep);
|
|
ssize_t a;
|
|
char extension[FILE_MAX];
|
|
bool has_extension = false;
|
|
|
|
if (string_len + sep_len + suffix_len >= maxlen)
|
|
return false;
|
|
|
|
for (a = string_len - 1; a >= 0; a--) {
|
|
if (string[a] == '.') {
|
|
has_extension = true;
|
|
break;
|
|
}
|
|
else if (ELEM(string[a], '/', '\\')) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!has_extension)
|
|
a = string_len;
|
|
|
|
BLI_strncpy(extension, string + a, sizeof(extension));
|
|
sprintf(string + a, "%s%s%s", sep, suffix, extension);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Replaces path with the path of its parent directory, returning true if
|
|
* it was able to find a parent directory within the pathname.
|
|
*/
|
|
bool BLI_parent_dir(char *path)
|
|
{
|
|
const char parent_dir[] = {'.', '.', SEP, '\0'}; /* "../" or "..\\" */
|
|
char tmp[FILE_MAX + 4];
|
|
|
|
BLI_join_dirfile(tmp, sizeof(tmp), path, parent_dir);
|
|
BLI_cleanup_dir(NULL, tmp); /* does all the work of normalizing the path for us */
|
|
|
|
if (!BLI_testextensie(tmp, parent_dir)) {
|
|
strcpy(path, tmp); /* We assume pardir is always shorter... */
|
|
return true;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
* *char_start and *char_end respectively. Returns true if such a sequence was found.
|
|
*/
|
|
static bool stringframe_chars(const char *path, int *char_start, int *char_end)
|
|
{
|
|
unsigned int ch_sta, ch_end, i;
|
|
/* Insert current frame: file### -> file001 */
|
|
ch_sta = ch_end = 0;
|
|
for (i = 0; path[i] != '\0'; i++) {
|
|
if (path[i] == '\\' || 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) {
|
|
*char_start = ch_sta;
|
|
*char_end = ch_end;
|
|
return true;
|
|
}
|
|
else {
|
|
*char_start = -1;
|
|
*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_last_slash(path);
|
|
|
|
if (file == NULL)
|
|
file = path;
|
|
|
|
if (strrchr(file, '#') == NULL) {
|
|
int len = strlen(file);
|
|
|
|
while (digits--) {
|
|
file[len++] = '#';
|
|
}
|
|
file[len] = '\0';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Replaces "#" character sequence in last slash-separated component of *path
|
|
* with frame as decimal integer, with leading zeroes as necessary, to make digits digits.
|
|
*/
|
|
bool BLI_path_frame(char *path, int frame, int digits)
|
|
{
|
|
int ch_sta, ch_end;
|
|
|
|
if (digits)
|
|
ensure_digits(path, digits);
|
|
|
|
if (stringframe_chars(path, &ch_sta, &ch_end)) { /* warning, ch_end is the last # +1 */
|
|
char tmp[FILE_MAX];
|
|
BLI_snprintf(tmp, sizeof(tmp),
|
|
"%.*s%.*d%s",
|
|
ch_sta, path, ch_end - ch_sta, frame, path + ch_end);
|
|
BLI_strncpy(path, tmp, FILE_MAX);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Replaces "#" character sequence in last slash-separated component of *path
|
|
* with sta and end as decimal integers, with leading zeroes as necessary, to make digits
|
|
* digits each, with a hyphen in-between.
|
|
*/
|
|
bool BLI_path_frame_range(char *path, int sta, int end, int digits)
|
|
{
|
|
int ch_sta, ch_end;
|
|
|
|
if (digits)
|
|
ensure_digits(path, digits);
|
|
|
|
if (stringframe_chars(path, &ch_sta, &ch_end)) { /* warning, ch_end is the last # +1 */
|
|
char tmp[FILE_MAX];
|
|
BLI_snprintf(tmp, sizeof(tmp),
|
|
"%.*s%.*d-%.*d%s",
|
|
ch_sta, path, ch_end - ch_sta, sta, ch_end - ch_sta, end, path + ch_end);
|
|
BLI_strncpy(path, tmp, FILE_MAX);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get the frame from a filename formatted by blender's frame scheme
|
|
*/
|
|
bool BLI_path_frame_get(char *path, int *r_frame, int *r_numdigits)
|
|
{
|
|
if (*path) {
|
|
char *file = (char *)BLI_last_slash(path);
|
|
char *c;
|
|
int len, numdigits;
|
|
|
|
numdigits = *r_numdigits = 0;
|
|
|
|
if (file == NULL)
|
|
file = path;
|
|
|
|
/* first get the extension part */
|
|
len = strlen(file);
|
|
|
|
c = file + len;
|
|
|
|
/* isolate extension */
|
|
while (--c != file) {
|
|
if (*c == '.') {
|
|
c--;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* find start of number */
|
|
while (c != (file - 1) && isdigit(*c)) {
|
|
c--;
|
|
numdigits++;
|
|
}
|
|
|
|
if (numdigits) {
|
|
char prevchar;
|
|
|
|
c++;
|
|
prevchar = c[numdigits];
|
|
c[numdigits] = 0;
|
|
|
|
/* was the number really an extension? */
|
|
*r_frame = atoi(c);
|
|
c[numdigits] = prevchar;
|
|
|
|
*r_numdigits = numdigits;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void BLI_path_frame_strip(char *path, bool set_frame_char, char *ext)
|
|
{
|
|
if (*path) {
|
|
char *file = (char *)BLI_last_slash(path);
|
|
char *c, *suffix;
|
|
int len;
|
|
int numdigits = 0;
|
|
|
|
if (file == NULL)
|
|
file = path;
|
|
|
|
/* first get the extension part */
|
|
len = strlen(file);
|
|
|
|
c = file + len;
|
|
|
|
/* isolate extension */
|
|
while (--c != file) {
|
|
if (*c == '.') {
|
|
c--;
|
|
break;
|
|
}
|
|
}
|
|
|
|
suffix = c + 1;
|
|
|
|
/* find start of number */
|
|
while (c != (file - 1) && isdigit(*c)) {
|
|
c--;
|
|
numdigits++;
|
|
}
|
|
|
|
c++;
|
|
|
|
if (numdigits) {
|
|
/* replace the number with the suffix and terminate the string */
|
|
while (numdigits--) {
|
|
*ext++ = *suffix;
|
|
*c++ = set_frame_char ? '#' : *suffix;
|
|
suffix++;
|
|
}
|
|
*c = '\0';
|
|
*ext = '\0';
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Check if we have '#' chars, usable for #BLI_path_frame, #BLI_path_frame_range
|
|
*/
|
|
bool BLI_path_frame_check_chars(const char *path)
|
|
{
|
|
int ch_sta, ch_end; /* dummy args */
|
|
return stringframe_chars(path, &ch_sta, &ch_end);
|
|
}
|
|
|
|
/**
|
|
* If path begins with "//", strips that and replaces it with basepath directory.
|
|
*
|
|
* \note Also converts drive-letter prefix to something more sensible
|
|
* if this is a non-drive-letter-based system.
|
|
*
|
|
* \param path: The path to convert.
|
|
* \param basepath: The directory to base relative paths with.
|
|
* \return true if the path was relative (started with "//").
|
|
*/
|
|
bool BLI_path_abs(char *path, const char *basepath)
|
|
{
|
|
const bool wasrelative = BLI_path_is_rel(path);
|
|
char tmp[FILE_MAX];
|
|
char base[FILE_MAX];
|
|
#ifdef WIN32
|
|
|
|
/* without this: "" --> "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(path)) {
|
|
char *p = path;
|
|
get_default_root(tmp);
|
|
// get rid of the slashes at the beginning of the path
|
|
while (*p == '\\' || *p == '/') {
|
|
p++;
|
|
}
|
|
strcat(tmp, p);
|
|
}
|
|
else {
|
|
BLI_strncpy(tmp, path, FILE_MAX);
|
|
}
|
|
#else
|
|
BLI_strncpy(tmp, path, sizeof(tmp));
|
|
|
|
/* Check for loading a windows path on a posix system
|
|
* in this case, there is no use in trying C:/ since it
|
|
* will never exist on a unix os.
|
|
*
|
|
* Add a / prefix and lowercase the driveletter, remove the :
|
|
* C:\foo.JPG -> /c/foo.JPG */
|
|
|
|
if (isalpha(tmp[0]) && tmp[1] == ':' && (tmp[2] == '\\' || tmp[2] == '/') ) {
|
|
tmp[1] = tolower(tmp[0]); /* replace ':' with driveletter */
|
|
tmp[0] = '/';
|
|
/* '\' the slash will be converted later */
|
|
}
|
|
|
|
#endif
|
|
|
|
/* 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) -jesterKing.
|
|
* 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 -elubie */
|
|
BLI_str_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;
|
|
BLI_strncpy(base, basepath, sizeof(base));
|
|
|
|
/* file component is ignored, so don't bother with the trailing slash */
|
|
BLI_cleanup_path(NULL, base);
|
|
lslash = BLI_last_slash(base);
|
|
BLI_str_replace_char(base + BLI_path_unc_prefix_len(base), '\\', '/');
|
|
|
|
if (lslash) {
|
|
const int baselen = (int) (lslash - base) + 1; /* length up to and including last "/" */
|
|
/* use path for temp storage here, we copy back over it right away */
|
|
BLI_strncpy(path, tmp + 2, FILE_MAX); /* strip "//" */
|
|
|
|
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
|
|
/* 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 :) -jesterKing
|
|
*/
|
|
BLI_str_replace_char(path + 2, '/', '\\');
|
|
#endif
|
|
|
|
/* ensure this is after correcting for path switch */
|
|
BLI_cleanup_path(NULL, path);
|
|
|
|
return wasrelative;
|
|
}
|
|
|
|
|
|
/**
|
|
* Expands path relative to the current working directory, if it was relative.
|
|
* Returns true if such expansion was done.
|
|
*
|
|
* \note Should only be done with command line paths.
|
|
* this is _not_ something blenders internal paths support like the "//" prefix
|
|
*/
|
|
bool BLI_path_cwd(char *path, const size_t maxlen)
|
|
{
|
|
bool wasrelative = true;
|
|
const int filelen = strlen(path);
|
|
|
|
#ifdef WIN32
|
|
if ((filelen >= 3 && BLI_path_is_abs(path)) || BLI_path_is_unc(path))
|
|
wasrelative = false;
|
|
#else
|
|
if (filelen >= 2 && path[0] == '/')
|
|
wasrelative = false;
|
|
#endif
|
|
|
|
if (wasrelative) {
|
|
char cwd[FILE_MAX];
|
|
/* in case the full path to the blend isn't used */
|
|
if (BLI_current_working_dir(cwd, sizeof(cwd))) {
|
|
char origpath[FILE_MAX];
|
|
BLI_strncpy(origpath, path, FILE_MAX);
|
|
BLI_join_dirfile(path, maxlen, cwd, origpath);
|
|
}
|
|
else {
|
|
printf("Could not get the current working directory - $PWD for an unknown reason.\n");
|
|
}
|
|
}
|
|
|
|
return wasrelative;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
/**
|
|
* Tries appending each of the semicolon-separated extensions in the PATHEXT
|
|
* environment variable (Windows-only) onto *name in turn until such a file is found.
|
|
* Returns success/failure.
|
|
*/
|
|
bool BLI_path_program_extensions_add_win32(char *name, const size_t maxlen)
|
|
{
|
|
bool retval = false;
|
|
int type;
|
|
|
|
type = BLI_exists(name);
|
|
if ((type == 0) || S_ISDIR(type)) {
|
|
/* typically 3-5, ".EXE", ".BAT"... etc */
|
|
const int ext_max = 12;
|
|
const char *ext = getenv("PATHEXT");
|
|
if (ext) {
|
|
const int name_len = strlen(name);
|
|
char *filename = alloca(name_len + ext_max);
|
|
char *filename_ext;
|
|
const char *ext_next;
|
|
|
|
/* null terminated in the loop */
|
|
memcpy(filename, name, name_len);
|
|
filename_ext = filename + 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(name, filename, maxlen);
|
|
break;
|
|
}
|
|
}
|
|
} while ((ext = ext_next));
|
|
}
|
|
}
|
|
else {
|
|
retval = true;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
#endif /* WIN32 */
|
|
|
|
/**
|
|
* Search for a binary (executable)
|
|
*/
|
|
bool BLI_path_program_search(
|
|
char *fullname, const size_t maxlen,
|
|
const char *name)
|
|
{
|
|
const char *path;
|
|
bool retval = false;
|
|
|
|
#ifdef _WIN32
|
|
const char separator = ';';
|
|
#else
|
|
const char separator = ':';
|
|
#endif
|
|
|
|
path = getenv("PATH");
|
|
if (path) {
|
|
char filename[FILE_MAX];
|
|
const char *temp;
|
|
|
|
do {
|
|
temp = strchr(path, separator);
|
|
if (temp) {
|
|
strncpy(filename, path, temp - path);
|
|
filename[temp - path] = 0;
|
|
path = temp + 1;
|
|
}
|
|
else {
|
|
strncpy(filename, path, sizeof(filename));
|
|
}
|
|
|
|
BLI_path_append(filename, maxlen, name);
|
|
if (
|
|
#ifdef _WIN32
|
|
BLI_path_program_extensions_add_win32(filename, maxlen)
|
|
#else
|
|
BLI_exists(filename)
|
|
#endif
|
|
)
|
|
{
|
|
BLI_strncpy(fullname, filename, maxlen);
|
|
retval = true;
|
|
break;
|
|
}
|
|
} while (temp);
|
|
}
|
|
|
|
if (retval == false) {
|
|
*fullname = '\0';
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* Sets the specified environment variable to the specified value,
|
|
* and clears it if val == NULL.
|
|
*/
|
|
void BLI_setenv(const char *env, const char *val)
|
|
{
|
|
/* free windows */
|
|
|
|
#if (defined(WIN32) || defined(WIN64))
|
|
uputenv(env, val);
|
|
|
|
#else
|
|
/* linux/osx/bsd */
|
|
if (val)
|
|
setenv(env, val, 1);
|
|
else
|
|
unsetenv(env);
|
|
#endif
|
|
}
|
|
|
|
|
|
/**
|
|
* Only set an env var if already not there.
|
|
* Like Unix setenv(env, val, 0);
|
|
*
|
|
* (not used anywhere).
|
|
*/
|
|
void BLI_setenv_if_new(const char *env, const char *val)
|
|
{
|
|
if (getenv(env) == NULL)
|
|
BLI_setenv(env, val);
|
|
}
|
|
|
|
/**
|
|
* Strips off nonexistent (or non-accessible) subdirectories from the end of *dir, leaving the path of
|
|
* the lowest-level directory that does exist and we can read.
|
|
*/
|
|
void BLI_make_exist(char *dir)
|
|
{
|
|
bool valid_path = true;
|
|
|
|
/* Loop as long as cur path is not a dir, and we can get a parent path. */
|
|
while ((BLI_access(dir, R_OK) != 0) && (valid_path = BLI_parent_dir(dir)));
|
|
|
|
/* If we could not find an existing dir, use default root... */
|
|
if (!valid_path || !dir[0]) {
|
|
#ifdef WIN32
|
|
get_default_root(dir);
|
|
#else
|
|
strcpy(dir, "/");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensures that the parent directory of *name exists.
|
|
*
|
|
* \return true on success (i.e. given path now exists on FS), false otherwise.
|
|
*/
|
|
bool BLI_make_existing_file(const char *name)
|
|
{
|
|
char di[FILE_MAX];
|
|
BLI_split_dir_part(name, di, sizeof(di));
|
|
|
|
/* make if the dir doesn't exist */
|
|
return BLI_dir_create_recursive(di);
|
|
}
|
|
|
|
/**
|
|
* Returns in *string the concatenation of *dir and *file (also with *relabase on the
|
|
* front if specified and *dir begins with "//"). Normalizes all occurrences of path
|
|
* separators, including ensuring there is exactly one between the copies of *dir and *file,
|
|
* and between the copies of *relabase and *dir.
|
|
*
|
|
* \param relabase Optional prefix to substitute for "//" on front of *dir
|
|
* \param string Area to return result
|
|
*/
|
|
void BLI_make_file_string(const char *relabase, char *string, const char *dir, const char *file)
|
|
{
|
|
int sl;
|
|
|
|
if (string) {
|
|
/* ensure this is always set even if dir/file are NULL */
|
|
string[0] = '\0';
|
|
|
|
if (ELEM(NULL, dir, file)) {
|
|
return; /* We don't want any NULLs */
|
|
}
|
|
}
|
|
else {
|
|
return; /* string is NULL, probably shouldnt happen but return anyway */
|
|
}
|
|
|
|
|
|
/* we first push all slashes into unix mode, just to make sure we don't get
|
|
* any mess with slashes later on. -jesterKing */
|
|
/* constant strings can be passed for those parameters - don't change them - elubie */
|
|
#if 0
|
|
BLI_str_replace_char(relabase, '\\', '/');
|
|
BLI_str_replace_char(dir, '\\', '/');
|
|
BLI_str_replace_char(file, '\\', '/');
|
|
#endif
|
|
|
|
/* Resolve relative references */
|
|
if (relabase && dir[0] == '/' && dir[1] == '/') {
|
|
char *lslash;
|
|
|
|
/* Get the file name, chop everything past the last slash (ie. the filename) */
|
|
strcpy(string, relabase);
|
|
|
|
lslash = (char *)BLI_last_slash(string);
|
|
if (lslash) *(lslash + 1) = 0;
|
|
|
|
dir += 2; /* Skip over the relative reference */
|
|
}
|
|
#ifdef WIN32
|
|
else {
|
|
if (BLI_strnlen(dir, 3) >= 2 && dir[1] == ':') {
|
|
BLI_strncpy(string, dir, 3);
|
|
dir += 2;
|
|
}
|
|
else if (BLI_strnlen(dir, 3) >= 2 && BLI_path_is_unc(dir)) {
|
|
string[0] = 0;
|
|
}
|
|
else { /* no drive specified */
|
|
/* first option: get the drive from the relabase if it has one */
|
|
if (relabase && BLI_strnlen(relabase, 3) >= 2 && relabase[1] == ':') {
|
|
BLI_strncpy(string, relabase, 3);
|
|
string[2] = '\\';
|
|
string[3] = '\0';
|
|
}
|
|
else { /* we're out of luck here, guessing the first valid drive, usually c:\ */
|
|
get_default_root(string);
|
|
}
|
|
|
|
/* ignore leading slashes */
|
|
while (*dir == '/' || *dir == '\\') dir++;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
strcat(string, dir);
|
|
|
|
/* Make sure string ends in one (and only one) slash */
|
|
/* first trim all slashes from the end of the string */
|
|
sl = strlen(string);
|
|
while (sl > 0 && (string[sl - 1] == '/' || string[sl - 1] == '\\') ) {
|
|
string[sl - 1] = '\0';
|
|
sl--;
|
|
}
|
|
/* since we've now removed all slashes, put back one slash at the end. */
|
|
strcat(string, "/");
|
|
|
|
while (*file && (*file == '/' || *file == '\\')) /* Trim slashes from the front of file */
|
|
file++;
|
|
|
|
strcat(string, file);
|
|
|
|
/* Push all slashes to the system preferred direction */
|
|
BLI_path_native_slash(string);
|
|
}
|
|
|
|
static bool testextensie_ex(const char *str, const size_t str_len,
|
|
const char *ext, const size_t ext_len)
|
|
{
|
|
BLI_assert(strlen(str) == str_len);
|
|
BLI_assert(strlen(ext) == ext_len);
|
|
|
|
return (((str_len == 0 || ext_len == 0 || ext_len >= str_len) == 0) &&
|
|
(BLI_strcasecmp(ext, str + str_len - ext_len) == 0));
|
|
}
|
|
|
|
/* does str end with ext. */
|
|
bool BLI_testextensie(const char *str, const char *ext)
|
|
{
|
|
return testextensie_ex(str, strlen(str), ext, strlen(ext));
|
|
}
|
|
|
|
bool BLI_testextensie_n(const char *str, ...)
|
|
{
|
|
const size_t str_len = strlen(str);
|
|
|
|
va_list args;
|
|
const char *ext;
|
|
bool ret = false;
|
|
|
|
va_start(args, str);
|
|
|
|
while ((ext = (const char *) va_arg(args, void *))) {
|
|
if (testextensie_ex(str, str_len, ext, strlen(ext))) {
|
|
ret = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
va_end(args);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* does str end with any of the suffixes in *ext_array. */
|
|
bool BLI_testextensie_array(const char *str, const char **ext_array)
|
|
{
|
|
const size_t str_len = strlen(str);
|
|
int i = 0;
|
|
|
|
while (ext_array[i]) {
|
|
if (testextensie_ex(str, str_len, ext_array[i], strlen(ext_array[i]))) {
|
|
return true;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Semicolon separated wildcards, eg:
|
|
* '*.zip;*.py;*.exe'
|
|
* does str match any of the semicolon-separated glob patterns in fnmatch.
|
|
*/
|
|
bool BLI_testextensie_glob(const char *str, 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 = BLI_strncpy_rlen(pattern, ext_step, sizeof(pattern));
|
|
}
|
|
|
|
if (fnmatch(pattern, str, FNM_CASEFOLD) == 0) {
|
|
return true;
|
|
}
|
|
ext_step += len_ext;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Removes any existing extension on the end of \a path and appends \a ext.
|
|
* \return false if there was no room.
|
|
*/
|
|
bool BLI_replace_extension(char *path, size_t maxlen, const char *ext)
|
|
{
|
|
const size_t path_len = strlen(path);
|
|
const size_t ext_len = strlen(ext);
|
|
ssize_t a;
|
|
|
|
for (a = path_len - 1; a >= 0; a--) {
|
|
if (ELEM(path[a], '.', '/', '\\')) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((a < 0) || (path[a] != '.')) {
|
|
a = path_len;
|
|
}
|
|
|
|
if (a + ext_len >= maxlen)
|
|
return false;
|
|
|
|
memcpy(path + a, ext, ext_len + 1);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Strip's trailing '.'s and adds the extension only when needed
|
|
*/
|
|
bool BLI_ensure_extension(char *path, size_t maxlen, const char *ext)
|
|
{
|
|
const size_t path_len = strlen(path);
|
|
const size_t ext_len = strlen(ext);
|
|
ssize_t a;
|
|
|
|
/* first check the extension is already there */
|
|
if ((ext_len <= path_len) && (STREQ(path + (path_len - ext_len), ext))) {
|
|
return true;
|
|
}
|
|
|
|
for (a = path_len - 1; a >= 0; a--) {
|
|
if (path[a] == '.') {
|
|
path[a] = '\0';
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
a++;
|
|
|
|
if (a + ext_len >= maxlen)
|
|
return false;
|
|
|
|
memcpy(path + a, ext, ext_len + 1);
|
|
return true;
|
|
}
|
|
|
|
bool BLI_ensure_filename(char *filepath, size_t maxlen, const char *filename)
|
|
{
|
|
char *c = (char *)BLI_last_slash(filepath);
|
|
if (!c || ((c - filepath) < maxlen - (strlen(filename) + 1))) {
|
|
strcpy(c ? &c[1] : filepath, filename);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Converts "/foo/bar.txt" to "/foo/" and "bar.txt"
|
|
* - wont change 'string'
|
|
* - wont create any directories
|
|
* - dosnt use CWD, or deal with relative paths.
|
|
* - Only fill's in *dir and *file when they are non NULL
|
|
* */
|
|
void BLI_split_dirfile(const char *string, char *dir, char *file, const size_t dirlen, const size_t filelen)
|
|
{
|
|
const char *lslash_str = BLI_last_slash(string);
|
|
const size_t lslash = lslash_str ? (size_t)(lslash_str - string) + 1 : 0;
|
|
|
|
if (dir) {
|
|
if (lslash) {
|
|
BLI_strncpy(dir, string, MIN2(dirlen, lslash + 1)); /* +1 to include the slash and the last char */
|
|
}
|
|
else {
|
|
dir[0] = '\0';
|
|
}
|
|
}
|
|
|
|
if (file) {
|
|
BLI_strncpy(file, string + lslash, filelen);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Copies the parent directory part of string into *dir, max length dirlen.
|
|
*/
|
|
void BLI_split_dir_part(const char *string, char *dir, const size_t dirlen)
|
|
{
|
|
BLI_split_dirfile(string, dir, NULL, dirlen, 0);
|
|
}
|
|
|
|
/**
|
|
* Copies the leaf filename part of string into *file, max length filelen.
|
|
*/
|
|
void BLI_split_file_part(const char *string, char *file, const size_t filelen)
|
|
{
|
|
BLI_split_dirfile(string, NULL, file, 0, filelen);
|
|
}
|
|
|
|
/**
|
|
* Append a filename to a dir, ensuring slash separates.
|
|
*/
|
|
void BLI_path_append(char *__restrict dst, const size_t maxlen, const char *__restrict file)
|
|
{
|
|
size_t dirlen = BLI_strnlen(dst, maxlen);
|
|
|
|
/* inline BLI_add_slash */
|
|
if ((dirlen > 0) && (dst[dirlen - 1] != SEP)) {
|
|
dst[dirlen++] = SEP;
|
|
dst[dirlen] = '\0';
|
|
}
|
|
|
|
if (dirlen >= maxlen) {
|
|
return; /* fills the path */
|
|
}
|
|
|
|
BLI_strncpy(dst + dirlen, file, maxlen - dirlen);
|
|
}
|
|
|
|
/**
|
|
* Simple appending of filename to dir, does not check for valid path!
|
|
* Puts result into *dst, which may be same area as *dir.
|
|
*/
|
|
void BLI_join_dirfile(char *__restrict dst, const size_t maxlen, const char *__restrict dir, const char *__restrict file)
|
|
{
|
|
size_t dirlen = BLI_strnlen(dir, maxlen);
|
|
|
|
/* args can't match */
|
|
BLI_assert(!ELEM(dst, dir, file));
|
|
|
|
if (dirlen == maxlen) {
|
|
memcpy(dst, dir, dirlen);
|
|
dst[dirlen - 1] = '\0';
|
|
return; /* dir fills the path */
|
|
}
|
|
else {
|
|
memcpy(dst, dir, dirlen + 1);
|
|
}
|
|
|
|
if (dirlen + 1 >= maxlen) {
|
|
return; /* fills the path */
|
|
}
|
|
|
|
/* inline BLI_add_slash */
|
|
if ((dirlen > 0) && !ELEM(dst[dirlen - 1], SEP, ALTSEP)) {
|
|
dst[dirlen++] = SEP;
|
|
dst[dirlen] = '\0';
|
|
}
|
|
|
|
if (dirlen >= maxlen) {
|
|
return; /* fills the path */
|
|
}
|
|
|
|
BLI_strncpy(dst + dirlen, file, maxlen - dirlen);
|
|
}
|
|
|
|
/**
|
|
* Join multiple strings into a path, ensuring only a single path separator between each,
|
|
* and trailing slash is kept.
|
|
*
|
|
* \note If you want a trailing slash, add ``SEP_STR`` as the last path argument,
|
|
* duplicate slashes will be cleaned up.
|
|
*/
|
|
size_t BLI_path_join(char *__restrict dst, const size_t dst_len, const char *path, ...)
|
|
{
|
|
if (UNLIKELY(dst_len == 0)) {
|
|
return 0;
|
|
}
|
|
const size_t dst_last = dst_len - 1;
|
|
size_t ofs = BLI_strncpy_rlen(dst, path, dst_len);
|
|
|
|
if (ofs == dst_last) {
|
|
return ofs;
|
|
}
|
|
|
|
/* remove trailing slashes, unless there are _only_ trailing slashes
|
|
* (allow "//" as the first argument). */
|
|
bool has_trailing_slash = false;
|
|
if (ofs != 0) {
|
|
size_t len = ofs;
|
|
while ((len != 0) && ELEM(path[len - 1], SEP, ALTSEP)) {
|
|
len -= 1;
|
|
}
|
|
if (len != 0) {
|
|
ofs = len;
|
|
}
|
|
has_trailing_slash = (path[len] != '\0');
|
|
}
|
|
|
|
va_list args;
|
|
va_start(args, path);
|
|
while ((path = (const char *) va_arg(args, const char *))) {
|
|
has_trailing_slash = false;
|
|
const char *path_init = path;
|
|
while (ELEM(path[0], SEP, ALTSEP)) {
|
|
path++;
|
|
}
|
|
size_t len = strlen(path);
|
|
if (len != 0) {
|
|
while ((len != 0) && ELEM(path[len - 1], SEP, ALTSEP)) {
|
|
len -= 1;
|
|
}
|
|
|
|
if (len != 0) {
|
|
/* the very first path may have a slash at the end */
|
|
if (ofs && !ELEM(dst[ofs - 1], SEP, ALTSEP)) {
|
|
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);
|
|
}
|
|
}
|
|
va_end(args);
|
|
|
|
if (has_trailing_slash) {
|
|
if ((ofs != dst_last) && (ofs != 0) && (ELEM(dst[ofs - 1], SEP, ALTSEP) == 0)) {
|
|
dst[ofs++] = SEP;
|
|
}
|
|
}
|
|
|
|
BLI_assert(ofs <= dst_last);
|
|
dst[ofs] = '\0';
|
|
|
|
return ofs;
|
|
}
|
|
|
|
/**
|
|
* like pythons os.path.basename()
|
|
*
|
|
* \return The pointer into \a path string immediately after last slash,
|
|
* or start of \a path if none found.
|
|
*/
|
|
const char *BLI_path_basename(const char *path)
|
|
{
|
|
const char * const filename = BLI_last_slash(path);
|
|
return filename ? filename + 1 : path;
|
|
}
|
|
|
|
/**
|
|
* Get an element of the path at an index, eg:
|
|
* "/some/path/file.txt" where an index of...
|
|
* - 0 or -3: "some"
|
|
* - 1 or -2: "path"
|
|
* - 2 or -1: "file.txt"
|
|
*
|
|
* Ignores multiple slashes at any point in the path (including start/end).
|
|
*/
|
|
bool BLI_path_name_at_index(const char *path, const int index, int *r_offset, int *r_len)
|
|
{
|
|
if (index >= 0) {
|
|
int index_step = 0;
|
|
int prev = -1;
|
|
int i = 0;
|
|
while (true) {
|
|
const char c = path[i];
|
|
if (ELEM(c, SEP, ALTSEP, '\0')) {
|
|
if (prev + 1 != i) {
|
|
prev += 1;
|
|
if (index_step == index) {
|
|
*r_offset = prev;
|
|
*r_len = i - prev;
|
|
/* printf("!!! %d %d\n", start, end); */
|
|
return true;
|
|
}
|
|
index_step += 1;
|
|
}
|
|
if (c == '\0') {
|
|
break;
|
|
}
|
|
prev = i;
|
|
}
|
|
i += 1;
|
|
}
|
|
return false;
|
|
}
|
|
else {
|
|
/* negative number, reverse where -1 is the last element */
|
|
int index_step = -1;
|
|
int prev = strlen(path);
|
|
int i = prev - 1;
|
|
while (true) {
|
|
const char c = i >= 0 ? path[i] : '\0';
|
|
if (ELEM(c, SEP, ALTSEP, '\0')) {
|
|
if (prev - 1 != i) {
|
|
i += 1;
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* UNUSED */
|
|
#if 0
|
|
/**
|
|
* Produce image export path.
|
|
*
|
|
* Returns:
|
|
* 0 if image filename is empty or if destination path
|
|
* matches image path (i.e. both are the same file).
|
|
* 2 if source is identical to destination.
|
|
* 1 if rebase was successful
|
|
* -------------------------------------------------------------
|
|
* Hint: Trailing slash in dest_dir is optional.
|
|
*
|
|
* Logic:
|
|
*
|
|
* - if an image is "below" current .blend file directory:
|
|
* rebuild the same dir structure in dest_dir
|
|
*
|
|
* Example:
|
|
* src : //textures/foo/bar.png
|
|
* dest: [dest_dir]/textures/foo/bar.png.
|
|
*
|
|
* - if an image is not "below" current .blend file directory,
|
|
* disregard it's path and copy it into the destination
|
|
* directory.
|
|
*
|
|
* Example:
|
|
* src : //../foo/bar.png becomes
|
|
* dest: [dest_dir]/bar.png.
|
|
*
|
|
* This logic ensures that all image paths are relative and
|
|
* that a user gets his images in one place. It'll also provide
|
|
* consistent behavior across exporters.
|
|
* IMPORTANT NOTE: If base_dir contains an empty string, then
|
|
* this function returns wrong results!
|
|
* XXX: test on empty base_dir and return an error ?
|
|
*/
|
|
|
|
/**
|
|
*
|
|
* \param abs Optional string to return new full path
|
|
* \param abs_len Size of *abs string
|
|
* \param rel Optional area to return new path relative to parent directory of .blend file
|
|
* (only meaningful if item is in a subdirectory thereof)
|
|
* \param rel_len Size of *rel area
|
|
* \param base_dir Path of .blend file
|
|
* \param src_dir Original path of item (any initial "//" will be expanded to
|
|
* parent directory of .blend file)
|
|
* \param dest_dir New directory into which item will be moved
|
|
* \return bli_rebase_state
|
|
*
|
|
* \note Not actually used anywhere!
|
|
*/
|
|
int BLI_rebase_path(char *abs, size_t abs_len,
|
|
char *rel, size_t rel_len,
|
|
const char *base_dir, const char *src_dir, const char *dest_dir)
|
|
{
|
|
char path[FILE_MAX]; /* original full path of item */
|
|
char dir[FILE_MAX]; /* directory part of src_dir */
|
|
char base[FILE_MAX]; /* basename part of src_dir */
|
|
char blend_dir[FILE_MAX]; /* directory, where current .blend file resides */
|
|
char dest_path[FILE_MAX];
|
|
char rel_dir[FILE_MAX];
|
|
int len;
|
|
|
|
if (abs)
|
|
abs[0] = 0;
|
|
|
|
if (rel)
|
|
rel[0] = 0;
|
|
|
|
BLI_split_dir_part(base_dir, blend_dir, sizeof(blend_dir));
|
|
|
|
if (src_dir[0] == '\0')
|
|
return BLI_REBASE_NO_SRCDIR;
|
|
|
|
BLI_strncpy(path, src_dir, sizeof(path));
|
|
|
|
/* expand "//" in filename and get absolute path */
|
|
BLI_path_abs(path, base_dir);
|
|
|
|
/* get the directory part */
|
|
BLI_split_dirfile(path, dir, base, sizeof(dir), sizeof(base));
|
|
|
|
len = strlen(blend_dir);
|
|
|
|
rel_dir[0] = 0;
|
|
|
|
/* if image is "below" current .blend file directory */
|
|
if (!BLI_path_ncmp(path, blend_dir, len)) {
|
|
|
|
if (BLI_path_cmp(dir, blend_dir) == 0) {
|
|
/* image is directly in .blend file parent directory => put directly in dest_dir */
|
|
BLI_join_dirfile(dest_path, sizeof(dest_path), dest_dir, base);
|
|
}
|
|
else {
|
|
/* "below" (in subdirectory of .blend file parent directory) => put in same relative directory structure in dest_dir */
|
|
/* rel = image_path_dir - blend_dir */
|
|
BLI_strncpy(rel_dir, dir + len, sizeof(rel_dir));
|
|
/* subdirectories relative to blend_dir */
|
|
BLI_join_dirfile(dest_path, sizeof(dest_path), dest_dir, rel_dir);
|
|
/* same subdirectories relative to dest_dir */
|
|
BLI_path_append(dest_path, sizeof(dest_path), base);
|
|
/* keeping original item basename */
|
|
}
|
|
|
|
}
|
|
/* image is out of current directory -- just put straight in dest_dir */
|
|
else {
|
|
BLI_join_dirfile(dest_path, sizeof(dest_path), dest_dir, base);
|
|
}
|
|
|
|
if (abs)
|
|
BLI_strncpy(abs, dest_path, abs_len);
|
|
|
|
if (rel) {
|
|
strncat(rel, rel_dir, rel_len);
|
|
strncat(rel, base, rel_len); /* FIXME: could overflow rel area! */
|
|
}
|
|
|
|
/* return 2 if (src == dest) */
|
|
if (BLI_path_cmp(path, dest_path) == 0) {
|
|
// if (G.debug & G_DEBUG) printf("%s and %s are the same file\n", path, dest_path);
|
|
return BLI_REBASE_IDENTITY;
|
|
}
|
|
|
|
return BLI_REBASE_OK;
|
|
}
|
|
#endif
|
|
|
|
|
|
/**
|
|
* Returns pointer to the leftmost path separator in string. Not actually used anywhere.
|
|
*/
|
|
const char *BLI_first_slash(const char *string)
|
|
{
|
|
const char * const ffslash = strchr(string, '/');
|
|
const char * const fbslash = strchr(string, '\\');
|
|
|
|
if (!ffslash) return fbslash;
|
|
else if (!fbslash) return ffslash;
|
|
|
|
return (ffslash < fbslash) ? ffslash : fbslash;
|
|
}
|
|
|
|
/**
|
|
* Returns pointer to the rightmost path separator in string.
|
|
*/
|
|
const char *BLI_last_slash(const char *string)
|
|
{
|
|
const char * const lfslash = strrchr(string, '/');
|
|
const char * const lbslash = strrchr(string, '\\');
|
|
|
|
if (!lfslash) return lbslash;
|
|
else if (!lbslash) return lfslash;
|
|
|
|
return (lfslash > lbslash) ? lfslash : lbslash;
|
|
}
|
|
|
|
/**
|
|
* Appends a slash to string if there isn't one there already.
|
|
* Returns the new length of the string.
|
|
*/
|
|
int BLI_add_slash(char *string)
|
|
{
|
|
int len = strlen(string);
|
|
if (len == 0 || string[len - 1] != SEP) {
|
|
string[len] = SEP;
|
|
string[len + 1] = '\0';
|
|
return len + 1;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
/**
|
|
* Removes the last slash and everything after it to the end of string, if there is one.
|
|
*/
|
|
void BLI_del_slash(char *string)
|
|
{
|
|
int len = strlen(string);
|
|
while (len) {
|
|
if (string[len - 1] == SEP) {
|
|
string[len - 1] = '\0';
|
|
len--;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Changes to the path separators to the native ones for this OS.
|
|
*/
|
|
void BLI_path_native_slash(char *path)
|
|
{
|
|
#ifdef WIN32
|
|
if (path && BLI_strnlen(path, 3) > 2) {
|
|
BLI_str_replace_char(path + 2, '/', '\\');
|
|
}
|
|
#else
|
|
BLI_str_replace_char(path + BLI_path_unc_prefix_len(path), '\\', '/');
|
|
#endif
|
|
}
|