Преглед на файлове

VCMIDirs update #5

- Minor fixes
- string based paths -> boost::filesystem::path paths (I hope it's
final)
- New user data path on windows
- New moving dir method on windows.
Karol преди 11 години
родител
ревизия
958839668c

+ 7 - 6
Global.h

@@ -96,7 +96,11 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
 /* ---------------------------------------------------------------------------- */
 /* Commonly used C++, Boost headers */
 /* ---------------------------------------------------------------------------- */
+#ifndef VCMI_WINDOWS
 #define WIN32_LEAN_AND_MEAN		// Exclude rarely-used stuff from Windows headers
+#define NOMINMAX				// Exclude min/max macros from <Windows.h>
+#endif
+
 #define _USE_MATH_DEFINES
 
 #include <cstdio>
@@ -148,6 +152,7 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
 #include <boost/format.hpp>
 #include <boost/functional/hash.hpp>
 #include <boost/lexical_cast.hpp>
+#include <boost/locale/generator.hpp>
 #include <boost/logic/tribool.hpp>
 #include <boost/optional.hpp>
 #include <boost/program_options.hpp>
@@ -158,10 +163,6 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
 #include <boost/variant.hpp>
 #include <boost/math/special_functions/round.hpp>
 
-#ifdef VCMI_ANDROID
-	#include <android/log.h>
-#endif
-
 #ifndef M_PI
 	#define M_PI 3.14159265358979323846
 #endif
@@ -196,7 +197,7 @@ typedef boost::lock_guard<boost::recursive_mutex> TLockGuardRec;
 /* Macros */
 /* ---------------------------------------------------------------------------- */
 // Import + Export macro declarations
-#ifdef _WIN32
+#ifdef VCMI_WINDOWS
 #  ifdef __GNUC__
 #    define DLL_EXPORT __attribute__((dllexport))
 #  else
@@ -210,7 +211,7 @@ typedef boost::lock_guard<boost::recursive_mutex> TLockGuardRec;
 #  endif
 #endif
 
-#ifdef _WIN32
+#ifdef VCMI_WINDOWS
 #  ifdef __GNUC__
 #    define DLL_IMPORT __attribute__((dllimport))
 #  else

+ 4 - 6
client/CPlayerInterface.cpp

@@ -1601,22 +1601,20 @@ int CPlayerInterface::getLastIndex( std::string namePrefix)
 	path gamesDir = VCMIDirs::get().userSavePath();
 	std::map<std::time_t, int> dates; //save number => datestamp
 
-	directory_iterator enddir;
+	const directory_iterator enddir;
 	if(!exists(gamesDir))
 		create_directory(gamesDir);
-
-	for (directory_iterator dir(gamesDir); dir!=enddir; dir++)
+	else
+	for (directory_iterator dir(gamesDir); dir != enddir; ++dir)
 	{
 		if(is_regular(dir->status()))
 		{
-			std::string name = dir->path().leaf().string();
+			std::string name = dir->path().filename().string();
 			if(starts_with(name, namePrefix) && ends_with(name, ".vcgm1"))
 			{
 				char nr = name[namePrefix.size()];
 				if(std::isdigit(nr))
-				{
 					dates[last_write_time(dir->path())] = boost::lexical_cast<int>(nr);
-				}
 			}
 		}
 	}

+ 1 - 1
editor/Editor.cpp

@@ -13,7 +13,7 @@ Editor::Editor(QWidget *parent)
 {
 	// Setup default logging(enough for now)
 	console = new CConsoleHandler;
-	CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() + "/VCMI_Editor_log.txt", console);
+	CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() / "VCMI_Editor_log.txt", console);
 	logConfig.configureDefault();
 
 	preinitDLL(console);

+ 10 - 1
launcher/StdInc.h

@@ -8,4 +8,13 @@
 #include <QVector>
 #include <QList>
 #include <QString>
-#include <QFile>
+#include <QFile>
+
+inline QString pathToQString(const boost::filesystem::path & path)
+{
+#ifdef VCMI_WINDOWS
+	return QString::fromStdWString(path.wstring());
+#else
+	return QString::fromStdString(path.string());
+#endif
+}

+ 2 - 2
launcher/launcherdirs.cpp

@@ -18,10 +18,10 @@ CLauncherDirs & CLauncherDirs::get()
 
 QString CLauncherDirs::downloadsPath()
 {
-	return QString::fromUtf8(VCMIDirs::get().userCachePath().c_str()) + "/downloads";
+	return pathToQString(VCMIDirs::get().userCachePath() / "downloads");
 }
 
 QString CLauncherDirs::modsPath()
 {
-	return QString::fromUtf8(VCMIDirs::get().userDataPath().c_str()) + "/Mods";
+	return pathToQString(VCMIDirs::get().userDataPath() / "Mods");
 }

+ 4 - 4
launcher/mainwindow_moc.cpp

@@ -13,15 +13,15 @@
 void MainWindow::load()
 {
 	console = new CConsoleHandler;
-	CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() + "/VCMI_Launcher_log.txt", console);
+	CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() / "VCMI_Launcher_log.txt", console);
 	logConfig.configureDefault();
 
 	CResourceHandler::initialize();
 	CResourceHandler::load("config/filesystem.json");
 
 	for (auto & string : VCMIDirs::get().dataPaths())
-		QDir::addSearchPath("icons", QString::fromUtf8(string.c_str()) + "/launcher/icons");
-	QDir::addSearchPath("icons", QString::fromUtf8(VCMIDirs::get().userDataPath().c_str()) + "/launcher/icons");
+		QDir::addSearchPath("icons", pathToQString(string / "launcher" / "icons"));
+	QDir::addSearchPath("icons", pathToQString(VCMIDirs::get().userDataPath() / "launcher" / "icons"));
 
 	settings.init();
 }
@@ -46,7 +46,7 @@ MainWindow::~MainWindow()
 
 void MainWindow::on_startGameButon_clicked()
 {
-	startExecutable(QString::fromUtf8(VCMIDirs::get().clientPath().c_str()));
+	startExecutable(pathToQString(VCMIDirs::get().clientPath()));
 }
 
 void MainWindow::startExecutable(QString name)

+ 1 - 1
launcher/modManager/cmodmanager.cpp

