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:
whitequark 2015-12-27 14:35:46 +08:00
parent 32383d22bf
commit 5c9c32cfc7
8 changed files with 157 additions and 131 deletions

View File

@ -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);

View File

@ -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 {

View File

@ -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])) {

View File

@ -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);

View File

@ -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 "";
}

View File

@ -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;

View File

@ -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;

View File

@ -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