mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-10 12:56:24 +00:00
1216 lines
29 KiB
C++
1216 lines
29 KiB
C++
/* $Id$ */
|
|
/*
|
|
Copyright (C) 2003 - 2009 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.
|
|
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);
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
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
|
|
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);
|
|
WRN_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 ¤t_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] == '@')
|
|
{
|
|
// TODO: remove this before 1.8
|
|
ERR_FS << filename << " style includes are obsolete and no longer work. Use '~' instead.\n";
|
|
}
|
|
|
|
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_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;
|
|
}
|