@@ -39,7 +39,7 @@ CModManager::CModManager(CModList * modList):
 
 QString CModManager::settingsPath()
 {
-	return QString::fromUtf8(VCMIDirs::get().userConfigPath().c_str()) + "/modSettings.json";
+	return pathToQString(VCMIDirs::get().userConfigPath() / "modSettings.json");
 }
 
 void CModManager::loadModSettings()

+ 3 - 3
launcher/settingsView/csettingsview_moc.cpp

@@ -46,9 +46,9 @@ void CSettingsView::loadSettings()
 	for (auto entry : urls.Vector())
 		ui->plainTextEditRepos->appendPlainText(QString::fromUtf8(entry.String().c_str()));
 
-	ui->lineEditUserDataDir->setText(QString::fromUtf8(VCMIDirs::get().userDataPath().c_str()));
-	ui->lineEditGameDir->setText(QString::fromUtf8(M_DATA_DIR));
-	ui->lineEditTempDir->setText(QString::fromUtf8(VCMIDirs::get().userCachePath().c_str()));
+	ui->lineEditUserDataDir->setText(pathToQString(VCMIDirs::get().userDataPath()));
+	ui->lineEditGameDir->setText(pathToQString(VCMIDirs::get().binaryPath()));
+	ui->lineEditTempDir->setText(pathToQString(VCMIDirs::get().userCachePath()));
 
 	std::string encoding = settings["general"]["encoding"].String();
 	size_t encodingIndex = boost::range::find(knownEncodingsList, encoding) - knownEncodingsList;

+ 18 - 14
lib/CGameInterface.cpp

