Logo Search packages:      
Sourcecode: wesnoth-1.7 version File versions  Download package

filesystem.cpp

Go to the documentation of this file.
/* $Id: filesystem.cpp 40957 2010-01-30 16:43:33Z silene $ */
/*
   Copyright (C) 2003 - 2010 by David White <dave@whitevine.net>
   Part of the Battle for Wesnoth Project http://www.wesnoth.org/

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License version 2
   or at your option any later version.
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY.

   See the COPYING file for more details.
*/

/**
 * @file filesystem.cpp
 * File-IO
 */

#include "global.hpp"

// Include files for opendir(3), readdir(3), etc.
// These files may vary from platform to platform,
// since these functions are NOT ANSI-conforming functions.
// They may have to be altered to port to new platforms

//for mkdir
#include <sys/stat.h>

#ifdef _WIN32
#include "filesystem_win32.ii"
#include <cctype>
#else /* !_WIN32 */
#include <unistd.h>
#include <dirent.h>
#include <libgen.h>
#endif /* !_WIN32 */

#ifdef __BEOS__
#include <Directory.h>
#include <FindDirectory.h>
#include <Path.h>
BPath be_path;
#endif

// for getenv
#include <cerrno>
#include <fstream>
#include <iomanip>
#include <set>
#include <boost/algorithm/string.hpp>

// for strerror
#include <cstring>

#include "config.hpp"
#include "filesystem.hpp"
#include "foreach.hpp"
#include "game_config.hpp"
#include "game_preferences.hpp"
#include "log.hpp"
#include "loadscreen.hpp"
#include "scoped_resource.hpp"

static lg::log_domain log_filesystem("filesystem");
#define DBG_FS LOG_STREAM(debug, log_filesystem)
#define LOG_FS LOG_STREAM(info, log_filesystem)
#define WRN_FS LOG_STREAM(warn, log_filesystem)
#define ERR_FS LOG_STREAM(err, log_filesystem)

namespace {
      const mode_t AccessMode = 00770;

      // These are the filenames that get special processing
      const std::string maincfg_filename = "_main.cfg";
      const std::string finalcfg_filename = "_final.cfg";
      const std::string initialcfg_filename = "_initial.cfg";

}

#ifdef __APPLE__
#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CFString.h>
#include <CoreFoundation/CFBase.h>
#endif

bool ends_with(const std::string& str, const std::string& suffix)
{
      return str.size() >= suffix.size() && std::equal(suffix.begin(),suffix.end(),str.end()-suffix.size());
}

