This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/blenlib/intern/path_util.c

2205 lines
57 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 "MEM_guardedalloc.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 "../blenkernel/BKE_blender.h" /* BLENDER_VERSION, bad level include (no function call) */
#include "GHOST_Path-api.h"
#if defined WIN32 && !defined _LIBC || defined __sun
# include "BLI_fnmatch.h" /* use fnmatch included in blenlib */
#else
# ifndef _GNU_SOURCE
# define _GNU_SOURCE
# endif
# include <fnmatch.h>
#endif
#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"
#else /* non windows */
# ifdef WITH_BINRELOC
# include "binreloc.h"
# endif
#endif /* WIN32 */
/* local */
#define UNIQUE_NAME_MAX 128
static char bprogname[FILE_MAX]; /* full path to program executable */
static char bprogdir[FILE_MAX]; /* full path to directory in which executable is located */
static char btempdir[FILE_MAX]; /* temporary directory */
/* 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 short nums = 0, nume = 0;
short i;
bool found_digit = false;
const char * const lslash = BLI_last_slash(string);
const unsigned short string_len = strlen(string);
const unsigned short lslash_len = lslash != NULL ? (int)(lslash - string) : 0;
unsigned short 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 >= 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);
}
/**
* Looks for a numeric suffix preceded by delim character on the end of
* name, puts preceding part into *left and value of suffix into *nr.
* Returns the length of *left.
*
* Foo.001 -> "Foo", 1
* Returning the length of "Foo"
*
* \param left Where to return copy of part preceding delim
* \param nr Where to return value of numeric suffix
* \param name String to split
* \param delim Delimiter character
* \return Length of \a left
*/
int BLI_split_name_num(char *left, int *nr, const char *name, const char delim)
{
const int name_len = strlen(name);
*nr = 0;
memcpy(left, name, (name_len + 1) * sizeof(char));
/* name doesn't end with a delimiter "foo." */
if ((name_len > 1 && name[name_len - 1] == delim) == 0) {
int a = name_len;
while (a--) {
if (name[a] == delim) {
left[a] = '\0'; /* truncate left part here */
*nr = atol(name + a + 1);
/* casting down to an int, can overflow for large numbers */
if (*nr < 0)
*nr = 0;
return a;
}
else if (isdigit(name[a]) == 0) {
/* non-numeric suffix - give up */
break;
}
}
}
return name_len;
}
/**
* Looks for a string of digits within name (using BLI_stringdec) and adjusts it by add.
*/
void BLI_newname(char *name, int add)
{
char head[UNIQUE_NAME_MAX], tail[UNIQUE_NAME_MAX];
int pic;
unsigned short digits;
pic = BLI_stringdec(name, head, tail, &digits);
/* are we going from 100 -> 99 or from 10 -> 9 */
if (add < 0 && digits < 4 && digits > 0) {
int i, exp;
exp = 1;
for (i = digits; i > 1; i--) exp *= 10;
if (pic >= exp && (pic + add) < exp) digits--;
}
pic += add;
if (digits == 4 && pic < 0) pic = 0;
BLI_stringenc(name, head, tail, digits, pic);
}
/**
* Ensures name is unique (according to criteria specified by caller in unique_check callback),
* incrementing its numeric suffix as necessary. Returns true if name had to be adjusted.
*
* \param unique_check Return true if name is not unique
* \param arg Additional arg to unique_check--meaning is up to caller
* \param defname To initialize name if latter is empty
* \param delim Delimits numeric suffix in name
* \param name Name to be ensured unique
* \param name_len Maximum length of name area
* \return true if there if the name was changed
*/
bool BLI_uniquename_cb(bool (*unique_check)(void *arg, const char *name),
void *arg, const char *defname, char delim, char *name, short name_len)
{
if (name[0] == '\0') {
BLI_strncpy(name, defname, name_len);
}
if (unique_check(arg, name)) {
char numstr[16];
char tempname[UNIQUE_NAME_MAX];
char left[UNIQUE_NAME_MAX];
int number;
int len = BLI_split_name_num(left, &number, name, delim);
do {
const int numlen = BLI_snprintf(numstr, sizeof(numstr), "%c%03d", delim, ++number);
/* highly unlikely the string only has enough room for the number
* but support anyway */
if ((len == 0) || (numlen >= name_len)) {
/* number is know not to be utf-8 */
BLI_strncpy(tempname, numstr, name_len);
}
else {
char *tempname_buf;
tempname[0] = '\0';
tempname_buf = BLI_strncat_utf8(tempname, left, name_len - numlen);
memcpy(tempname_buf, numstr, numlen + 1);
}
} while (unique_check(arg, tempname));
BLI_strncpy(name, tempname, name_len);
return true;
}
return false;
}
/* little helper macro for BLI_uniquename */
#ifndef GIVE_STRADDR
#define GIVE_STRADDR(data, offset) ( ((char *)data) + offset)
#endif
/* Generic function to set a unique name. It is only designed to be used in situations
* where the name is part of the struct, and also that the name is at most UNIQUE_NAME_MAX chars long.
*
* For places where this is used, see constraint.c for example...
*
* name_offs: should be calculated using offsetof(structname, membername) macro from stddef.h
* len: maximum length of string (to prevent overflows, etc.)
* defname: the name that should be used by default if none is specified already
* delim: the character which acts as a delimiter between parts of the name
*/
static bool uniquename_find_dupe(ListBase *list, void *vlink, const char *name, short name_offs)
{
Link *link;
for (link = list->first; link; link = link->next) {
if (link != vlink) {
if (!strcmp(GIVE_STRADDR(link, name_offs), name)) {
return true;
}
}
}
return false;
}
static bool uniquename_unique_check(void *arg, const char *name)
{
struct {ListBase *lb; void *vlink; short name_offs; } *data = arg;
return uniquename_find_dupe(data->lb, data->vlink, name, data->name_offs);
}
/**
* Ensures that the specified block has a unique name within the containing list,
* incrementing its numeric suffix as necessary.
*
* \param list List containing the block
* \param vlink The block to check the name for
* \param defname To initialize block name if latter is empty
* \param delim Delimits numeric suffix in name
* \param name_offs Offset of name within block structure
* \param name_len Maximum length of name area
*/
void BLI_uniquename(ListBase *list, void *vlink, const char *defname, char delim, short name_offs, short name_len)
{
struct {ListBase *lb; void *vlink; short name_offs; } data;
data.lb = list;
data.vlink = vlink;
data.name_offs = name_offs;
assert((name_len > 1) && (name_len <= UNIQUE_NAME_MAX));
/* See if we are given an empty string */
if (ELEM(NULL, vlink, defname))
return;
BLI_uniquename_cb(uniquename_unique_check, &data, defname, delim, GIVE_STRADDR(vlink, name_offs), name_len);
}
/* ******************** string encoding ***************** */
/* This is quite an ugly function... its purpose is to
* take the dir name, make it absolute, and clean it up, replacing
* excess file entry stuff (like /tmp/../tmp/../)
* note that dir isn't protected for max string names...
*
* If relbase is NULL then its ignored
*/
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
/* Note, this should really be moved to the file selector,
* since this function is used in many areas */
if (strcmp(path, ".") == 0) { /* happens for example in FILE_MAIN */
get_default_root(path);
return;
}
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);
}
while ( (start = strstr(path, "\\\\")) ) {
eind = start + strlen("\\\\") - 1;
memmove(start, eind, strlen(eind) + 1);
}
#else
if (path[0] == '.') { /* happens, for example in FILE_MAIN */
path[0] = '/';
path[1] = 0;
return;
}
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
}
void BLI_cleanup_dir(const char *relabase, char *dir)
{
BLI_cleanup_path(relabase, dir);
BLI_add_slash(dir);
}
void BLI_cleanup_file(const char *relabase, char *path)
{
BLI_cleanup_path(relabase, path);
BLI_del_slash(path);
}
/**
* 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] == '/';
}
/**
* 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 && relfile[1] != ':') {
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) {
if (temp[1] == ':' && file[1] == ':' && temp[0] != file[0])
return;
}
#else
BLI_strncpy(temp, relfile, FILE_MAX);
#endif
BLI_char_switch(temp, '\\', '/');
BLI_char_switch(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 */
char *p = temp;
char *q = file;
#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; }
}
strcpy(res, "//");
/* 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
*/
while (p && p < lslash) {
if (*p == '/')
strcat(res, "../");
p++;
}
strcat(res, q + 1); /* don't copy the slash at the beginning */
#ifdef WIN32
BLI_char_switch(res + 2, '/', '\\');
#endif
strcpy(file, res);
}
}
/**
* Cleans path and makes sure it ends with a slash.
* \return true if \a path has more than one other path separator in it.
*/
bool BLI_has_parent(char *path)
{
int len;
int slashes = 0;
BLI_clean(path);
len = BLI_add_slash(path) - 1;
while (len >= 0) {
if ((path[len] == '\\') || (path[len] == '/'))
slashes++;
len--;
}
return slashes > 1;
}
/**
* 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)
{
static char parent_dir[] = {'.', '.', SEP, '\0'}; /* "../" or "..\\" */
char tmp[FILE_MAX + 4];
BLI_strncpy(tmp, path, sizeof(tmp) - 4);
BLI_add_slash(tmp);
strcat(tmp, parent_dir);
BLI_cleanup_dir(NULL, tmp); /* does all the work of normalizing the path for us */
if (!BLI_testextensie(tmp, parent_dir)) {
BLI_strncpy(path, tmp, sizeof(tmp));
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)
{
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];
sprintf(tmp, "%.*s%.*d%s", ch_sta, path, ch_end - ch_sta, frame, path + ch_end);
strcpy(path, tmp);
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;
}
/**
* If path begins with "//", strips that and replaces it with basepath directory. Also converts
* a drive-letter prefix to something more sensible if this is a non-drive-letter-based system.
* Returns true if "//" prefix expansion was done.
*/
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
char vol[3] = {'\0', '\0', '\0'};
BLI_strncpy(vol, path, 3);
/* 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 && (vol[1] != ':' && (vol[0] == '\0' || vol[0] == '/' || vol[0] == '\\'))) {
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
BLI_strncpy(base, basepath, sizeof(base));
/* file component is ignored, so don't bother with the trailing slash */
BLI_cleanup_path(NULL, base);
/* 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. */
BLI_char_switch(tmp, '\\', '/');
BLI_char_switch(base, '\\', '/');
/* 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 * const lslash = BLI_last_slash(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);
}
BLI_cleanup_path(NULL, path);
#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_char_switch(path + 2, '/', '\\');
#endif
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)
{
bool wasrelative = true;
const int filelen = strlen(path);
#ifdef WIN32
if (filelen >= 3 && path[1] == ':' && (path[2] == '\\' || path[2] == '/'))
wasrelative = false;
#else
if (filelen >= 2 && path[0] == '/')
wasrelative = false;
#endif
if (wasrelative) {
char cwd[FILE_MAX] = "";
BLI_current_working_dir(cwd, sizeof(cwd)); /* in case the full path to the blend isn't used */
if (cwd[0] == '\0') {
printf("Could not get the current working directory - $PWD for an unknown reason.\n");
}
else {
/* uses the blend path relative to cwd important for loading relative linked files.
*
* cwd should contain c:\ etc on win32 so the relbase can be NULL
* relbase being NULL also prevents // being misunderstood as relative to the current
* blend file which isn't a feature we want to use in this case since were dealing
* with a path from the command line, rather than from inside Blender */
char origpath[FILE_MAX];
BLI_strncpy(origpath, path, FILE_MAX);
BLI_make_file_string(NULL, path, cwd, origpath);
}
}
return wasrelative;
}
/**
* Copies into *last the part of *dir following the second-last slash.
*/
void BLI_getlastdir(const char *dir, char *last, const size_t maxlen)
{
const char *s = dir;
const char *lslash = NULL;
const char *prevslash = NULL;
while (*s) {
if ((*s == '\\') || (*s == '/')) {
prevslash = lslash;
lslash = s;
}
s++;
}
if (prevslash) {
BLI_strncpy(last, prevslash + 1, maxlen);
}
else {
BLI_strncpy(last, dir, maxlen);
}
}
/* This is now only used to really get the user's default document folder */
/* On Windows I chose the 'Users/<MyUserName>/Documents' since it's used
* as default location to save documents */
const char *BLI_getDefaultDocumentFolder(void)
{
#ifndef WIN32
const char * const xdg_documents_dir = getenv("XDG_DOCUMENTS_DIR");
if (xdg_documents_dir)
return xdg_documents_dir;
return getenv("HOME");
#else /* Windows */
static char documentfolder[MAXPATHLEN];
HRESULT hResult;
/* Check for %HOME% env var */
if (uput_getenv("HOME", documentfolder, MAXPATHLEN)) {
if (BLI_is_dir(documentfolder)) return documentfolder;
}
/* add user profile support for WIN 2K / NT.
* This is %APPDATA%, which translates to either
* %USERPROFILE%\Application Data or since Vista
* to %USERPROFILE%\AppData\Roaming
*/
hResult = SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, documentfolder);
if (hResult == S_OK) {
if (BLI_is_dir(documentfolder)) return documentfolder;
}
return NULL;
#endif /* WIN32 */
}
/* NEW stuff, to be cleaned up when fully migrated */
/* ************************************************************* */
/* ************************************************************* */
// #define PATH_DEBUG
/* returns a formatted representation of the specified version number. Non-reentrant! */
static char *blender_version_decimal(const int ver)
{
static char version_str[5];
sprintf(version_str, "%d.%02d", ver / 100, ver % 100);
return version_str;
}
/**
* Concatenates path_base, (optional) path_sep and (optional) folder_name into targetpath,
* returning true if result points to a directory.
*/
static bool test_path(char *targetpath, const char *path_base, const char *path_sep, const char *folder_name)
{
char tmppath[FILE_MAX];
if (path_sep) BLI_join_dirfile(tmppath, sizeof(tmppath), path_base, path_sep);
else BLI_strncpy(tmppath, path_base, sizeof(tmppath));
/* rare cases folder_name is omitted (when looking for ~/.blender/2.xx dir only) */
if (folder_name)
BLI_make_file_string("/", targetpath, tmppath, folder_name);
else
BLI_strncpy(targetpath, tmppath, sizeof(tmppath));
/* FIXME: why is "//" on front of tmppath expanded to "/" (by BLI_join_dirfile)
* if folder_name is specified but not otherwise? */
if (BLI_is_dir(targetpath)) {
#ifdef PATH_DEBUG
printf("\t%s found: %s\n", __func__, targetpath);
#endif
return true;
}
else {
#ifdef PATH_DEBUG
printf("\t%s missing: %s\n", __func__, targetpath);
#endif
//targetpath[0] = '\0';
return false;
}
}
/**
* Puts the value of the specified environment variable into *path if it exists
* and points at a directory. Returns true if this was done.
*/
static bool test_env_path(char *path, const char *envvar)
{
const char *env = envvar ? getenv(envvar) : NULL;
if (!env) return false;
if (BLI_is_dir(env)) {
BLI_strncpy(path, env, FILE_MAX);
#ifdef PATH_DEBUG
printf("\t%s env %s found: %s\n", __func__, envvar, env);
#endif
return true;
}
else {
path[0] = '\0';
#ifdef PATH_DEBUG
printf("\t%s env %s missing: %s\n", __func__, envvar, env);
#endif
return false;
}
}
/**
* Constructs in \a targetpath the name of a directory relative to a version-specific
* subdirectory in the parent directory of the Blender executable.
*
* \param targetpath String to return path
* \param folder_name Optional folder name within version-specific directory
* \param subfolder_name Optional subfolder name within folder_name
* \param ver To construct name of version-specific directory within bprogdir
* \return true if such a directory exists.
*/
static bool get_path_local(char *targetpath, const char *folder_name, const char *subfolder_name, const int ver)
{
char relfolder[FILE_MAX];
#ifdef PATH_DEBUG
printf("%s...\n", __func__);
#endif
if (folder_name) {
if (subfolder_name) {
BLI_join_dirfile(relfolder, sizeof(relfolder), folder_name, subfolder_name);
}
else {
BLI_strncpy(relfolder, folder_name, sizeof(relfolder));
}
}
else {
relfolder[0] = '\0';
}
/* try EXECUTABLE_DIR/2.5x/folder_name - new default directory for local blender installed files */
return test_path(targetpath, bprogdir, blender_version_decimal(ver), relfolder);
}
/**
* Is this an install with user files kept together with the Blender executable and its
* installation files.
*/
static bool is_portable_install(void)
{
/* detect portable install by the existence of config folder */
const int ver = BLENDER_VERSION;
char path[FILE_MAX];
return get_path_local(path, "config", NULL, ver);
}
/**
* Returns the path of a folder within the user-files area.
*
*
* \param targetpath String to return path
* \param folder_name default name of folder within user area
* \param subfolder_name optional name of subfolder within folder
* \param envvar name of environment variable which, if defined, overrides folder_name
* \param ver Blender version, used to construct a subdirectory name
* \return true if it was able to construct such a path.
*/
static bool get_path_user(char *targetpath, const char *folder_name, const char *subfolder_name, const char *envvar, const int ver)
{
char user_path[FILE_MAX];
const char *user_base_path;
/* for portable install, user path is always local */
if (is_portable_install())
return get_path_local(targetpath, folder_name, subfolder_name, ver);
user_path[0] = '\0';
if (test_env_path(user_path, envvar)) {
if (subfolder_name) {
return test_path(targetpath, user_path, NULL, subfolder_name);
}
else {
BLI_strncpy(targetpath, user_path, FILE_MAX);
return true;
}
}
user_base_path = (const char *)GHOST_getUserDir(ver, blender_version_decimal(ver));
if (user_base_path)
BLI_strncpy(user_path, user_base_path, FILE_MAX);
if (!user_path[0])
return false;
#ifdef PATH_DEBUG
printf("%s: %s\n", __func__, user_path);
#endif
if (subfolder_name) {
return test_path(targetpath, user_path, folder_name, subfolder_name);
}
else {
return test_path(targetpath, user_path, NULL, folder_name);
}
}
/**
* Returns the path of a folder within the Blender installation directory.
*
* \param targetpath String to return path
* \param folder_name default name of folder within installation area
* \param subfolder_name optional name of subfolder within folder
* \param envvar name of environment variable which, if defined, overrides folder_name
* \param ver Blender version, used to construct a subdirectory name
* \return true if it was able to construct such a path.
*/
static bool get_path_system(char *targetpath, const char *folder_name, const char *subfolder_name, const char *envvar, const int ver)
{
char system_path[FILE_MAX];
const char *system_base_path;
char cwd[FILE_MAX];
char relfolder[FILE_MAX];
if (folder_name) {
if (subfolder_name) {
BLI_join_dirfile(relfolder, sizeof(relfolder), folder_name, subfolder_name);
}
else {
BLI_strncpy(relfolder, folder_name, sizeof(relfolder));
}
}
else {
relfolder[0] = '\0';
}
/* first allow developer only overrides to the system path
* these are only used when running blender from source */
/* try CWD/release/folder_name */
if (BLI_current_working_dir(cwd, sizeof(cwd))) {
if (test_path(targetpath, cwd, "release", relfolder)) {
return true;
}
}
/* try EXECUTABLE_DIR/release/folder_name */
if (test_path(targetpath, bprogdir, "release", relfolder))
return true;
/* end developer overrides */
system_path[0] = '\0';
if (test_env_path(system_path, envvar)) {
if (subfolder_name) {
return test_path(targetpath, system_path, NULL, subfolder_name);
}
else {
BLI_strncpy(targetpath, system_path, FILE_MAX);
return true;
}
}
system_base_path = (const char *)GHOST_getSystemDir(ver, blender_version_decimal(ver));
if (system_base_path)
BLI_strncpy(system_path, system_base_path, FILE_MAX);
if (!system_path[0])
return false;
#ifdef PATH_DEBUG
printf("%s: %s\n", __func__, system_path);
#endif
if (subfolder_name) {
/* try $BLENDERPATH/folder_name/subfolder_name */
return test_path(targetpath, system_path, folder_name, subfolder_name);
}
else {
/* try $BLENDERPATH/folder_name */
return test_path(targetpath, system_path, NULL, folder_name);
}
}
/* get a folder out of the 'folder_id' presets for paths */
/* returns the path if found, NULL string if not */
const char *BLI_get_folder(int folder_id, const char *subfolder)
{
const int ver = BLENDER_VERSION;
static char path[FILE_MAX] = "";
switch (folder_id) {
case BLENDER_DATAFILES: /* general case */
if (get_path_user(path, "datafiles", subfolder, "BLENDER_USER_DATAFILES", ver)) break;
if (get_path_local(path, "datafiles", subfolder, ver)) break;
if (get_path_system(path, "datafiles", subfolder, "BLENDER_SYSTEM_DATAFILES", ver)) break;
return NULL;
case BLENDER_USER_DATAFILES:
if (get_path_user(path, "datafiles", subfolder, "BLENDER_USER_DATAFILES", ver)) break;
return NULL;
case BLENDER_SYSTEM_DATAFILES:
if (get_path_local(path, "datafiles", subfolder, ver)) break;
if (get_path_system(path, "datafiles", subfolder, "BLENDER_SYSTEM_DATAFILES", ver)) break;
return NULL;
case BLENDER_USER_AUTOSAVE:
if (get_path_user(path, "autosave", subfolder, "BLENDER_USER_DATAFILES", ver)) break;
return NULL;
case BLENDER_USER_CONFIG:
if (get_path_user(path, "config", subfolder, "BLENDER_USER_CONFIG", ver)) break;
return NULL;
case BLENDER_USER_SCRIPTS:
if (get_path_user(path, "scripts", subfolder, "BLENDER_USER_SCRIPTS", ver)) break;
return NULL;
case BLENDER_SYSTEM_SCRIPTS:
if (get_path_local(path, "scripts", subfolder, ver)) break;
if (get_path_system(path, "scripts", subfolder, "BLENDER_SYSTEM_SCRIPTS", ver)) break;
return NULL;
case BLENDER_SYSTEM_PYTHON:
if (get_path_local(path, "python", subfolder, ver)) break;
if (get_path_system(path, "python", subfolder, "BLENDER_SYSTEM_PYTHON", ver)) break;
return NULL;
}
return path;
}
/**
* Returns the path to a folder in the user area without checking that it actually exists first.
*/
const char *BLI_get_user_folder_notest(int folder_id, const char *subfolder)
{
const int ver = BLENDER_VERSION;
static char path[FILE_MAX] = "";
switch (folder_id) {
case BLENDER_USER_DATAFILES:
get_path_user(path, "datafiles", subfolder, "BLENDER_USER_DATAFILES", ver);
break;
case BLENDER_USER_CONFIG:
get_path_user(path, "config", subfolder, "BLENDER_USER_CONFIG", ver);
break;
case BLENDER_USER_AUTOSAVE:
get_path_user(path, "autosave", subfolder, "BLENDER_USER_AUTOSAVE", ver);
break;
case BLENDER_USER_SCRIPTS:
get_path_user(path, "scripts", subfolder, "BLENDER_USER_SCRIPTS", ver);
break;
}
if ('\0' == path[0]) {
return NULL;
}
return path;
}
/**
* Returns the path to a folder in the user area, creating it if it doesn't exist.
*/
const char *BLI_get_folder_create(int folder_id, const char *subfolder)
{
const char *path;
/* only for user folders */
if (!ELEM4(folder_id, BLENDER_USER_DATAFILES, BLENDER_USER_CONFIG, BLENDER_USER_SCRIPTS, BLENDER_USER_AUTOSAVE))
return NULL;
path = BLI_get_folder(folder_id, subfolder);
if (!path) {
path = BLI_get_user_folder_notest(folder_id, subfolder);
if (path) BLI_dir_create_recursive(path);
}
return path;
}
/**
* Returns the path of the top-level version-specific local, user or system directory.
* If do_check, then the result will be NULL if the directory doesn't exist.
*/
const char *BLI_get_folder_version(const int id, const int ver, const bool do_check)
{
static char path[FILE_MAX] = "";
bool ok;
switch (id) {
case BLENDER_RESOURCE_PATH_USER:
ok = get_path_user(path, NULL, NULL, NULL, ver);
break;
case BLENDER_RESOURCE_PATH_LOCAL:
ok = get_path_local(path, NULL, NULL, ver);
break;
case BLENDER_RESOURCE_PATH_SYSTEM:
ok = get_path_system(path, NULL, NULL, NULL, ver);
break;
default:
path[0] = '\0'; /* in case do_check is false */
ok = false;
BLI_assert(!"incorrect ID");
}
if (!ok && do_check) {
return NULL;
}
return path;
}
/* End new stuff */
/* ************************************************************* */
/* ************************************************************* */
#ifdef PATH_DEBUG
# undef PATH_DEBUG
#endif
/**
* Sets the specified environment variable to the specified value.
*/
void BLI_setenv(const char *env, const char *val)
{
/* free windows */
#if (defined(WIN32) || defined(WIN64)) && defined(FREE_WINDOWS)
char *envstr = MEM_mallocN(sizeof(char) * (strlen(env) + strlen(val) + 2), "envstr"); /* one for = another for \0 */
sprintf(envstr, "%s=%s", env, val);
putenv(envstr);
MEM_freeN(envstr);
/* non-free windows */
#elif (defined(WIN32) || defined(WIN64)) /* not free windows */
uputenv(env, val);
#else
/* linux/osx/bsd */
setenv(env, val, 1);
#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);
}
/**
* Changes to the path separators to the native ones for this OS.
*/
void BLI_clean(char *path)
{
#ifdef WIN32
if (path && BLI_strnlen(path, 3) > 2) {
BLI_char_switch(path + 2, '/', '\\');
}
#else
BLI_char_switch(path, '\\', '/');
#endif
}
/**
* Change every \a from in \a string into \a to. The
* result will be in \a string
*
* \param string The string to work on
* \param from The character to replace
* \param to The character to replace with
*/
void BLI_char_switch(char *string, char from, char to)
{
while (*string != 0) {
if (*string == from) *string = to;
string++;
}
}
/**
* Strips off nonexistent subdirectories from the end of *dir, leaving the path of
* the lowest-level directory that does exist.
*/
void BLI_make_exist(char *dir)
{
int a;
BLI_char_switch(dir, ALTSEP, SEP);
a = strlen(dir);
while (!BLI_is_dir(dir)) {
a--;
while (dir[a] != SEP) {
a--;
if (a <= 0) break;
}
if (a >= 0) {
dir[a + 1] = '\0';
}
else {
#ifdef WIN32
get_default_root(dir);
#else
strcpy(dir, "/");
#endif
break;
}
}
}
/**
* Ensures that the parent directory of *name exists.
*/
void BLI_make_existing_file(const char *name)
{
char di[FILE_MAX];
BLI_split_dir_part(name, di, sizeof(di));
/* make if if the dir doesn't exist */
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_char_switch(relabase, '\\', '/');
BLI_char_switch(dir, '\\', '/');
BLI_char_switch(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 { /* no drive specified */
/* first option: get the drive from the relabase if it has one */
if (relabase && strlen(relabase) >= 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_clean(string);
}
/* does str end with ext. */
bool BLI_testextensie(const char *str, const char *ext)
{
const size_t a = strlen(str);
const size_t b = strlen(ext);
return !(a == 0 || b == 0 || b >= a) && (BLI_strcasecmp(ext, str + a - b) == 0);
}
/* does str end with any of the suffixes in *ext_array. */
bool BLI_testextensie_array(const char *str, const char **ext_array)
{
int i = 0;
while (ext_array[i]) {
if (BLI_testextensie(str, 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]) {
char *ext_next;
int len_ext;
if ((ext_next = strchr(ext_step, ';'))) {
len_ext = (int)(ext_next - ext_step) + 1;
}
else {
len_ext = sizeof(pattern);
}
BLI_strncpy(pattern, ext_step, len_ext);
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 (ELEM3(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) &&
(strcmp(path + (path_len - ext_len), ext) == 0))
{
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;
}
/* 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);
}
/**
* 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 *dst, const size_t maxlen, const char *dir, const char *file)
{
size_t dirlen = BLI_strnlen(dir, maxlen);
if (dst != dir) {
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) && (dst[dirlen - 1] != SEP)) {
dst[dirlen++] = SEP;
dst[dirlen] = '\0';
}
if (dirlen >= maxlen) {
return; /* fills the path */
}
if (file == NULL) {
return;
}
BLI_strncpy(dst + dirlen, file, maxlen - dirlen);
}
/**
* 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;
}
/**
* 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_join_dirfile(dest_path, sizeof(dest_path), 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;
}
/**
* Returns pointer to the leftmost path separator in string. Not actually used anywhere.
*/
const char *BLI_first_slash(const char *string)
{
char * const ffslash = strchr(string, '/');
char * const fbslash = strchr(string, '\\');
if (!ffslash) return fbslash;
else if (!fbslash) return ffslash;
if ((intptr_t)ffslash < (intptr_t)fbslash) return ffslash;
else return 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;
if ((intptr_t)lfslash < (intptr_t)lbslash) return lbslash;
else return lfslash;
}
/**
* 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;
}
}
}
/**
* 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.
*/
static int add_win32_extension(char *name)
{
int retval = 0;
int type;
type = BLI_exists(name);
if ((type == 0) || S_ISDIR(type)) {
#ifdef _WIN32
char filename[FILE_MAX];
char ext[FILE_MAX];
const char *extensions = getenv("PATHEXT");
if (extensions) {
char *temp;
do {
strcpy(filename, name);
temp = strstr(extensions, ";");
if (temp) {
strncpy(ext, extensions, temp - extensions);
ext[temp - extensions] = 0;
extensions = temp + 1;
strcat(filename, ext);
}
else {
strcat(filename, extensions);
}
type = BLI_exists(filename);
if (type && (!S_ISDIR(type))) {
retval = 1;
strcpy(name, filename);
break;
}
} while (temp);
}
#endif
}
else {
retval = 1;
}
return (retval);
}
/**
* Checks if name is a fully qualified filename to an executable.
* If not it searches $PATH for the file. On Windows it also
* adds the correct extension (.com .exe etc) from
* $PATHEXT if necessary. Also on Windows it translates
* the name to its 8.3 version to prevent problems with
* spaces and stuff. Final result is returned in fullname.
*
* \param fullname The full path and full name of the executable
* (must be FILE_MAX minimum)
* \param name The name of the executable (usually argv[0]) to be checked
*/
static void bli_where_am_i(char *fullname, const size_t maxlen, const char *name)
{
char filename[FILE_MAX];
const char *path = NULL, *temp;
#ifdef _WIN32
const char *separator = ";";
#else
const char *separator = ":";
#endif
#ifdef WITH_BINRELOC
/* linux uses binreloc since argv[0] is not reliable, call br_init( NULL ) first */
path = br_find_exe(NULL);
if (path) {
BLI_strncpy(fullname, path, maxlen);
free((void *)path);
return;
}
#endif
#ifdef _WIN32
wchar_t *fullname_16 = MEM_mallocN(maxlen * sizeof(wchar_t), "ProgramPath");
if (GetModuleFileNameW(0, fullname_16, maxlen)) {
conv_utf_16_to_8(fullname_16, fullname, maxlen);
if (!BLI_exists(fullname)) {
printf("path can't be found: \"%.*s\"\n", maxlen, fullname);
MessageBox(NULL, "path contains invalid characters or is too long (see console)", "Error", MB_OK);
}
MEM_freeN(fullname_16);
return;
}
MEM_freeN(fullname_16);
#endif
/* unix and non linux */
if (name && name[0]) {
BLI_strncpy(fullname, name, maxlen);
if (name[0] == '.') {
char wdir[FILE_MAX] = "";
BLI_current_working_dir(wdir, sizeof(wdir)); /* backup cwd to restore after */
// not needed but avoids annoying /./ in name
if (name[1] == SEP)
BLI_join_dirfile(fullname, maxlen, wdir, name + 2);
else
BLI_join_dirfile(fullname, maxlen, wdir, name);
add_win32_extension(fullname); /* XXX, doesnt respect length */
}
else if (BLI_last_slash(name)) {
// full path
BLI_strncpy(fullname, name, maxlen);
add_win32_extension(fullname);
}
else {
// search for binary in $PATH
path = getenv("PATH");
if (path) {
do {
temp = strstr(path, separator);
if (temp) {
strncpy(filename, path, temp - path);
filename[temp - path] = 0;
path = temp + 1;
}
else {
strncpy(filename, path, sizeof(filename));
}
BLI_join_dirfile(fullname, maxlen, fullname, name);
if (add_win32_extension(filename)) {
BLI_strncpy(fullname, filename, maxlen);
break;
}
} while (temp);
}
}
#if defined(DEBUG)
if (strcmp(name, fullname)) {
printf("guessing '%s' == '%s'\n", name, fullname);
}
#endif
}
}
void BLI_init_program_path(const char *argv0)
{
bli_where_am_i(bprogname, sizeof(bprogname), argv0);
BLI_split_dir_part(bprogname, bprogdir, sizeof(bprogdir));
}
/**
* Path to executable
*/
const char *BLI_program_path(void)
{
return bprogname;
}
/**
* Path to directory of executable
*/
const char *BLI_program_dir(void)
{
return bprogdir;
}
/**
* Gets the temp directory when blender first runs.
* If the default path is not found, use try $TEMP
*
* Also make sure the temp dir has a trailing slash
*
* \param fullname The full path to the temp directory
* \param maxlen The size of the fullname buffer
* \param userdir Directory specified in user preferences
*/
static void BLI_where_is_temp(char *fullname, const size_t maxlen, char *userdir)
{
fullname[0] = '\0';
if (userdir && BLI_is_dir(userdir)) {
BLI_strncpy(fullname, userdir, maxlen);
}
#ifdef WIN32
if (fullname[0] == '\0') {
const char *tmp = getenv("TEMP"); /* Windows */
if (tmp && BLI_is_dir(tmp)) {
BLI_strncpy(fullname, tmp, maxlen);
}
}
#else
/* Other OS's - Try TMP and TMPDIR */
if (fullname[0] == '\0') {
const char *tmp = getenv("TMP");
if (tmp && BLI_is_dir(tmp)) {
BLI_strncpy(fullname, tmp, maxlen);
}
}
if (fullname[0] == '\0') {
const char *tmp = getenv("TMPDIR");
if (tmp && BLI_is_dir(tmp)) {
BLI_strncpy(fullname, tmp, maxlen);
}
}
#endif
if (fullname[0] == '\0') {
BLI_strncpy(fullname, "/tmp/", maxlen);
}
else {
/* add a trailing slash if needed */
BLI_add_slash(fullname);
#ifdef WIN32
if (userdir && userdir != fullname) {
BLI_strncpy(userdir, fullname, maxlen); /* also set user pref to show %TEMP%. /tmp/ is just plain confusing for Windows users. */
}
#endif
}
}
/**
* Sets btempdir to userdir if specified and is a valid directory, otherwise
* chooses a suitable OS-specific temporary directory.
*/
void BLI_init_temporary_dir(char *userdir)
{
BLI_where_is_temp(btempdir, FILE_MAX, userdir);
}
/**
* Path to temporary directory (with trailing slash)
*/
const char *BLI_temporary_dir(void)
{
return btempdir;
}
/**
* Path to the system temporary directory (with trailing slash)
*/
void BLI_system_temporary_dir(char *dir)
{
BLI_where_is_temp(dir, FILE_MAX, NULL);
}
#ifdef WITH_ICONV
/**
* Converts a string encoded in the charset named by *code to UTF-8.
* Opens a new iconv context each time it is run, which is probably not the
* most efficient. */
void BLI_string_to_utf8(char *original, char *utf_8, const char *code)
{
size_t inbytesleft = strlen(original);
size_t outbytesleft = 512;
size_t rv = 0;
iconv_t cd;
if (NULL == code) {
code = locale_charset();
}
cd = iconv_open("UTF-8", code);
if (cd == (iconv_t)(-1)) {
printf("iconv_open Error");
*utf_8 = '\0';
return;
}
rv = iconv(cd, &original, &inbytesleft, &utf_8, &outbytesleft);
if (rv == (size_t) -1) {
printf("iconv Error\n");
iconv_close(cd);
return;
}
*utf_8 = '\0';
iconv_close(cd);
}
#endif // WITH_ICONV