@@ -35,7 +35,7 @@ extern "C" DLL_EXPORT void BattleAI_GetNewBattleAI(shared_ptr<CBattleGameInterfa
 #endif
 
 template<typename rett>
-shared_ptr<rett> createAny(std::string dllname, std::string methodName)
+shared_ptr<rett> createAny(const boost::filesystem::path& libpath, const std::string& methodName)
 {
 	typedef void(*TGetAIFun)(shared_ptr<rett>&); 
 	typedef void(*TGetNameFun)(char*); 
@@ -45,7 +45,13 @@ shared_ptr<rett> createAny(std::string dllname, std::string methodName)
 	TGetAIFun getAI = nullptr;
 	TGetNameFun getName = nullptr;
 
-#ifdef __ANDROID__
+#ifndef VCMI_WINDOWS
+	std::string dllname = libpath.string();
+		// I don't know other platforms.
+		// Somebody should remove it soon.
+#endif
+
+#ifdef VCMI_ANDROID
 	// this is awful but it seems using shared libraries on some devices is even worse
 	if (dllname.find("libVCAI.so") != std::string::npos) {
 		getName = (TGetNameFun)VCAI_GetAiName;
@@ -61,12 +67,12 @@ shared_ptr<rett> createAny(std::string dllname, std::string methodName)
 	}
 #else
 
-#ifdef _WIN32
-	HINSTANCE dll = LoadLibraryA(dllname.c_str());
+#ifdef VCMI_WINDOWS
+	HMODULE dll = LoadLibraryW(libpath.c_str());
 	if (dll)
 	{
-		getName = (TGetNameFun)GetProcAddress(dll,"GetAiName");
-		getAI = (TGetAIFun)GetProcAddress(dll,methodName.c_str());
+		getName = (TGetNameFun)GetProcAddress(dll, "GetAiName");
+		getAI = (TGetAIFun)GetProcAddress(dll, methodName.c_str());
 	}
 #else
 	void *dll = dlopen(dllname.c_str(), RTLD_LOCAL | RTLD_LAZY);
@@ -80,21 +86,20 @@ shared_ptr<rett> createAny(std::string dllname, std::string methodName)
 #endif
 	if (!dll)
 	{
-        logGlobal->errorStream() << "Cannot open dynamic library ("<<dllname<<"). Throwing...";
+        logGlobal->errorStream() << "Cannot open dynamic library ("<<libpath<<"). Throwing...";
 		throw std::runtime_error("Cannot open dynamic library");
 	}
 	else if(!getName || !getAI)
 	{
-        logGlobal->errorStream() << dllname << " does not export method " << methodName;
-#ifdef _WIN32
+		logGlobal->errorStream() << libpath << " does not export method " << methodName;
+#ifdef VCMI_WINDOWS
 		FreeLibrary(dll);
 #else
 		dlclose(dll);
 #endif
 		throw std::runtime_error("Cannot find method " + methodName);
 	}
-
-#endif // __ANDROID__
+#endif // VCMI_ANDROID
 
 	getName(temp);
     logGlobal->infoStream() << "Loaded " << temp;
@@ -113,9 +118,8 @@ shared_ptr<rett> createAnyAI(std::string dllname, std::string methodName)
     logGlobal->infoStream() << "Opening " << dllname;
 	const boost::filesystem::path filePath =
 		VCMIDirs::get().libraryPath() / "AI" / VCMIDirs::get().libraryName(dllname);
-	// TODO: createAny Should take boost::filesystem::path in argument.
-	auto ret = createAny<rett>(filePath.string(), methodName);
-	ret->dllName = dllname;
+	auto ret = createAny<rett>(filePath, methodName);
+	ret->dllName = std::move(dllname);
 	return ret;
 }
 

+ 159 - 12
lib/VCMIDirs.cpp

@@ -30,6 +30,75 @@ void IVCMIDirs::init()
 #include <Shlobj.h>
 #include <Shellapi.h>
 
+// Generates script file named _temp.bat in 'to' directory and runs it
+// Script will:
+// - Wait util 'exeName' ends.
+// - Copy all files from 'from' to 'to'
+// - Ask user to replace files existed in 'to'.
+// - Run 'exeName'
+// - Delete itself.
+bool StartBatchCopyDataProgram(
+	const bfs::path& from, const bfs::path& to, const bfs::path& exeName,
+	const bfs::path& currentPath = bfs::current_path())
+{
+	static const char base[] =
+		"@echo off"												"\n"
+		"echo Preparing to move VCMI data system."				"\n"
+
+		":CLIENT_RUNNING_LOOP"									"\n"
+		"TASKLIST | FIND /I %1% > nul"							"\n"
+		"IF ERRORLEVEL 1 ("										"\n"
+			"GOTO CLIENT_NOT_RUNNING"							"\n"
+		") ELSE ("												"\n"
+			"echo %1% is still running..."						"\n"
+			"echo Waiting until process ends..."				"\n"
+			"ping 1.1.1.1 -n 1 -w 3000 > nul"					"\n" // Sleep ~3 seconds. I love Windows :)
+			"goto :CLIENT_RUNNING_LOOP"							"\n"
+		")"														"\n"
+
+		":CLIENT_NOT_RUNNING"									"\n"
+		"echo %1% turned off..."								"\n"
+		"echo Attempt to move datas."							"\n"
+		"echo From: %2%"										"\n"
+		"echo To: %4%"											"\n"
+		"echo Please resolve any conflicts..."					"\n"
+		"move /-Y %3% %4%"										"\n" // Move all files from %3% to %4%.
+																	 // /-Y ask what to do when file exists in %4%
+		":REMOVE_OLD_DIR"										"\n"
+		"rd %2% || rem"											"\n" // Remove empty directory. Sets error flag if fail.
+		"IF ERRORLEVEL 145 ("									"\n" // Directory not empty
+			"echo Directory %2% is not empty."					"\n"
+			"echo Please move rest of files manually now."		"\n"
+			"pause"												"\n" // Press any key to continue...
+			"goto REMOVE_OLD_DIR"								"\n"
+		")"														"\n"
+		"echo Game data updated succefully."					"\n"
+		"echo Please update your shortcuts."					"\n"
+		"echo Press any key to start a game . . ."				"\n"
+		"pause > nul"											"\n"
+		"%5%"													"\n"
+		"del \"%%~f0\"&exit"									"\n" // Script deletes itself
+		;
+	
+	const auto startGameString =
+		bfs::equivalent(currentPath, from) ?
+		(boost::format("start \"\" %1%") % (to / exeName)) :						// Start game in new path.
+		(boost::format("start \"\" /D %1% %2%") % currentPath % (to / exeName));	// Start game in 'currentPath"
+
+	const bfs::path bathFilename = to / "_temp.bat";
+	bfs::ofstream bathFile(bathFilename, bfs::ofstream::trunc | bfs::ofstream::out);
+	if (!bathFile.is_open())
+		return false;
+	bathFile << (boost::format(base) % exeName % from % (from / "*.*") % to % startGameString.str()).str();
+	bathFile.close();
+
+	std::system(("start \"Updating VCMI datas\" /D \"" + to.string() + "\" \"" + bathFilename.string() + '\"').c_str());
+	// start won't block std::system
+	// /D start bat in other directory insteand of current directory.
+
+	return true;
+}
+
 class VCMIDirsWIN32 : public IVCMIDirs
 {
 	public:
@@ -50,6 +119,9 @@ class VCMIDirsWIN32 : public IVCMIDirs
 		std::string genHelpString() const override;
 
 		void init() override;
+	protected:
+		boost::filesystem::path oldUserDataPath() const;
+		boost::filesystem::path oldUserSavePath() const;
 };
 
 void VCMIDirsWIN32::init()
@@ -57,15 +129,21 @@ void VCMIDirsWIN32::init()
 	// Call base (init dirs)
 	IVCMIDirs::init();
 
-	auto moveDirIfExists = [](const bfs::path& from, const bfs::path& to)
+	// Moves one directory (from) contents to another directory (to)
+	// Shows user the "moving file dialog" and ask to resolve conflits.
+	// If necessary updates current directory.
+	auto moveDirIfExists = [](const bfs::path& from, const bfs::path& to) -> bool
 	{
 		if (!bfs::is_directory(from))
-			return; // Nothing to do here. Flies away.
+			return true; // Nothing to do here. Flies away.
 
 		if (bfs::is_empty(from))
 		{
+			if (bfs::current_path() == from)
+				bfs::current_path(to);
+
 			bfs::remove(from);
-			return; // Nothing to do here. Flies away.
+			return true; // Nothing to do here. Flies away.
 		}
 
 		if (!bfs::is_directory(to))
@@ -103,23 +181,87 @@ void VCMIDirsWIN32::init()
 		fileOp.lpszProgressTitle = nullptr;
 
 		const int errorCode = SHFileOperationW(&fileOp);
-		if (errorCode != 0); // TODO: Log error. User should try to move files.
-		else if (fileOp.fAnyOperationsAborted); // TODO: Log warn. User aborted operation. User should move files.
-		else if (!bfs::is_empty(from)); // TODO: Log warn. Some files not moved. User should try to move files.
-		else // TODO: Log fact that we moved files succefully.
-			bfs::remove(from);
+		if (errorCode != 0) // TODO: Log error. User should try to move files.
+			return false;
+		else if (fileOp.fAnyOperationsAborted) // TODO: Log warn. User aborted operation. User should move files.
+			return false;
+		else if (!bfs::is_empty(from)) // TODO: Log warn. Some files not moved. User should try to move files.
+			return false;
+		
+		if (bfs::current_path() == from)
+			bfs::current_path(to);
+
+		// TODO: Log fact that we moved files succefully.
+		bfs::remove(from);
+		return true;
+	};
+	
+	// Retrieves the fully qualified path for the file that contains the specified module.
+	// The module must have been loaded by the current process.
+	// If this parameter is nullptr, retrieves the path of the executable file of the current process.
+	auto getModulePath = [](HMODULE hModule) -> bfs::path
+	{
+		wchar_t exePathW[MAX_PATH];
+		DWORD nSize = GetModuleFileNameW(hModule, exePathW, MAX_PATH);
+		DWORD error = GetLastError();
+		// WARN: Windows XP don't set ERROR_INSUFFICIENT_BUFFER error.
+		if (nSize != 0 && error != ERROR_INSUFFICIENT_BUFFER)
+			return bfs::path(std::wstring(exePathW, nSize));
+		// TODO: Error handling
+		return bfs::path();
 	};
 
-	moveDirIfExists(userDataPath() / "Games", userSavePath());
+	// Moves one directory contents to another directory
+	// Shows user the "moving file dialog" and ask to resolve conflicts.
+	// It takes into account that 'from' path can contain current executable.
+	// If necessary closes program and starts update script.
+	auto advancedMoveDirIfExists = [getModulePath, moveDirIfExists](const bfs::path& from, const bfs::path& to) -> bool
+	{
+		const bfs::path executablePath = getModulePath(nullptr);
+
+		// VCMI cann't determine executable path.
+		// Use standard way to move directory and exit function.
+		if (executablePath.empty())
+			return moveDirIfExists(from, to);
+
+		const bfs::path executableName = executablePath.filename();
+
+		// Current executabl isn't in 'from' path.
+		// Use standard way to move directory and exit function.
+		if (!bfs::equivalent(executablePath, from / executableName))
+			return moveDirIfExists(from, to);
+
+		// Try standard way to move directory.
+		// I don't know how other systems, but Windows 8.1 allow to move running executable.
+		if (moveDirIfExists(from, to))
+			return true;
+
+		// Start copying script and exit program.
+		if (StartBatchCopyDataProgram(from, to, executableName))
+			exit(ERROR_SUCCESS);
+		
+		// Everything failed :C
+		return false;
+	};
+
+	moveDirIfExists(oldUserSavePath(), userSavePath());
+	advancedMoveDirIfExists(oldUserDataPath(), userDataPath());
 }
 
 bfs::path VCMIDirsWIN32::userDataPath() const
 {
 	wchar_t profileDir[MAX_PATH];
 
-	// The user's profile folder. A typical path is C:\Users\username.
-	// FIXME: Applications should not create files or folders at this level;
-	// they should put their data under the locations referred to by CSIDL_APPDATA or CSIDL_LOCAL_APPDATA.
+	if (SHGetSpecialFolderPathW(nullptr, profileDir, CSIDL_MYDOCUMENTS, FALSE) != FALSE)
+		return bfs::path(profileDir) / "My Games\\vcmi";
+	
+	return ".";
+}
+
+bfs::path VCMIDirsWIN32::oldUserDataPath() const
+{
+	wchar_t profileDir[MAX_PATH];
+	
 	if (SHGetSpecialFolderPathW(nullptr, profileDir, CSIDL_PROFILE, FALSE) == FALSE) // WinAPI way failed
 	{
 #if defined(_MSC_VER) && _MSC_VER >= 1700
@@ -145,6 +287,8 @@ bfs::path VCMIDirsWIN32::userDataPath() const
 
 	//return dataPaths()[0] ???;
 }
+bfs::path VCMIDirsWIN32::oldUserSavePath() const { return userDataPath() / "Games"; }
+
 bfs::path VCMIDirsWIN32::userCachePath() const { return userDataPath(); }
 bfs::path VCMIDirsWIN32::userConfigPath() const { return userDataPath() / "config"; }
 
@@ -421,6 +565,9 @@ namespace VCMIDirs
 		static bool initialized = false;
 		if (!initialized)
 		{
+			std::locale::global(boost::locale::generator().generate("en_US.UTF-8"));
+			boost::filesystem::path::imbue(std::locale());
+
 			singleton.init();
 			initialized = true;
 		}

+ 0 - 9
lib/VCMIDirs.h

@@ -9,15 +9,6 @@
 */
 #pragma once
 
-// STL C++
-#include <string>
-#include <vector>
-
-// Boost
-#include <boost/filesystem/path.hpp>
-
-// TODO: File should be renamed to IVCMIDirs.h
-
 class DLL_LINKAGE IVCMIDirs
 {
 	public:

+ 10 - 19
lib/filesystem/CArchiveLoader.cpp

@@ -13,9 +13,9 @@ ArchiveEntry::ArchiveEntry()
 
 }
 
-CArchiveLoader::CArchiveLoader(const std::string &mountPoint, const std::string & archive):
-    archive(archive),
-    mountPoint(mountPoint)
+CArchiveLoader::CArchiveLoader(std::string _mountPoint, boost::filesystem::path _archive) :
+    archive(std::move(_archive)),
+    mountPoint(std::move(_mountPoint))
 {
 	// Open archive file(.snd, .vid, .lod)
 	CFileInputStream fileStream(archive);
@@ -25,28 +25,19 @@ CArchiveLoader::CArchiveLoader(const std::string &mountPoint, const std::string
 		return;
 
 	// Retrieve file extension of archive in uppercase
-	CFileInfo fileInfo(archive);
-	std::string ext = fileInfo.getExtension();
-	boost::to_upper(ext);
+	const std::string ext = boost::to_upper_copy(archive.extension().string());
 
 	// Init the specific lod container format
 	if(ext == ".LOD" || ext == ".PAC")
-	{
 		initLODArchive(mountPoint, fileStream);
-	}
 	else if(ext == ".VID")
-	{
 		initVIDArchive(mountPoint, fileStream);
-	}
 	else if(ext == ".SND")
-	{
 		initSNDArchive(mountPoint, fileStream);
-	}
 	else
-	{
-		throw std::runtime_error("LOD archive format unknown. Cannot deal with " + archive);
-	}
-	logGlobal->traceStream() << ext << "Archive loaded, " << entries.size() << " files found";
+		throw std::runtime_error("LOD archive format unknown. Cannot deal with " + archive.string());
+
+	logGlobal->traceStream() << ext << "Archive \""<<archive.filename()<<"\" loaded (" << entries.size() << " files found).";
 }
 
 void CArchiveLoader::initLODArchive(const std::string &mountPoint, CFileInputStream & fileStream)
@@ -61,7 +52,7 @@ void CArchiveLoader::initLODArchive(const std::string &mountPoint, CFileInputStr
 	fileStream.seek(0x5c);
 
 	// Insert entries to list
-	for(ui32 i = 0; i < totalFiles; i++)
+	for(ui32 i = 0; i < totalFiles; ++i)
 	{
 		char filename[16];
 		reader.read(reinterpret_cast<ui8*>(filename), 16);
@@ -90,7 +81,7 @@ void CArchiveLoader::initVIDArchive(const std::string &mountPoint, CFileInputStr
 	std::set<int> offsets;
 
 	// Insert entries to list
-	for(ui32 i = 0; i < totalFiles; i++)
+	for(ui32 i = 0; i < totalFiles; ++i)
 	{
 		char filename[40];
 		reader.read(reinterpret_cast<ui8*>(filename), 40);
@@ -122,7 +113,7 @@ void CArchiveLoader::initSNDArchive(const std::string &mountPoint, CFileInputStr
 	ui32 totalFiles = reader.readUInt32();
 
 	// Insert entries to list
-	for(ui32 i = 0; i < totalFiles; i++)
+	for(ui32 i = 0; i < totalFiles; ++i)
 	{
 		char filename[40];
 		reader.read(reinterpret_cast<ui8*>(filename), 40);

+ 2 - 2
lib/filesystem/CArchiveLoader.h

@@ -55,7 +55,7 @@ public:
 	 *
 	 * @throws std::runtime_error if the archive wasn't found or if the archive isn't supported
 	 */
-	explicit CArchiveLoader(const std::string & mountPoint, const std::string & archive);
+	CArchiveLoader(std::string mountPoint, boost::filesystem::path archive);
 
 	/// Interface implementation
 	/// @see ISimpleResourceLoader
@@ -87,7 +87,7 @@ private:
 	void initSNDArchive(const std::string &mountPoint, CFileInputStream & fileStream);
 
 	/** The file path to the archive which is scanned and indexed. */
-	std::string archive;
+	boost::filesystem::path archive;
 
 	std::string mountPoint;
 

+ 15 - 14
lib/filesystem/CFileInfo.cpp

@@ -41,17 +41,17 @@ std::string CFileInfo::getPath() const
 std::string CFileInfo::getExtension() const
 {
 	// Get position of file extension dot
-	size_t dotPos = name.find_last_of("/.");
+	size_t dotPos = name.find_last_of('.');
 
-	if(dotPos != std::string::npos && name[dotPos] == '.')
+	if(dotPos != std::string::npos)
 		return name.substr(dotPos);
-	else
-		return "";
+
+	return "";
 }
 
 std::string CFileInfo::getFilename() const
 {
-	size_t found = name.find_last_of("/\\");
+	const size_t found = name.find_last_of("/\\");
 	return name.substr(found + 1);
 }
 
@@ -60,9 +60,9 @@ std::string CFileInfo::getStem() const
 	std::string rslt = name;
 
 	// Remove file extension
-	size_t dotPos = name.find_last_of("/.");
+	const size_t dotPos = name.find_last_of('.');
 
-	if(dotPos != std::string::npos && name[dotPos] == '.')
+	if(dotPos != std::string::npos)
 		rslt.erase(dotPos);
 
 	return rslt;
@@ -70,18 +70,19 @@ std::string CFileInfo::getStem() const
 
 std::string CFileInfo::getBaseName() const
 {
-	size_t begin = name.find_last_of("/");
-	size_t end = name.find_last_of("/.");
-
-	if(end != std::string::npos && name[end] == '/')
-		end = std::string::npos;
+	size_t begin = name.find_last_of("/\\");
+	size_t end = name.find_last_of(".");
 
 	if(begin == std::string::npos)
 		begin = 0;
 	else
-		begin++;
+		++begin;
+	
+	if (end < begin)
+		end = std::string::npos;
 
-	return name.substr(begin, end - begin);
+	size_t len = (end == std::string::npos ? std::string::npos : end - begin);
+	return name.substr(begin, len);
 }
 
 EResType::Type CFileInfo::getType() const

+ 4 - 6
lib/filesystem/CFileInputStream.cpp

@@ -3,7 +3,7 @@
 
 #include "CFileInfo.h"
 
-CFileInputStream::CFileInputStream(const std::string & file, si64 start, si64 size)
+CFileInputStream::CFileInputStream(const boost::filesystem::path & file, si64 start, si64 size)
 {
 	open(file, start, size);
 }
@@ -18,14 +18,12 @@ CFileInputStream::~CFileInputStream()
 	fileStream.close();
 }
 
-void CFileInputStream::open(const std::string & file, si64 start, si64 size)
+void CFileInputStream::open(const boost::filesystem::path & file, si64 start, si64 size)
 {
-	fileStream.open(file.c_str(), std::ios::in | std::ios::binary);
+	fileStream.open(file, std::ios::in | std::ios::binary);
 
 	if (fileStream.fail())
-	{
-		throw std::runtime_error("File " + file + " isn't available.");
-	}
+		throw std::runtime_error("File " + file.string() + " isn't available.");
 
 	dataStart = start;
 	dataSize = size;

+ 3 - 3
lib/filesystem/CFileInputStream.h

@@ -25,7 +25,7 @@ public:
 	 *
 	 * @see CFileInputStream::open
 	 */
-	CFileInputStream(const std::string & file, si64 start=0, si64 size=0);
+	CFileInputStream(const boost::filesystem::path & file, si64 start = 0, si64 size = 0);
 
 	/**
 	 * C-tor. Opens the specified file.
@@ -88,11 +88,11 @@ private:
 	 *
 	 * @throws std::runtime_error if file wasn't found
 	 */
-	void open(const std::string & file, si64 start, si64 size);
+	void open(const boost::filesystem::path & file, si64 start, si64 size);
 
 	si64 dataStart;
 	si64 dataSize;
 
 	/** Native c++ input file stream object. */
-	std::ifstream fileStream;
+	boost::filesystem::ifstream fileStream;
 };

+ 56 - 30
lib/filesystem/CFilesystemLoader.cpp

@@ -4,9 +4,11 @@
 #include "CFileInfo.h"
 #include "CFileInputStream.h"
 
-CFilesystemLoader::CFilesystemLoader(const std::string &mountPoint, const std::string & baseDirectory, size_t depth, bool initial):
-    baseDirectory(baseDirectory),
-    mountPoint(mountPoint),
+namespace bfs = boost::filesystem;
+
+CFilesystemLoader::CFilesystemLoader(std::string _mountPoint, bfs::path baseDirectory, size_t depth, bool initial):
+    baseDirectory(std::move(baseDirectory)),
+	mountPoint(std::move(_mountPoint)),
     fileList(listFiles(mountPoint, depth, initial))
 {
 	logGlobal->traceStream() << "Filesystem loaded, " << fileList.size() << " files found";
@@ -16,7 +18,7 @@ std::unique_ptr<CInputStream> CFilesystemLoader::load(const ResourceID & resourc
 {
 	assert(fileList.count(resourceName));
 
-	std::unique_ptr<CInputStream> stream(new CFileInputStream(baseDirectory + '/' + fileList.at(resourceName)));
+	std::unique_ptr<CInputStream> stream(new CFileInputStream(baseDirectory / fileList.at(resourceName)));
 	return stream;
 }
 
@@ -34,7 +36,7 @@ boost::optional<std::string> CFilesystemLoader::getResourceName(const ResourceID
 {
 	assert(existsResource(resourceName));
 
-	return baseDirectory + '/' + fileList.at(resourceName);
+	return (baseDirectory / fileList.at(resourceName)).string();
 }
 
 std::unordered_set<ResourceID> CFilesystemLoader::getFilteredFiles(std::function<bool(const ResourceID &)> filter) const
@@ -45,7 +47,8 @@ std::unordered_set<ResourceID> CFilesystemLoader::getFilteredFiles(std::function
 	{
 		if (filter(file.first))
 			foundID.insert(file.first);
-	}	return foundID;
+	}
+	return foundID;
 }
 
 bool CFilesystemLoader::createResource(std::string filename, bool update)
@@ -65,7 +68,7 @@ bool CFilesystemLoader::createResource(std::string filename, bool update)
 
 	if (!update)
 	{
-		std::ofstream newfile (baseDirectory + "/" + filename);
+		bfs::ofstream newfile(baseDirectory / filename);
 		if (!newfile.good())
 			return false;
 	}
@@ -73,49 +76,72 @@ bool CFilesystemLoader::createResource(std::string filename, bool update)
 	return true;
 }
 
-std::unordered_map<ResourceID, std::string> CFilesystemLoader::listFiles(const std::string &mountPoint, size_t depth, bool initial) const
+std::unordered_map<ResourceID, bfs::path> CFilesystemLoader::listFiles(const std::string &mountPoint, size_t depth, bool initial) const
 {
-	std::set<EResType::Type> initialTypes;
-	initialTypes.insert(EResType::DIRECTORY);
-	initialTypes.insert(EResType::TEXT);
-	initialTypes.insert(EResType::ARCHIVE_LOD);
-	initialTypes.insert(EResType::ARCHIVE_VID);
-	initialTypes.insert(EResType::ARCHIVE_SND);
-	initialTypes.insert(EResType::ARCHIVE_ZIP);
+	static const EResType::Type initArray[] = {
+		EResType::DIRECTORY,
+		EResType::TEXT,
+		EResType::ARCHIVE_LOD,
+		EResType::ARCHIVE_VID,
+		EResType::ARCHIVE_SND,
+		EResType::ARCHIVE_ZIP };
+	static const std::set<EResType::Type> initialTypes(initArray, initArray + ARRAY_COUNT(initArray));
 
-	assert(boost::filesystem::is_directory(baseDirectory));
-	std::unordered_map<ResourceID, std::string> fileList;
+	assert(bfs::is_directory(baseDirectory));
+	std::unordered_map<ResourceID, bfs::path> fileList;
 
-	std::vector<std::string> path;//vector holding relative path to our file
+	std::vector<bfs::path> path; //vector holding relative path to our file
 
-	boost::filesystem::recursive_directory_iterator enddir;
-	boost::filesystem::recursive_directory_iterator it(baseDirectory, boost::filesystem::symlink_option::recurse);
+	bfs::recursive_directory_iterator enddir;
+	bfs::recursive_directory_iterator it(baseDirectory, bfs::symlink_option::recurse);
 
 	for(; it != enddir; ++it)
 	{
 		EResType::Type type;
 
-		if (boost::filesystem::is_directory(it->status()))
+		if (bfs::is_directory(it->status()))
 		{
-			path.resize(it.level()+1);
-			path.back() = it->path().leaf().string();
+			path.resize(it.level() + 1);
+			path.back() = it->path().filename();
 			// don't iterate into directory if depth limit reached
 			it.no_push(depth <= it.level());
 
 			type = EResType::DIRECTORY;
 		}
 		else
-			type = EResTypeHelper::getTypeFromExtension(boost::filesystem::extension(*it));
+			type = EResTypeHelper::getTypeFromExtension(it->path().extension().string());
 
 		if (!initial || vstd::contains(initialTypes, type))
 		{
 			//reconstruct relative filename (not possible via boost AFAIK)
-			std::string filename;
-			for (size_t i=0; i<it.level() && i<path.size(); i++)
-				filename += path[i] + '/';
-			filename += it->path().leaf().string();
-
-			fileList[ResourceID(mountPoint + filename, type)] = filename;
+			bfs::path filename;
+			const size_t iterations = std::min((size_t)it.level(), path.size());
+			if (iterations)
+			{
+				filename = path.front();
+				for (size_t i = 1; i < iterations; ++i)
+					filename /= path[i];
+				filename /= it->path().filename();
+			}
+			else
+				filename = it->path().filename();
+
+			std::string resName;
+			if (bfs::path::preferred_separator != '/')
+			{
+				// resource names are using UNIX slashes (/)
+				resName.reserve(resName.size() + filename.native().size());
+				resName = mountPoint;
+				for (const char c : filename.string())
+					if (c != bfs::path::preferred_separator)
+						resName.push_back(c);
+					else
+						resName.push_back('/');
+			}
+			else
+				resName = mountPoint + filename.string();
+
+			fileList[ResourceID(resName, type)] = std::move(filename);
 		}
 	}
 

+ 5 - 5
lib/filesystem/CFilesystemLoader.h

@@ -30,7 +30,7 @@ public:
 	 *
 	 * @throws std::runtime_error if the base directory is not a directory or if it is not available
 	 */
-	explicit CFilesystemLoader(const std::string & mountPoint, const std::string & baseDirectory, size_t depth = 16, bool initial = false);
+	explicit CFilesystemLoader(std::string mountPoint, boost::filesystem::path baseDirectory, size_t depth = 16, bool initial = false);
 
 	/// Interface implementation
 	/// @see ISimpleResourceLoader
@@ -39,11 +39,11 @@ public:
 	std::string getMountPoint() const override;
 	bool createResource(std::string filename, bool update = false) override;
 	boost::optional<std::string> getResourceName(const ResourceID & resourceName) const override;
-	std::unordered_set<ResourceID> getFilteredFiles(std::function<bool(const ResourceID &)> filter) const;
+	std::unordered_set<ResourceID> getFilteredFiles(std::function<bool(const ResourceID &)> filter) const override;
 
 private:
 	/** The base directory which is scanned and indexed. */
-	std::string baseDirectory;
+	boost::filesystem::path baseDirectory;
 
 	std::string mountPoint;
 
@@ -51,7 +51,7 @@ private:
 	 * key = ResourceID for resource loader
 	 * value = name that can be used to access file
 	*/
-	std::unordered_map<ResourceID, std::string> fileList;
+	std::unordered_map<ResourceID, boost::filesystem::path> fileList;
 
 	/**
 	 * Returns a list of pathnames denoting the files in the directory denoted by this pathname.
@@ -62,5 +62,5 @@ private:
 	 * @return a list of pathnames denoting the files and directories in the directory denoted by this pathname
 	 * The array will be empty if the directory is empty. Ptr is null if the directory doesn't exist or if it isn't a directory.
 	 */
-	std::unordered_map<ResourceID, std::string> listFiles(const std::string &mountPoint, size_t depth, bool initial) const;
+	std::unordered_map<ResourceID, boost::filesystem::path> listFiles(const std::string &mountPoint, size_t depth, bool initial) const;
 };

+ 5 - 7
lib/filesystem/Filesystem.cpp

@@ -130,19 +130,18 @@ ISimpleResourceLoader * CResourceHandler::createInitial()
 			auto filename = loader->getResourceName(ID);
 			if (filename)
 			{
-				auto dir = new CFilesystemLoader(URI + "/", *filename, depth, true);
+				auto dir = new CFilesystemLoader(URI + '/', *filename, depth, true);
 				initialLoader->addLoader(dir, false);
 			}
 		}
 	};
 
-	// TODO: CFilesystemLoader: Should take boost::filesystem::path in argument
 	for (auto & path : VCMIDirs::get().dataPaths())
 	{
 		if (boost::filesystem::is_directory(path)) // some of system-provided paths may not exist
-			initialLoader->addLoader(new CFilesystemLoader("", path.string(), 0, true), false);
+			initialLoader->addLoader(new CFilesystemLoader("", path, 0, true), false);
 	}
-	initialLoader->addLoader(new CFilesystemLoader("", VCMIDirs::get().userDataPath().string(), 0, true), false);
+	initialLoader->addLoader(new CFilesystemLoader("", VCMIDirs::get().userDataPath(), 0, true), false);
 
 	recurseInDir("CONFIG", 0);// look for configs
 	recurseInDir("DATA", 0); // look for archives
@@ -167,10 +166,9 @@ void CResourceHandler::initialize()
 	//    |-saves
 	//    |-config
 
-	// TODO: CFilesystemLoader should take boost::filesystem::path
 	knownLoaders["root"] = new CFilesystemList();
-	knownLoaders["saves"] = new CFilesystemLoader("SAVES/", VCMIDirs::get().userSavePath().string());
-	knownLoaders["config"] = new CFilesystemLoader("CONFIG/", VCMIDirs::get().userConfigPath().string());
+	knownLoaders["saves"] = new CFilesystemLoader("SAVES/", VCMIDirs::get().userSavePath());
+	knownLoaders["config"] = new CFilesystemLoader("CONFIG/", VCMIDirs::get().userConfigPath());
 
 	auto localFS = new CFilesystemList();
 	localFS->addLoader(knownLoaders["saves"], true);

+ 1 - 4
lib/logging/CBasicLogConfigurator.cpp

@@ -3,10 +3,7 @@
 
 #include "../CConfigHandler.h"
 
-CBasicLogConfigurator::CBasicLogConfigurator(const boost::filesystem::path & filePath, CConsoleHandler * const console) :
-	filePath(filePath), console(console), appendToLogFile(false) {}
-
-CBasicLogConfigurator::CBasicLogConfigurator(boost::filesystem::path && filePath, CConsoleHandler * const console) :
+CBasicLogConfigurator::CBasicLogConfigurator(boost::filesystem::path filePath, CConsoleHandler * const console) :
 	filePath(std::move(filePath)), console(console), appendToLogFile(false) {}
 
 void CBasicLogConfigurator::configureDefault()

+ 1 - 2
lib/logging/CBasicLogConfigurator.h

@@ -22,8 +22,7 @@ class JsonNode;
 class DLL_LINKAGE CBasicLogConfigurator
 {
 public:
-	CBasicLogConfigurator(const boost::filesystem::path & filePath, CConsoleHandler * const console);
-	CBasicLogConfigurator(boost::filesystem::path && filePath, CConsoleHandler * const console);
+	CBasicLogConfigurator(boost::filesystem::path filePath, CConsoleHandler * const console);
 
 	/// Configures the logging system by parsing the logging settings. It adds the console target and the file target to the global logger.
 	/// Doesn't throw, but logs on success or fault.

+ 26 - 19
lib/logging/CLogger.cpp

@@ -1,14 +1,30 @@
 #include "StdInc.h"
 #include "CLogger.h"
 
-const std::string CLoggerDomain::DOMAIN_GLOBAL = "global";
+#ifdef VCMI_ANDROID
+#include <android/log.h>
 
-CLoggerDomain::CLoggerDomain(const std::string & name) : name(name)
+namespace ELogLevel
 {
-	if(name.empty())
-		throw std::runtime_error("Logger domain cannot be empty.");
+	int toAndroid(ELogLevel logLevel)
+	{
+		switch (logLevel)
+		{
+			case TRACE: return ANDROID_LOG_VERBOSE;
+			case DEBUG: return ANDROID_LOG_DEBUG;
+			case INFO:  return ANDROID_LOG_INFO;
+			case WARN:  return ANDROID_LOG_WARN;
+			case ERROR: return ANDROID_LOG_ERROR;
+			default:;
+		}
+		return ANDROID_LOG_UNKNOWN;
+	}
 }
-CLoggerDomain::CLoggerDomain(std::string && name) : name(std::move(name))
+#endif
+
+const std::string CLoggerDomain::DOMAIN_GLOBAL = "global";
+
+CLoggerDomain::CLoggerDomain(std::string name) : name(std::move(name))
 {
 	if (this->name.empty())
 		throw std::runtime_error("Logger domain cannot be empty.");
@@ -302,15 +318,16 @@ CLogConsoleTarget::CLogConsoleTarget(CConsoleHandler * console) : console(consol
 
 void CLogConsoleTarget::write(const LogRecord & record)
 {
-	if(threshold > record.level) return;
+	if(threshold > record.level)
+		return;
 
 	std::string message = formatter.format(record);
 
 #ifdef VCMI_ANDROID
-	__android_log_print(ANDROID_LOG_INFO, "VCMI", "%s", message.c_str());
+	__android_log_write(ELogLevel::toAndroid(record.level), "VCMI", message.c_str());
 #endif
 
-	bool printToStdErr = record.level >= ELogLevel::WARN;
+	const bool printToStdErr = record.level >= ELogLevel::WARN;
 	if(console)
 	{
 		const EConsoleTextColor::EConsoleTextColor textColor =
@@ -340,22 +357,12 @@ void CLogConsoleTarget::setFormatter(const CLogFormatter & formatter) { this->fo
 const CColorMapping & CLogConsoleTarget::getColorMapping() const { return colorMapping; }
 void CLogConsoleTarget::setColorMapping(const CColorMapping & colorMapping) { this->colorMapping = colorMapping; }
 
-CLogFileTarget::CLogFileTarget(const boost::filesystem::path & filePath, bool append /*= true*/)
-	: file(filePath, append ? std::ios_base::app : std::ios_base::out)
-{
-	formatter.setPattern("%d %l %n [%t] - %m");
-}
-CLogFileTarget::CLogFileTarget(boost::filesystem::path && filePath, bool append /*= true*/)
+CLogFileTarget::CLogFileTarget(boost::filesystem::path filePath, bool append /*= true*/)
 	: file(std::move(filePath), append ? std::ios_base::app : std::ios_base::out)
 {
 	formatter.setPattern("%d %l %n [%t] - %m");
 }
 
-CLogFileTarget::~CLogFileTarget()
-{
-	file.close();
-}
-
 void CLogFileTarget::write(const LogRecord & record)
 {
 	TLockGuard _(mx);

+ 15 - 14
lib/logging/CLogger.h

@@ -19,15 +19,19 @@ class ILogTarget;
 
 namespace ELogLevel
 {
-enum ELogLevel
-{
-	NOT_SET = 0,
-	TRACE,
-	DEBUG,
-	INFO,
-	WARN,
-	ERROR
-};
+	enum ELogLevel
+	{
+		NOT_SET = 0,
+		TRACE,
+		DEBUG,
+		INFO,
+		WARN,
+		ERROR
+	};
+
+	#ifdef VCMI_ANDROID
+		int toAndroid(ELogLevel logLevel);
+	#endif
 }
 
 /// The class CLoggerDomain provides convenient access to super domains from a sub domain.
@@ -36,8 +40,7 @@ class DLL_LINKAGE CLoggerDomain
 public:
 	/// Constructs a CLoggerDomain with the domain designated by name.
 	/// Sub-domains can be specified by separating domains by a dot, e.g. "ai.battle". The global domain is named "global".
-	explicit CLoggerDomain(const std::string & name);
-	explicit CLoggerDomain(std::string && name);
+	explicit CLoggerDomain(std::string name);
 
 	const std::string& getName() const;
 	CLoggerDomain getParent() const;
@@ -288,9 +291,7 @@ class DLL_LINKAGE CLogFileTarget : public ILogTarget
 public:
 	/// Constructs a CLogFileTarget and opens the file designated by file_path. If the append parameter is true, the file
 	/// will be appended to. Otherwise the file designated by filePath will be truncated before being opened.
-	explicit CLogFileTarget(const boost::filesystem::path & file_path, bool append = true);
-	explicit CLogFileTarget(boost::filesystem::path && file_path, bool append = true);
-	~CLogFileTarget();
+	explicit CLogFileTarget(boost::filesystem::path file_path, bool append = true);
 
 	const CLogFormatter & getFormatter() const;
 	void setFormatter(const CLogFormatter & formatter);

+ 6 - 6
scripting/erm/ERMInterpreter.cpp

@@ -368,19 +368,19 @@ void ERMInterpreter::scanForScripts()
 {
 	using namespace boost::filesystem;
 	//parser checking
-	if(!exists(VCMIDirs::get().dataPaths().back() + "/Data/s/"))
+	const path dataPath = VCMIDirs::get().dataPaths().back() / "Data" / "s";
+	if(!exists(dataPath))
 	{
-		logGlobal->warnStream() << "Warning: Folder " << VCMIDirs::get().dataPaths().back() << "/Data/s/ doesn't exist!";
+		logGlobal->warnStream() << "Warning: Folder " << dataPath << " doesn't exist!";
 		return;
 	}
 	directory_iterator enddir;
-	for (directory_iterator dir(VCMIDirs::get().dataPaths().back() + "/Data/s"); dir!=enddir; dir++)
+	for (directory_iterator dir(dataPath); dir!=enddir; dir++)
 	{
 		if(is_regular(dir->status()))
 		{
-			std::string name = dir->path().leaf().string();
-			if( boost::algorithm::ends_with(name, ".erm") ||
-				boost::algorithm::ends_with(name, ".verm") )
+			const std::string ext = boost::to_upper_copy(dir->path().extension().string());
+			if (ext == ".ERM" || ext == ".VERM")
 			{
 				ERMParser ep(dir->path().string());
 				FileInfo * finfo = new FileInfo;

+ 1 - 1
test/CVcmiTestConfig.cpp

@@ -22,7 +22,7 @@
 CVcmiTestConfig::CVcmiTestConfig()
 {
 	console = new CConsoleHandler;
-	CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() + "/VCMI_Test_log.txt", console);
+	CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() / "VCMI_Test_log.txt", console);
 	logConfig.configureDefault();
 	preinitDLL(console);
 	settings.init();