Rigorously treat paths on every platform.
After this commit, SolveSpace deals with paths as follows: * Paths are generally treated as opaque platform-specific strings. This helps on Linux, because paths on Linux don't have any specific encoding and it helps to avoid any operations on them. * The UI in some places wants to get a basename. In this case, the newly introduced PATH_SEP is used. This allows to treat backslash as a regular character, which it is on Linux and OS X. * The only place where any nontrivial operations on paths are performed is the g->impFile/impFileRel logic. Specifically, when saved, g->impFile always contains an absolute path with separators of the current platform, and g->impFileRel always contains a relative path with UNIX separators. This allows to treat backslash as a regular character. Old files will contain g->impFileRel with Windows separators; these are detected by looking for a drive letter in g->impFile and in that case mapping Windows separators to UNIX ones. There is no need to treat UNIX separators (forward slashes) in any special way on Windows because there is no way on Windows, not even via UNC paths, to create or address a directory entry with a forward slash in its name.
This commit is contained in:
parent
32383d22bf
commit
5c9c32cfc7
|
@ -873,12 +873,7 @@ void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const std::string &filename, S
|
|||
|
||||
std::string baseFilename = filename;
|
||||
|
||||
#ifdef WIN32
|
||||
size_t lastSlash = baseFilename.rfind('\\');
|
||||
#else
|
||||
size_t lastSlash = baseFilename.rfind('/');
|
||||
#endif
|
||||
|
||||
size_t lastSlash = baseFilename.rfind(PATH_SEP);
|
||||
if(lastSlash == std::string::npos) oops();
|
||||
baseFilename.erase(0, lastSlash + 1);
|
||||
|
||||
|
|
153
src/file.cpp
153
src/file.cpp
|
@ -640,7 +640,135 @@ bool SolveSpaceUI::LoadEntitiesFromFile(const std::string &filename, EntityList
|
|||
return true;
|
||||
}
|
||||
|
||||
void SolveSpaceUI::ReloadAllImported(void) {
|
||||
//-----------------------------------------------------------------------------
|
||||
// Handling of the relative-absolute path transformations for imports
|
||||
//-----------------------------------------------------------------------------
|
||||
static std::vector<std::string> Split(const std::string &haystack, const std::string &needle)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
|
||||
size_t oldpos = 0, pos = 0;
|
||||
while(true) {
|
||||
oldpos = pos;
|
||||
pos = haystack.find(needle, pos);
|
||||
if(pos == std::string::npos) break;
|
||||
result.push_back(haystack.substr(oldpos, pos - oldpos));
|
||||
pos += needle.length();
|
||||
}
|
||||
|
||||
if(oldpos != haystack.length() - 1)
|
||||
result.push_back(haystack.substr(oldpos));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::string Join(const std::vector<std::string> &parts, const std::string &separator)
|
||||
{
|
||||
bool first = true;
|
||||
std::string result;
|
||||
for(auto &part : parts) {
|
||||
if(!first) result += separator;
|
||||
result += part;
|
||||
first = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool PlatformPathEqual(const std::string &a, const std::string &b)
|
||||
{
|
||||
// Case-sensitivity is actually per-volume on both Windows and OS X,
|
||||
// but it is extremely tedious to implement and test for little benefit.
|
||||
#if defined(WIN32)
|
||||
std::wstring wa = Widen(a), wb = Widen(b);
|
||||
return std::equal(wa.begin(), wa.end(), wb.begin(), /*wb.end(),*/
|
||||
[](wchar_t wca, wchar_t wcb) { return towlower(wca) == towlower(wcb); });
|
||||
#elif defined(__APPLE__)
|
||||
return !strcasecmp(a.c_str(), b.c_str());
|
||||
#else
|
||||
return a == b;
|
||||
#endif
|
||||
}
|
||||
|
||||
static std::string MakePathRelative(const std::string &base, const std::string &path)
|
||||
{
|
||||
std::vector<std::string> baseParts = Split(base, PATH_SEP),
|
||||
pathParts = Split(path, PATH_SEP),
|
||||
resultParts;
|
||||
baseParts.pop_back();
|
||||
|
||||
int common;
|
||||
for(common = 0; common < baseParts.size() && common < pathParts.size(); common++) {
|
||||
if(!PlatformPathEqual(baseParts[common], pathParts[common]))
|
||||
break;
|
||||
}
|
||||
|
||||
for(int i = common; i < baseParts.size(); i++)
|
||||
resultParts.push_back("..");
|
||||
|
||||
resultParts.insert(resultParts.end(),
|
||||
pathParts.begin() + common, pathParts.end());
|
||||
|
||||
return Join(resultParts, PATH_SEP);
|
||||
}
|
||||
|
||||
static std::string MakePathAbsolute(const std::string &base, const std::string &path)
|
||||
{
|
||||
std::vector<std::string> resultParts = Split(base, PATH_SEP),
|
||||
pathParts = Split(path, PATH_SEP);
|
||||
resultParts.pop_back();
|
||||
|
||||
for(auto &part : pathParts) {
|
||||
if(part == ".") {
|
||||
/* do nothing */
|
||||
} else if(part == "..") {
|
||||
if(resultParts.empty()) oops();
|
||||
resultParts.pop_back();
|
||||
} else {
|
||||
resultParts.push_back(part);
|
||||
}
|
||||
}
|
||||
|
||||
return Join(resultParts, PATH_SEP);
|
||||
}
|
||||
|
||||
static void PathSepNormalize(std::string &filename)
|
||||
{
|
||||
for(int i = 0; i < filename.length(); i++) {
|
||||
if(filename[i] == '\\')
|
||||
filename[i] = '/';
|
||||
}
|
||||
}
|
||||
|
||||
static std::string PathSepPlatformToUNIX(const std::string &filename)
|
||||
{
|
||||
#if defined(WIN32)
|
||||
std::string result = filename;
|
||||
for(int i = 0; i < result.length(); i++) {
|
||||
if(result[i] == '\\')
|
||||
result[i] = '/';
|
||||
}
|
||||
return result;
|
||||
#else
|
||||
return filename;
|
||||
#endif
|
||||
}
|
||||
|
||||
static std::string PathSepUNIXToPlatform(const std::string &filename)
|
||||
{
|
||||
#if defined(WIN32)
|
||||
std::string result = filename;
|
||||
for(int i = 0; i < result.length(); i++) {
|
||||
if(result[i] == '/')
|
||||
result[i] = '\\';
|
||||
}
|
||||
return result;
|
||||
#else
|
||||
return filename;
|
||||
#endif
|
||||
}
|
||||
|
||||
void SolveSpaceUI::ReloadAllImported(void)
|
||||
{
|
||||
allConsistent = false;
|
||||
|
||||
int i;
|
||||
|
@ -648,15 +776,12 @@ void SolveSpaceUI::ReloadAllImported(void) {
|
|||
Group *g = &(SK.group.elem[i]);
|
||||
if(g->type != Group::IMPORTED) continue;
|
||||
|
||||
#ifndef WIN32
|
||||
// Change backslashes to forward slashes on Unix.
|
||||
// Done unconditionally to get the displayed filename
|
||||
// consistent with current filesystem type.
|
||||
for(int j = 0; j < g->impFileRel.length(); j++) {
|
||||
if(g->impFileRel[j] == '\\')
|
||||
g->impFileRel[j] = '/';
|
||||
if(isalpha(g->impFile[0]) && g->impFile[1] == ':') {
|
||||
// Make sure that g->impFileRel always contains a relative path
|
||||
// in an UNIX format, even after we load an old file which had
|
||||
// the path in Windows format
|
||||
PathSepNormalize(g->impFileRel);
|
||||
}
|
||||
#endif
|
||||
|
||||
g->impEntity.Clear();
|
||||
g->impMesh.Clear();
|
||||
|
@ -669,7 +794,8 @@ void SolveSpaceUI::ReloadAllImported(void) {
|
|||
// It doesn't exist. Perhaps the entire tree has moved, and we
|
||||
// can use the relative filename to get us back.
|
||||
if(!SS.saveFile.empty()) {
|
||||
std::string fromRel = MakePathAbsolute(SS.saveFile, g->impFileRel);
|
||||
std::string rel = PathSepUNIXToPlatform(g->impFileRel);
|
||||
std::string fromRel = MakePathAbsolute(SS.saveFile, rel);
|
||||
test = fopen(fromRel.c_str(), "rb");
|
||||
if(test) {
|
||||
fclose(test);
|
||||
|
@ -684,9 +810,12 @@ void SolveSpaceUI::ReloadAllImported(void) {
|
|||
if(!SS.saveFile.empty()) {
|
||||
// Record the imported file's name relative to our filename;
|
||||
// if the entire tree moves, then everything will still work
|
||||
g->impFileRel = MakePathRelative(SS.saveFile, g->impFile);
|
||||
std::string rel = MakePathRelative(SS.saveFile, g->impFile);
|
||||
g->impFileRel = PathSepPlatformToUNIX(rel);
|
||||
} else {
|
||||
// We're not yet saved, so can't make it absolute
|
||||
// We're not yet saved, so can't make it absolute.
|
||||
// This will only be used for display purposes, as SS.saveFile
|
||||
// is always nonempty when we are actually writing anything.
|
||||
g->impFileRel = g->impFile;
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -196,15 +196,13 @@ void Group::MenuGroup(int id) {
|
|||
std::string groupName = g.impFile;
|
||||
size_t pos;
|
||||
|
||||
pos = groupName.rfind(PATH_SEP);
|
||||
if(pos != std::string::npos)
|
||||
groupName.erase(0, pos + 1);
|
||||
|
||||
pos = groupName.rfind('.');
|
||||
if(pos != std::string::npos)
|
||||
groupName.erase(pos);
|
||||
pos = groupName.find('/');
|
||||
if(pos != std::string::npos)
|
||||
groupName.erase(0, pos);
|
||||
pos = groupName.find('\\');
|
||||
if(pos != std::string::npos)
|
||||
groupName.erase(0, pos);
|
||||
|
||||
for(int i = 0; i < groupName.length(); i++) {
|
||||
if(isalnum(groupName[i])) {
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <limits.h>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#ifdef HAVE_STDINT_H
|
||||
# include <stdint.h>
|
||||
#endif
|
||||
|
@ -25,6 +26,7 @@
|
|||
# include <windows.h> // required by GL headers
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
# include <strings.h> // for strcasecmp in file.cpp
|
||||
# include <OpenGL/gl.h>
|
||||
# include <OpenGL/glu.h>
|
||||
#else
|
||||
|
@ -116,6 +118,12 @@ class RgbaColor;
|
|||
|
||||
//================
|
||||
// From the platform-specific code.
|
||||
#if defined(WIN32)
|
||||
#define PATH_SEP "\\"
|
||||
#else
|
||||
#define PATH_SEP "/"
|
||||
#endif
|
||||
|
||||
#define MAX_RECENT 8
|
||||
#define RECENT_OPEN (0xf000)
|
||||
#define RECENT_IMPORT (0xf100)
|
||||
|
@ -194,7 +202,6 @@ int LoadAutosaveYesNo(void);
|
|||
#define CSV_EXT "csv"
|
||||
bool GetSaveFile(std::string &filename, const char *defExtension, const char *selPattern);
|
||||
bool GetOpenFile(std::string &filename, const char *defExtension, const char *selPattern);
|
||||
std::string GetAbsoluteFilename(const std::string &filename);
|
||||
void LoadAllFontFiles(void);
|
||||
|
||||
void OpenWebsite(const char *url);
|
||||
|
@ -330,8 +337,6 @@ void MakeMatrix(double *mat, double a11, double a12, double a13, double a14,
|
|||
double a21, double a22, double a23, double a24,
|
||||
double a31, double a32, double a33, double a34,
|
||||
double a41, double a42, double a43, double a44);
|
||||
std::string MakePathRelative(const std::string &base, const std::string &path);
|
||||
std::string MakePathAbsolute(const std::string &base, const std::string &path);
|
||||
bool MakeAcceleratorLabel(int accel, char *out);
|
||||
bool StringAllPrintable(const char *str);
|
||||
bool FilenameHasExtension(const std::string &str, const char *ext);
|
||||
|
|
|
@ -229,13 +229,10 @@ void TtfFont::LoadGlyph(int index) {
|
|||
// entities that reference us will store it.
|
||||
//-----------------------------------------------------------------------------
|
||||
std::string TtfFont::FontFileBaseName(void) {
|
||||
size_t pos;
|
||||
pos = fontFile.rfind('/');
|
||||
std::string baseName = fontFile;
|
||||
size_t pos = baseName.rfind(PATH_SEP);
|
||||
if(pos != std::string::npos)
|
||||
return fontFile.erase(0, pos);
|
||||
pos = fontFile.rfind('\\');
|
||||
if(pos != std::string::npos)
|
||||
return fontFile.erase(0, pos);
|
||||
return baseName.erase(0, pos + 1);
|
||||
return "";
|
||||
}
|
||||
|
||||
|
|
|
@ -30,20 +30,6 @@ void dbp(const char *str, ...)
|
|||
fputc('\n', stderr);
|
||||
}
|
||||
|
||||
std::string GetAbsoluteFilename(const std::string &filename)
|
||||
{
|
||||
// We rely on the POSIX.1-2008 version of realpath that allocates
|
||||
// storage for itself; the POSIX.1-2001 version is broken by design.
|
||||
char *expanded = realpath(filename.c_str(), NULL);
|
||||
if(expanded) {
|
||||
std::string result(expanded);
|
||||
free(expanded);
|
||||
return result;
|
||||
} else {
|
||||
return filename;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t GetUnixTime(void)
|
||||
{
|
||||
time_t ret;
|
||||
|
|
75
src/util.cpp
75
src/util.cpp
|
@ -6,81 +6,6 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
#include "solvespace.h"
|
||||
|
||||
std::string SolveSpace::MakePathRelative(const std::string &basep, const std::string &pathp)
|
||||
{
|
||||
int i;
|
||||
const char *p;
|
||||
std::string base, path;
|
||||
|
||||
// Convert everything to lowercase
|
||||
// FIXME: This is not generally safe to do on Linux/FreeBSD
|
||||
// FIXME: tolower will try to respect locale on POSIX. This is, obviously, broken
|
||||
p = &basep[0];
|
||||
for(i = 0; *p; p++)
|
||||
base += (char)tolower(*p);
|
||||
|
||||
p = &pathp[0];
|
||||
for(i = 0; *p; p++)
|
||||
path += (char)tolower(*p);
|
||||
|
||||
|
||||
// Find the length of the common prefix
|
||||
int com;
|
||||
for(com = 0; com < base.length() && com < path.length(); com++) {
|
||||
if(base[com] != path[com]) break;
|
||||
}
|
||||
if(com == base.length() || com == path.length())
|
||||
return pathp; // weird, prefix is entire string
|
||||
if(com == 0)
|
||||
return pathp; // maybe on different drive letters?
|
||||
|
||||
// Align the common prefix to the nearest slash; otherwise would break
|
||||
// on subdirectories or filenames that shared a prefix.
|
||||
while(com >= 1 && base[com-1] != '/' && base[com-1] != '\\')
|
||||
com--;
|
||||
if(com == 0) return pathp;
|
||||
|
||||
int sections = 0;
|
||||
int secLen = 0, secStart = 0;
|
||||
for(i = com; i < base.length(); i++) {
|
||||
if(base[i] == '/' || base[i] == '\\') {
|
||||
if(secLen == 2 && memcmp(&base[secStart], "..", 2)==0) return pathp;
|
||||
if(secLen == 1 && memcmp(&base[secStart], ".", 1)==0) return pathp;
|
||||
|
||||
sections++;
|
||||
secLen = 0;
|
||||
secStart = i+1;
|
||||
} else {
|
||||
secLen++;
|
||||
}
|
||||
}
|
||||
|
||||
// For every directory in the prefix of the base, we must go down a
|
||||
// directory in the relative path name
|
||||
std::string out;
|
||||
for(i = 0; i < sections; i++) {
|
||||
out += "../";
|
||||
}
|
||||
// comparison is case-insensitive, but output preserves input case
|
||||
out += pathp.substr(com);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string SolveSpace::MakePathAbsolute(const std::string &basep, const std::string &pathp) {
|
||||
std::string out = basep;
|
||||
|
||||
// Chop off the filename
|
||||
int i;
|
||||
for(i = (int)out.length() - 1; i >= 0; i--) {
|
||||
if(out[i] == '\\' || out[i] == '/') break;
|
||||
}
|
||||
if(i < 0) return pathp; // base is not an absolute path, or something?
|
||||
out.resize(i + 1);
|
||||
|
||||
out += pathp;
|
||||
return GetAbsoluteFilename(out);
|
||||
}
|
||||
|
||||
bool SolveSpace::StringAllPrintable(const char *str)
|
||||
{
|
||||
const char *t;
|
||||
|
|
|
@ -22,15 +22,6 @@ void dbp(const char *str, ...)
|
|||
OutputDebugString(buf);
|
||||
}
|
||||
|
||||
std::string GetAbsoluteFilename(const std::string &in)
|
||||
{
|
||||
std::string out;
|
||||
DWORD len = GetFullPathName(in.data(), 0, NULL, NULL);
|
||||
out.resize(len - 1);
|
||||
GetFullPathName(in.c_str(), len, &out[0], NULL);
|
||||
return out;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// A separate heap, on which we allocate expressions. Maybe a bit faster,
|
||||
// since no fragmentation issues whatsoever, and it also makes it possible
|
||||
|
|
Loading…
Reference in New Issue
Block a user