// Don't pass directory as reference, it seems to break on
// arklinux with GCC-4.3.
00094 void get_files_in_dir(const std::string directory,
                                std::vector<std::string>* files,
                                std::vector<std::string>* dirs,
                                file_name_option mode,
                                file_filter_option filter,
                                file_reorder_option reorder,
                                file_tree_checksum* checksum)
{
      // If we have a path to find directories in,
      // then convert relative pathnames to be rooted
      // on the wesnoth path
#ifndef __AMIGAOS4__
      if(!directory.empty() && directory[0] != '/' && !game_config::path.empty()){
            const std::string& dir = game_config::path + "/" + directory;
            if(is_directory(dir)) {
                  get_files_in_dir(dir,files,dirs,mode,filter,reorder,checksum);
                  return;
            }
      }
#endif /* __AMIGAOS4__ */

      struct stat st;

      if (reorder == DO_REORDER) {
            LOG_FS << "searching for _main.cfg in directory " << directory << '\n';
            std::string maincfg;
            if (directory.empty() || directory[directory.size()-1] == '/'
#ifdef __AMIGAOS4__
                  || (directory[directory.size()-1]==':')
#endif /* __AMIGAOS4__ */
            )
                  maincfg = directory + maincfg_filename;
            else
                  maincfg = (directory + "/") + maincfg_filename;

            if (::stat(maincfg.c_str(), &st) != -1) {
                  LOG_FS << "_main.cfg found : " << maincfg << '\n';
                  if (files != NULL) {
                        if (mode == ENTIRE_FILE_PATH)
                              files->push_back(maincfg);
                        else
                              files->push_back(maincfg_filename);
                  }
                  return;
            }
      }

      DIR* dir = opendir(directory.c_str());

      if(dir == NULL) {
            return;
      }

      struct dirent* entry;
      while((entry = readdir(dir)) != NULL) {
            if(entry->d_name[0] == '.')
                  continue;
#ifdef __APPLE__
            // HFS Mac OS X decomposes filenames using combining unicode characters.
            // Try to get the precomposed form.
            char macname[MAXNAMLEN+1];
            CFStringRef cstr = CFStringCreateWithCString(NULL,
                                           entry->d_name,
                                           kCFStringEncodingUTF8);
            CFMutableStringRef mut_str = CFStringCreateMutableCopy(NULL,
                                           0, cstr);
            CFStringNormalize(mut_str, kCFStringNormalizationFormC);
            CFStringGetCString(mut_str,
                        macname,sizeof(macname)-1,
                        kCFStringEncodingUTF8);
            CFRelease(cstr);
            CFRelease(mut_str);
            const std::string basename = macname;
#else
            // generic Unix
            const std::string basename = entry->d_name;
#endif /* !APPLE */

            std::string fullname;
            if (directory.empty() || directory[directory.size()-1] == '/'
#ifdef __AMIGAOS4__
                  || (directory[directory.size()-1]==':')
#endif /* __AMIGAOS4__ */
            )
                  fullname = directory + basename;
            else
                  fullname = directory + "/" + basename;

            if (::stat(fullname.c_str(), &st) != -1) {
                  if (S_ISREG(st.st_mode)) {
                        if (files != NULL) {
                              if (mode == ENTIRE_FILE_PATH)
                                    files->push_back(fullname);
                              else
                                    files->push_back(basename);
                        }
                        if (checksum != NULL) {
                              if(st.st_mtime > checksum->modified) {
                                    checksum->modified = st.st_mtime;
                              }
                              checksum->sum_size += st.st_size;
                              checksum->nfiles++;
                        }
                  } else if (S_ISDIR(st.st_mode)) {
                        if (filter == SKIP_MEDIA_DIR
                                    && (basename == "images"|| basename == "sounds"))
                              continue;

                        if (reorder == DO_REORDER &&
                                    ::stat((fullname+"/"+maincfg_filename).c_str(), &st)!=-1 &&
                                    S_ISREG(st.st_mode)) {
                              LOG_FS << "_main.cfg found : ";
                              if (files != NULL) {
                                    if (mode == ENTIRE_FILE_PATH) {
                                          files->push_back(fullname + "/" + maincfg_filename);
                                          LOG_FS << fullname << "/" << maincfg_filename << '\n';
                                    } else {
                                          files->push_back(basename + "/" + maincfg_filename);
                                          LOG_FS << basename << "/" << maincfg_filename << '\n';
                              }
                              } else {
                              // Show what I consider strange
                                    LOG_FS << fullname << "/" << maincfg_filename << " not used now but skip the directory \n";
                              }
                        } else if (dirs != NULL) {
                              if (mode == ENTIRE_FILE_PATH)
                                    dirs->push_back(fullname);
                              else
                                    dirs->push_back(basename);
                        }
                  }
            }
      }

      closedir(dir);

      if(files != NULL)
            std::sort(files->begin(),files->end());

      if (dirs != NULL)
            std::sort(dirs->begin(),dirs->end());

      if (files != NULL && reorder == DO_REORDER) {
            // move finalcfg_filename, if present, to the end of the vector
            for (unsigned int i = 0; i < files->size(); i++) {
                  if (ends_with((*files)[i], "/" + finalcfg_filename)) {
                        files->push_back((*files)[i]);
                        files->erase(files->begin()+i);
                        break;
                  }
            }
            // move initialcfg_filename, if present, to the beginning of the vector
            int foundit = -1;
            for (unsigned int i = 0; i < files->size(); i++)
                  if (ends_with((*files)[i], "/" + initialcfg_filename)) {
                        foundit = i;
                        break;
                  }
            if (foundit > 0) {
                  std::string initialcfg = (*files)[foundit];
                  for (unsigned int i = foundit; i > 0; i--)
                        (*files)[i] = (*files)[i-1];
                  (*files)[0] = initialcfg;
            }
      }
}

std::string get_prefs_file()
{
      return get_user_data_dir() + "/preferences";
}

std::string get_save_index_file()
{
      return get_user_data_dir() + "/save_index.gz";
}

std::string get_saves_dir()
{
      const std::string dir_path = get_user_data_dir() + "/saves";
      return get_dir(dir_path);
}

std::string get_cache_dir()
{
      const std::string dir_path = get_user_data_dir() + "/cache";
      return get_dir(dir_path);
}

