SGPath/Dir extensions to ease file handling in TerraGear. Also a unit-test, shocking.

This commit is contained in:
James Turner
2011-10-14 11:37:36 +01:00
parent e4e31be7d4
commit d37bf8a4ae
6 changed files with 450 additions and 39 deletions

View File

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

142
simgear/misc/path_test.cxx Normal file
View File

@@ -0,0 +1,142 @@
#include <simgear/compiler.h>
#include <iostream>
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 <simgear/misc/sg_path.hxx>
#include <simgear/misc/sg_dir.hxx>
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
}

View File

@@ -24,27 +24,59 @@
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <direct.h>
#else
# include <sys/types.h>
# include <dirent.h>
# include <sys/stat.h>
# include <unistd.h>
# include <errno.h>
#endif
#include <simgear/debug/logstream.hxx>
#include <boost/foreach.hpp>
#include <cstring>
#include <iostream>
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

View File

@@ -31,6 +31,10 @@
#include <simgear/math/sg_types.hxx>
#include <simgear/misc/sg_path.hxx>
#ifdef _MSC_VER
typedef int mode_t;
#endif
namespace simgear
{
typedef std::vector<SGPath> 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;
};

View File

@@ -28,13 +28,16 @@
#include <simgear/debug/logstream.hxx>
#include <stdio.h>
#include <sys/stat.h>
#include <errno.h>
#ifdef _WIN32
# include <direct.h>
#endif
#include "sg_path.hxx"
using std::string;
#include <boost/algorithm/string/case_conv.hpp>
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 "";
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 {
++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);
}

View File

@@ -32,6 +32,7 @@
#include <simgear/compiler.h>
#include <string>
#include <ctime>
#include <simgear/math/sg_types.hxx>
@@ -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;
};