]> git.mxchange.org Git - simgear.git/blob - simgear/misc/sg_path.cxx
cppbind: automatic conversion of SGReferenced derived pointers.
[simgear.git] / simgear / misc / sg_path.cxx
1 // sg_path.cxx -- routines to abstract out path separator differences
2 //               between MacOS and the rest of the world
3 //
4 // Written by Curtis L. Olson, started April 1999.
5 //
6 // Copyright (C) 1999  Curtis L. Olson - http://www.flightgear.org/~curt
7 //
8 // This library is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU Library General Public
10 // License as published by the Free Software Foundation; either
11 // version 2 of the License, or (at your option) any later version.
12 //
13 // This library is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 // Library General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 //
22 // $Id$
23
24
25 #include <simgear/compiler.h>
26
27 #include <simgear_config.h>
28 #include <simgear/debug/logstream.hxx>
29 #include <simgear/misc/strutils.hxx>
30 #include <stdio.h>
31 #include <sys/stat.h>
32 #include <errno.h>
33 #include <fstream>
34
35 #ifdef _WIN32
36 #  include <direct.h>
37 #endif
38 #include "sg_path.hxx"
39
40 #include <boost/algorithm/string/case_conv.hpp>
41
42 using std::string;
43 using simgear::strutils::starts_with;
44
45 /**
46  * define directory path separators
47  */
48
49 static const char sgDirPathSep = '/';
50 static const char sgDirPathSepBad = '\\';
51
52 #ifdef _WIN32
53 static const char sgSearchPathSep = ';';
54 #else
55 static const char sgSearchPathSep = ':';
56 #endif
57
58
59 // For windows, replace "\" by "/".
60 void
61 SGPath::fix()
62 {
63     string::size_type sz = path.size();
64     for ( string::size_type i = 0; i < sz; ++i ) {
65         if ( path[i] == sgDirPathSepBad ) {
66             path[i] = sgDirPathSep;
67         }
68     }
69     // drop trailing "/"
70     while ((sz>1)&&(path[sz-1]==sgDirPathSep))
71     {
72         path.resize(--sz);
73     }
74 }
75
76
77 // default constructor
78 SGPath::SGPath()
79     : path(""),
80     _cached(false),
81     _cacheEnabled(true)
82 {
83 }
84
85
86 // create a path based on "path"
87 SGPath::SGPath( const std::string& p )
88     : path(p),
89     _cached(false),
90     _cacheEnabled(true)
91 {
92     fix();
93 }
94
95 // create a path based on "path" and a "subpath"
96 SGPath::SGPath( const SGPath& p, const std::string& r )
97     : path(p.path),
98     _cached(false),
99     _cacheEnabled(p._cacheEnabled)
100 {
101     append(r);
102     fix();
103 }
104
105 SGPath::SGPath(const SGPath& p) :
106   path(p.path),
107   _cached(p._cached),
108   _cacheEnabled(p._cacheEnabled),
109   _exists(p._exists),
110   _isDir(p._isDir),
111   _isFile(p._isFile),
112   _modTime(p._modTime)
113 {
114 }
115
116 SGPath& SGPath::operator=(const SGPath& p)
117 {
118   path = p.path;
119   _cached = p._cached;
120   _cacheEnabled = p._cacheEnabled;
121   _exists = p._exists;
122   _isDir = p._isDir;
123   _isFile = p._isFile;
124   _modTime = p._modTime;
125   return *this;
126 }
127
128 // destructor
129 SGPath::~SGPath() {
130 }
131
132
133 // set path
134 void SGPath::set( const string& p ) {
135     path = p;
136     fix();
137     _cached = false;
138 }
139
140 void SGPath::set_cached(bool cached)
141 {
142     _cacheEnabled = cached;
143 }
144
145 // append another piece to the existing path
146 void SGPath::append( const string& p ) {
147     if ( path.empty() ) {
148         path = p;
149     } else {
150     if ( p[0] != sgDirPathSep ) {
151         path += sgDirPathSep;
152     }
153         path += p;
154     }
155     fix();
156     _cached = false;
157 }
158
159 //------------------------------------------------------------------------------
160 SGPath SGPath::operator/( const std::string& p ) const
161 {
162   SGPath ret = *this;
163   ret.append(p);
164   return ret;
165 }
166
167 //add a new path component to the existing path string
168 void SGPath::add( const string& p ) {
169     append( sgSearchPathSep+p );
170 }
171
172
173 // concatenate a string to the end of the path without inserting a
174 // path separator
175 void SGPath::concat( const string& p ) {
176     if ( path.empty() ) {
177         path = p;
178     } else {
179         path += p;
180     }
181     fix();
182     _cached = false;
183 }
184
185
186 // Get the file part of the path (everything after the last path sep)
187 string SGPath::file() const
188 {
189     string::size_type index = path.rfind(sgDirPathSep);
190     if (index != string::npos) {
191         return path.substr(index + 1);
192     } else {
193         return path;
194     }
195 }
196   
197
198 // get the directory part of the path.
199 string SGPath::dir() const {
200     int index = path.rfind(sgDirPathSep);
201     if (index >= 0) {
202         return path.substr(0, index);
203     } else {
204         return "";
205     }
206 }
207
208 // get the base part of the path (everything but the extension.)
209 string SGPath::base() const
210 {
211     string::size_type index = path.rfind(".");
212     string::size_type lastSep = path.rfind(sgDirPathSep);
213     
214 // tolerate dots inside directory names
215     if ((lastSep != string::npos) && (index < lastSep)) {
216         return path;
217     }
218     
219     if (index != string::npos) {
220         return path.substr(0, index);
221     } else {
222         return path;
223     }
224 }
225
226 string SGPath::file_base() const
227 {
228     string::size_type index = path.rfind(sgDirPathSep);
229     if (index == string::npos) {
230         index = 0; // no separator in the name
231     } else {
232         ++index; // skip past the separator
233     }
234     
235     string::size_type firstDot = path.find(".", index);
236     if (firstDot == string::npos) {
237         return path.substr(index); // no extensions
238     }
239     
240     return path.substr(index, firstDot - index);
241 }
242
243 // get the extension (everything after the final ".")
244 // but make sure no "/" follows the "." character (otherwise it
245 // is has to be a directory name containing a ".").
246 string SGPath::extension() const {
247     int index = path.rfind(".");
248     if ((index >= 0)  && (path.find("/", index) == string::npos)) {
249         return path.substr(index + 1);
250     } else {
251         return "";
252     }
253 }
254
255 string SGPath::lower_extension() const {
256     return boost::to_lower_copy(extension());
257 }
258
259 string SGPath::complete_lower_extension() const
260 {
261     string::size_type index = path.rfind(sgDirPathSep);
262     if (index == string::npos) {
263         index = 0; // no separator in the name
264     } else {
265         ++index; // skip past the separator
266     }
267     
268     string::size_type firstDot = path.find(".", index);
269     if ((firstDot != string::npos)  && (path.find(sgDirPathSep, firstDot) == string::npos)) {
270         return boost::to_lower_copy(path.substr(firstDot + 1));
271     } else {
272         return "";
273     }
274 }
275
276 void SGPath::validate() const
277 {
278   if (_cached && _cacheEnabled) {
279     return;
280   }
281   
282 #ifdef _WIN32
283   struct _stat buf ;
284
285   bool remove_trailing = false;
286   if ( path.length() > 1 && path[path.length()-1] == '/' )
287       remove_trailing=true;
288   if (_stat (path.substr(0,remove_trailing?path.length()-1:path.length()).c_str(), &buf ) < 0) {
289     _exists = false;
290   } else {
291     _exists = true;
292     _isFile = ((S_IFREG & buf.st_mode ) !=0);
293     _isDir = ((S_IFDIR & buf.st_mode ) !=0);
294     _modTime = buf.st_mtime;
295   }
296
297 #else
298   struct stat buf ;
299
300   if (stat(path.c_str(), &buf ) < 0) {
301     _exists = false;
302   } else {
303     _exists = true;
304     _isFile = ((S_ISREG(buf.st_mode )) != 0);
305     _isDir = ((S_ISDIR(buf.st_mode )) != 0);
306     _modTime = buf.st_mtime;
307   }
308   
309 #endif
310   _cached = true;
311 }
312
313 bool SGPath::exists() const
314 {
315   validate();
316   return _exists;
317 }
318
319 bool SGPath::isDir() const
320 {
321   validate();
322   return _exists && _isDir;
323 }
324
325 bool SGPath::isFile() const
326 {
327   validate();
328   return _exists && _isFile;
329 }
330
331 #ifdef _WIN32
332 #  define sgMkDir(d,m)       _mkdir(d)
333 #else
334 #  define sgMkDir(d,m)       mkdir(d,m)
335 #endif
336
337
338 int SGPath::create_dir( mode_t mode ) {
339     string_list dirlist = sgPathSplit(dir());
340     if ( dirlist.empty() )
341         return -1;
342     string path = dirlist[0];
343     string_list path_elements = sgPathBranchSplit(path);
344     bool absolute = !path.empty() && path[0] == sgDirPathSep;
345
346     unsigned int i = 1;
347     SGPath dir = absolute ? string( 1, sgDirPathSep ) : "";
348     dir.concat( path_elements[0] );
349 #ifdef _WIN32
350     if ( dir.str().find(':') != string::npos && path_elements.size() >= 2 ) {
351         dir.append( path_elements[1] );
352         i = 2;
353     }
354 #endif
355     struct stat info;
356     int r;
357     for(; ( r = stat( dir.c_str(), &info ) ) == 0 && i < path_elements.size(); i++) {
358         dir.append(path_elements[i]);
359     }
360     if ( r == 0 ) {
361         return 0; // Directory already exists
362     }
363     if ( sgMkDir( dir.c_str(), mode) ) {
364         SG_LOG( SG_IO, SG_ALERT, "Error creating directory: " + dir.str() );
365         return -2;
366     }
367     for(; i < path_elements.size(); i++) {
368         dir.append(path_elements[i]);
369         if ( sgMkDir( dir.c_str(), mode) ) {
370             SG_LOG( SG_IO, SG_ALERT, "Error creating directory: " + dir.str() );
371             return -2;
372         }
373     }
374
375     return 0;
376 }
377
378 string_list sgPathBranchSplit( const string &dirpath ) {
379     string_list path_elements;
380     string element, path = dirpath;
381     while ( ! path.empty() ) {
382         size_t p = path.find( sgDirPathSep );
383         if ( p != string::npos ) {
384             element = path.substr( 0, p );
385             path.erase( 0, p + 1 );
386         } else {
387             element = path;
388             path = "";
389         }
390         if ( ! element.empty() )
391             path_elements.push_back( element );
392     }
393     return path_elements;
394 }
395
396
397 string_list sgPathSplit( const string &search_path ) {
398     string tmp = search_path;
399     string_list result;
400     result.clear();
401
402     bool done = false;
403
404     while ( !done ) {
405         int index = tmp.find(sgSearchPathSep);
406         if (index >= 0) {
407             result.push_back( tmp.substr(0, index) );
408             tmp = tmp.substr( index + 1 );
409         } else {
410             if ( !tmp.empty() )
411                 result.push_back( tmp );
412             done = true;
413         }
414     }
415
416     return result;
417 }
418
419 bool SGPath::isAbsolute() const
420 {
421   if (path.empty()) {
422     return false;
423   }
424   
425 #ifdef _WIN32
426   // detect '[A-Za-z]:/'
427   if (path.size() > 2) {
428     if (isalpha(path[0]) && (path[1] == ':') && (path[2] == sgDirPathSep)) {
429       return true;
430     }
431   }
432 #endif
433   
434   return (path[0] == sgDirPathSep);
435 }
436
437 bool SGPath::isNull() const
438 {
439   return path.empty();
440 }
441
442 std::string SGPath::str_native() const
443 {
444 #ifdef _WIN32
445     std::string s = str();
446     std::string::size_type pos;
447     std::string nativeSeparator;
448     nativeSeparator = sgDirPathSepBad;
449
450     while( (pos=s.find( sgDirPathSep )) != std::string::npos ) {
451         s.replace( pos, 1, nativeSeparator );
452     }
453     return s;
454 #else
455     return str();
456 #endif
457 }
458
459 bool SGPath::remove()
460 {
461     int err = ::unlink(c_str());
462     if (err) {
463         SG_LOG(SG_IO, SG_WARN,  "file remove failed: (" << str() << ") " << strerror(errno));
464     }
465     return (err == 0);
466 }
467
468 time_t SGPath::modTime() const
469 {
470     validate();
471     return _modTime;
472 }
473
474 bool SGPath::operator==(const SGPath& other) const
475 {
476     return (path == other.path);
477 }
478
479 bool SGPath::operator!=(const SGPath& other) const
480 {
481     return (path != other.path);
482 }
483
484 bool SGPath::rename(const SGPath& newName)
485 {
486     if (::rename(c_str(), newName.c_str()) != 0) {
487         SG_LOG(SG_IO, SG_WARN, "renamed failed: from " << str() << " to " << newName.str()
488             << " reason: " << strerror(errno));
489         return false;
490     }
491     
492     path = newName.path;
493     _cached = false;
494     return true;
495 }
496
497 //------------------------------------------------------------------------------
498 SGPath SGPath::fromEnv(const char* name, const SGPath& def)
499 {
500   const char* val = getenv(name);
501   if( val && val[0] )
502     return SGPath(val);
503   return def;
504 }
505
506 #ifdef _WIN32
507 //------------------------------------------------------------------------------
508 SGPath SGPath::home()
509 {
510   // TODO
511   return SGPath();
512 }
513 #else
514 //------------------------------------------------------------------------------
515 SGPath SGPath::home()
516 {
517   return fromEnv("HOME");
518 }
519 #endif
520
521 #ifdef _WIN32
522
523 #include <ShlObj.h> // for CSIDL
524
525 //------------------------------------------------------------------------------
526 SGPath SGPath::desktop()
527 {
528         typedef BOOL (WINAPI*GetSpecialFolderPath)(HWND, LPSTR, int, BOOL);
529         static GetSpecialFolderPath SHGetSpecialFolderPath = NULL;
530
531         // lazy open+resolve of shell32
532         if (!SHGetSpecialFolderPath) {
533                 HINSTANCE shellDll = ::LoadLibrary("shell32");
534                 SHGetSpecialFolderPath = (GetSpecialFolderPath) GetProcAddress(shellDll, "SHGetSpecialFolderPathA");
535         }
536
537         if (!SHGetSpecialFolderPath){
538                 return SGPath();
539         }
540
541         char path[MAX_PATH];
542         if (SHGetSpecialFolderPath(0, path, CSIDL_DESKTOPDIRECTORY, false)) {
543                 return SGPath(path);
544         }
545
546         SG_LOG(SG_GENERAL, SG_ALERT, "SGPath::desktop() failed, bad" );
547         return SGPath();
548 }
549 #elif __APPLE__
550 #include <CoreServices/CoreServices.h>
551
552 //------------------------------------------------------------------------------
553 SGPath SGPath::desktop()
554 {
555   FSRef ref;
556   OSErr err = FSFindFolder(kUserDomain, kDesktopFolderType, false, &ref);
557   if (err) {
558     return SGPath();
559   }
560
561   unsigned char path[1024];
562   if (FSRefMakePath(&ref, path, 1024) != noErr) {
563     return SGPath();
564   }
565
566   return SGPath((const char*) path);
567 }
568 #else
569 //------------------------------------------------------------------------------
570 SGPath SGPath::desktop()
571 {
572   // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
573
574   // $XDG_CONFIG_HOME defines the base directory relative to which user specific
575   // configuration files should be stored. If $XDG_CONFIG_HOME is either not set
576   // or empty, a default equal to $HOME/.config should be used.
577   const SGPath user_dirs = fromEnv("XDG_CONFIG_HOME", home() / ".config")
578                          / "user-dirs.dirs";
579
580   // Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped
581   // homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an absolute
582   // path. No other format is supported.
583   const std::string DESKTOP = "XDG_DESKTOP_DIR=\"";
584
585   std::ifstream user_dirs_file( user_dirs.c_str() );
586   std::string line;
587   while( std::getline(user_dirs_file, line).good() )
588   {
589     if( !starts_with(line, DESKTOP) || *line.rbegin() != '"' )
590       continue;
591
592     // Extract dir from XDG_DESKTOP_DIR="<dir>"
593     line = line.substr(DESKTOP.length(), line.length() - DESKTOP.length() - 1 );
594
595     const std::string HOME = "$HOME";
596     if( starts_with(line, HOME) )
597       return home() / simgear::strutils::unescape(line.substr(HOME.length()));
598
599     return SGPath(line);
600   }
601
602   return home() / "Desktop";
603 }
604 #endif
605
606 std::string SGPath::realpath() const
607 {
608 #if (defined(__APPLE__) && MAC_OS_X_VERSION_MAX_ALLOWED <= 1050)
609     // Workaround for Mac OS 10.5. Somehow fgfs crashes on Mac at ::realpath. 
610     // This means relative paths cannot be used on Mac OS <= 10.5
611     return path;
612 #else
613   #if defined(_MSC_VER) /*for MS compilers */ || defined(_WIN32) /*needed for non MS windows compilers like MingW*/
614     // with absPath NULL, will allocate, and ignore length
615     char *buf = _fullpath( NULL, path.c_str(), _MAX_PATH );
616   #else
617     // POSIX
618     char* buf = ::realpath(path.c_str(), NULL);
619   #endif
620     if (!buf)
621     {
622         SG_LOG(SG_IO, SG_ALERT, "ERROR: The path '" << path << "' does not exist in the file system.");
623         return path;
624     }
625     std::string p(buf);
626     free(buf);
627     return p;
628 #endif
629 }