]> git.mxchange.org Git - simgear.git/commitdiff
SGPath/Dir extensions to ease file handling in TerraGear. Also a unit-test, shocking.
authorJames Turner <zakalawe@mac.com>
Fri, 14 Oct 2011 10:37:36 +0000 (11:37 +0100)
committerJames Turner <zakalawe@mac.com>
Fri, 14 Oct 2011 10:37:36 +0000 (11:37 +0100)
simgear/misc/CMakeLists.txt
simgear/misc/path_test.cxx [new file with mode: 0644]
simgear/misc/sg_dir.cxx
simgear/misc/sg_dir.hxx
simgear/misc/sg_path.cxx
simgear/misc/sg_path.hxx

index 6b21c7ca47d0fe6a7165ad14482474227bf31e67..7a06f3bd465fc4d31df72aba07141f6ddd03e610 100644 (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)
+
diff --git a/simgear/misc/path_test.cxx b/simgear/misc/path_test.cxx
new file mode 100644 (file)
index 0000000..0c31668
--- /dev/null
@@ -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
+}
+
index 95929229a57a6b01647351d42ef45c4f548f1f13..99ede3e4c6e6bd037194ac292f4171f3260281ef 100644 (file)
 #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
index 0ebc2bf7a3cb005b68902f1fe97576d376dde251..707053ab27926dfa0e6daefe463ef75fb834ddc0 100644 (file)
 #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;
   };
index 20dd4fbcea2c4400efcfc64fc6d78b69f1078a2b..53504a2013adc84a6b2a977092d46ad88c38ead2 100644 (file)
 #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 "";
+    }
+}
+
+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);
+}
+
index 0684d5701927b5f7b0bb320218a1b6dd06280ec3..27b375d9d0ff8fe1e59aac52f8c840bc33f1f99b 100644 (file)
@@ -32,6 +32,7 @@
 
 #include <simgear/compiler.h>
 #include <string>
+#include <ctime>
 
 #include <simgear/math/sg_types.hxx>
 
 
 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;
 };