std::string get_addon_campaigns_dir()
{
      const std::string dir_path = get_user_data_dir() + "/data/add-ons";
      return get_dir(dir_path);
}

std::string get_intl_dir()
{
#ifdef _WIN32
      return get_cwd() + "/translations";
#else

#ifdef USE_INTERNAL_DATA
      return get_cwd() + "/" LOCALEDIR;
#endif

#if HAS_RELATIVE_LOCALEDIR
      std::string res = game_config::path + "/" LOCALEDIR;
#else
      std::string res = LOCALEDIR;
#endif

      return res;
#endif
}

std::string get_screenshot_dir()
{
      const std::string dir_path = get_user_data_dir() + "/screenshots";
      return get_dir(dir_path);
}

00315 std::string get_next_filename(const std::string& name, const std::string& extension)
{
      std::string next_filename;
      int counter = 0;

      do {
            std::stringstream filename;

            filename << name;
            filename.width(3);
            filename.fill('0');
            filename.setf(std::ios_base::right);
            filename << counter << extension;
            counter++;
            next_filename = filename.str();
      } while(file_exists(next_filename) && counter < 1000);
      return next_filename;
}


std::string get_upload_dir()
{
      const std::string dir_path = get_user_data_dir() + "/upload";
      return get_dir(dir_path);
}

std::string get_dir(const std::string& dir_path)
{
      DIR* dir = opendir(dir_path.c_str());
      if(dir == NULL) {
            const int res = mkdir(dir_path.c_str(),AccessMode);
            if(res == 0) {
                  dir = opendir(dir_path.c_str());
            } else {
                  ERR_FS << "could not open or create directory: " << dir_path << '\n';
            }
      }

      if(dir == NULL)
            return "";

      closedir(dir);

      return dir_path;
}

bool make_directory(const std::string& path)
{
      return (mkdir(path.c_str(),AccessMode) == 0);
}

// This deletes a directory with no hidden files and subdirectories.
// Also deletes a single file.
bool delete_directory(const std::string& path)
{
      bool ret = true;
      std::vector<std::string> files;
      std::vector<std::string> dirs;

      get_files_in_dir(path, &files, &dirs, ENTIRE_FILE_PATH);

      if(!files.empty()) {
            for(std::vector<std::string>::const_iterator i = files.begin(); i != files.end(); ++i) {
                  errno = 0;
                  if(remove((*i).c_str()) != 0) {
                        LOG_FS << "remove(" << (*i) << "): " << strerror(errno) << "\n";
                        ret = false;
                  }
            }
      }

      if(!dirs.empty()) {
            for(std::vector<std::string>::const_iterator j = dirs.begin(); j != dirs.end(); ++j) {
                  if(!delete_directory(*j))
                        ret = false;
            }
      }

      errno = 0;
#ifdef _WIN32
      // remove() doesn't delete directories on windows.
      int (*remove)(const char*);
      if(is_directory(path))
            remove = rmdir;
      else
            remove = ::remove;
#endif
      if(remove(path.c_str()) != 0) {
            LOG_FS << "remove(" << path << "): " << strerror(errno) << "\n";
            ret = false;
      }
      return ret;
}

std::string get_cwd()
{
      char buf[1024];
      const char* const res = getcwd(buf,sizeof(buf));
      if(res != NULL) {
            std::string str(res);

#ifdef _WIN32
            std::replace(str.begin(),str.end(),'\\','/');
#endif

            return str;
      } else {
            return "";
      }
}

std::string get_exe_dir()
{
#ifndef _WIN32
      char buf[1024];
      size_t path_size = readlink("/proc/self/exe", buf, 1024);
      if(path_size == static_cast<size_t>(-1))
            return std::string();
      buf[path_size] = 0;
      return std::string(dirname(buf));
#else
      return get_cwd();
#endif
}

00440 bool create_directory_if_missing(const std::string& dirname)
{
      if(is_directory(dirname)) {
            DBG_FS << "directory " << dirname << " exists, not creating\n";
            return true;
      } else if(file_exists(dirname)) {
            ERR_FS << "cannot create directory " << dirname << "; file exists\n";
            return false;
      }
      DBG_FS << "creating missing directory " << dirname << '\n';
      return make_directory(dirname);
}


namespace {
      std::string user_data_dir;
}

static std::string setup_user_data_dir();

