]> git.mxchange.org Git - simgear.git/blob - simgear/misc/sg_path.cxx
HTTP: Rename urlretrieve/urlload to save/load.
[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         _cached = false; // stat again if required
466     return (err == 0);
467 }
468
469 time_t SGPath::modTime() const
470 {
471     validate();
472     return _modTime;
473 }
474
475 bool SGPath::operator==(const SGPath& other) const
476 {
477     return (path == other.path);
478 }
479
480 bool SGPath::operator!=(const SGPath& other) const
481 {
482     return (path != other.path);
483 }
484
485 bool SGPath::rename(const SGPath& newName)
486 {
487 #ifdef SG_WINDOWS
488         if (newName.exists()) {
489                 SGPath r(newName);
490                 if (!r.remove()) {
491                         return false;
492                 }
493         }
494 #endif
495     if (::rename(c_str(), newName.c_str()) != 0) {
496         SG_LOG(SG_IO, SG_WARN, "renamed failed: from " << str() << " to " << newName.str()
497             << " reason: " << strerror(errno));
498         return false;
499     }
500     
501     path = newName.path;
502     _cached = false;
503     return true;
504 }
505
506 //------------------------------------------------------------------------------
507 SGPath SGPath::fromEnv(const char* name, const SGPath& def)
508 {
509   const char* val = getenv(name);
510   if( val && val[0] )
511     return SGPath(val);
512   return def;
513 }
514
515 #ifdef _WIN32
516 //------------------------------------------------------------------------------
517 SGPath SGPath::home()
518 {
519   // TODO
520   return SGPath();
521 }
522 #else
523 //------------------------------------------------------------------------------
524 SGPath SGPath::home()
525 {
526   return fromEnv("HOME");
527 }
528 #endif
529
530 #ifdef _WIN32
531
532 #include <ShlObj.h> // for CSIDL
533
534 //------------------------------------------------------------------------------
535 SGPath SGPath::desktop()
536 {
537         typedef BOOL (WINAPI*GetSpecialFolderPath)(HWND, LPSTR, int, BOOL);
538         static GetSpecialFolderPath SHGetSpecialFolderPath = NULL;
539
540         // lazy open+resolve of shell32
541         if (!SHGetSpecialFolderPath) {
542                 HINSTANCE shellDll = ::LoadLibrary("shell32");
543                 SHGetSpecialFolderPath = (GetSpecialFolderPath) GetProcAddress(shellDll, "SHGetSpecialFolderPathA");
544         }
545
546         if (!SHGetSpecialFolderPath){
547                 return SGPath();
548         }
549
550         char path[MAX_PATH];
551         if (SHGetSpecialFolderPath(0, path, CSIDL_DESKTOPDIRECTORY, false)) {
552                 return SGPath(path);
553         }
554
555         SG_LOG(SG_GENERAL, SG_ALERT, "SGPath::desktop() failed, bad" );
556         return SGPath();
557 }
558 #elif __APPLE__
559 #include <CoreServices/CoreServices.h>
560
561 //------------------------------------------------------------------------------
562 SGPath SGPath::desktop()
563 {
564   FSRef ref;
565   OSErr err = FSFindFolder(kUserDomain, kDesktopFolderType, false, &ref);
566   if (err) {
567     return SGPath();
568   }
569
570   unsigned char path[1024];
571   if (FSRefMakePath(&ref, path, 1024) != noErr) {
572     return SGPath();
573   }
574
575   return SGPath((const char*) path);
576 }
577 #else
578 //------------------------------------------------------------------------------
579 SGPath SGPath::desktop()
580 {
581   // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
582
583   // $XDG_CONFIG_HOME defines the base directory relative to which user specific
584   // configuration files should be stored. If $XDG_CONFIG_HOME is either not set
585   // or empty, a default equal to $HOME/.config should be used.
586   const SGPath user_dirs = fromEnv("XDG_CONFIG_HOME", home() / ".config")
587                          / "user-dirs.dirs";
588
589   // Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped
590   // homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an absolute
591   // path. No other format is supported.
592   const std::string DESKTOP = "XDG_DESKTOP_DIR=\"";
593
594   std::ifstream user_dirs_file( user_dirs.c_str() );
595   std::string line;
596   while( std::getline(user_dirs_file, line).good() )
597   {
598     if( !starts_with(line, DESKTOP) || *line.rbegin() != '"' )
599       continue;
600
601     // Extract dir from XDG_DESKTOP_DIR="<dir>"
602     line = line.substr(DESKTOP.length(), line.length() - DESKTOP.length() - 1 );
603
604     const std::string HOME = "$HOME";
605     if( starts_with(line, HOME) )
606       return home() / simgear::strutils::unescape(line.substr(HOME.length()));
607
608     return SGPath(line);
609   }
610
611   return home() / "Desktop";
612 }
613 #endif
614
615 std::string SGPath::realpath() const
616 {
617 #if (defined(__APPLE__) && MAC_OS_X_VERSION_MAX_ALLOWED <= 1050)
618     // Workaround for Mac OS 10.5. Somehow fgfs crashes on Mac at ::realpath. 
619     // This means relative paths cannot be used on Mac OS <= 10.5
620     return path;
621 #else
622   #if defined(_MSC_VER) /*for MS compilers */ || defined(_WIN32) /*needed for non MS windows compilers like MingW*/
623     // with absPath NULL, will allocate, and ignore length
624     char *buf = _fullpath( NULL, path.c_str(), _MAX_PATH );
625   #else
626     // POSIX
627     char* buf = ::realpath(path.c_str(), NULL);
628   #endif
629     if (!buf)
630     {
631         SG_LOG(SG_IO, SG_ALERT, "ERROR: The path '" << path << "' does not exist in the file system.");
632         return path;
633     }
634     std::string p(buf);
635     free(buf);
636     return p;
637 #endif
638 }