Przeglądaj źródła

VCMIDirs update #3

bfs = boost::filesystem;
- Updateting filenames (std::string -> bfs::path) #1
- Added platform detection, and some specyfic boost::filesystem includes
to Global.h
- Updated CBasicLogConfigurator. Now class uses bfs::path pathes.
Karol 11 lat temu
rodzic
commit
a302f6c7ad

+ 49 - 4
Global.h

@@ -49,6 +49,50 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
 #  pragma warning (disable : 4800 ) /* disable conversion to bool warning -- I think it's intended in all places */
 #endif
 
+/* ---------------------------------------------------------------------------- */
+/* System detection. */
+/* ---------------------------------------------------------------------------- */
+// Based on: http://sourceforge.net/p/predef/wiki/OperatingSystems/
+//	 and on: http://stackoverflow.com/questions/5919996/how-to-detect-reliably-mac-os-x-ios-linux-windows-in-c-preprocessor
+// TODO?: Should be moved to vstd\os_detect.h (and then included by Global.h)
+#ifdef _WIN16			// Defined for 16-bit environments
+	#error "16-bit Windows isn't supported"
+#elif defined(_WIN64)	// Defined for 64-bit environments
+	#define VCMI_WINDOWS
+	#define VCMI_WINDOWS_64
+#elif defined(_WIN32)	// Defined for both 32-bit and 64-bit environments
+	#define VCMI_WINDOWS
+	#define VCMI_WINDOWS_32
+#elif defined(_WIN32_WCE)
+	#error "Windows CE isn't supported"
+#elif defined(__linux__) || defined(__gnu_linux__) || defined(linux) || defined(__linux)
+	#define VCMI_UNIX
+	#define VCMI_LINUX
+	#ifdef __ANDROID__
+		#define VCMI_ANDROID 
+	#endif
+#elif defined(__APPLE__) && defined(__MACH__)
+	#define VCMI_UNIX
+	#define VCMI_APPLE
+	#include "TargetConditionals.h"
+	#if TARGET_IPHONE_SIMULATOR
+		#define VCMI_IOS
+		#define VCMI_IOS_SIM
+	#elif TARGET_OS_IPHONE
+		#define VCMI_IOS
+	#elif TARGET_OS_MAC
+		#define VCMI_MAC
+	#else
+		//#warning "Unknown Apple target."?
+	#endif
+#else
+	#error "VCMI supports only Windows, OSX, Linux and Android targets"
+#endif
+
+#ifdef VCMI_IOS
+	#error "iOS system isn't yet supported."
+#endif
+
 /* ---------------------------------------------------------------------------- */
 /* Commonly used C++, Boost headers */
 /* ---------------------------------------------------------------------------- */
@@ -99,6 +143,8 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/date_time/posix_time/posix_time_io.hpp>
 #include <boost/filesystem.hpp>
+#include <boost/filesystem/path.hpp>
+#include <boost/filesystem/fstream.hpp>
 #include <boost/format.hpp>
 #include <boost/functional/hash.hpp>
 #include <boost/lexical_cast.hpp>
@@ -112,13 +158,12 @@ 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 ANDROID
-#include <android/log.h>
+#ifdef VCMI_ANDROID
+	#include <android/log.h>
 #endif
 
 #ifndef M_PI
-#define M_PI 3.14159265358979323846
+	#define M_PI 3.14159265358979323846
 #endif
 
 /* ---------------------------------------------------------------------------- */

+ 35 - 38
client/CMT.cpp

@@ -51,6 +51,7 @@
 #endif
 
 namespace po = boost::program_options;