void set_preferences_dir(std::string path)
{
#ifndef PREFERENCES_DIR
const std::string PREFERENCES_DIR = ".wesnoth" + std::string(game_config::version).substr(0,3);
#endif
#ifdef _WIN32
      if(path.empty()) {
            game_config::preferences_dir = get_cwd() + "/userdata";
      } else if (path.size() > 2 && path[1] == ':') {
            //allow absolute path override
            game_config::preferences_dir = path;
      } else {
            BOOL (*SHGetSpecialFolderPath)(HWND, LPTSTR, int, BOOL);
            HMODULE module = LoadLibrary("shell32");
            SHGetSpecialFolderPath = (BOOL (*)(HWND, LPTSTR, int, BOOL))GetProcAddress(module, "SHGetSpecialFolderPathA");
            if(SHGetSpecialFolderPath) {
                  LOG_FS << "Using SHGetSpecialFolderPath to find My Documents\n";
                  char my_documents_path[MAX_PATH];
                  if(SHGetSpecialFolderPath(NULL, my_documents_path, 5, 1)) {
                        std::string mygames_path = std::string(my_documents_path) + "/" + "My Games";
                        boost::algorithm::replace_all(mygames_path, std::string("\\"), std::string("/"));
                        create_directory_if_missing(mygames_path);
                        game_config::preferences_dir = mygames_path + "/" + path;
                  } else {
                        WRN_FS << "SHGetSpecialFolderPath failed\n";
                        game_config::preferences_dir = get_cwd() + "/" + path;
                  }
            } else {
                  LOG_FS << "Failed to load SHGetSpecialFolderPath function\n";
                  game_config::preferences_dir = get_cwd() + "/" + path;
            }
      }

#else /*_WIN32*/
      if (path.empty()) {
            path = PREFERENCES_DIR;
      }
#ifndef __AMIGAOS4__
      const char* const current_dir = ".";
      const char* home_str = getenv("HOME");
#else
      const char* const current_dir = " ";
      const char* home_str = "PROGDIR:";
#endif
      if(home_str == NULL)
            home_str = current_dir;

      const std::string home(home_str);

#ifndef __AMIGAOS4__
      game_config::preferences_dir = home + std::string("/") + path;
#else
      game_config::preferences_dir = home + path;
#endif

#endif /*_WIN32*/
      user_data_dir = setup_user_data_dir();
}


static std::string setup_user_data_dir()
{
      if (game_config::preferences_dir.empty())
            set_preferences_dir(std::string());
#ifdef _WIN32
      _mkdir((game_config::preferences_dir).c_str());
      _mkdir((game_config::preferences_dir + "/editor").c_str());
      _mkdir((game_config::preferences_dir + "/editor/maps").c_str());
      _mkdir((game_config::preferences_dir + "/data").c_str());
      _mkdir((game_config::preferences_dir + "/data/add-ons").c_str());
      _mkdir((game_config::preferences_dir + "/saves").c_str());

      return game_config::preferences_dir;
#elif defined(__BEOS__)
      if (be_path.InitCheck() != B_OK) {
            BPath tpath;
            if (find_directory(B_USER_SETTINGS_DIRECTORY, &be_path, true) == B_OK) {
                  be_path.Append("wesnoth");
            } else {
                  be_path.SetTo("/boot/home/config/settings/wesnoth");
            }
      #define BEOS_CREATE_PREFERENCES_SUBDIR(subdir) \
                  tpath = be_path;                       \
                  tpath.Append(subdir);                  \
                  create_directory(tpath.Path(), 0775);

            BEOS_CREATE_PREFERENCES_SUBDIR("editor");
            BEOS_CREATE_PREFERENCES_SUBDIR("editor/maps");
            BEOS_CREATE_PREFERENCES_SUBDIR("data");
            BEOS_CREATE_PREFERENCES_SUBDIR("data/add-ons");
            BEOS_CREATE_PREFERENCES_SUBDIR("saves");
      #undef BEOS_CREATE_PREFERENCES_SUBDIR
      }
      return be_path.Path();
#else
      const std::string& dir_path = game_config::preferences_dir;

      const bool res = create_directory_if_missing(dir_path);
      // probe read permissions (if we could make the directory)
      DIR* const dir = res ? opendir(dir_path.c_str()) : NULL;
      if(dir == NULL) {
            ERR_FS << "could not open or create preferences directory at " << dir_path << '\n';
            return "";
      }
      closedir(dir);

      // Create user data and add-on directories
      create_directory_if_missing(dir_path + "/editor");
      create_directory_if_missing(dir_path + "/editor/maps");
      create_directory_if_missing(dir_path + "/data");
      create_directory_if_missing(dir_path + "/data/add-ons");
      create_directory_if_missing(dir_path + "/saves");

      return dir_path;
#endif
}

