From: James Turner Date: Fri, 14 Oct 2011 10:37:36 +0000 (+0100) Subject: SGPath/Dir extensions to ease file handling in TerraGear. Also a unit-test, shocking. X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=d37bf8a4ae4473dfc5e86e9f5b38de41512aa985;p=simgear.git SGPath/Dir extensions to ease file handling in TerraGear. Also a unit-test, shocking. --- diff --git a/simgear/misc/CMakeLists.txt b/simgear/misc/CMakeLists.txt index 6b21c7ca..7a06f3bd 100644 --- a/simgear/misc/CMakeLists.txt +++ b/simgear/misc/CMakeLists.txt @@ -41,3 +41,8 @@ target_link_libraries(test_tabbed_values sgmisc) add_executable(test_strings strutils_test.cxx ) add_test(test_strings ${EXECUTABLE_OUTPUT_PATH}/test_strings) target_link_libraries(test_strings sgmisc) + +add_executable(test_path path_test.cxx ) +add_test(test_path ${EXECUTABLE_OUTPUT_PATH}/test_path) +target_link_libraries(test_path sgmisc sgdebug) + diff --git a/simgear/misc/path_test.cxx b/simgear/misc/path_test.cxx new file mode 100644 index 00000000..0c316682 --- /dev/null +++ b/simgear/misc/path_test.cxx @@ -0,0 +1,142 @@ + +#include + +#include + +using std::cout; +using std::cerr; +using std::endl; + +#define COMPARE(a, b) \ + if ((a) != (b)) { \ + cerr << "failed:" << #a << " != " << #b << endl; \ + exit(1); \ + } + +#define VERIFY(a) \ + if (!(a)) { \ + cerr << "failed:" << #a << endl; \ + exit(1); \ + } + +#include +#include + +void test_dir() +{ + simgear::Dir temp = simgear::Dir::tempDir("foo"); + cout << "created:" << temp.path().str() << endl; + + VERIFY(temp.exists()); + VERIFY(temp.path().isDir()); + VERIFY(!temp.path().isFile()); + + SGPath fileInDir = temp.file("foobaz"); + VERIFY(!fileInDir.exists()); + + if (!temp.remove(true)) { + cout << "remove failed!" << endl; + } + + cout << temp.path().modTime() << endl; +} + +int main(int argc, char* argv[]) +{ + SGPath pa; + VERIFY(pa.isNull()); + COMPARE(pa.exists(), false); + +// test basic parsing + SGPath pb("/Foo/bar/something.png"); + COMPARE(pb.str(), std::string("/Foo/bar/something.png")); + COMPARE(strcmp(pb.c_str(), "/Foo/bar/something.png"), 0); + COMPARE(pb.dir(), std::string("/Foo/bar")); + COMPARE(pb.file(), std::string("something.png")); + COMPARE(pb.base(), std::string("/Foo/bar/something")); + COMPARE(pb.file_base(), std::string("something")); + COMPARE(pb.extension(), std::string("png")); + VERIFY(pb.isAbsolute()); + VERIFY(!pb.isRelative()); + +// relative paths + SGPath ra("where/to/begin.txt"); + COMPARE(ra.str(), std::string("where/to/begin.txt")); + COMPARE(ra.dir(), std::string("where/to")); + COMPARE(ra.file(), std::string("begin.txt")); + COMPARE(ra.file_base(), std::string("begin")); + VERIFY(!ra.isAbsolute()); + VERIFY(ra.isRelative()); + +// dots in paths / missing extensions + SGPath pk("/Foo/bar.dot/thing"); + COMPARE(pk.dir(), std::string("/Foo/bar.dot")); + COMPARE(pk.file(), std::string("thing")); + COMPARE(pk.base(), std::string("/Foo/bar.dot/thing")); + COMPARE(pk.file_base(), std::string("thing")); + COMPARE(pk.extension(), std::string()); + +// multiple file extensions + SGPath pj("/Foo/zot.dot/thing.tar.gz"); + COMPARE(pj.dir(), std::string("/Foo/zot.dot")); + COMPARE(pj.file(), std::string("thing.tar.gz")); + COMPARE(pj.base(), std::string("/Foo/zot.dot/thing")); + COMPARE(pj.file_base(), std::string("thing")); + COMPARE(pj.extension(), std::string("gz")); + COMPARE(pj.complete_lower_extension(), std::string("tar.gz")); + +// path fixing + SGPath rd("where\\to\\begin.txt"); + COMPARE(rd.str(), std::string("where/to/begin.txt")); + +// test modification +// append + SGPath d1("/usr/local"); + SGPath pc = d1; + COMPARE(pc.str(), std::string("/usr/local")); + pc.append("include"); + + COMPARE(pc.str(), std::string("/usr/local/include")); + COMPARE(pc.file(), std::string("include")); + +// add + pc.add("/opt/local"); + COMPARE(pc.str(), std::string("/usr/local/include/:/opt/local")); + +// concat + SGPath pd = pb; + pd.concat("-1"); + COMPARE(pd.str(), std::string("/Foo/bar/something.png-1")); + +// create with relative path + SGPath rb(d1, "include/foo"); + COMPARE(rb.str(), std::string("/usr/local/include/foo")); + VERIFY(rb.isAbsolute()); + +// lower-casing of file extensions + SGPath extA("FOO.ZIP"); + COMPARE(extA.base(), "FOO"); + COMPARE(extA.extension(), "ZIP"); + COMPARE(extA.lower_extension(), "zip"); + COMPARE(extA.complete_lower_extension(), "zip"); + + SGPath extB("BAH/FOO.HTML.GZ"); + COMPARE(extB.extension(), "GZ"); + COMPARE(extB.base(), "BAH/FOO"); + COMPARE(extB.lower_extension(), "gz"); + COMPARE(extB.complete_lower_extension(), "html.gz"); +#ifdef _WIN32 + COMPARE(d1.str_native(), std::string("\\usr\\local")); + + SGPath winAbs("C:\\Windows\\System32"); + COMPARE(winAbs.str(), std::string("C:/Windows/System32")); +#else + COMPARE(d1.str_native(), std::string("/usr/local")); +#endif + + test_dir(); + + cout << "all tests passed OK" << endl; + return 0; // passed +} + diff --git a/simgear/misc/sg_dir.cxx b/simgear/misc/sg_dir.cxx index 95929229..99ede3e4 100644 --- a/simgear/misc/sg_dir.cxx +++ b/simgear/misc/sg_dir.cxx @@ -24,27 +24,59 @@ #ifdef _WIN32 # define WIN32_LEAN_AND_MEAN # include +# include #else # include # include +# include +# include +# include #endif #include +#include #include #include +using std::string; + namespace simgear { Dir::Dir(const SGPath& path) : _path(path) { + _path.set_cached(false); // disable caching, so create/remove work } Dir::Dir(const Dir& rel, const SGPath& relPath) : _path(rel.file(relPath.str())) { + _path.set_cached(false); // disable caching, so create/remove work +} + +Dir Dir::current() +{ +#ifdef _WIN32 + char* buf = _getcwd(NULL, 0); +#else + char* buf = ::getcwd(NULL, 0); +#endif + SGPath p(buf); + free(buf); + return Dir(p); +} + +Dir Dir::tempDir(const std::string& templ) +{ + SGPath p(tempnam(0, templ.c_str())); + Dir t(p); + if (!t.create(0700)) { + SG_LOG(SG_IO, SG_WARN, "failed to create temporary directory at " << p.str()); + } + + return t; } PathList Dir::children(int types, const std::string& nameFilter) const @@ -175,8 +207,79 @@ bool Dir::exists() const SGPath Dir::file(const std::string& name) const { SGPath childPath = _path; + childPath.set_cached(true); childPath.append(name); return childPath; } +#ifdef _WIN32 +# define sgMkDir(d,m) _mkdir(d) +#else +# define sgMkDir(d,m) mkdir(d,m) +#endif + +bool Dir::create(mode_t mode) +{ + if (exists()) { + return false; // already exists + } + +// recursively create parent directories + Dir pr(parent()); + if (!pr.exists()) { + bool ok = pr.create(mode); + if (!ok) { + return false; + } + } + +// finally, create ourselves + int err = sgMkDir(_path.c_str(), mode); + if (err) { + SG_LOG(SG_IO, SG_WARN, "directory creation failed: (" << _path.str() << ") " << strerror(errno) ); + } + + return (err == 0); +} + +bool Dir::remove(bool recursive) +{ + if (!exists()) { + SG_LOG(SG_IO, SG_WARN, "attempt to remove non-existant dir:" << _path.str()); + return false; + } + + if (recursive) { + bool ok; + PathList cs = children(NO_DOT_OR_DOTDOT | INCLUDE_HIDDEN | TYPE_FILE | TYPE_DIR); + BOOST_FOREACH(SGPath path, cs) { + if (path.isDir()) { + Dir childDir(path); + ok = childDir.remove(true); + } else { + ok = path.remove(); + } + + if (!ok) { + return false; + } + } // of child iteration + } // of recursive deletion + +#ifdef _WIN32 + int err = _rmdir(_path.c_str()); +#else + int err = rmdir(_path.c_str()); +#endif + if (err) { + SG_LOG(SG_IO, SG_WARN, "rmdir failed:" << _path.str() << ":" << strerror(errno)); + } + return (err == 0); +} + +Dir Dir::parent() const +{ + return Dir(_path.dir()); +} + } // of namespace simgear diff --git a/simgear/misc/sg_dir.hxx b/simgear/misc/sg_dir.hxx index 0ebc2bf7..707053ab 100644 --- a/simgear/misc/sg_dir.hxx +++ b/simgear/misc/sg_dir.hxx @@ -31,6 +31,10 @@ #include #include +#ifdef _MSC_VER + typedef int mode_t; +#endif + namespace simgear { typedef std::vector PathList; @@ -38,6 +42,13 @@ namespace simgear class Dir { public: + static Dir current(); + + /** + * create a temporary directory, using the supplied name + */ + static Dir tempDir(const std::string& templ); + Dir(const SGPath& path); Dir(const Dir& rel, const SGPath& relPath); @@ -53,10 +64,31 @@ namespace simgear SGPath file(const std::string& name) const; + SGPath path() const + { return _path; } + + /** + * create the directory (and any parents as required) with the + * request mode, or return failure + */ + bool create(mode_t mode); + + /** + * remove the directory. + * If recursive is true, contained files and directories are + * recursively removed + */ + bool remove(bool recursive = false); + /** * Check that the directory at the path exists (and is a directory!) */ bool exists() const; + + /** + * parent directory, if one exists + */ + Dir parent() const; private: mutable SGPath _path; }; diff --git a/simgear/misc/sg_path.cxx b/simgear/misc/sg_path.cxx index 20dd4fbc..53504a20 100644 --- a/simgear/misc/sg_path.cxx +++ b/simgear/misc/sg_path.cxx @@ -28,13 +28,16 @@ #include #include #include +#include + #ifdef _WIN32 # include #endif #include "sg_path.hxx" -using std::string; +#include +using std::string; /** * define directory path separators @@ -58,14 +61,14 @@ SGPath::fix() { for ( string::size_type i = 0; i < path.size(); ++i ) { #if defined( WIN32 ) - // for windoze, don't replace the ":" for the second character - if ( i == 1 ) { - continue; - } + // for windoze, don't replace the ":" for the second character + if ( i == 1 ) { + continue; + } #endif - if ( path[i] == sgDirPathSepBad ) { - path[i] = sgDirPathSep; - } + if ( path[i] == sgDirPathSepBad ) { + path[i] = sgDirPathSep; + } } } @@ -73,7 +76,8 @@ SGPath::fix() // default constructor SGPath::SGPath() : path(""), - _cached(false) + _cached(false), + _cacheEnabled(true) { } @@ -81,7 +85,8 @@ SGPath::SGPath() // create a path based on "path" SGPath::SGPath( const std::string& p ) : path(p), - _cached(false) + _cached(false), + _cacheEnabled(true) { fix(); } @@ -89,7 +94,8 @@ SGPath::SGPath( const std::string& p ) // create a path based on "path" and a "subpath" SGPath::SGPath( const SGPath& p, const std::string& r ) : path(p.path), - _cached(false) + _cached(false), + _cacheEnabled(p._cacheEnabled) { append(r); fix(); @@ -98,9 +104,11 @@ SGPath::SGPath( const SGPath& p, const std::string& r ) SGPath::SGPath(const SGPath& p) : path(p.path), _cached(p._cached), + _cacheEnabled(p._cacheEnabled), _exists(p._exists), _isDir(p._isDir), - _isFile(p._isFile) + _isFile(p._isFile), + _modTime(p._modTime) { } @@ -108,9 +116,11 @@ SGPath& SGPath::operator=(const SGPath& p) { path = p.path; _cached = p._cached; + _cacheEnabled = p._cacheEnabled; _exists = p._exists; _isDir = p._isDir; _isFile = p._isFile; + _modTime = p._modTime; return *this; } @@ -126,16 +136,20 @@ void SGPath::set( const string& p ) { _cached = false; } +void SGPath::set_cached(bool cached) +{ + _cacheEnabled = cached; +} // append another piece to the existing path void SGPath::append( const string& p ) { if ( path.size() == 0 ) { - path = p; + path = p; } else { - if ( p[0] != sgDirPathSep ) { - path += sgDirPathSep; - } - path += p; + if ( p[0] != sgDirPathSep ) { + path += sgDirPathSep; + } + path += p; } fix(); _cached = false; @@ -151,9 +165,9 @@ void SGPath::add( const string& p ) { // path separator void SGPath::concat( const string& p ) { if ( path.size() == 0 ) { - path = p; + path = p; } else { - path += p; + path += p; } fix(); _cached = false; @@ -164,9 +178,9 @@ void SGPath::concat( const string& p ) { string SGPath::file() const { int index = path.rfind(sgDirPathSep); if (index >= 0) { - return path.substr(index + 1); + return path.substr(index + 1); } else { - return ""; + return ""; } } @@ -175,20 +189,45 @@ string SGPath::file() const { string SGPath::dir() const { int index = path.rfind(sgDirPathSep); if (index >= 0) { - return path.substr(0, index); + return path.substr(0, index); } else { - return ""; + return ""; } } // get the base part of the path (everything but the extension.) string SGPath::base() const { - int index = path.rfind("."); - if ((index >= 0) && (path.find("/", index) == string::npos)) { - return path.substr(0, index); + unsigned int index = path.rfind(sgDirPathSep); + if (index == string::npos) { + index = 0; // no separator in the name } else { - return ""; + ++index; // skip past the separator } + +// find the first dot after the final separator + unsigned int firstDot = path.find(".", index); + if (firstDot == string::npos) { + return path; // no extension, return whole path + } + + return path.substr(0, firstDot); +} + +string SGPath::file_base() const +{ + unsigned int index = path.rfind(sgDirPathSep); + if (index == string::npos) { + index = 0; // no separator in the name + } else { + ++index; // skip past the separator + } + + unsigned int firstDot = path.find(".", index); + if (firstDot == string::npos) { + return path.substr(index); // no extensions + } + + return path.substr(index, firstDot - index); } // get the extension (everything after the final ".") @@ -197,15 +236,36 @@ string SGPath::base() const { string SGPath::extension() const { int index = path.rfind("."); if ((index >= 0) && (path.find("/", index) == string::npos)) { - return path.substr(index + 1); + return path.substr(index + 1); + } else { + return ""; + } +} + +string SGPath::lower_extension() const { + return boost::to_lower_copy(extension()); +} + +string SGPath::complete_lower_extension() const +{ + unsigned int index = path.rfind(sgDirPathSep); + if (index == string::npos) { + index = 0; // no separator in the name } else { - return ""; + ++index; // skip past the separator + } + + int firstDot = path.find(".", index); + if ((firstDot >= 0) && (path.find(sgDirPathSep, firstDot) == string::npos)) { + return boost::to_lower_copy(path.substr(firstDot + 1)); + } else { + return ""; } } void SGPath::validate() const { - if (_cached) { + if (_cached && _cacheEnabled) { return; } @@ -221,6 +281,7 @@ void SGPath::validate() const _exists = true; _isFile = ((S_IFREG & buf.st_mode ) !=0); _isDir = ((S_IFDIR & buf.st_mode ) !=0); + _modTime = buf.st_mtime; } #else @@ -232,6 +293,7 @@ void SGPath::validate() const _exists = true; _isFile = ((S_ISREG(buf.st_mode )) != 0); _isDir = ((S_ISDIR(buf.st_mode )) != 0); + _modTime = buf.st_mtime; } #endif @@ -383,3 +445,29 @@ std::string SGPath::str_native() const return str(); #endif } + +bool SGPath::remove() +{ + int err = ::unlink(c_str()); + if (err) { + SG_LOG(SG_IO, SG_WARN, "file remove failed: (" << str() << ") " << strerror(errno)); + } + return (err == 0); +} + +time_t SGPath::modTime() const +{ + validate(); + return _modTime; +} + +bool SGPath::operator==(const SGPath& other) const +{ + return (path == other.path); +} + +bool SGPath::operator!=(const SGPath& other) const +{ + return (path != other.path); +} + diff --git a/simgear/misc/sg_path.hxx b/simgear/misc/sg_path.hxx index 0684d570..27b375d9 100644 --- a/simgear/misc/sg_path.hxx +++ b/simgear/misc/sg_path.hxx @@ -32,6 +32,7 @@ #include #include +#include #include @@ -49,10 +50,6 @@ class SGPath { -private: - - std::string path; - public: /** Default constructor */ @@ -86,6 +83,15 @@ public: void set( const std::string& p ); SGPath& operator= ( const char* p ) { this->set(p); return *this; } + bool operator==(const SGPath& other) const; + bool operator!=(const SGPath& other) const; + + /** + * Set if file information (exists, type, mod-time) is cached or + * retrieved each time it is queried. Caching is enabled by default + */ + void set_cached(bool cached); + /** * Append another piece to the existing path. Inserts a path * separator between the existing component and the new component. @@ -123,12 +129,33 @@ public: */ std::string base() const; + /** + * Get the base part of the filename (everything before the first '.') + * @return the base filename + */ + std::string file_base() const; + /** * Get the extension part of the path (everything after the final ".") * @return the extension string */ std::string extension() const; - + + /** + * Get the extension part of the path (everything after the final ".") + * converted to lowercase + * @return the extension string + */ + std::string lower_extension() const; + + /** + * Get the complete extension part of the path (everything after the first ".") + * this might look like 'tar.gz' or 'txt.Z', or might be identical to 'extension' above + * the extension is converted to lowercase. + * @return the extension string + */ + std::string complete_lower_extension() const; + /** * Get the path string * @return path string @@ -176,16 +203,30 @@ public: * check for default constructed path */ bool isNull() const; + + /** + * delete the file, if possible + */ + bool remove(); + + /** + * modification time of the file + */ + time_t modTime() const; private: void fix(); void validate() const; - mutable bool _cached; - mutable bool _exists; - mutable bool _isDir; - mutable bool _isFile; + std::string path; + + mutable bool _cached : 1; + bool _cacheEnabled : 1; ///< cacheing can be disbled if required + mutable bool _exists : 1; + mutable bool _isDir : 1; + mutable bool _isFile : 1; + mutable time_t _modTime; };