filename.cpp

Go to the documentation of this file.
00001 #ifndef FILENAME_IMPLEMENTATION_FILE
00002 #define FILENAME_IMPLEMENTATION_FILE
00003 
00004 /*****************************************************************************\
00005 *                                                                             *
00006 *  Name   : filename                                                          *
00007 *  Author : Chris Koeritz                                                     *
00008 *                                                                             *
00009 *******************************************************************************
00010 * Copyright (c) 1993-$now By Author.  This program is free software; you can  *
00011 * redistribute it and/or modify it under the terms of the GNU General Public  *
00012 * License as published by the Free Software Foundation; either version 2 of   *
00013 * the License or (at your option) any later version.  This is online at:      *
00014 *     http://www.fsf.org/copyleft/gpl.html                                    *
00015 * Please send any updates to: fred@gruntose.com                               *
00016 \*****************************************************************************/
00017 
00018 // implementation note: the filename is kept canonicalized.  any constructor
00019 // or assignment operator should ensure this (except the blank constructor).
00020 
00021 #include "byte_filer.h"
00022 #include "filename.h"
00023 
00024 #include <basis/byte_array.h>
00025 #include <basis/function.h>
00026 #include <basis/istring.h>
00027 #include <basis/log_base.h>
00028 #include <basis/portable.h>
00029 #include <basis/string_array.h>
00030 
00031 #include <stdio.h>
00032 #include <sys/stat.h>
00033 #include <sys/types.h>
00034 #ifdef __WIN32__
00035   #include <io.h>
00036 #endif
00037 
00038 #if defined(__WIN32__) || defined(__VMS__)
00039   const char DEFAULT_SEPARATOR = '\\';
00040 #elif defined(__UNIX__)
00041   const char DEFAULT_SEPARATOR = '/';
00042 #else
00043   #error "We have no idea what the default path separator is."
00044 #endif
00045 
00046 const char *NO_PARENT_DEFAULT = ".";
00047   // used when no directory name can be popped off.
00048 
00049 filename::filename()
00050 : _had_directory(false),
00051   _contents(new istring)
00052 {}
00053 
00054 filename::filename(const istring &name)
00055 : _had_directory(true),
00056   _contents(new istring(name))
00057 { canonicalize(); }
00058 
00059 filename::filename(const istring &directory, const istring &name_of_file)
00060 : _had_directory(true),
00061   _contents(new istring(directory))
00062 {
00063   // if the directory is empty, use the current directory.
00064   if (!directory) {
00065     *_contents = istring(NO_PARENT_DEFAULT);
00066     _had_directory = false;
00067   }
00068   // check for a slash on the end of the directory.  add one if there is none
00069   // currently.
00070   bool add_slash = false;
00071   if ( (directory[directory.end()] != '\\')
00072        && (directory[directory.end()] != '/') ) add_slash = true;
00073   if (add_slash) *_contents += DEFAULT_SEPARATOR;
00074   *_contents += name_of_file;
00075   canonicalize();
00076 }
00077 
00078 filename::filename(const filename &to_copy)
00079 : packable(),
00080   _had_directory(to_copy._had_directory),
00081   _contents(new istring(*to_copy._contents))
00082 { canonicalize(); }
00083 
00084 filename::~filename() { WHACK(_contents); }
00085 
00086 istring filename::default_separator() { return istring(DEFAULT_SEPARATOR, 1); }
00087 
00088 filename::operator const istring &() const { return *_contents; }
00089 
00090 istring &filename::raw() { return *_contents; }
00091 
00092 const istring &filename::raw() const { return *_contents; }
00093 
00094 bool filename::good() const { return exists(); }
00095 
00096 bool filename::unlink() const { return ::unlink(_contents->s()) == 0; }
00097 
00098 bool filename::separator(char is_it)
00099 { return (is_it == pc_separator) || (is_it == unix_separator); }
00100 
00101 filename &filename::operator = (const filename &to_copy)
00102 {
00103   if (this == &to_copy)
00104     return *this;
00105   *_contents = *to_copy._contents;
00106   _had_directory = to_copy._had_directory;
00107   return *this;
00108 }
00109 
00110 filename &filename::operator = (const istring &to_copy)
00111 {
00112   _had_directory = true;
00113   *_contents = to_copy;
00114   canonicalize();
00115   return *this;
00116 }
00117 
00118 bool filename::operator == (const filename &that) const
00119 { return *_contents == *that._contents; }
00120 
00121 istring filename::pop()
00122 {
00123   istring to_return = basename();
00124   filename parent_dir = parent();
00125   if (parent_dir.raw() == NO_PARENT_DEFAULT) {
00126     // we haven't gone anywhere.
00127     return "";  // signal that nothing was removed.
00128   }
00129   *this = parent_dir;
00130   return to_return;
00131 }
00132 
00133 filename filename::parent() const { return dirname(); }
00134 
00135 void filename::push(const istring &to_push)
00136 {
00137   *this = filename(*this, to_push);
00138 }
00139 
00140 void filename::canonicalize()
00141 {
00142   // turn all the non-default separators into the default.
00143   bool found_sep = false;
00144   for (int j = 0; j < _contents->length(); j++) {
00145     if (separator(_contents->get(j))) {
00146       found_sep = true;
00147       _contents->put(j, DEFAULT_SEPARATOR);
00148     }
00149   }
00150 
00151   // if there wasn't a single directory separator, then they must not have
00152   // specified any directory name for this filename (although it could itself
00153   // be a directory).
00154   if (!found_sep) _had_directory = false;
00155 
00156   // remove all occurrences of double separators except for the first
00157   // double set, which could be a UNC filename.  that's why the index below
00158   // starts at one rather than zero.
00159   bool saw_sep = false;
00160   for (int i = 1; i < _contents->length(); i++) {
00161     if (separator(_contents->get(i))) {
00162       if (saw_sep) {
00163         _contents->zap(i, i);
00164           // two in a row is no good, except for the first two.
00165         i--;  // skip back one and try again.
00166         continue;
00167       }
00168       saw_sep = true;
00169     } else saw_sep = false;
00170   }
00171 
00172   // we don't crop the last separator if the name's too small.  for msdos
00173   // names, that would be chopping a slash off the c:\ style name.
00174   if (_contents->length() > 3) {
00175     // zap any separators that are hiding on the end.
00176     const int last = _contents->end();
00177     if (separator(_contents->get(last))) _contents->zap(last, last);
00178   } else if ( (_contents->length() == 2) && (_contents->get(1) == ':') ) {
00179     // special case for dos drive names.  we turn it back into a valid
00180     // directory rather than leaving it as just "X:".  that form of the name
00181     // means something else under dos/windows.
00182     *_contents += istring(DEFAULT_SEPARATOR, 1);
00183   }
00184 }
00185 
00186 char filename::drive(bool interact_with_fs) const
00187 {
00188   // first guess: if second letter's a colon, first letter's the drive.
00189   if (_contents->length() < 2)
00190     return '\0';
00191   if (_contents->get(1) == ':')
00192     return _contents->get(0);
00193   if (!interact_with_fs)
00194     return '\0';
00195 
00196   // otherwise, retrieve the file system's record for the file.
00197   status_info fill;
00198   if (!get_info(fill))
00199     return '\0';
00200   return char('A' + fill.st_dev);
00201 }
00202 
00203 istring filename::extension() const
00204 {
00205   istring base(basename().raw());
00206   int posn = base.find('.', base.end(), true);
00207   if (negative(posn))
00208     return "";
00209   return base.substring(posn + 1, base.length() - 1);
00210 }
00211 
00212 istring filename::rootname() const
00213 {
00214   istring base(basename().raw());
00215   int posn = base.find('.', base.end(), true);
00216   if (negative(posn))
00217     return base;
00218   return base.substring(0, posn - 1);
00219 }
00220 
00221 bool filename::get_info(status_info &to_fill) const
00222 {
00223   int ret = stat(_contents->observe(), &to_fill);
00224   if (ret)
00225     return false;
00226   return true;
00227 }
00228 
00229 bool filename::is_directory() const
00230 {
00231   status_info fill;
00232   if (!get_info(fill))
00233     return false;
00234   return !!(fill.st_mode & S_IFDIR);
00235 }
00236 
00237 bool filename::is_writable() const
00238 {
00239   status_info fill;
00240   if (!get_info(fill))
00241     return false;
00242   return !!(fill.st_mode & S_IWRITE);
00243 }
00244 
00245 bool filename::is_readable() const
00246 {
00247   status_info fill;
00248   if (!get_info(fill))
00249     return false;
00250   return !!(fill.st_mode & S_IREAD);
00251 }
00252 
00253 bool filename::is_executable() const
00254 {
00255   status_info fill;
00256   if (!get_info(fill))
00257     return false;
00258   return !!(fill.st_mode & S_IEXEC);
00259 }
00260 
00261 int filename::find_last_separator(const istring &look_at) const
00262 {
00263   int last_sep = -1;
00264   int sep = 0;
00265   while (sep >= 0) {
00266     sep = look_at.find(DEFAULT_SEPARATOR, last_sep + 1);
00267     if (sep >= 0) last_sep = sep;
00268   }
00269   return last_sep;
00270 }
00271 
00272 filename filename::basename() const
00273 {
00274   istring basename = *_contents;
00275   int last_sep = find_last_separator(basename);
00276   if (last_sep >= 0) basename.zap(0, last_sep);
00277   return basename;
00278 }
00279 
00280 filename filename::dirname() const
00281 {
00282   istring dirname = *_contents;
00283   int last_sep = find_last_separator(dirname);
00284   // we don't accept ripping off the first slash.
00285   if (last_sep >= 1) {
00286     // we can rip the slash and suffix off to get the directory name.  however,
00287     // this might be in the form X: on windows.  if they want the slash to
00288     // remain, they can use the dirname that appends it.
00289     dirname.zap(last_sep, dirname.end());
00290   } else {
00291     if (_contents->get(0) == DEFAULT_SEPARATOR) {
00292       // handle when we're up at the top of the filesystem.  on unix, once
00293       // you hit the root, you can keep going up but you still remain at
00294       // the root.  similarly on windoze, if there's no drive name in there.
00295       dirname = istring(DEFAULT_SEPARATOR, 1);
00296     } else {
00297       // there's no slash at all in the filename any more.  we assume that
00298       // the directory is the current one, if no other information is
00299       // available.  this default is already used by some code.
00300       dirname = NO_PARENT_DEFAULT;
00301     }
00302   }
00303   return dirname;
00304 }
00305 
00306 istring filename::dirname(bool add_slash) const
00307 {
00308   istring tempname = dirname().raw();
00309   if (add_slash) tempname += DEFAULT_SEPARATOR;
00310   return tempname;
00311 }
00312 
00313 bool filename::exists() const
00314 {
00315   if (is_directory())
00316     return true;
00317   if (!_contents->length())
00318     return false;
00319   byte_filer opened(_contents->observe(), "rb");
00320   return opened.good();
00321 }
00322 
00323 bool filename::legal_character(char to_check)
00324 {
00325   switch (to_check) {
00326     case ':': case ';':
00327     case '\\': case '/':
00328     case '*': case '?': case '$': case '&': case '|':
00329     case '\'': case '"': case '`':
00330     case '(': case ')':
00331     case '[': case ']':
00332     case '<': case '>':
00333     case '{': case '}':
00334       return false;
00335     default: return true;
00336   }
00337 }
00338 
00339 void filename::detooth_filename(istring &to_clean, char replacement)
00340 {
00341   for (int i = 0; i < to_clean.length(); i++) {
00342     if (!legal_character(to_clean[i]))
00343       to_clean[i] = replacement;
00344   }
00345 }
00346 
00347 void filename::pack(byte_array &packed_form) const
00348 {
00349   basis::attach(packed_form, int(_had_directory));
00350   _contents->pack(packed_form);
00351 }
00352 
00353 bool filename::unpack(byte_array &packed_form)
00354 {
00355   int temp;
00356   if (!basis::detach(packed_form, temp))
00357     return false;
00358   _had_directory = temp;
00359   if (!_contents->unpack(packed_form))
00360     return false;
00361   return true;
00362 }
00363 
00364 void filename::separate(string_array &pieces) const
00365 {
00366   pieces.reset();
00367   const istring &raw_form = raw();
00368   istring accumulator;  // holds the names we find.
00369   for (int i = 0; i < raw_form.length(); i++) {
00370     if (separator(raw_form[i])) {
00371       // this is a separator character, so eat it and add the accumulated
00372       // string to the list.
00373       if (!i || accumulator.length()) pieces += accumulator;
00374       // now reset our accumulated text.
00375       accumulator = istring::empty_string();
00376     } else {
00377       // not a separator, so just accumulate it.
00378       accumulator += raw_form[i];
00379     }
00380   }
00381   if (accumulator.length()) pieces += accumulator;
00382 }
00383 
00384 void filename::join(const string_array &pieces)
00385 {
00386   istring constructed_name;  // we'll make a filename here.
00387   for (int i = 0; i < pieces.length(); i++) {
00388     constructed_name += pieces[i];
00389     if (!i || (i != pieces.length() - 1))
00390       constructed_name += DEFAULT_SEPARATOR;
00391   }
00392   *this = constructed_name;
00393 }
00394 
00395 bool filename::base_compare_prefix(const filename &to_compare,
00396     string_array &first, string_array &second)
00397 {
00398   separate(first);
00399   to_compare.separate(second);
00400   // that case should never be allowed, since there are some bits missing
00401   // in the name to be compared.
00402   if (first.length() > second.length())
00403     return false;
00404 
00405   // compare each of the pieces.
00406   for (int i = 0; i < first.length(); i++) {
00407 #if defined(__WIN32__) || defined(__VMS__)
00408     // case-insensitive compare.
00409     if (!first[i].iequals(second[i]))
00410       return false;
00411 #else
00412     // case-sensitive compare.
00413     if (first[i] != second[i])
00414       return false;
00415 #endif
00416   }
00417   return true;
00418 }
00419 
00420 bool filename::compare_prefix(const filename &to_compare, istring &sequel)
00421 {
00422   sequel = istring::empty_string();  // clean our output parameter.
00423   string_array first;
00424   string_array second;
00425   if (!base_compare_prefix(to_compare, first, second))
00426     return false;
00427 
00428   // create the sequel string.
00429   int extra_strings = second.length() - first.length();
00430   for (int i = second.length() - extra_strings; i < second.length(); i++) {
00431     sequel += second[i];
00432     if (i != second.length() - 1) sequel += DEFAULT_SEPARATOR;
00433   }
00434 
00435   return true;
00436 }
00437 
00438 bool filename::compare_prefix(const filename &to_compare)
00439 {
00440   string_array first;
00441   string_array second;
00442   return base_compare_prefix(to_compare, first, second);
00443 }
00444 
00445 bool filename::base_compare_suffix(const filename &to_compare,
00446     string_array &first, string_array &second)
00447 {
00448   separate(first);
00449   to_compare.separate(second);
00450   // that case should never be allowed, since there are some bits missing
00451   // in the name to be compared.
00452   if (first.length() > second.length())
00453     return false;
00454 
00455   // compare each of the pieces.
00456   for (int i = first.length() - 1; i >= 0; i--) {
00457 //clean up this computation; the difference in lengths is constant--use that.
00458     int distance_from_end = first.length() - 1 - i;
00459     int j = second.length() - 1 - distance_from_end;
00460 #if defined(__WIN32__) || defined(__VMS__)
00461     // case-insensitive compare.
00462     if (!first[i].iequals(second[j]))
00463       return false;
00464 #else
00465     // case-sensitive compare.
00466     if (first[i] != second[j])
00467       return false;
00468 #endif
00469   }
00470   return true;
00471 }
00472 
00473 bool filename::compare_suffix(const filename &to_compare, istring &prequel)
00474 {
00475   prequel = istring::empty_string();  // clean our output parameter.
00476   string_array first;
00477   string_array second;
00478   if (!base_compare_suffix(to_compare, first, second))
00479     return false;
00480 
00481   // create the prequel string.
00482   int extra_strings = second.length() - first.length();
00483   for (int i = 0; i < extra_strings; i++) {
00484     prequel += second[i];
00485     if (i != second.length() - 1) prequel += DEFAULT_SEPARATOR;
00486   }
00487   return true;
00488 }
00489 
00490 bool filename::compare_suffix(const filename &to_compare)
00491 {
00492   string_array first;
00493   string_array second;
00494   return base_compare_suffix(to_compare, first, second);
00495 }
00496 
00497 bool filename::chmod(int write_mode, int owner_mode) const
00498 {
00499   int chmod_value;
00500 #ifdef __UNIX__
00501   if (write_mode & ALLOW_READ) {
00502     if (owner_mode & USER_RIGHTS) chmod_value |= S_IRUSR;
00503     if (owner_mode & GROUP_RIGHTS) chmod_value |= S_IRGRP;
00504     if (owner_mode & OTHER_RIGHTS) chmod_value |= S_IROTH;
00505   }
00506   if (write_mode & ALLOW_WRITE) {
00507     if (owner_mode & USER_RIGHTS) chmod_value |= S_IWUSR;
00508     if (owner_mode & GROUP_RIGHTS) chmod_value |= S_IWGRP;
00509     if (owner_mode & OTHER_RIGHTS) chmod_value |= S_IWOTH;
00510   }
00512 #elif defined(__WIN32__)
00513   if (write_mode & ALLOW_READ) {
00514     chmod_value |= _S_IREAD;
00515   }
00516   if (write_mode & ALLOW_WRITE) {
00517     chmod_value |= _S_IWRITE;
00518   }
00519 #else
00520   #error unsupported OS type currently.
00521 #endif
00522   int chmod_result = ::chmod(raw().s(), chmod_value);
00523   if (chmod_result) {
00524 //    LOG(istring("there was a problem changing permissions on ") + raw());
00525     return false;
00526   }
00527   return true;
00528 }
00529 
00530 
00531 #endif //FILENAME_IMPLEMENTATION_FILE
00532 

Generated on Thu Nov 20 04:29:03 2008 for HOOPLE Libraries by  doxygen 1.5.1