const std::string& get_user_data_dir()
{
      // ensure setup gets called only once per session
      // FIXME: this is okay and optimized, but how should we react
      // if the user deletes a dir while we are running?
      if (user_data_dir.empty())
      {
            user_data_dir = setup_user_data_dir();
      }
      return user_data_dir;
}

static std::string read_stream(std::istream& s)
{
      std::stringstream ss;
      ss << s.rdbuf();
      return ss.str();
}

std::istream *istream_file(const std::string &fname)
{
      LOG_FS << "Streaming " << fname << " for reading.\n";
      if (fname.empty())
      {
            ERR_FS << "Trying to open file with empty name.\n";
            std::ifstream *s = new std::ifstream();
            s->clear(std::ios_base::failbit);
            return s;
      }
#ifndef _WIN32
      // TODO: Should also be done for Windows; but *nix systems should
      // already be sufficient to catch most offenders.
      if (fname[0] != '/') {
            WRN_FS << "Trying to open file with relative path: '" << fname << "'.\n";
#if 0
            std::ifstream *s = new std::ifstream();
            s->clear(std::ios_base::failbit);
            return s;
#endif
      }
#endif
      std::ifstream *s = new std::ifstream(fname.c_str(),std::ios_base::binary);
      if (s->is_open())
            return s;
      ERR_FS << "Could not open '" << fname << "' for reading.\n";
      return s;

}

00626 std::string read_file(const std::string &fname)
{
      scoped_istream s = istream_file(fname);
      return read_stream(*s);
}

std::ostream *ostream_file(std::string const &fname)
{
      LOG_FS << "streaming " << fname << " for writing.\n";
      return new std::ofstream(fname.c_str(), std::ios_base::binary);
}

// Throws io_exception if an error occurs
00639 void write_file(const std::string& fname, const std::string& data)
{
      //const util::scoped_resource<FILE*,close_FILE> file(fopen(fname.c_str(),"wb"));
      const util::scoped_FILE file(fopen(fname.c_str(),"wb"));
      if(file.get() == NULL) {
            throw io_exception("Could not open file for writing: '" + fname + "'");
      }

      const size_t block_size = 4096;
      char buf[block_size];

      for(size_t i = 0; i < data.size(); i += block_size) {
            const size_t bytes = std::min<size_t>(block_size,data.size() - i);
            std::copy(data.begin() + i, data.begin() + i + bytes,buf);
            const size_t res = fwrite(buf,1,bytes,file.get());
            if(res != bytes) {
                  throw io_exception("Error writing to file: '" + fname + "'");
            }
      }
}


std::string read_map(const std::string& name)
{
      std::string res;
      std::string map_location = get_wml_location("maps/" + name);
      if(!map_location.empty()) {
            res = read_file(map_location);
      }

      if (res.empty()) {
            res = read_file(get_user_data_dir() + "/editor/maps/" + name);
      }

      return res;
}

static bool is_directory_internal(const std::string& fname)
{
#ifdef _WIN32
      _finddata_t info;
      const long handle = _findfirst((fname + "/*").c_str(),&info);
      if(handle >= 0) {
            _findclose(handle);
            return true;
      } else {
            return false;
      }

#else
      struct stat dir_stat;
      if(::stat(fname.c_str(), &dir_stat) == -1) {
            return false;
      }
      return S_ISDIR(dir_stat.st_mode);
#endif
}

bool is_directory(const std::string& fname)
{
      if(fname.empty()) {
            return false;
      }
      if(fname[0] != '/' && !game_config::path.empty()) {
            if(is_directory_internal(game_config::path + "/" + fname))
                  return true;
      }

      return is_directory_internal(fname);
}

bool file_exists(const std::string& name)
{
#ifdef _WIN32
       struct stat st;
       return (::stat(name.c_str(), &st) == 0);
#else
      struct stat st;
      return (::stat(name.c_str(), &st) != -1);
#endif
}

time_t file_create_time(const std::string& fname)
{
      struct stat buf;
      if(::stat(fname.c_str(),&buf) == -1)
            return 0;

      return buf.st_mtime;
}

