Files
stk-code_catmod/src/io/file_manager.cpp
T

1650 lines
58 KiB
C++

//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2004-2015 Steve Baker <sjbaker1@airmail.net>
// Copyright (C) 2008-2015 Steve Baker, Joerg Henrichs
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "io/file_manager.hpp"
#include "config/user_config.hpp"
#include "graphics/irr_driver.hpp"
#include "graphics/material_manager.hpp"
#include "guiengine/engine.hpp"
#include "guiengine/skin.hpp"
#include "karts/kart_properties_manager.hpp"
#include "tracks/track_manager.hpp"
#include "utils/command_line.hpp"
#include "utils/extract_mobile_assets.hpp"
#include "utils/file_utils.hpp"
#include "utils/log.hpp"
#include "utils/mem_utils.hpp"
#include "utils/string_utils.hpp"
#ifdef ANDROID
#include "io/assets_android.hpp"
#endif
#ifdef __HAIKU__
#include <Path.h>
#include <FindDirectory.h>
#endif
#include <stdio.h>
#include <stdexcept>
#include <sstream>
#include <sys/stat.h>
#include <iostream>
#include <string>
#include <IFileSystem.h>
namespace irr {
namespace io
{
IFileSystem* createFileSystem();
}
}
// For mkdir
#if !defined(WIN32)
# include <sys/stat.h>
# include <sys/types.h>
# include <dirent.h>
# include <unistd.h>
#else
# define WIN32_LEAN_AND_MEAN
# include <direct.h>
# include <windows.h>
# include <stdio.h>
# if !defined(__MINGW32__)
/*Needed by the remove directory function */
# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
# define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG)
# endif
#endif
std::vector<std::string> FileManager::m_root_dirs;
std::string FileManager::m_stdout_filename = "stdout.log";
std::string FileManager::m_stdout_dir;
#ifdef __APPLE__
// dynamic data path detection onmac
# include <CoreFoundation/CoreFoundation.h>
bool macSetBundlePathIfRelevant(std::string& data_dir)
{
Log::debug("[FileManager]", "Checking whether we are using an app bundle... ");
// the following code will enable STK to find its data when placed in an
// app bundle on mac OS X.
// returns true if path is set, returns false if path was not set
char path[1024];
CFBundleRef main_bundle = CFBundleGetMainBundle(); assert(main_bundle);
CFURLRef main_bundle_URL = CFBundleCopyBundleURL(main_bundle);
assert(main_bundle_URL);
CFStringRef cf_string_ref = CFURLCopyFileSystemPath(main_bundle_URL,
kCFURLPOSIXPathStyle);
assert(cf_string_ref);
CFStringGetCString(cf_string_ref, path, 1024, kCFStringEncodingUTF8);
CFRelease(main_bundle_URL);
CFRelease(cf_string_ref);
// IOS version of stk store data folder directly inside supertuxkart.app
std::string contents = std::string(path) + "/";
#ifndef IOS_STK
contents += std::string("Contents");
#endif
if(contents.find(".app") != std::string::npos)
{
#ifdef IOS_STK
data_dir = contents;
return true;
#else
Log::debug("[FileManager]", "yes");
// executable is inside an app bundle, use app bundle-relative paths
data_dir = contents + std::string("/Resources/");
return true;
#endif
}
else
{
Log::debug("[FileManager]", "no");
return false;
}
}
#endif
// ============================================================================
FileManager* file_manager = 0;
/** The constructor of the file manager creates an irrlicht file system and
* detects paths for the user config file and assets base directory (data).
* A second initialisation is done later once (see init()), once the user
* config file is read. This is necessary since part of discoverPaths
* depend on artist debug mode.
*/
FileManager::FileManager()
{
m_root_dirs.clear();
resetSubdir();
#ifdef __APPLE__
// irrLicht's createDevice method has a nasty habit of messing the CWD.
// since the code above may rely on it, save it to be able to restore
// it after.
char buffer[256];
getcwd(buffer, 256);
#endif
#ifdef __APPLE__
chdir( buffer );
#endif
m_file_system = irr::io::createFileSystem();
#ifdef ANDROID
AssetsAndroid android_assets(this);
android_assets.init();
#endif
std::string exe_path;
// Search for the root directory
// =============================
// Also check for data dirs relative to the path of the executable.
// This is esp. useful for Visual Studio, since it's not necessary
// to define the working directory when debugging, it works automatically.
std::string root_dir;
const std::string version = std::string("supertuxkart.") + STK_VERSION;
if (fileExists(CommandLine::getExecName()))
{
exe_path = StringUtils::getPath(CommandLine::getExecName());
}
if(exe_path.size()==0 || exe_path[exe_path.size()-1]!='/')
exe_path += "/";
if ( getenv ( "SUPERTUXKART_DATADIR" ) != NULL )
root_dir = std::string(getenv("SUPERTUXKART_DATADIR"))+"/data/" ;
#ifdef __APPLE__
else if( macSetBundlePathIfRelevant( root_dir ) ) { root_dir = root_dir + "data/"; }
#endif
#ifdef __SWITCH__
else if(fileExists("sdmc:/stk-data/", version))
root_dir = "sdmc:/stk-data/";
else if(fileExists("romfs:/data/", version))
root_dir = "romfs:/data/";
#endif
else if(fileExists("./data/", version))
root_dir = "./data/" ;
else if(fileExists("../data/", version))
root_dir = "../data/" ;
else if(fileExists("../../data/", version))
root_dir = "../../data/" ;
// Test for old style build environment, with executable in root of stk
else if(fileExists(exe_path+"data/"+version))
root_dir = (exe_path+"data/").c_str();
// Check for windows cmake style: bld/Debug/bin/supertuxkart.exe
else if (fileExists(exe_path + "../../../data/"+version))
root_dir = exe_path + "../../../data/";
else if (fileExists(exe_path + "../data/"+version))
{
root_dir = exe_path.c_str();
root_dir += "../data/";
}
else
{
#ifdef SUPERTUXKART_DATADIR
root_dir = SUPERTUXKART_DATADIR"/data/";
#else
root_dir = "/usr/local/share/games/supertuxkart/";
#endif
}
if (!m_file_system->existFile((root_dir + version).c_str()))
{
Log::error("FileManager", "Could not find file '%s'in any "
"standard location (esp. ../data).", version.c_str());
Log::error("FileManager",
"Last location checked '%s'.", root_dir.c_str());
Log::fatal("FileManager",
"Set $SUPERTUXKART_DATADIR to point to the data directory.");
// fatal will exit the application
}
addRootDirs(root_dir);
std::string assets_dir;
#ifdef MOBILE_STK
m_stk_assets_download_dir = getenv("HOME");
#ifdef IOS_STK
m_stk_assets_download_dir += "/Library/Application Support/SuperTuxKart/stk-assets/";
#elif defined (ANDROID)
m_stk_assets_download_dir += "/stk-assets/";
#else
#error You must set m_stk_assets_download_dir to appropriate place for your platform
#endif
#else
if (getenv("SUPERTUXKART_ASSETS_DIR") != NULL)
{
assets_dir = std::string(getenv("SUPERTUXKART_ASSETS_DIR"));
}
else if (fileExists(root_dir + "../../stk-assets"))
{
assets_dir = root_dir + "../../stk-assets";
}
else if (fileExists(root_dir + "../../supertuxkart-assets"))
{
assets_dir = root_dir + "../../supertuxkart-assets";
}
else if (getenv("SUPERTUXKART_ROOT_PATH") != NULL)
{
//is this needed?
assets_dir = std::string(getenv("SUPERTUXKART_ROOT_PATH"));
}
#endif
if (!assets_dir.empty() && assets_dir != root_dir)
{
addRootDirs(assets_dir);
}
checkAndCreateConfigDir();
checkAndCreateAddonsDir();
checkAndCreateScreenshotDir();
checkAndCreateReplayDir();
checkAndCreateCachedTexturesDir();
checkAndCreateGPDir();
redirectOutput();
} // FileManager
// ----------------------------------------------------------------------------
/** Reset subdirectories to initial state, for example after download assets
*/
void FileManager::resetSubdir()
{
m_subdir_name.clear();
m_subdir_name.resize(ASSET_COUNT);
m_subdir_name[CHALLENGE ] = "challenges";
m_subdir_name[GFX ] = "gfx";
m_subdir_name[GRANDPRIX ] = "grandprix";
m_subdir_name[GUI_ICON ] = "gui/icons";
m_subdir_name[GUI_SCREEN ] = "gui/screens";
m_subdir_name[GUI_DIALOG ] = "gui/dialogs";
m_subdir_name[LIBRARY ] = "library";
m_subdir_name[MODEL ] = "models";
m_subdir_name[MUSIC ] = "music";
m_subdir_name[REPLAY ] = "replay";
m_subdir_name[SCRIPT ] = "tracks";
m_subdir_name[SFX ] = "sfx";
m_subdir_name[SKIN ] = "skins";
m_subdir_name[SHADER ] = "shaders";
m_subdir_name[TEXTURE ] = "textures";
m_subdir_name[TTF ] = "ttf";
m_subdir_name[TRANSLATION] = "po";
} // resetSubdir
// ----------------------------------------------------------------------------
/** Detects where the assets are stored.
*/
void FileManager::discoverPaths()
{
resetSubdir();
// We can't use _() here, since translations will only be initalised
// after the filemanager (to get the path to the tranlsations from it)
for(unsigned int i=0; i<m_root_dirs.size(); i++)
Log::info("[FileManager]", "Data files will be fetched from: '%s'",
m_root_dirs[i].c_str());
Log::info("[FileManager]", "User directory is '%s'.",
m_user_config_dir.c_str());
Log::info("[FileManager]", "Addons files will be stored in '%s'.",
m_addons_dir.c_str());
Log::info("[FileManager]", "Screenshots will be stored in '%s'.",
m_screenshot_dir.c_str());
Log::info("[FileManager]", "User-defined grand prix will be stored in '%s'.",
m_gp_dir.c_str());
// Reset for re-downloading assets if needed
TrackManager::removeTrackSearchDirs();
KartPropertiesManager::removeKartSearchDirs();
/** Now search for the path to all needed subdirectories. */
// ==========================================================
// This must be done here since otherwise translations will not be found.
std::vector<bool> dir_found;
dir_found.resize(ASSET_COUNT, false);
#ifdef MOBILE_STK
assert(!m_root_dirs.empty());
for (unsigned j = ASSET_MIN; j <= BUILTIN_ASSETS; j++)
{
if (!dir_found[j] && fileExists(m_root_dirs[0] + m_subdir_name[j]))
{
dir_found[j] = true;
m_subdir_name[j] = m_root_dirs[0] + m_subdir_name[j] + "/";
}
}
bool has_full_assets = ExtractMobileAssets::hasFullAssets();
// Clear previous assets version to free space
if (!has_full_assets && fileExists(m_stk_assets_download_dir))
removeDirectory(m_stk_assets_download_dir);
// Use stk-assets-full for karts, tracks, textures..., otherwise in data/
std::string assets_root = has_full_assets ?
m_stk_assets_download_dir : m_root_dirs[0];
for (unsigned j = LIBRARY; j <= ASSET_MAX; j++)
{
if (!dir_found[j] && fileExists(assets_root + m_subdir_name[j]))
{
dir_found[j] = true;
m_subdir_name[j] = assets_root + m_subdir_name[j] + "/";
}
}
if (fileExists(assets_root + "tracks/"))
TrackManager::addTrackSearchDir(assets_root + "tracks/");
if (fileExists(assets_root + "karts/"))
KartPropertiesManager::addKartSearchDir(assets_root + "karts/");
if (UserConfigParams::m_artist_debug_mode)
{
if (fileExists(assets_root + "wip-tracks/"))
TrackManager::addTrackSearchDir(assets_root + "wip-tracks/");
if (fileExists(assets_root + "wip-karts/"))
KartPropertiesManager::addKartSearchDir(assets_root + "wip-karts/");
}
#else
for(unsigned int i=0; i<m_root_dirs.size(); i++)
{
if(fileExists(m_root_dirs[i]+"tracks/"))
TrackManager::addTrackSearchDir(m_root_dirs[i]+"tracks/");
if(fileExists(m_root_dirs[i]+"karts/"))
KartPropertiesManager::addKartSearchDir(m_root_dirs[i]+"karts/");
// If artist debug mode is enabled, add
// work-in-progress tracks and karts
if (UserConfigParams::m_artist_debug_mode)
{
if(fileExists(m_root_dirs[i] + "wip-tracks/"))
TrackManager::addTrackSearchDir(m_root_dirs[i] + "wip-tracks/");
if(fileExists(m_root_dirs[i] + "wip-karts/"))
KartPropertiesManager::addKartSearchDir(m_root_dirs[i] + "wip-karts/");
}
for(unsigned int j=ASSET_MIN; j<=ASSET_MAX; j++)
{
if(!dir_found[j] && fileExists(m_root_dirs[i]+m_subdir_name[j]))
{
dir_found[j] = true;
m_subdir_name[j] = m_root_dirs[i]+m_subdir_name[j]+"/";
} // !dir_found && file_exist
} // for j=ASSET_MIN; j<=ASSET_MAX
} // for i<m_root_dirs
#endif
bool was_error = false;
for(unsigned int i=ASSET_MIN; i<=ASSET_MAX; i++)
{
if(!dir_found[i])
{
Log::warn("[FileManager]", "Directory '%s' not found, aborting.",
m_subdir_name[i].c_str());
was_error = true;
}
else
Log::info("[FileManager]", "Asset %d will be loaded from '%s'.",
i, m_subdir_name[i].c_str());
}
if(was_error)
Log::fatal("[FileManager]", "Not all assets found - aborting.");
} // discoverPaths
//-----------------------------------------------------------------------------
/** This function is used to initialise the file-manager after reading in
* the user configuration data. Esp. discovering the paths of all assets
* depends on the user config file (artist debug mode).
*/
void FileManager::init()
{
discoverPaths();
addAssetsSearchPath();
m_cert_bundle_location = FileUtils::getPortableWritingPath(
m_file_system->getAbsolutePath(getAsset("cacert.pem").c_str()).c_str());
// Clean up left-over files in addons/tmp that are older than 24h
// ==============================================================
// (The 24h delay is useful when debugging a problem with a zip file)
// We do when starting STK because for mobile STK destructor of file
// manager may never be called if only home button is pressed
std::set<std::string> allfiles;
std::string tmp=getAddonsFile("tmp");
listFiles(allfiles, tmp);
for(std::set<std::string>::iterator i=allfiles.begin();
i!=allfiles.end(); i++)
{
if((*i)=="." || (*i)=="..") continue;
// For now there should be only zip files or .part files
// (not fully downloaded files) in tmp. Warn about any
// other files.
std::string full_path=tmp+"/"+*i;
if(StringUtils::getExtension(*i)!="zip" &&
StringUtils::getExtension(*i)!="part" )
{
Log::warn("[FileManager]", "Unexpected tmp file '%s' found.",
full_path.c_str());
continue;
}
if(isDirectory(full_path))
{
// Gee, a .zip file which is a directory - stay away from it
Log::warn("[FileManager]", "'%s' is a directory and will not be deleted.",
full_path.c_str());
continue;
}
struct stat mystat;
FileUtils::statU8Path(full_path, &mystat);
StkTime::TimeType current = StkTime::getTimeSinceEpoch();
if(current - mystat.st_ctime <24*3600)
{
if(UserConfigParams::logAddons())
Log::verbose("[FileManager]", "'%s' is less than 24h old "
"and will not be deleted.",
full_path.c_str());
continue;
}
if(UserConfigParams::logAddons())
Log::verbose("[FileManager]", "Deleting tmp file'%s'.",full_path.c_str());
removeFile(full_path);
} // for i in all files in tmp
} // init
//-----------------------------------------------------------------------------
void FileManager::addAssetsSearchPath()
{
// Note that we can't push the texture search path in the constructor
// since this also adds a file archive to the file system - and
// m_file_system is deleted (in irr_driver)
pushTextureSearchPath(m_subdir_name[TEXTURE], "textures");
pushTextureSearchPath(m_subdir_name[TEXTURE]+"skybox/", "skybox");
if (fileExists(m_subdir_name[TEXTURE]+"deprecated/"))
pushTextureSearchPath(m_subdir_name[TEXTURE]+"deprecated/", "deprecatedtex");
pushTextureSearchPath(m_subdir_name[GUI_ICON], "gui/icons");
pushModelSearchPath (m_subdir_name[MODEL]);
pushMusicSearchPath (m_subdir_name[MUSIC]);
// Add more paths from the STK_MUSIC_PATH environment variable
if(getenv("SUPERTUXKART_MUSIC_PATH")!=NULL)
{
std::string path=getenv("SUPERTUXKART_MUSIC_PATH");
std::vector<std::string> dirs = StringUtils::splitPath(path);
for(int i=0;i<(int)dirs.size(); i++)
pushMusicSearchPath(dirs[i]);
}
} // addAssetsSearchPath
//-----------------------------------------------------------------------------
void FileManager::reinitAfterDownloadAssets()
{
m_file_system->removeAllFileArchives();
m_texture_search_path.clear();
m_model_search_path.clear();
m_music_search_path.clear();
discoverPaths();
addAssetsSearchPath();
// Add back addons search path
KartPropertiesManager::addKartSearchDir(
file_manager->getAddonsFile("karts/"));
track_manager->addTrackSearchDir(
file_manager->getAddonsFile("tracks/"));
} // reinitAfterDownloadAssets
//-----------------------------------------------------------------------------
FileManager::~FileManager()
{
// Clean up rest of file manager
// =============================
popMusicSearchPath();
popModelSearchPath();
popTextureSearchPath();
popTextureSearchPath();
m_file_system->drop();
m_file_system = NULL;
} // ~FileManager
// ----------------------------------------------------------------------------
/** Returns true if the specified file exists.
*/
bool FileManager::fileExists(const std::string& path) const
{
#ifdef DEBUG
bool exists = m_file_system->existFile(path.c_str());
if(exists) return true;
// Now the original file was not found. Test if replacing \ with / helps:
std::string s = StringUtils::replace(path, "\\", "/");
exists = m_file_system->existFile(s.c_str());
if(exists)
Log::warn("FileManager", "File '%s' does not exists, but '%s' does!",
path.c_str(), s.c_str());
return exists;
#else
return m_file_system->existFile(path.c_str());
#endif
} // fileExists
//-----------------------------------------------------------------------------
/** Adds paths to the list of stk root directories.
* \param roots A ":" separated string of directories to add.
*/
void FileManager::addRootDirs(const std::string &roots)
{
std::vector<std::string> all = StringUtils::splitPath(roots);
for(unsigned int i=0; i<all.size(); i++)
{
if(all[i].size()==0 || all[i][all[i].size()-1]!='/')
all[i] += "/";
m_root_dirs.push_back(all[i]);
}
} // addRootDirs
//-----------------------------------------------------------------------------
io::IXMLReader *FileManager::createXMLReader(const std::string &filename)
{
return m_file_system->createXMLReader(filename.c_str());
} // getXMLReader
//-----------------------------------------------------------------------------
/** Reads in a XML file and converts it into a XMLNode tree.
* \param filename Name of the XML file to read.
*/
XMLNode *FileManager::createXMLTree(const std::string &filename)
{
try
{
XMLNode* node = new XMLNode(filename);
return node;
}
catch (std::runtime_error& e)
{
if (UserConfigParams::logMisc())
{
Log::error("[FileManager]", "createXMLTree: %s", e.what());
}
return NULL;
}
} // createXMLTree
//-----------------------------------------------------------------------------
/** Reads in XML from a string and converts it into a XMLNode tree.
* \param content the string containing the XML content.
*/
XMLNode *FileManager::createXMLTreeFromString(const std::string & content)
{
try
{
char *b = new char[content.size()];
assert(b);
memcpy(b, content.c_str(), content.size());
io::IReadFile * ireadfile =
m_file_system->createMemoryReadFile(b, (int)content.size(),
"tempfile", true);
io::IXMLReader * reader = m_file_system->createXMLReader(ireadfile);
XMLNode* node = new XMLNode(reader);
reader->drop();
ireadfile->drop();
return node;
}
catch (std::runtime_error& e)
{
if (UserConfigParams::logMisc())
{
Log::error("[FileManager]", "createXMLTreeFromString: %s", e.what());
}
return NULL;
}
} // createXMLTreeFromString
//-----------------------------------------------------------------------------
/** In order to add and later remove paths we have to specify the absolute
* filename (and replace '\' with '/' on windows).
*/
io::path FileManager::createAbsoluteFilename(const std::string &f)
{
io::path abs_path=m_file_system->getAbsolutePath(f.c_str());
abs_path=m_file_system->flattenFilename(abs_path);
return abs_path;
} // createAbsoluteFilename
//-----------------------------------------------------------------------------
/** Adds a model search path to the list of model search paths.
* This path will be searched before any other existing paths.
*/
void FileManager::pushModelSearchPath(const std::string& path)
{
m_model_search_path.push_back(path);
std::unique_lock<std::recursive_mutex> ul = m_file_system->acquireFileArchivesMutex();
const int n=m_file_system->getFileArchiveCount();
m_file_system->addFileArchive(createAbsoluteFilename(path),
/*ignoreCase*/false,
/*ignorePaths*/false,
io::EFAT_FOLDER);
// A later added file archive should be searched first (so that
// track specific models are found before models in data/models).
// This is not necessary if this is the first member, or if the
// addFileArchive call did not add this file systems (this can
// happen if the file archive has been added prevously, which
// commonly happens since each kart/track specific path is added
// twice: once for textures and once for models).
if(n>0 && (int)m_file_system->getFileArchiveCount()>n)
{
// In this case move the just added file archive
// (which has index n) to position 0 (by -n positions):
m_file_system->moveFileArchive(n, -n);
}
} // pushModelSearchPath
//-----------------------------------------------------------------------------
/** Adds a texture search path to the list of texture search paths.
* This path will be searched before any other existing paths.
*/
void FileManager::pushTextureSearchPath(const std::string& path, const std::string& container_id)
{
m_texture_search_path.push_back(TextureSearchPath(path, container_id));
std::unique_lock<std::recursive_mutex> ul = m_file_system->acquireFileArchivesMutex();
const int n=m_file_system->getFileArchiveCount();
m_file_system->addFileArchive(createAbsoluteFilename(path),
/*ignoreCase*/false,
/*ignorePaths*/false,
io::EFAT_FOLDER);
// A later added file archive should be searched first (so that
// e.g. track specific textures are found before textures in
// data/textures).
// This is not necessary if this is the first member, or if the
// addFileArchive call did not add this file systems (this can
// happen if the file archive has been added previously, which
// commonly happens since each kart/track specific path is added
// twice: once for textures and once for models).
if(n>0 && (int)m_file_system->getFileArchiveCount()>n)
{
// In this case move the just added file archive
// (which has index n) to position 0 (by -n positions):
m_file_system->moveFileArchive(n, -n);
}
} // pushTextureSearchPath
//-----------------------------------------------------------------------------
/** Removes the last added texture search path from the list of paths.
*/
void FileManager::popTextureSearchPath()
{
if (!m_texture_search_path.empty())
{
TextureSearchPath dir = m_texture_search_path.back();
m_texture_search_path.pop_back();
m_file_system->removeFileArchive(createAbsoluteFilename(dir.m_texture_search_path));
}
} // popTextureSearchPath
//-----------------------------------------------------------------------------
/** Removes the last added model search path from the list of paths.
*/
void FileManager::popModelSearchPath()
{
if (!m_model_search_path.empty())
{
std::string dir = m_model_search_path.back();
m_model_search_path.pop_back();
m_file_system->removeFileArchive(createAbsoluteFilename(dir));
}
} // popModelSearchPath
// ------------------------------------------------------------------------
/** Removes the last added directory from the music search path.
*/
void FileManager::popMusicSearchPath()
{
if(!m_music_search_path.empty())
{
m_music_search_path.pop_back();
}
}
//-----------------------------------------------------------------------------
/** Tries to find the specified file in any of the given search paths.
* \param full_path On return contains the full path of the file, or
* "" if the file is not found.
* \param file_name The name of the file to look for.
* \param search_path The list of paths to search for the file.
* \return True if the file is found, false otherwise.
*/
bool FileManager::findFile(std::string& full_path,
const std::string& file_name,
const std::vector<std::string>& search_path) const
{
for(std::vector<std::string>::const_reverse_iterator
i = search_path.rbegin();
i != search_path.rend(); ++i)
{
full_path = *i + file_name;
if(m_file_system->existFile(full_path.c_str())) return true;
}
full_path="";
return false;
} // findFile
//-----------------------------------------------------------------------------
/** Tries to find the specified file in any of the given search paths.
* \param full_path On return contains the full path of the file, or
* "" if the file is not found.
* \param file_name The name of the file to look for.
* \param search_path The list of paths to search for the file.
* \return True if the file is found, false otherwise.
*/
bool FileManager::findFile(std::string& full_path,
const std::string& file_name,
const std::vector<TextureSearchPath>& search_path) const
{
for (std::vector<TextureSearchPath>::const_reverse_iterator
i = search_path.rbegin();
i != search_path.rend(); ++i)
{
full_path = i->m_texture_search_path + file_name;
if (m_file_system->existFile(full_path.c_str())) return true;
}
full_path = "";
return false;
} // findFile
//-----------------------------------------------------------------------------
std::string FileManager::getAssetChecked(FileManager::AssetType type,
const std::string& name,
bool abort_on_error) const
{
std::string path = m_subdir_name[type]+name;
if(fileExists(path))
return path;
if(abort_on_error)
{
Log::fatal("[FileManager]", "Can not find file '%s' in '%s'",
name.c_str(), m_subdir_name[type].c_str());
}
return "";
} // getAssetChecked
//-----------------------------------------------------------------------------
/** Returns the full path of a file of the given asset class. It is not
* checked if the file actually exists (use getAssetChecked() instead if
* checking is needed).
* \param type Type of the asset class.
* \param name Name of the file to search.
* \return Full path to the file.
*/
std::string FileManager::getAsset(FileManager::AssetType type,
const std::string &name) const
{
if (type == GUI_ICON && GUIEngine::getSkin()->hasIconTheme())
{
return GUIEngine::getSkin()->getThemedIcon("gui/icons/" + name);
}
return m_subdir_name[type] + name;
} // getAsset
//-----------------------------------------------------------------------------
/** Searches in all root directories for the specified file.
* \param name Name of the file to find.
* \return Full path of the file, or "" if not found.
*/
std::string FileManager::getAsset(const std::string &name) const
{
std::string path;
findFile(path, name, m_root_dirs);
return path;
} // getAsset
//-----------------------------------------------------------------------------
/** Returns the directory in which screenshots should be stored.
*/
std::string FileManager::getScreenshotDir() const
{
return m_screenshot_dir;
} // getScreenshotDir
//-----------------------------------------------------------------------------
/** Returns the directory in which replay file should be stored.
*/
std::string FileManager::getReplayDir() const
{
return m_replay_dir;
} // getReplayDir
//-----------------------------------------------------------------------------
/** Returns the directory in which resized textures should be cached.
*/
std::string FileManager::getCachedTexturesDir() const
{
return m_cached_textures_dir;
} // getCachedTexturesDir
//-----------------------------------------------------------------------------
/** Returns the directory in which user-defined grand prix should be stored.
*/
std::string FileManager::getGPDir() const
{
return m_gp_dir;
} // getGPDir
//-----------------------------------------------------------------------------
/** Returns the full path of a texture file name by searching in all
* directories currently in the texture search path. The difference to
* a call getAsset(TEXTURE,...) is that the latter will only return
* textures from .../textures, while the searchTexture will also
* search e.g. in kart or track directories (depending on what is currently
* being loaded).
* \param file_name Name of the texture file to search.
* \return The full path for the texture, or "" if the texture was not found.
*/
std::string FileManager::searchTexture(const std::string& file_name) const
{
std::string path;
findFile(path, file_name, m_texture_search_path);
return path;
} // searchTexture
//-----------------------------------------------------------------------------
bool FileManager::searchTextureContainerId(std::string& container_id,
const std::string& file_name) const
{
std::string full_path;
for (std::vector<TextureSearchPath>::const_reverse_iterator
i = m_texture_search_path.rbegin();
i != m_texture_search_path.rend(); ++i)
{
full_path = i->m_texture_search_path + file_name;
if (m_file_system->existFile(full_path.c_str()))
{
container_id = i->m_container_id;
return true;
}
}
full_path = "";
return false;
} // findFile
//-----------------------------------------------------------------------------
/** Returns the list of all directories in which music files are searched.
*/
std::vector<std::string> FileManager::getMusicDirs() const
{
return m_music_search_path;
} // getMusicDirs
//-----------------------------------------------------------------------------
/** If the directory specified in path does not exist, it is created. This
* function does not support recursive operations, so if a directory "a/b"
* is tested, and "a" does not exist, this function will fail.
* \params path Directory to test.
* \return True if the directory exists or could be created,
* false otherwise.
*/
bool FileManager::checkAndCreateDirectory(const std::string &path)
{
// irrlicht apparently returns true for files and directory
// (using access/_access internally):
if(m_file_system->existFile(io::path(path.c_str())))
return true;
Log::info("FileManager", "Creating directory '%s'.", path.c_str());
// Otherwise try to create the directory:
#if defined(WIN32)
bool error = _wmkdir(StringUtils::utf8ToWide(path).c_str()) != 0;
#else
bool error = mkdir(path.c_str(), 0777) != 0;
#endif
return !error;
} // checkAndCreateDirectory
//-----------------------------------------------------------------------------
/** If the directory specified in path does not exist, it is created
* recursively (mkdir -p style).
* \params path Directory to test.
* \return True if the directory exists or could be created, false otherwise.
*/
bool FileManager::checkAndCreateDirectoryP(const std::string &path)
{
// irrlicht apparently returns true for files and directory
// (using access/_access internally):
if(m_file_system->existFile(io::path(path.c_str())))
return true;
Log::info("[FileManager]", "Creating directory(ies) '%s'", path.c_str());
std::vector<std::string> split = StringUtils::split(path,'/');
std::string current_path = "";
for (unsigned int i=0; i<split.size(); i++)
{
current_path += split[i] + "/";
//Log::verbose("[FileManager]", "Checking for: '%s",
// current_path.c_str());
if (!checkAndCreateDirectory(current_path))
{
Log::error("[FileManager]", "Can't create dir '%s'",
current_path.c_str());
break;
}
}
bool error = checkAndCreateDirectory(path);
return error;
} // checkAndCreateDirectory
//-----------------------------------------------------------------------------
/** Checks if the config directory exists, and it not, tries to create it.
* It will set m_user_config_dir to the path to which user-specific config
* files are stored.
*/
void FileManager::checkAndCreateConfigDir()
{
if(getenv("SUPERTUXKART_SAVEDIR") &&
checkAndCreateDirectory(getenv("SUPERTUXKART_SAVEDIR")) )
{
m_user_config_dir = getenv("SUPERTUXKART_SAVEDIR");
}
else
{
#if defined(WIN32)
// Try to use the APPDATA directory to store config files and highscore
// lists. If not defined, used the current directory.
std::vector<wchar_t> env;
// An environment variable has a maximum size limit of 32,767 characters
env.resize(32767, 0);
DWORD length = GetEnvironmentVariable(L"APPDATA", env.data(), 32767);
if (length != 0)
{
m_user_config_dir = StringUtils::wideToUtf8(env.data());
if (!checkAndCreateDirectory(m_user_config_dir))
{
Log::error("[FileManager]", "Can't create config dir '%s"
", falling back to '.'.", m_user_config_dir.c_str());
m_user_config_dir = ".";
}
}
else
m_user_config_dir = ".";
m_user_config_dir += "/supertuxkart";
#elif defined(__APPLE__)
if (getenv("HOME") != NULL)
{
m_user_config_dir = getenv("HOME");
}
else
{
Log::error("[FileManager]",
"No home directory, this should NOT happen!");
// Fall back to system-wide app data (rather than
// user-specific data), but should not happen anyway.
m_user_config_dir = "";
}
m_user_config_dir += "/Library/Application Support/";
const std::string CONFIGDIR("SuperTuxKart");
m_user_config_dir += CONFIGDIR;
#elif defined(__HAIKU__)
BPath settings_dir;
if (find_directory(B_USER_SETTINGS_DIRECTORY, &settings_dir, true, NULL) == B_OK) {
m_user_config_dir = std::string(settings_dir.Path());
} else {
if (getenv("HOME") != NULL)
{
m_user_config_dir = getenv("HOME");
m_user_config_dir += "/config/settings";
}
}
#else
// Remaining unix variants. Use the new standards for config directory
// i.e. either XDG_CONFIG_HOME or $HOME/.config
if (getenv("XDG_CONFIG_HOME") !=NULL)
{
m_user_config_dir = getenv("XDG_CONFIG_HOME");
checkAndCreateDirectory(m_user_config_dir);
}
else if (!getenv("HOME"))
{
Log::error("[FileManager]",
"No home directory, this should NOT happen "
"- trying '.' for config files!");
m_user_config_dir = ".";
}
else
{
m_user_config_dir = getenv("HOME");
checkAndCreateDirectory(m_user_config_dir);
m_user_config_dir += "/.config";
if(!checkAndCreateDirectory(m_user_config_dir))
{
// If $HOME/.config can not be created:
Log::error("[FileManager]",
"Cannot create directory '%s', falling back to use '%s'",
m_user_config_dir.c_str(), getenv("HOME"));
m_user_config_dir = getenv("HOME");
}
}
m_user_config_dir += "/supertuxkart";
#endif
} // if(getenv("SUPERTUXKART_SAVEDIR") && checkAndCreateDirectory(...))
if(m_user_config_dir.size()>0 && *m_user_config_dir.rbegin()!='/')
m_user_config_dir += "/";
m_user_config_dir += "config-0.10/";
if(!checkAndCreateDirectoryP(m_user_config_dir))
{
Log::warn("FileManager", "Can not create config dir '%s', "
"falling back to '.'.", m_user_config_dir.c_str());
m_user_config_dir = "./";
}
if (m_stdout_dir.empty())
{
m_stdout_dir = m_user_config_dir;
}
return;
} // checkAndCreateConfigDir
// ----------------------------------------------------------------------------
/** Creates the directories for the addons data. This will set m_addons_dir
* with the appropriate path, and also create the subdirectories in this
* directory.
*/
void FileManager::checkAndCreateAddonsDir()
{
#if defined(WIN32)
m_addons_dir = m_user_config_dir+"../addons/";
#elif defined(__APPLE__)
m_addons_dir = getenv("HOME");
m_addons_dir += "/Library/Application Support/SuperTuxKart/Addons/";
#elif defined(__HAIKU__)
m_addons_dir = m_user_config_dir+"addons/";
#else
m_addons_dir = checkAndCreateLinuxDir("XDG_DATA_HOME", "supertuxkart",
".local/share", ".stkaddons");
m_addons_dir += "addons/";
#endif
if(!checkAndCreateDirectory(m_addons_dir))
{
Log::error("FileManager", "Can not create add-ons dir '%s', "
"falling back to '.'.", m_addons_dir.c_str());
m_addons_dir = "./";
}
if (!checkAndCreateDirectory(m_addons_dir + "icons/"))
{
Log::error("FileManager", "Failed to create add-ons icon dir at '%s'.",
(m_addons_dir + "icons/").c_str());
}
if (!checkAndCreateDirectory(m_addons_dir + "tmp/"))
{
Log::error("FileManager", "Failed to create add-ons tmp dir at '%s'.",
(m_addons_dir + "tmp/").c_str());
}
} // checkAndCreateAddonsDir
// ----------------------------------------------------------------------------
/** Creates the directories for screenshots. This will set m_screenshot_dir
* with the appropriate path.
*/
void FileManager::checkAndCreateScreenshotDir()
{
#if defined(WIN32) || defined(__HAIKU__)
m_screenshot_dir = m_user_config_dir+"screenshots/";
#elif defined(__APPLE__)
m_screenshot_dir = getenv("HOME");
m_screenshot_dir += "/Library/Application Support/SuperTuxKart/Screenshots/";
#else
m_screenshot_dir = checkAndCreateLinuxDir("XDG_DATA_HOME", "supertuxkart",
".local/share", ".stkscreenshots");
m_screenshot_dir += "screenshots/";
#endif
if(!checkAndCreateDirectory(m_screenshot_dir))
{
Log::error("FileManager", "Can not create screenshot directory '%s', "
"falling back to '.'.", m_screenshot_dir.c_str());
m_screenshot_dir = ".";
}
} // checkAndCreateScreenshotDir
// ----------------------------------------------------------------------------
/** Creates the directories for replay recorded. This will set m_replay_dir
* with the appropriate path.
*/
void FileManager::checkAndCreateReplayDir()
{
#if defined(WIN32) || defined(__HAIKU__)
m_replay_dir = m_user_config_dir + "replay/";
#elif defined(__APPLE__)
m_replay_dir = getenv("HOME");
m_replay_dir += "/Library/Application Support/SuperTuxKart/replay/";
#else
m_replay_dir = checkAndCreateLinuxDir("XDG_DATA_HOME", "supertuxkart",
".local/share", ".supertuxkart");
m_replay_dir += "replay/";
#endif
if(!checkAndCreateDirectory(m_replay_dir))
{
Log::error("FileManager", "Can not create replay directory '%s', "
"falling back to '.'.", m_replay_dir.c_str());
m_replay_dir = ".";
}
} // checkAndCreateReplayDir
// ----------------------------------------------------------------------------
/** Creates the directories for cached textures. This will set
* m_cached_textures_dir with the appropriate path.
*/
void FileManager::checkAndCreateCachedTexturesDir()
{
#if defined(WIN32)
m_cached_textures_dir = m_user_config_dir + "cached-textures/";
#elif defined(__APPLE__)
m_cached_textures_dir = getenv("HOME");
m_cached_textures_dir += "/Library/Application Support/SuperTuxKart/CachedTextures/";
#else
m_cached_textures_dir = checkAndCreateLinuxDir("XDG_CACHE_HOME", "supertuxkart", ".cache/", ".");
m_cached_textures_dir += "cached-textures/";
#endif
if (!checkAndCreateDirectory(m_cached_textures_dir))
{
Log::error("FileManager", "Can not create cached textures directory '%s', "
"falling back to '.'.", m_cached_textures_dir.c_str());
m_cached_textures_dir = ".";
}
} // checkAndCreateCachedTexturesDir
// ----------------------------------------------------------------------------
/** Creates the directories for user-defined grand prix. This will set m_gp_dir
* with the appropriate path.
*/
void FileManager::checkAndCreateGPDir()
{
#if defined(WIN32) || defined(__HAIKU__)
m_gp_dir = m_user_config_dir + "grandprix/";
#elif defined(__APPLE__)
m_gp_dir = getenv("HOME");
m_gp_dir += "/Library/Application Support/SuperTuxKart/grandprix/";
#else
m_gp_dir = checkAndCreateLinuxDir("XDG_DATA_HOME", "supertuxkart",
".local/share", ".supertuxkart");
m_gp_dir += "grandprix/";
#endif
if(!checkAndCreateDirectory(m_gp_dir))
{
Log::error("FileManager", "Can not create user-defined grand prix directory '%s', "
"falling back to '.'.", m_gp_dir.c_str());
m_gp_dir = ".";
}
} // checkAndCreateGPDir
// ----------------------------------------------------------------------------
#if !defined(WIN32) && !defined(__APPLE__)
/** Find a directory to use for remaining unix variants. Use the new standards
* for config directory based on XDG_* environment variables, or a
* subdirectory under $HOME, trying two different fallbacks. It will also
* check if the directory 'dirname' can be created (to avoid problems that
* e.g. $env_name is '/', which exists, but can not be written to.
* \param env_name Name of the environment variable to test first.
* \param dir_name Name of the directory to create
* \param fallback1 Subdirectory under $HOME to use if the environment
* variable is not defined or can not be created.
* \param fallback2 Subdirectory under $HOME to use if the environment
* variable and fallback1 are not defined or can not be created.
*/
std::string FileManager::checkAndCreateLinuxDir(const char *env_name,
const char *dir_name,
const char *fallback1,
const char *fallback2)
{
bool dir_ok = false;
std::string dir;
if (getenv(env_name)!=NULL)
{
dir = getenv(env_name);
dir_ok = checkAndCreateDirectory(dir);
if(!dir_ok)
Log::warn("FileManager", "Cannot create $%s.", env_name);
if(dir[dir.size()-1]!='/') dir += "/";
// Do an additional test here, e.g. in case that XDG_DATA_HOME is '/'
// and since dir_ok is set, it would not test any of the other options
// like $HOME/.local/share
dir_ok = checkAndCreateDirectory(dir+dir_name);
if(!dir_ok)
Log::warn("FileManager", "Cannot create $%s/%s.", dir.c_str(),
dir_name);
}
if(!dir_ok && getenv("HOME"))
{
// Use ~/.local/share :
dir = getenv("HOME");
if(dir.size()>0 && dir[dir.size()-1]!='/') dir += "/";
dir += fallback1;
// This will create each individual subdirectory if
// dir_name contains "/".
dir_ok = checkAndCreateDirectoryP(dir);
if(!dir_ok)
Log::warn("FileManager", "Cannot create $HOME/%s.",
fallback1);
}
if(!dir_ok && fallback2 && getenv("HOME"))
{
dir = getenv("HOME");
if(dir.size()>0 && dir[dir.size()-1]!='/') dir += "/";
dir += fallback2;
dir_ok = checkAndCreateDirectory(dir);
if(!dir_ok)
Log::warn("FileManager", "Cannot create $HOME/%s.",
fallback2);
}
if(!dir_ok)
{
Log::warn("FileManager", "Falling back to use '.'.");
dir = "./";
}
if(dir.size()>0 && dir[dir.size()-1]!='/') dir += "/";
dir += dir_name;
dir_ok = checkAndCreateDirectory(dir);
if(!dir_ok)
{
// If the directory can not be created
Log::error("FileManager", "Cannot create directory '%s', "
"falling back to use '.'.", dir.c_str());
dir="./";
}
if(dir.size()>0 && dir[dir.size()-1]!='/') dir += "/";
return dir;
} // checkAndCreateLinuxDir
#endif
//-----------------------------------------------------------------------------
/** Sets the name for the stdout log file.
* \param filename Filename to use (relative to the user config dir).
*/
void FileManager::setStdoutName(const std::string& filename)
{
m_stdout_filename = filename;
} // setStdoutName
//-----------------------------------------------------------------------------
/** Sets the directory for the stdout log file.
* \param dir Directory to use
*/
void FileManager::setStdoutDir(const std::string& dir)
{
m_stdout_dir = dir;
if (!m_stdout_dir.empty() && m_stdout_dir[m_stdout_dir.size() - 1] != '/')
{
m_stdout_dir += "/";
}
} // setStdoutDir
//-----------------------------------------------------------------------------
/** Redirects output to go into files in the user's config directory
* instead of to the console. It keeps backup copies of previous stdout files
* (3 atm), which can help to diagnose problems caused by a previous crash.
*/
void FileManager::redirectOutput()
{
// Do a simple log rotate: stdout.log.2 becomes stdout.log.3 etc
const int NUM_BACKUPS=3;
std::string logoutfile = m_stdout_dir + m_stdout_filename;
for(int i=NUM_BACKUPS; i>1; i--)
{
std::ostringstream out_old;
out_old << logoutfile << "." << i;
removeFile(out_old.str());
std::ostringstream out_new;
out_new << logoutfile << "." << i-1;
if(fileExists(out_new.str()))
{
FileUtils::renameU8Path(out_new.str(), out_old.str());
}
} // for i in NUM_BACKUPS
if(fileExists(logoutfile))
{
std::ostringstream out;
out << logoutfile<<".1";
// No good place to log error messages when log is not yet initialised
FileUtils::renameU8Path(logoutfile, out.str());
}
//Enable logging of stdout and stderr to logfile
Log::verbose("main", "Error messages and other text output will "
"be logged to %s.", logoutfile.c_str());
Log::openOutputFiles(logoutfile);
} // redirectOutput
//-----------------------------------------------------------------------------
/** Returns the directory for addon files. */
const std::string &FileManager::getAddonsDir() const
{
return m_addons_dir;
} // getAddonsDir
//-----------------------------------------------------------------------------
/** Returns the full path of a file in the addons directory.
* \param name Name of the file.
*/
std::string FileManager::getAddonsFile(const std::string &name)
{
return getAddonsDir()+name;
} // getAddonsFile
//-----------------------------------------------------------------------------
/** Returns the full path of the config directory.
*/
std::string FileManager::getUserConfigFile(const std::string &fname) const
{
return m_user_config_dir+fname;
} // getUserConfigFile
//-----------------------------------------------------------------------------
/** Returns the full path of a music file by searching all music search paths.
* It throws an exception if the file is not found.
* \param file_name File name to search for.
*/
std::string FileManager::searchMusic(const std::string& file_name) const
{
std::string path;
bool success = findFile(path, file_name, m_music_search_path);
if(!success)
{
// If a music file is not found in any of the music search paths
// check all root dirs. This is used by stk_config to load the
// title music before any music search path is defined)
path = getAsset(MUSIC, file_name);
success = fileExists(path);
}
if (!success)
{
throw std::runtime_error(
"[FileManager::getMusicFile] Cannot find music file '"
+file_name+"'.");
}
return path;
} // searchMusic
//-----------------------------------------------------------------------------
/** Returns the full path of a model file by searching all model search paths.
* It throws an exception if the file is not found.
* \param file_name File name to search for.
*/
std::string FileManager::searchModel(const std::string& file_name) const
{
std::string path;
bool success = findFile(path, file_name, m_model_search_path);
if (!success)
{
throw std::runtime_error(
"[FileManager::searchModel] Cannot find model file '"
+file_name+"'.");
}
return path;
} // searchModel
//-----------------------------------------------------------------------------
/** Returns true if the given name is a directory.
* \param path File name to test.
*/
bool FileManager::isDirectory(const std::string &path)
{
struct stat mystat;
std::string s(path);
// At least on windows stat returns an error if there is
// a '/' at the end of the path.
if(s[s.size()-1]=='/')
s.erase(s.end()-1, s.end());
if(FileUtils::statU8Path(s, &mystat) < 0) return false;
return S_ISDIR(mystat.st_mode);
} // isDirectory
//-----------------------------------------------------------------------------
/** Returns a list of files in a given directory.
* \param result A reference to a std::vector<std::string> which will
* hold all files in a directory. The vector will be cleared.
* \param dir The director for which to get the directory listing.
* \param make_full_path If set to true, all listed files will be full paths.
*/
void FileManager::listFiles(std::set<std::string>& result,
const std::string& dir,
bool make_full_path) const
{
result.clear();
if (!isDirectory(dir))
return;
irr::io::IFileList* files = m_file_system->createFileList(dir.c_str());
for (int n = 0; n < (int)files->getFileCount(); n++)
{
result.insert(make_full_path ? dir + "/" + files->getFileName(n).c_str()
: files->getFileName(n).c_str());
}
files->drop();
} // listFiles
//-----------------------------------------------------------------------------
/** Creates a directory for an addon.
* \param addons_name Name of the directory to create.
* \param addons_type The type, which is used as a subdirectory. E.g.:
* 'karts' (m_addons_dir/karts/name will be created).
*/
void FileManager::checkAndCreateDirForAddons(const std::string &dir)
{
// Tries to create directory recursively
bool success = checkAndCreateDirectoryP(dir);
if(!success)
{
Log::warn("FileManager", "There is a problem with the addons dir.");
return;
}
} // checkAndCreateDirForAddons
// ----------------------------------------------------------------------------
/** Removes the specified file.
* \return True if successful, or false if the file is not a regular file or
* can not be removed.
*/
bool FileManager::removeFile(const std::string &name) const
{
// If the file does not exists, everything is fine
if(!fileExists(name))
return true;
struct stat mystat;
if(FileUtils::statU8Path(name, &mystat) < 0) return false;
if( S_ISREG(mystat.st_mode))
{
#if defined(WIN32)
return _wremove(StringUtils::utf8ToWide(name).c_str()) == 0;
#else
return remove(name.c_str()) == 0;
#endif
}
return false;
} // removeFile
// ----------------------------------------------------------------------------
/** Removes a directory (including all files contained). The function could
* easily recursively delete further subdirectories, but this is commented
* out atm (to limit the amount of damage in case of a bug).
* \param name Directory name to remove.
* \param return True if removal was successful.
*/
bool FileManager::removeDirectory(const std::string &name) const
{
std::set<std::string> files;
listFiles(files, name, /*is full path*/ true);
for (std::string file : files)
{
if (file == "." || file == ".." || file == name + "/." ||
file == name + "/..")
continue;
if (UserConfigParams::logMisc())
Log::verbose("FileManager", "Deleting directory '%s'.",
file.c_str());
if (isDirectory(file))
{
// This should not be necessary (since this function is only
// used to remove addons), and it limits the damage in case
// of any bugs - i.e. if name should be "/" or so.
// We need to remove whole data directory on Android though, i.e.
// when we install newer STK version and new assets are extracted.
// So enable it only for Android for now.
#ifdef MOBILE_STK
removeDirectory(file);
#else
if (file.find(m_addons_dir) != std::string::npos)
removeDirectory(file);
#endif
}
else
{
removeFile(file);
}
}
#if defined(WIN32)
return RemoveDirectory(StringUtils::utf8ToWide(name).c_str())==TRUE;
#else
return remove(name.c_str())==0;
#endif
} // remove directory
// ----------------------------------------------------------------------------
/** Copies the file source to dest.
* \param source The file to read.
* \param dest The new filename.
* \return True if the copy was successful, false otherwise.
*/
bool FileManager::copyFile(const std::string &source, const std::string &dest)
{
FILE *f_source = FileUtils::fopenU8Path(source, "rb");
FILE *f_dest = FileUtils::fopenU8Path(dest, "wb");
constexpr int BUFFER_SIZE=32768;
char *buffer = new char[BUFFER_SIZE];
auto scoped = [&]() {
if (f_source) fclose(f_source);
if (f_dest) fclose(f_dest);
if (buffer) delete [] buffer;
};
MemUtils::deref<decltype(scoped)> cls(scoped);
if(!f_source || !f_dest || !buffer) return false;
size_t n;
while((n=fread(buffer, 1, BUFFER_SIZE, f_source))>0)
{
if(fwrite(buffer, 1, n, f_dest)!=n)
{
Log::error("FileManager", "Write error copying '%s' to '%s",
source.c_str(), dest.c_str());
return false;
} // if fwrite()!=n
} // while
return true;
} // copyFile
// ----------------------------------------------------------------------------
/** Returns true if the first file is newer than the second. The comparison is
* based on the modification time of the two files.
*/
bool FileManager::fileIsNewer(const std::string& f1, const std::string& f2) const
{
struct stat stat1;
struct stat stat2;
FileUtils::statU8Path(f1, &stat1);
FileUtils::statU8Path(f2, &stat2);
return stat1.st_mtime > stat2.st_mtime;
} // fileIsNewer
// ----------------------------------------------------------------------------
/** Move the source directory into the target directory location.
* The target directory must be on the same drive as the source.
*/
bool FileManager::moveDirectoryInto(std::string source, std::string target)
{
if (!isDirectory(source) || !isDirectory(target))
return false;
// Remove the last '/'
if (source[source.size() - 1] == '/')
source.erase(source.end() - 1, source.end());
std::string folder = StringUtils::getBasename(source);
if (target[target.size() - 1] != '/')
target += "/";
target += folder;
// The result target directory must not already exist
if (isDirectory(target))
return false;
#if defined(WIN32)
return MoveFileExW(StringUtils::utf8ToWide(source).c_str(),
StringUtils::utf8ToWide(target).c_str(),
MOVEFILE_WRITE_THROUGH) != 0;
#else
return rename(source.c_str(), target.c_str()) != -1;
#endif
} // moveDirectoryInto