+namespace bfs = boost::filesystem;
 
 /*
  * CMT.cpp, part of VCMI engine
@@ -72,13 +73,12 @@ int preferredDriverIndex = -1;
 SDL_Window * mainWindow = nullptr;
 SDL_Renderer * mainRenderer = nullptr;
 SDL_Texture * screenTexture = nullptr;
-
 #endif // VCMI_SDL1
 
 extern boost::thread_specific_ptr<bool> inGuiThread;
 
 SDL_Surface *screen = nullptr, //main screen surface
-	*screen2 = nullptr,//and hlp surface (used to store not-active interfaces layer)
+	*screen2 = nullptr, //and hlp surface (used to store not-active interfaces layer)
 	*screenBuf = screen; //points to screen (if only advmapint is present) or screen2 (else) - should be used when updating controls which are not regularly redrawed
 	
 std::queue<SDL_Event> events;
@@ -106,24 +106,23 @@ void endGame();
 #include <getopt.h>
 #endif
 
-void startGameFromFile(const std::string &fname)
+void startGameFromFile(const bfs::path &fname)
 {
 	StartInfo si;
 	try //attempt retrieving start info from given file
 	{
-		if(!fname.size() || !boost::filesystem::exists(fname))
-			throw std::runtime_error("Startfile \"" + fname + "\" does not exist!");
+		if(fname.empty() || !bfs::exists(fname))
+			throw std::runtime_error("Startfile \"" + fname.string() + "\" does not exist!");
 
-		CLoadFile out(fname);
-		if(!out.sfile || !*out.sfile)
-		{
-			throw std::runtime_error("Cannot read from startfile \"" + fname + "\"!");
-		}
+		// TODO: CLoadFile should take boost::path as an argument
+		CLoadFile out(fname.string());
+		if (!out.sfile || !*out.sfile)
+			throw std::runtime_error("Cannot read from startfile \"" + fname.string() +"\"!");
 		out >> si;
 	}
 	catch(std::exception &e)
 	{
-		logGlobal->errorStream() << "Failed to start from the file: " + fname << ". Error: " << e.what()
+		logGlobal->errorStream() << "Failed to start from the file: " << fname << ". Error: " << e.what()
 			<< " Falling back to main menu.";
 		GH.curInt = CGPreGame::create();
 		return;
@@ -218,7 +217,7 @@ int main(int argc, char** argv)
 		("help,h", "display help and exit")
 		("version,v", "display version information and exit")
 		("battle,b", po::value<std::string>(), "runs game in duel mode (battle-only")
-		("start", po::value<std::string>(), "starts game from saved StartInfo file")
+		("start", po::value<bfs::path>(), "starts game from saved StartInfo file")
 		("onlyAI", "runs without human player, all players will be default AI")
 		("noGUI", "runs without GUI, implies --onlyAI")
 		("ai", po::value<std::vector<std::string>>(), "AI to be used for the player, can be specified several times for the consecutive players")
@@ -271,15 +270,15 @@ int main(int argc, char** argv)
 	CStopWatch total, pomtime;
 	std::cout.flags(std::ios::unitbuf);
 	console = new CConsoleHandler;
-	*console->cb = std::bind(&processCommand, _1);
+	*console->cb = processCommand;
 	console->start();
 	atexit(dispose);
 
-	const auto logPath = VCMIDirs::get().userCachePath() + "/VCMI_Client_log.txt";
-	CBasicLogConfigurator logConfig(logPath, console);
+	const bfs::path log_path = VCMIDirs::get().userCachePath() / "VCMI_Client_log.txt";
+	CBasicLogConfigurator logConfig(log_path, console);
     logConfig.configureDefault();
 	logGlobal->infoStream() << "Creating console and configuring logger: " << pomtime.getDiff();
-	logGlobal->infoStream() << "The log file will be saved to " << logPath;
+	logGlobal->infoStream() << "The log file will be saved to " << log_path;
 
 #ifdef __ANDROID__
 	// boost will crash without this
@@ -430,15 +429,15 @@ int main(int argc, char** argv)
 		session["autoSkip"].Bool()  = vm.count("autoSkip");
 		session["oneGoodAI"].Bool() = vm.count("oneGoodAI");
 
-		std::string fileToStartFrom; //none by default
+		bfs::path fileToStartFrom; //none by default
 		if(vm.count("start"))
-			fileToStartFrom = vm["start"].as<std::string>();
+			fileToStartFrom = vm["start"].as<bfs::path>();
 
-		if(fileToStartFrom.size() && boost::filesystem::exists(fileToStartFrom))
+		if(!fileToStartFrom.empty() && bfs::exists(fileToStartFrom))
 			startGameFromFile(fileToStartFrom); //ommit pregame and start the game using settings from file
 		else
 		{
-			if(fileToStartFrom.size())
+			if(!fileToStartFrom.empty())
 			{
                 logGlobal->warnStream() << "Warning: cannot find given file to start from (" << fileToStartFrom
                     << "). Falling back to main menu.";
@@ -584,7 +583,8 @@ void processCommand(const std::string &message)
 	{
         std::cout<<"Command accepted.\t";
 
-		std::string outPath = VCMIDirs::get().userCachePath() + "/extracted/";
+		const bfs::path out_path =
+			VCMIDirs::get().userCachePath() / "extracted";
 
 		auto list = CResourceHandler::get()->getFilteredFiles([](const ResourceID & ident)
 		{
@@ -593,18 +593,19 @@ void processCommand(const std::string &message)
 
 		for (auto & filename : list)
 		{
-			std::string outName = outPath + filename.getName();
-
-			boost::filesystem::create_directories(outName.substr(0, outName.find_last_of("/")));
+			const bfs::path file_path = out_path / (filename.getName() + ".TXT");
+			std::string outName = file_path.string();
+			
+			bfs::create_directories(file_path.parent_path());
 
-			std::ofstream file(outName + ".TXT");
+			bfs::ofstream file(file_path);
 			auto text = CResourceHandler::get()->load(filename)->readAll();
 
 			file.write((char*)text.first.get(), text.second);
 		}
 
         std::cout << "\rExtracting done :)\n";
-        std::cout << " Extracted files can be found in " << outPath << " directory\n";
+		std::cout << " Extracted files can be found in " << out_path << " directory\n";
 	}
 	else if(cn=="crash")
 	{
@@ -710,15 +711,13 @@ void processCommand(const std::string &message)
 		{
 			CDefEssential * cde = CDefHandler::giveDefEss(URI);
 
-			std::string outName = URI;
-			std::string outPath = VCMIDirs::get().userCachePath() + "/extracted/";
-
-			boost::filesystem::create_directories(outPath + outName);
+			const bfs::path out_path = VCMIDirs::get().userCachePath() / "extraced" / URI;
+			bfs::create_directories(out_path);
 
-			for (size_t i=0; i<cde->ourImages.size(); i++)
+			for (size_t i = 0; i < cde->ourImages.size(); ++i)
 			{
-				std::string filename = outPath + outName + '/' + boost::lexical_cast<std::string>(i) + ".bmp";
-				SDL_SaveBMP(cde->ourImages[i].bitmap, filename.c_str());
+				const bfs::path file_path = out_path / (boost::lexical_cast<std::string>(i) + ".bmp");
+				SDL_SaveBMP(cde->ourImages[i].bitmap, file_path.string().c_str());
 			}
 		}
 		else
@@ -731,14 +730,12 @@ void processCommand(const std::string &message)
 
 		if (CResourceHandler::get()->existsResource(ResourceID(URI)))
 		{
-			std::string outName = URI;
-			std::string outPath = VCMIDirs::get().userCachePath() + "/extracted/";
-			std::string fullPath = outPath + outName;
+			const bfs::path out_path = VCMIDirs::get().userCachePath() / "extracted" / URI;
 
 			auto data = CResourceHandler::get()->load(ResourceID(URI))->readAll();
 
-			boost::filesystem::create_directories(fullPath.substr(0, fullPath.find_last_of("/")));
-			std::ofstream outFile(outPath + outName, std::ofstream::binary);
+			bfs::create_directories(out_path.parent_path());
+			bfs::ofstream outFile(out_path, bfs::ofstream::binary);
 			outFile.write((char*)data.first.get(), data.second);
 		}
 		else

+ 5 - 4
client/Client.cpp

@@ -777,8 +777,8 @@ std::string CClient::aiNameForPlayer(const PlayerSettings &ps, bool battleAI)
 {
 	if(ps.name.size())
 	{
-		std::string filename = VCMIDirs::get().libraryPath() + "/AI/" + VCMIDirs::get().libraryName(ps.name);
-		if(boost::filesystem::exists(filename))
+		boost::filesystem::path ai_path = VCMIDirs::get().libraryPath() / "AI" / VCMIDirs::get().libraryName(ps.name);
+		if (boost::filesystem::exists(ai_path))
 			return ps.name;
 	}
 
@@ -865,8 +865,9 @@ CServerHandler::~CServerHandler()
 void CServerHandler::callServer()
 {
 	setThreadName("CServerHandler::callServer");
-	std::string logName = VCMIDirs::get().userCachePath() + "/server_log.txt";
-	std::string comm = VCMIDirs::get().serverPath() + " --port=" + port + " > " + logName;
+	// TODO: Make more boost::filesystem::path based
+	const std::string logName = (VCMIDirs::get().userCachePath() / "server_log.txt").string();
+	const std::string comm = VCMIDirs::get().serverPath().string() + " --port=" + port + " > " + logName;
 	int result = std::system(comm.c_str());
 	if (result == 0)
         logNetwork->infoStream() << "Server closed correctly";

+ 4 - 3
lib/CGameInterface.cpp

@@ -111,9 +111,10 @@ template<typename rett>
 shared_ptr<rett> createAnyAI(std::string dllname, std::string methodName)
 {
     logGlobal->infoStream() << "Opening " << dllname;
-	std::string filename = VCMIDirs::get().libraryName(dllname);
-
-	auto ret = createAny<rett>(VCMIDirs::get().libraryPath() + "/AI/" + filename, methodName);
+	const boost::filesystem::path file_path =
+		VCMIDirs::get().libraryPath() / "AI" / VCMIDirs::get().libraryName(dllname);
+	// TODO: createAny Should take boost::filesystem::path in argument.
+	auto ret = createAny<rett>(file_path.string(), methodName);
 	ret->dllName = dllname;
 	return ret;
 }

+ 171 - 146
lib/VCMIDirs.cpp

@@ -11,11 +11,24 @@
 #include "StdInc.h"
 #include "VCMIDirs.h"
 
-namespace bfs = boost::filesystem; // Should be in each cpp file
+namespace bfs = boost::filesystem;
 
-#ifdef _WIN32
-// File: VCMIDirs_win32.h
-//#include "IVCMIDirs.h"
+bfs::path IVCMIDirs::userSavePath() const { return userDataPath() / "Saves"; }
+
+void IVCMIDirs::init()
+{
+	// TODO: Log errors
+	bfs::create_directory(userDataPath());
+	bfs::create_directory(userCachePath());
+	bfs::create_directory(userConfigPath());
+	bfs::create_directory(userSavePath());
+}
+
+#ifdef VCMI_WINDOWS
+
+#include <Windows.h>
+#include <Shlobj.h>
+#include <Shellapi.h>
 
 class VCMIDirs_win32 : public IVCMIDirs
 {
@@ -23,7 +36,6 @@ class VCMIDirs_win32 : public IVCMIDirs
 		boost::filesystem::path userDataPath() const override;
 		boost::filesystem::path userCachePath() const override;
 		boost::filesystem::path userConfigPath() const override;
-		boost::filesystem::path userSavePath() const override;
 
 		std::vector<boost::filesystem::path> dataPaths() const override;
 
@@ -36,23 +48,69 @@ class VCMIDirs_win32 : public IVCMIDirs
 		std::string libraryName(const std::string& basename) const override;
 
 		std::string genHelpString() const override;
-};
-// End of file: VCMIDirs_win32.h
 
-// File: VCMIDirs_win32.cpp
-//#include "StdInc.h"
-//#include "VCMIDirs_win32"
-// WinAPI
-#include <Windows.h>	// WideCharToMultiByte
-#include <Shlobj.h>		// SHGetSpecialFolderPathW
+		void init() override;
+};
 
-namespace VCMIDirs
+void VCMIDirs_win32::init()
 {
-	const IVCMIDirs& get()
+	// Call base (init dirs)
+	IVCMIDirs::init();
+
+	auto moveDirIfExists = [](const bfs::path& from, const bfs::path& to)
 	{
-		static VCMIDirs_win32 singleton;
-		return singleton;
-	}
+		if (!bfs::is_directory(from))
+			return; // Nothing to do here. Flies away.
+
+		if (bfs::is_empty(from))
+		{
+			bfs::remove(from);
+			return; // Nothing to do here. Flies away.
+		}
+
+		if (!bfs::is_directory(to))
+		{
+			// IVCMIDirs::init() should create all destination directories.
+			// TODO: Log fact, that we shouldn't be here.
+			bfs::create_directory(to);
+		}
+
+		// Why the hell path strings should be end with double null :/
+		auto make_double_nulled = [](const bfs::path& path) -> std::unique_ptr<wchar_t[]>
+		{
+			const std::wstring& path_str = path.native();
+			std::unique_ptr<wchar_t[]> result(new wchar_t[path_str.length() + 2]);
+			
+			size_t i = 0;
+			for (const wchar_t ch : path_str)
+				result[i++] = ch;
+			result[i++] = L'\0';
+			result[i++] = L'\0';
+
+			return result;
+		};
+
+		auto from_dnulled = make_double_nulled(from / L"*.*");
+		auto to_dnulled = make_double_nulled(to);
+
+		SHFILEOPSTRUCTW file_op;
+		file_op.hwnd = GetConsoleWindow();
+		file_op.wFunc = FO_MOVE;
+		file_op.pFrom = from_dnulled.get();
+		file_op.pTo = to_dnulled.get();
+		file_op.fFlags = 0;
+		file_op.hNameMappings = nullptr;
+		file_op.lpszProgressTitle = nullptr;
+
+		const int error_code = SHFileOperationW(&file_op);
+		if (error_code != 0); // TODO: Log error. User should try to move files.
+		else if (file_op.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);
+	};
+
+	moveDirIfExists(userDataPath() / "Games", userSavePath());
 }
 
 bfs::path VCMIDirs_win32::userDataPath() const
@@ -65,7 +123,12 @@ bfs::path VCMIDirs_win32::userDataPath() const
 	// they should put their data under the locations referred to by CSIDL_APPDATA or CSIDL_LOCAL_APPDATA.
 	if (SHGetSpecialFolderPathW(nullptr, profile_dir_w, CSIDL_PROFILE, FALSE) == FALSE) // WinAPI way failed
 	{
-		// FIXME: Create macro for MS Visual Studio.
+		// FIXME: Use _wdupenv_s on MS Visual Studio.
+		//    or: define _CRT_SECURE_NO_WARNINGS in preprocessor global settings.
+		// warning C4996: 'getenv': This function or variable may be unsafe.
+		// Consider using _dupenv_s instead.
+		// To disable deprecation, use _CRT_SECURE_NO_WARNINGS.
+		// See online help for details.
 		if (profile_dir_a = std::getenv("userprofile")) // STL way succeed
 			return bfs::path(profile_dir_a) / "vcmi";
 		else
@@ -78,7 +141,6 @@ bfs::path VCMIDirs_win32::userDataPath() const
 }
 bfs::path VCMIDirs_win32::userCachePath() const { return userDataPath(); }
 bfs::path VCMIDirs_win32::userConfigPath() const { return userDataPath() / "config"; }
-bfs::path VCMIDirs_win32::userSavePath() const { return userDataPath() / "Games"; }
 
 std::vector<bfs::path> VCMIDirs_win32::dataPaths() const
 {
@@ -93,61 +155,26 @@ bfs::path VCMIDirs_win32::binaryPath() const { return ".";  }
 
 std::string VCMIDirs_win32::genHelpString() const
 {
-	// I think this function should have multiple versions
-	// 1. For various arguments
-	// 2. Inverse functions
-	// and should be moved to vstd
-	// or use http://utfcpp.sourceforge.net/
-	auto utf8_convert = [](const bfs::path& path) -> std::string
-	{
-		const auto& path_string = path.native();
-		auto perform_convert = [&path_string](LPSTR output, int output_size)
-		{
-			return WideCharToMultiByte(
-				CP_UTF8,				// Use wchar_t -> utf8 char_t
-				WC_ERR_INVALID_CHARS,	// Fails when invalid char occur
-				path_string.c_str(),	// String to convert
-				path_string.size(),		// String to convert size
-				output,					// Result
-				output_size,			// Result size
-				nullptr, nullptr);		// For the ... CP_UTF8 settings for CodePage, this parameter must be set to NULL
-		};
-
-		int char_count = perform_convert(nullptr, 0); // Result size (0 - obtain size)
-		if (char_count > 0)
-		{
-			std::unique_ptr<char[]> buffer(new char[char_count]);
-			if ((char_count = perform_convert(buffer.get(), char_count)) > 0)
-				return std::string(buffer.get(), char_count);
-		}
-
-		// Conversion failed :C
-		return path.string();
-	};
 
 	std::vector<std::string> temp_vec;
 	for (const bfs::path& path : dataPaths())
-		temp_vec.push_back(utf8_convert(path));
-	std::string gd_string_a = boost::algorithm::join(temp_vec, L";");
+		temp_vec.push_back(path.string());
+	std::string gd_string_a = boost::algorithm::join(temp_vec, ";");
 
 
 	return
-		"  game data:   " + gd_string_a + "\n" +
-		"  libraries:   " + utf8_convert(libraryPath()) + "\n" +
-		"  server:      " + utf8_convert(serverPath()) + "\n" +
-		"\n" +
-		"  user data:   " + utf8_convert(userDataPath()) + "\n" +
-		"  user cache:  " + utf8_convert(userCachePath()) + "\n" +
-		"  user config: " + utf8_convert(userConfigPath()) + "\n" +
-		"  user saves:  " + utf8_convert(userSavePath()) + "\n"; // Should end without new-line?
+		"  game data:   " + gd_string_a + "\n"
+		"  libraries:   " + libraryPath().string() + "\n"
+		"  server:      " + serverPath().string() + "\n"
+		"\n"
+		"  user data:   " + userDataPath().string() + "\n"
+		"  user cache:  " + userCachePath().string() + "\n"
+		"  user config: " + userConfigPath().string() + "\n"
+		"  user saves:  " + userSavePath().string() + "\n"; // Should end without new-line?
 }
 
 std::string VCMIDirs_win32::libraryName(const std::string& basename) const { return basename + ".dll"; }
-// End of file: VCMIDirs_win32.cpp
-#else // UNIX
-// File: IVCMIDirs_UNIX.h
-//#include "IVCMIDirs.h"
-
+#elif defined(VCMI_UNIX)
 class IVCMIDirs_UNIX : public IVCMIDirs
 {
 	public:
@@ -156,11 +183,6 @@ class IVCMIDirs_UNIX : public IVCMIDirs
 
 		std::string genHelpString() const override;
 };
-// End of file: IVCMIDirs_UNIX.h
-
-// File: IVCMIDirs_UNIX.cpp
-//#include "StdInc.h"
-//#include "IVCMIDirs_UNIX.h"
 
 bfs::path IVCMIDirs_UNIX::clientPath() const { return binaryPath() / "vcmiclient"; }
 bfs::path IVCMIDirs_UNIX::clientPath() const { return binaryPath() / "vcmiserver"; }
@@ -170,32 +192,27 @@ std::string IVCMIDirs_UNIX::genHelpString() const
 	std::vector<std::string> temp_vec;
 	for (const bfs::path& path : dataPaths())
 		temp_vec.push_back(path.string());
-	std::string gd_string_a = boost::algorithm::join(temp_vec, L";");
+	std::string gd_string_a = boost::algorithm::join(temp_vec, ":");
 
 
 	return
-		"  game data:   " + gd_string_a + "\n" +
-		"  libraries:   " + libraryPath().string() + "\n" +
-		"  server:      " + serverPath().string() + "\n" +
-		"\n" +
-		"  user data:   " + userDataPath().string() + "\n" +
-		"  user cache:  " + userCachePath().string() + "\n" +
-		"  user config: " + userConfigPath().string() + "\n" +
+		"  game data:   " + gd_string_a + "\n"
+		"  libraries:   " + libraryPath().string() + "\n"
+		"  server:      " + serverPath().string() + "\n"
+		"\n"
+		"  user data:   " + userDataPath().string() + "\n"
+		"  user cache:  " + userCachePath().string() + "\n"
+		"  user config: " + userConfigPath().string() + "\n"
 		"  user saves:  " + userSavePath().string() + "\n"; // Should end without new-line?
 }
-// End of file: IVCMIDirs_UNIX.cpp
-
-#ifdef __APPLE__
-// File: VCMIDirs_OSX.h
-//#include "IVCMIDirs_UNIX.h"
 
+#ifdef VCMI_APPLE
 class VCMIDirs_OSX : public IVCMIDirs_UNIX
 {
 	public:
 		boost::filesystem::path userDataPath() const override;
 		boost::filesystem::path userCachePath() const override;
 		boost::filesystem::path userConfigPath() const override;
-		boost::filesystem::path userSavePath() const override;
 
 		std::vector<boost::filesystem::path> dataPaths() const override;
 
@@ -203,20 +220,50 @@ class VCMIDirs_OSX : public IVCMIDirs_UNIX
 		boost::filesystem::path binaryPath() const override;
 
 		std::string libraryName(const std::string& basename) const override;
-};
-// End of file: VCMIDirs_OSX.h
 
-// File: VCMIDirs_OSX.cpp
-//#include "StdInc.h"
-//#include "VCMIDirs_OSX.h"
+		void init() override;
+};
 
-namespace VCMIDirs
+void VCMIDirs_OSX::init()
 {
-	const IVCMIDirs& get()
+	// Call base (init dirs)
+	IVCMIDirs_UNIX::init();
+
+	auto moveDirIfExists = [](const bfs::path& from, const bfs::path& to)
 	{
-		static VCMIDirs_OSX singleton;
-		return singleton;
-	}
+		if (!bfs::is_directory(from))
+			return; // Nothing to do here. Flies away.
+
+		if (bfs::is_empty(from))
+		{
+			bfs::remove(from);
+			return; // Nothing to do here. Flies away.
+		}
+
+		if (!bfs::is_directory(to))
+		{
+			// IVCMIDirs::init() should create all destination directories.
+			// TODO: Log fact, that we shouldn't be here.
+			bfs::create_directory(to);
+		}
+
+		for (bfs::directory_iterator file(from); file != bfs::directory_iterator(); ++file)
+		{
+			const boost::filesystem::path& src_file_path = file->path();
+			const boost::filesystem::path  dst_file_path = to / src_file_path.filename();
+
+			// TODO: Aplication should ask user what to do when file exists:
+			// replace/ignore/stop process/replace all/ignore all
+			if (!boost::filesystem::exists(dst_file_path))
+				bfs::rename(src_file_path, dst_file_path);
+		}
+
+		if (!bfs::is_empty(from)); // TODO: Log warn. Some files not moved. User should try to move files.
+		else
+			bfs::remove(from);
+	};
+
+	moveDirIfExists(userDataPath() / "Games", userSavePath());
 }
 
 bfs::path VCMIDirs_OSX::userDataPath() const
@@ -234,7 +281,6 @@ bfs::path VCMIDirs_OSX::userDataPath() const
 }
 bfs::path VCMIDirs_OSX::userCachePath() const { return userDataPath(); }
 bfs::path VCMIDirs_OSX::userConfigPath() const { return userDataPath() / "config"; }
-bfs::path VCMIDirs_OSX::userSavePath() const { return userDataPath() / "Games"; }
 
 std::vector<bfs::path> VCMIDirs_OSX::dataPaths() const
 {
@@ -245,18 +291,13 @@ bfs::path VCMIDirs_OSX::libraryPath() const { return "."; }
 bfs::path VCMIDirs_OSX::binaryPath() const { return "."; }
 
 std::string libraryName(const std::string& basename) { return "lib" + basename + ".dylib"; }
-// End of file: VCMIDirs_OSX.cpp
-#else
-// File: VCMIDirs_Linux.h
-//#include "IVCMIDirs_UNIX.h"
-
+#elif defined(VCMI_LINUX)
 class VCMIDirs_Linux : public IVCMIDirs_UNIX
 {
 public:
 	boost::filesystem::path userDataPath() const override;
 	boost::filesystem::path userCachePath() const override;
 	boost::filesystem::path userConfigPath() const override;
-	boost::filesystem::path userSavePath() const override;
 
 	std::vector<boost::filesystem::path> dataPaths() const override;
 
@@ -265,20 +306,6 @@ public:
 
 	std::string libraryName(const std::string& basename) const override;
 };
-// End of file: VCMIDirs_Linux.h
-
-// File: VCMIDirs_Linux.cpp
-//#include "StdInc.h"
-//#include "VCMIDirs_Linux.h"
-
-namespace VCMIDirs
-{
-	const IVCMIDirs& get()
-	{
-		static VCMIDirs_Linux singleton;
-		return singleton;
-	}
-}
 
 bfs::path VCMIDirs_Linux::userDataPath() const
 {
@@ -313,10 +340,6 @@ bfs::path VCMIDirs_Linux::userConfigPath() const
 	else
 		return ".";
 }
-bfs::path VCMIDirs_Linux::userSavePath() const
-{
-	return userDataPath() / "Saves";
-}
 
 std::vector<bfs::path> VCMIDirs_Linux::dataPaths() const
 {
@@ -328,8 +351,6 @@ std::vector<bfs::path> VCMIDirs_Linux::dataPaths() const
 	std::vector<bfs::path> ret;
 
 	const char* home_dir;
-	if (home_dir = getenv("HOME")) // compatibility, should be removed after 0.96
-		ret.push_back(bfs::path(home_dir) / ".vcmi");
 	ret.push_back(M_DATA_DIR);
 
 	if ((home_dir = getenv("XDG_DATA_DIRS")) != nullptr)
@@ -353,46 +374,50 @@ bfs::path VCMIDirs_Linux::libraryPath() const { return M_LIB_PATH; }
 bfs::path VCMIDirs_Linux::binaryPath() const { return M_BIN_DIR; }
 
 std::string VCMIDirs_Linux::libraryName(const std::string& basename) const { return "lib" + basename + ".so"; }
-// End of file VCMIDirs_Linux.cpp
-#ifdef __ANDROID__
-// File: VCMIDirs_Android.h
-//#include "VCMIDirs_Linux.h"
+#ifdef VCMI_ANDROID
 class VCMIDirs_Android : public VCMIDirs_Linux
 {
 public:
 	boost::filesystem::path userDataPath() const override;
 	boost::filesystem::path userCachePath() const override;
 	boost::filesystem::path userConfigPath() const override;
-	boost::filesystem::path userSavePath() const override;
 
 	std::vector<boost::filesystem::path> dataPaths() const override;
 };
-// End of file: VCMIDirs_Android.h
-
-// File: VCMIDirs_Android.cpp
-//#include "StdInc.h"
-//#include "VCMIDirs_Android.h"
-
-namespace VCMIDirs
-{
-	const IVCMIDirs& get()
-	{
-		static VCMIDirs_Android singleton;
-		return singleton;
-	}
-}
 
 // on Android HOME will be set to something like /sdcard/data/Android/is.xyz.vcmi/files/
 bfs::path VCMIDirs_Android::userDataPath() const { return getenv("HOME"); }
 bfs::path VCMIDirs_Android::userCachePath() const { return userDataPath() / "cache"; }
 bfs::path VCMIDirs_Android::userConfigPath() const { return userDataPath() / "config"; }
-bfs::path VCMIDirs_Android::userSavePath() const { return userDataPath() / "Saves"; }
 
 std::vector<bfs::path> VCMIDirs_Android::dataPaths() const
 {
 	return std::vector<bfs::path>(1, userDataPath());
 }
-// End of file: VCMIDirs_Android.cpp
-#endif
-#endif
-#endif
+#endif // VCMI_ANDROID
+#endif // VCMI_APPLE, VCMI_LINUX
+#endif // VCMI_WINDOWS, VCMI_UNIX
+
+// Getters for interfaces are separated for clarity.
+namespace VCMIDirs
+{
+	const IVCMIDirs& get()
+	{
+		#ifdef VCMI_WINDOWS
+			static VCMIDirs_win32 singleton;
+		#elif defined(VCMI_ANDROID)
+			static VCMIDirs_Android singleton;
+		#elif defined(VCMI_LINUX)
+			static VCMIDirs_Linux singleton;
+		#elif defined(VCMI_APPLE)
+			static VCMIDirs_OSX singleton;
+		#endif
+		static bool initialized = false;
+		if (!initialized)
+		{
+			singleton.init();
+			initialized = true;
+		}
+		return singleton;
+	}
+}

+ 8 - 3
lib/VCMIDirs.h

@@ -16,9 +16,9 @@
 // Boost
 #include <boost/filesystem/path.hpp>
 
-// TODO: File should be rename to IVCMIDirs.h
+// TODO: File should be renamed to IVCMIDirs.h
 
-class IVCMIDirs
+class DLL_LINKAGE IVCMIDirs
 {
 	public:
 		// Path to user-specific data directory
@@ -31,7 +31,7 @@ class IVCMIDirs
 		virtual boost::filesystem::path userConfigPath() const = 0;
 
 		// Path to saved games
-		virtual boost::filesystem::path userSavePath() const = 0;
+		virtual boost::filesystem::path userSavePath() const;
 
 		// Paths to global system-wide data directories. First items have higher priority
 		virtual std::vector<boost::filesystem::path> dataPaths() const = 0;
@@ -54,6 +54,11 @@ class IVCMIDirs
 		// virtual std::string libraryName(std::string&& basename) const = 0;?
 
 		virtual std::string genHelpString() const = 0;
+		
+		// Creates not existed, but required directories.
+		// Updates directories what change name/path between versions.
+		// Function called automatically.
+		virtual void init();
 };
 
 namespace VCMIDirs

+ 7 - 5
lib/filesystem/Filesystem.cpp

@@ -69,7 +69,7 @@ void CFilesystemGenerator::loadDirectory(const std::string &mountPoint, const Js
 	std::string URI = prefix + config["path"].String();
 	int depth = 16;
 	if (!config["depth"].isNull())
-		depth = config["depth"].Float();
+		depth = (int)config["depth"].Float();
 
 	ResourceID resID(URI, EResType::DIRECTORY);
 
@@ -136,12 +136,13 @@ ISimpleResourceLoader * CResourceHandler::createInitial()
 		}
 	};
 
+	// 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, 0, true), false);
+			initialLoader->addLoader(new CFilesystemLoader("", path.string(), 0, true), false);
 	}
-	initialLoader->addLoader(new CFilesystemLoader("", VCMIDirs::get().userDataPath(), 0, true), false);
+	initialLoader->addLoader(new CFilesystemLoader("", VCMIDirs::get().userDataPath().string(), 0, true), false);
 
 	recurseInDir("CONFIG", 0);// look for configs
 	recurseInDir("DATA", 0); // look for archives
@@ -166,9 +167,10 @@ void CResourceHandler::initialize()
 	//    |-saves
 	//    |-config
 
+	// TODO: CFilesystemLoader should take boost::filesystem::path
 	knownLoaders["root"] = new CFilesystemList();
-	knownLoaders["saves"] = new CFilesystemLoader("SAVES/", VCMIDirs::get().userSavePath());
-	knownLoaders["config"] = new CFilesystemLoader("CONFIG/", VCMIDirs::get().userConfigPath());
+	knownLoaders["saves"] = new CFilesystemLoader("SAVES/", VCMIDirs::get().userSavePath().string());
+	knownLoaders["config"] = new CFilesystemLoader("CONFIG/", VCMIDirs::get().userConfigPath().string());
 
 	auto localFS = new CFilesystemList();
 	localFS->addLoader(knownLoaders["saves"], true);

+ 14 - 17
lib/logging/CBasicLogConfigurator.cpp

@@ -3,16 +3,17 @@
 
 #include "../CConfigHandler.h"
 
-CBasicLogConfigurator::CBasicLogConfigurator(const std::string & filePath, CConsoleHandler * console) : filePath(filePath),
-	console(console), appendToLogFile(false)
-{
+CBasicLogConfigurator::CBasicLogConfigurator(const boost::filesystem::path & file_path, CConsoleHandler * const console) :
+	file_path(file_path), console(console), appendToLogFile(false) {}
 
-}
+CBasicLogConfigurator::CBasicLogConfigurator(boost::filesystem::path && file_path, CConsoleHandler * const console) :
+	file_path(std::move(file_path)), console(console), appendToLogFile(false) {}
 
 void CBasicLogConfigurator::configureDefault()
 {
 	CLogger::getGlobalLogger()->addTarget(make_unique<CLogConsoleTarget>(console));
-	CLogger::getGlobalLogger()->addTarget(make_unique<CLogFileTarget>(filePath, appendToLogFile));
+	// TODO: CLogFileTarget should take boost::filesystem::path as an argument
+	CLogger::getGlobalLogger()->addTarget(make_unique<CLogFileTarget>(file_path.string(), appendToLogFile));
 	appendToLogFile = true;
 }
 
@@ -21,7 +22,8 @@ void CBasicLogConfigurator::configure()
 	try
 	{
 		const JsonNode & loggingNode = settings["logging"];
-		if(loggingNode.isNull()) throw std::runtime_error("Settings haven't been loaded.");
+		if(loggingNode.isNull())
+			throw std::runtime_error("Settings haven't been loaded.");
 
 		// Configure loggers
 		const JsonNode & loggers = loggingNode["loggers"];
@@ -67,8 +69,9 @@ void CBasicLogConfigurator::configure()
 		}
 		CLogger::getGlobalLogger()->addTarget(std::move(consoleTarget));
 
+		// TODO: CLogFileTarget should take boost::filesystem::path as an argument
 		// Add file target
-		auto fileTarget = make_unique<CLogFileTarget>(filePath, appendToLogFile);
+		auto fileTarget = make_unique<CLogFileTarget>(file_path.string(), appendToLogFile);
 		const JsonNode & fileNode = loggingNode["file"];
 		if(!fileNode.isNull())
 		{
@@ -87,7 +90,7 @@ void CBasicLogConfigurator::configure()
 	logGlobal->infoStream() << "Initialized logging system based on settings successfully.";
 }
 
-ELogLevel::ELogLevel CBasicLogConfigurator::getLogLevel(const std::string & level) const
+ELogLevel::ELogLevel CBasicLogConfigurator::getLogLevel(const std::string & level)
 {
 	static const std::map<std::string, ELogLevel::ELogLevel> levelMap = boost::assign::map_list_of
 			("trace", ELogLevel::TRACE)
@@ -95,18 +98,15 @@ ELogLevel::ELogLevel CBasicLogConfigurator::getLogLevel(const std::string & leve
 			("info", ELogLevel::INFO)
 			("warn", ELogLevel::WARN)
 			("error", ELogLevel::ERROR);
+	
 	const auto & levelPair = levelMap.find(level);
 	if(levelPair != levelMap.end())
-	{
 		return levelPair->second;
-	}
 	else
-	{
 		throw std::runtime_error("Log level " + level + " unknown.");
-	}
 }
 
-EConsoleTextColor::EConsoleTextColor CBasicLogConfigurator::getConsoleColor(const std::string & colorName) const
+EConsoleTextColor::EConsoleTextColor CBasicLogConfigurator::getConsoleColor(const std::string & colorName)
 {
 	static const std::map<std::string, EConsoleTextColor::EConsoleTextColor> colorMap = boost::assign::map_list_of
 			("default", EConsoleTextColor::DEFAULT)
@@ -117,13 +117,10 @@ EConsoleTextColor::EConsoleTextColor CBasicLogConfigurator::getConsoleColor(cons
 			("white", EConsoleTextColor::WHITE)
 			("gray", EConsoleTextColor::GRAY)
 			("teal", EConsoleTextColor::TEAL);
+
 	const auto & colorPair = colorMap.find(colorName);
 	if(colorPair != colorMap.end())
-	{
 		return colorPair->second;
-	}
 	else
-	{
 		throw std::runtime_error("Color " + colorName + " unknown.");
-	}
 }

+ 10 - 6
lib/logging/CBasicLogConfigurator.h

@@ -22,7 +22,8 @@ class JsonNode;
 class DLL_LINKAGE CBasicLogConfigurator
 {
 public:
-	CBasicLogConfigurator(const std::string & filePath, CConsoleHandler * console);
+	CBasicLogConfigurator(const boost::filesystem::path & file_path, CConsoleHandler * const console);
+	CBasicLogConfigurator(boost::filesystem::path && file_path, 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.
@@ -30,12 +31,15 @@ public:
 
 	/// Configures a default logging system by adding the console target and the file target to the global logger.
 	void configureDefault();
-
 private:
-	ELogLevel::ELogLevel getLogLevel(const std::string & level) const;
-	EConsoleTextColor::EConsoleTextColor getConsoleColor(const std::string & colorName) const;
-
-	std::string filePath;
+	// Gets ELogLevel enum from string. (Should be moved to CLogger as a separate function?)
+	// Throws: std::runtime_error
+	static ELogLevel::ELogLevel getLogLevel(const std::string & level);
+	// Gets EConsoleTextColor enum from strings. (Should be moved to CLogger as a separate function?)
+	// Throws: std::runtime_error
+	static EConsoleTextColor::EConsoleTextColor getConsoleColor(const std::string & colorName);
+
+	boost::filesystem::path file_path;
 	CConsoleHandler * console;
 	bool appendToLogFile;
 };

+ 1 - 1
server/CVCMIServer.cpp

@@ -596,7 +596,7 @@ int main(int argc, char** argv)
 	#endif
 
 	console = new CConsoleHandler;
-	CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() + "/VCMI_Server_log.txt", console);
+	CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() / "VCMI_Server_log.txt", console);
 	logConfig.configureDefault();
 
 	preinitDLL(console);