std::string next_filename(const std::string &dirname, unsigned int max)
{
      std::vector<std::string> files;
      std::stringstream fname;
      unsigned int num = 1;

      // These are sorted, so we can simply add one to last one.
      get_files_in_dir(dirname, &files);

      // Make sure we skip over any files we didn't create ourselves.
      std::vector<std::string>::reverse_iterator i;
      for (i = files.rbegin(); i != files.rend(); ++i) {
            if (i->length() == 8) {
                  try {
                        num = lexical_cast<int>(*i)+1;
                        break;
                  } catch (bad_lexical_cast &) {
                  }
            }
      }

      // Erase oldest files if we have too many
      if (max) {
            for (unsigned int j = 0; j + max < files.size(); j++) {
                  delete_directory(dirname + "/" + files[j]);
            }
      }

      fname << std::setw(8) << std::setfill('0') << num;
      return dirname + "/" + fname.str();
}

/**
 * Returns true if the file ends with '.gz'.
 *
 * @param filename                The name to test.
 */
bool is_gzip_file(const std::string& filename)
{
      return (filename.length() > 3
            && filename.substr(filename.length() - 3) == ".gz");
}

file_tree_checksum::file_tree_checksum()
      : nfiles(0), sum_size(0), modified(0)
{}

file_tree_checksum::file_tree_checksum(const config& cfg) :
      nfiles      (lexical_cast_default<size_t>(cfg["nfiles"])),
      sum_size(lexical_cast_default<size_t>(cfg["size"])),
      modified(lexical_cast_default<time_t>(cfg["modified"]))
{
}

void file_tree_checksum::write(config& cfg) const
{
      cfg["nfiles"] = lexical_cast<std::string>(nfiles);
      cfg["size"] = lexical_cast<std::string>(sum_size);
      cfg["modified"] = lexical_cast<std::string>(modified);
}

bool operator==(const file_tree_checksum& lhs, const file_tree_checksum& rhs)
{
      return lhs.nfiles == rhs.nfiles && lhs.sum_size == rhs.sum_size &&
               lhs.modified == rhs.modified;
}

bool operator!=(const file_tree_checksum& lhs, const file_tree_checksum& rhs)
{
      return !operator==(lhs,rhs);
}

static void get_file_tree_checksum_internal(const std::string& path, file_tree_checksum& res)
{

      std::vector<std::string> dirs;
      get_files_in_dir(path,NULL,&dirs, ENTIRE_FILE_PATH, SKIP_MEDIA_DIR, DONT_REORDER, &res);
      increment_filesystem_progress();

      for(std::vector<std::string>::const_iterator j = dirs.begin(); j != dirs.end(); ++j) {
            get_file_tree_checksum_internal(*j,res);
      }
}

const file_tree_checksum& data_tree_checksum(bool reset)
{
      static file_tree_checksum checksum;
      if (reset)
            checksum.reset();
      if(checksum.nfiles == 0) {
            get_file_tree_checksum_internal("data/",checksum);
            get_file_tree_checksum_internal(get_user_data_dir() + "/data/",checksum);
            LOG_FS << "calculated data tree checksum: "
                     << checksum.nfiles << " files; "
                     << checksum.sum_size << " bytes\n";
      }

      return checksum;
}

int file_size(const std::string& fname)
{
      struct stat buf;
      if(::stat(fname.c_str(),&buf) == -1)
            return -1;

      return buf.st_size;
}

std::string file_name(const std::string& file)
// Analogous to POSIX basename(3), but for C++ string-object pathnames
{
#ifdef _WIN32
      static const std::string dir_separators = "\\/:";
#else
      static const std::string dir_separators = "/";
#endif

      std::string::size_type pos = file.find_last_of(dir_separators);

      if(pos == std::string::npos)
            return file;
      if(pos >= file.size()-1)
            return "";

      return file.substr(pos+1);
}

std::string directory_name(const std::string& file)
// Analogous to POSIX dirname(3), but for C++ string-object pathnames
{
#ifdef _WIN32
      static const std::string dir_separators = "\\/:";
#else
      static const std::string dir_separators = "/";
#endif

      std::string::size_type pos = file.find_last_of(dir_separators);

      if(pos == std::string::npos)
            return "";

      return file.substr(0,pos+1);
}

namespace {

std::set<std::string> binary_paths;

typedef std::map<std::string,std::vector<std::string> > paths_map;
paths_map binary_paths_cache;

}

static void init_binary_paths()
{
      if(binary_paths.empty()) {
            binary_paths.insert("");
      }
}

binary_paths_manager::binary_paths_manager() : paths_()
{}

binary_paths_manager::binary_paths_manager(const config& cfg) : paths_()
{
      set_paths(cfg);
}

binary_paths_manager::~binary_paths_manager()
{
      cleanup();
}

void binary_paths_manager::set_paths(const config& cfg)
{
      cleanup();
      init_binary_paths();

      foreach (const config &bp, cfg.child_range("binary_path"))
      {
            std::string path = bp["path"].str();
            if (path.find("..") != std::string::npos) {
                  ERR_FS << "Invalid binary path '" << path << "'\n";
                  continue;
            }
            if (!path.empty() && path[path.size()-1] != '/') path += "/";
            if(binary_paths.count(path) == 0) {
                  binary_paths.insert(path);
                  paths_.push_back(path);
            }
      }
}

void binary_paths_manager::cleanup()
{
      binary_paths_cache.clear();

      for(std::vector<std::string>::const_iterator i = paths_.begin(); i != paths_.end(); ++i) {
            binary_paths.erase(*i);
      }
}

void clear_binary_paths_cache()
{
      binary_paths_cache.clear();
}

const std::vector<std::string>& get_binary_paths(const std::string& type)
{
      const paths_map::const_iterator itor = binary_paths_cache.find(type);
      if(itor != binary_paths_cache.end()) {
            return itor->second;
      }

      if (type.find("..") != std::string::npos) {
            // Not an assertion, as language.cpp is passing user data as type.
            ERR_FS << "Invalid WML type '" << type << "' for binary paths\n";
            static std::vector<std::string> dummy;
            return dummy;
      }

      std::vector<std::string>& res = binary_paths_cache[type];

      init_binary_paths();

      foreach (const std::string &path, binary_paths)
      {
            res.push_back(get_user_data_dir() + "/" + path + type + "/");

            if(!game_config::path.empty()) {
                  res.push_back(game_config::path + "/" + path + type + "/");
            }
      }

      // not found in "/type" directory, try main directory
      res.push_back(get_user_data_dir() + "/");

      if(!game_config::path.empty())
            res.push_back(game_config::path+"/");

      return res;
}

std::string get_binary_file_location(const std::string& type, const std::string& filename)
{
      DBG_FS << "Looking for '" << filename << "'.\n";

      if (filename.empty()) {
            LOG_FS << "  invalid filename (type: " << type <<")\n";
            return std::string();
      }

      // Some parts of Wesnoth enjoy putting ".." inside filenames. This is
      // bad and should be fixed. But in the meantime, deal with them in a dumb way.
      std::string::size_type pos = filename.rfind("../");
      if (pos != std::string::npos) {
            std::string nf = filename.substr(pos + 3);
            LOG_FS << "Illegal path '" << filename << "' replaced by '" << nf << "'\n";
            return get_binary_file_location(type, nf);
      }

      if (filename.find("..") != std::string::npos) {
            ERR_FS << "Illegal path '" << filename << "' (\"..\" not allowed).\n";
            return std::string();
      }

      foreach (const std::string &path, get_binary_paths(type))
      {
            const std::string file = path + filename;
            DBG_FS << "  checking '" << path << "'\n";
            if(file_exists(file)) {
                  DBG_FS << "  found at '" << file << "'\n";
                  return file;
            }
      }

      DBG_FS << "  not found\n";
      return std::string();
}

std::string get_binary_dir_location(const std::string &type, const std::string &filename)
{
      DBG_FS << "Looking for '" << filename << "'.\n";

      if (filename.empty()) {
            LOG_FS << "  invalid filename (type: " << type <<")\n";
            return std::string();
      }

      if (filename.find("..") != std::string::npos) {
            ERR_FS << "Illegal path '" << filename << "' (\"..\" not allowed).\n";
            return std::string();
      }

      foreach (const std::string &path, get_binary_paths(type))
      {
            const std::string file = path + filename;
            DBG_FS << "  checking '" << path << "'\n";
            if (is_directory(file)) {
                  DBG_FS << "  found at '" << file << "'\n";
                  return file;
            }
      }

      DBG_FS << "  not found\n";
      return std::string();
}

std::string get_wml_location(const std::string &filename, const std::string &current_dir)
{
      DBG_FS << "Looking for '" << filename << "'.\n";

      std::string result;

      if (filename.empty()) {
            LOG_FS << "  invalid filename\n";
            return result;
      }

      if (filename.find("..") != std::string::npos) {
            ERR_FS << "Illegal path '" << filename << "' (\"..\" not allowed).\n";
            return result;
      }

      bool already_found = false;

      if (filename[0] == '~')
      {
            // If the filename starts with '~', look in the user data directory.
            result = get_user_data_dir() + "/data/" + filename.substr(1);
            DBG_FS << "  trying '" << result << "'\n";

            already_found = file_exists(result) || is_directory(result);
      }
      else if (filename.size() >= 2 && filename[0] == '.' && filename[1] == '/')
      {
            // If the filename begins with a "./", look in the same directory
            // as the file currrently being preprocessed.
            result = current_dir + filename.substr(2);
      }
      else if (!game_config::path.empty())
            result = game_config::path + "/data/" + filename;

      DBG_FS << "  trying '" << result << "'\n";

      if (result.empty() ||
          (!already_found && !file_exists(result) && !is_directory(result)))
      {
            DBG_FS << "  not found\n";
            result.clear();
      }
      else
            DBG_FS << "  found: '" << result << "'\n";

      return result;
}

std::string get_short_wml_path(const std::string &filename)
{
      std::string match = get_user_data_dir() + "/data/";
      if (filename.find(match) == 0) {
            return "~" + filename.substr(match.size());
      }
      match = game_config::path + "/data/";
      if (filename.find(match) == 0) {
            return filename.substr(match.size());
      }
      return filename;
}

std::string get_program_invocation(const std::string& program_name) {
#ifdef DEBUG
  #ifdef _WIN32
      const char *program_suffix = "-debug.exe";
  #else
      const char *program_suffix = "-debug";
  #endif
#else
  #ifdef _WIN32
      const char *program_suffix = ".exe";
  #else
      const char *program_suffix = "";
  #endif
#endif

      const std::string real_program_name(program_name + program_suffix);
      if(game_config::wesnoth_program_dir.empty()) return real_program_name;
#ifdef _WIN32
      return game_config::wesnoth_program_dir + "\\" + real_program_name;
#else
      return game_config::wesnoth_program_dir + "/" + real_program_name;
#endif
}

static bool is_path_sep(char c)
{
#ifdef _WIN32
      if (c == '/' || c == '\\') return true;
#else
      if (c == '/') return true;
#endif
      return false;
}

std::string normalize_path(const std::string &p1)
{
      if (p1.empty()) return p1;

      std::string p2;
#ifdef _WIN32
      if (p1.size() >= 2 && p1[1] == ':')
            // Windows relative paths with explicit drive name are not handled.
            p2 = p1;
      else
#endif
      if (!is_path_sep(p1[0]))
            p2 = get_cwd() + "/" + p1;
      else
            p2 = p1;

#ifdef _WIN32
      std::string drive;
      if (p2.size() >= 2 && p2[1] == ':') {
            drive = p2.substr(0, 2);
            p2.erase(0, 2);
      }
#endif

      std::vector<std::string> components(1);
      for (int i = 0, i_end = p2.size(); i <= i_end; ++i)
      {
            std::string &last = components[components.size() - 1];
            char c = p2.c_str()[i];
            if (is_path_sep(c) || c == 0)
            {
                  if (last == ".")
                        last.clear();
                  else if (last == "..")
                  {
                        if (components.size() >= 2) {
                              components.pop_back();
                              components[components.size() - 1].clear();
                        } else
                              last.clear();
                  }
                  else if (!last.empty())
                        components.push_back(std::string());
            }
            else
                  last += c;
      }

      std::ostringstream p4;
      components.pop_back();

#ifdef _WIN32
      p4 << drive;
#endif

      foreach (const std::string &s, components)
      {
            p4 << '/' << s;
      }

      DBG_FS << "Normalizing '" << p2 << "' to '" << p4.str() << "'\n";

      return p4.str();
}

scoped_istream& scoped_istream::operator=(std::istream *s)
{
      delete stream;
      stream = s;
      return *this;
}

scoped_istream::~scoped_istream()
{
      delete stream;
}

scoped_ostream& scoped_ostream::operator=(std::ostream *s)
{
      delete stream;
      stream = s;
      return *this;
}

scoped_ostream::~scoped_ostream()
{
      delete stream;
}

Generated by  Doxygen 1.6.0   Back to index