mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Start implementing actulal json serialization
This commit is contained in:
		| @@ -20,7 +20,10 @@ CMemoryBuffer::CMemoryBuffer(): | |||||||
|  |  | ||||||
| si64 CMemoryBuffer::write(const ui8 * data, si64 size) | si64 CMemoryBuffer::write(const ui8 * data, si64 size) | ||||||
| { | { | ||||||
| 	buffer.reserve(tell()+size); | 	//do not shrink | ||||||
|  | 	const si64 newSize = tell()+size; | ||||||
|  | 	if(newSize>getSize()) | ||||||
|  | 		buffer.resize(newSize); | ||||||
| 	 | 	 | ||||||
| 	std::copy(data, data + size, buffer.data() + position); | 	std::copy(data, data + size, buffer.data() + position); | ||||||
| 	position += size; | 	position += size; | ||||||
| @@ -32,8 +35,12 @@ si64 CMemoryBuffer::read(ui8 * data, si64 size) | |||||||
| { | { | ||||||
| 	si64 toRead = std::min(getSize() - tell(), size); | 	si64 toRead = std::min(getSize() - tell(), size); | ||||||
| 	 | 	 | ||||||
| 	std::copy(buffer.data() + position, buffer.data() + position + toRead, data); | 	if(toRead > 0) | ||||||
| 	position += toRead; | 	{ | ||||||
|  | 		std::copy(buffer.data() + position, buffer.data() + position + toRead, data); | ||||||
|  | 		position += toRead;		 | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
| 	 | 	 | ||||||
| 	return toRead;	 | 	return toRead;	 | ||||||
| } | } | ||||||
| @@ -42,7 +49,7 @@ si64 CMemoryBuffer::seek(si64 position) | |||||||
| { | { | ||||||
| 	this->position = position; | 	this->position = position; | ||||||
| 	if (this->position >=getSize()) | 	if (this->position >=getSize()) | ||||||
| 		this->position = getSize()-1; | 		this->position = getSize(); | ||||||
| 	return this->position; | 	return this->position; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,8 +12,9 @@ | |||||||
| #include "CZipSaver.h" | #include "CZipSaver.h" | ||||||
|  |  | ||||||
| ///CZipOutputStream | ///CZipOutputStream | ||||||
| CZipOutputStream::CZipOutputStream(zipFile archive, const std::string & archiveFilename): | CZipOutputStream::CZipOutputStream(CZipSaver * owner_, zipFile archive, const std::string & archiveFilename): | ||||||
| 	handle(archive) | 	handle(archive), | ||||||
|  | 	owner(owner_) | ||||||
| { | { | ||||||
| 	//zip_fileinfo fileInfo; | 	//zip_fileinfo fileInfo; | ||||||
| 	 | 	 | ||||||
| @@ -27,14 +28,15 @@ CZipOutputStream::CZipOutputStream(zipFile archive, const std::string & archiveF | |||||||
|                        "", |                        "", | ||||||
|                        Z_DEFLATED, |                        Z_DEFLATED, | ||||||
|                        Z_DEFAULT_COMPRESSION); |                        Z_DEFAULT_COMPRESSION); | ||||||
|  | 	owner->activeStream = this; | ||||||
| } | } | ||||||
|  |  | ||||||
| CZipOutputStream::~CZipOutputStream() | CZipOutputStream::~CZipOutputStream() | ||||||
| { | { | ||||||
| 	zipCloseFileInZip(handle); | 	zipCloseFileInZip(handle); | ||||||
|  | 	owner->activeStream = nullptr; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| si64 CZipOutputStream::write(const ui8 * data, si64 size) | si64 CZipOutputStream::write(const ui8 * data, si64 size) | ||||||
| { | { | ||||||
| 	int ret = zipWriteInFileInZip(handle, (const void*)data, (unsigned)size); | 	int ret = zipWriteInFileInZip(handle, (const void*)data, (unsigned)size); | ||||||
| @@ -49,18 +51,24 @@ si64 CZipOutputStream::write(const ui8 * data, si64 size) | |||||||
| CZipSaver::CZipSaver(std::shared_ptr<CIOApi> api, const std::string & path): | CZipSaver::CZipSaver(std::shared_ptr<CIOApi> api, const std::string & path): | ||||||
| 	ioApi(api), | 	ioApi(api), | ||||||
| 	zipApi(ioApi->getApiStructure()), | 	zipApi(ioApi->getApiStructure()), | ||||||
| 	handle(nullptr)	 | 	handle(nullptr), | ||||||
|  | 	activeStream(nullptr) | ||||||
| { | { | ||||||
| 	 |  | ||||||
| 	 |  | ||||||
| 	handle = zipOpen2_64(path.c_str(), APPEND_STATUS_CREATE, nullptr, &zipApi); | 	handle = zipOpen2_64(path.c_str(), APPEND_STATUS_CREATE, nullptr, &zipApi); | ||||||
| 	 | 	 | ||||||
| 	if (handle == nullptr) | 	if (handle == nullptr) | ||||||
| 		throw new std::runtime_error("Failed to create archive"); | 		throw new std::runtime_error("Failed to create archive");	 | ||||||
| } | } | ||||||
|  |  | ||||||
| CZipSaver::~CZipSaver() | CZipSaver::~CZipSaver() | ||||||
| { | { | ||||||
|  | 	if(activeStream != nullptr) | ||||||
|  | 	{ | ||||||
|  | 		logGlobal->error("CZipSaver::~CZipSaver: active stream found");	 | ||||||
|  | 		zipCloseFileInZip(handle); | ||||||
|  | 	} | ||||||
|  | 		 | ||||||
|  | 	 | ||||||
| 	if(handle != nullptr) | 	if(handle != nullptr) | ||||||
| 		zipClose(handle, nullptr); | 		zipClose(handle, nullptr); | ||||||
| } | } | ||||||
| @@ -70,10 +78,7 @@ std::unique_ptr<COutputStream> CZipSaver::addFile(const std::string & archiveFil | |||||||
| 	if(activeStream != nullptr) | 	if(activeStream != nullptr) | ||||||
| 		throw new std::runtime_error("CZipSaver::addFile: stream already opened"); | 		throw new std::runtime_error("CZipSaver::addFile: stream already opened"); | ||||||
| 	 | 	 | ||||||
| 	std::unique_ptr<COutputStream> stream(new CZipOutputStream(handle, archiveFilename)); | 	std::unique_ptr<COutputStream> stream(new CZipOutputStream(this, handle, archiveFilename)); | ||||||
| 	 | 	return std::move(stream); | ||||||
| 	activeStream = stream.get(); |  | ||||||
| 	 |  | ||||||
| 	return stream; |  | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,6 +14,8 @@ | |||||||
|  |  | ||||||
| #include "MinizipExtensions.h" | #include "MinizipExtensions.h" | ||||||
|  |  | ||||||
|  | class CZipSaver; | ||||||
|  |  | ||||||
| class DLL_LINKAGE CZipOutputStream: public COutputStream | class DLL_LINKAGE CZipOutputStream: public COutputStream | ||||||
| { | { | ||||||
| public: | public: | ||||||
| @@ -22,7 +24,7 @@ public: | |||||||
| 	 * @param archive archive handle, must be opened | 	 * @param archive archive handle, must be opened | ||||||
| 	 * @param archiveFilename name of file to write | 	 * @param archiveFilename name of file to write | ||||||
| 	 */	 | 	 */	 | ||||||
| 	explicit CZipOutputStream(zipFile archive, const std::string & archiveFilename); | 	explicit CZipOutputStream(CZipSaver * owner_, zipFile archive, const std::string & archiveFilename); | ||||||
| 	~CZipOutputStream(); | 	~CZipOutputStream(); | ||||||
| 	 | 	 | ||||||
| 	si64 write(const ui8 * data, si64 size) override; | 	si64 write(const ui8 * data, si64 size) override; | ||||||
| @@ -33,6 +35,7 @@ public: | |||||||
| 	si64 getSize() override {return 0;};	 | 	si64 getSize() override {return 0;};	 | ||||||
| private: | private: | ||||||
| 	zipFile handle; | 	zipFile handle; | ||||||
|  | 	CZipSaver * owner; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class DLL_LINKAGE CZipSaver | class DLL_LINKAGE CZipSaver | ||||||
| @@ -50,4 +53,5 @@ private: | |||||||
| 	 | 	 | ||||||
| 	///due to minizip design only one file stream may opened at a time | 	///due to minizip design only one file stream may opened at a time | ||||||
| 	COutputStream * activeStream; | 	COutputStream * activeStream; | ||||||
|  | 	friend class CZipOutputStream; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -154,7 +154,7 @@ CProxyIOApi::~CProxyIOApi() | |||||||
|  |  | ||||||
| CInputOutputStream * CProxyIOApi::openFile(const std::string& filename, int mode) const | CInputOutputStream * CProxyIOApi::openFile(const std::string& filename, int mode) const | ||||||
| { | { | ||||||
| 	logGlobal->traceStream() << "CProxyIOApi: stream opened for" <<filename<<" with mode "<<mode;  | 	logGlobal->traceStream() << "CProxyIOApi: stream opened for " <<filename<<" with mode "<<mode;  | ||||||
| 	 | 	 | ||||||
| 	data->seek(0); | 	data->seek(0); | ||||||
| 	return data;//todo: check that only one "copy" is opened | 	return data;//todo: check that only one "copy" is opened | ||||||
|   | |||||||
| @@ -51,6 +51,9 @@ static EventCondition JsonToCondition(const JsonNode & node) | |||||||
| } | } | ||||||
|  |  | ||||||
| ///CMapFormatJson | ///CMapFormatJson | ||||||
|  | const int CMapFormatJson::VERSION_MAJOR = 1; | ||||||
|  | const int CMapFormatJson::VERSION_MINOR = 0; | ||||||
|  |  | ||||||
| const std::string CMapFormatJson::HEADER_FILE_NAME = "header.json"; | const std::string CMapFormatJson::HEADER_FILE_NAME = "header.json"; | ||||||
|  |  | ||||||
| void CMapFormatJson::readTriggeredEvents(const JsonNode & input) | void CMapFormatJson::readTriggeredEvents(const JsonNode & input) | ||||||
| @@ -102,9 +105,19 @@ void CMapPatcher::readPatchData() | |||||||
| 	readTriggeredEvents(input); | 	readTriggeredEvents(input); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | ///CMapFormatZip | ||||||
|  | CMapFormatZip::CMapFormatZip(CInputOutputStream * stream): | ||||||
|  | 	buffer(stream), | ||||||
|  | 	ioApi(new CProxyIOApi(buffer)) | ||||||
|  | { | ||||||
|  | 	 | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| ///CMapLoaderJson | ///CMapLoaderJson | ||||||
| CMapLoaderJson::CMapLoaderJson(CInputStream * stream): | CMapLoaderJson::CMapLoaderJson(CInputOutputStream * stream): | ||||||
| 	input(stream) | 	CMapFormatZip(stream), | ||||||
|  | 	loader("", "_", ioApi)	 | ||||||
| { | { | ||||||
| 	 | 	 | ||||||
| } | } | ||||||
| @@ -168,26 +181,66 @@ JsonNode eventToJson(const EventCondition & cond) | |||||||
| void CMapLoaderJson::readMap() | void CMapLoaderJson::readMap() | ||||||
| { | { | ||||||
| 	readHeader(); | 	readHeader(); | ||||||
|  | 	map->initTerrain(); | ||||||
| 	//TODO:readMap | 	//TODO:readMap | ||||||
| } | } | ||||||
|  |  | ||||||
| void CMapLoaderJson::readHeader() | void CMapLoaderJson::readHeader() | ||||||
| { | { | ||||||
|  | 	//do not use map field here, use only mapHeader | ||||||
|  | 	ResourceID headerID(HEADER_FILE_NAME, EResType::TEXT); | ||||||
|  |  | ||||||
|  | 	if(!loader.existsResource(headerID)) | ||||||
|  | 		throw new std::runtime_error(HEADER_FILE_NAME+" not found"); | ||||||
|  |  | ||||||
|  | 	auto headerData = loader.load(headerID)->readAll(); | ||||||
|  |  | ||||||
|  | 	const JsonNode header(reinterpret_cast<char*>(headerData.first.get()), headerData.second); | ||||||
|  |  | ||||||
| 	//TODO: read such data like map name & size | 	//TODO: read such data like map name & size | ||||||
| //	readTriggeredEvents(); | 	//mapHeader->version = ??? //todo: new version field | ||||||
|  |  | ||||||
|  | 	//todo: multilevel map load support	 | ||||||
|  | 	const JsonNode levels = header["mapLevels"];	 | ||||||
|  | 	mapHeader->height = levels["surface"]["height"].Float(); | ||||||
|  | 	mapHeader->width = levels["surface"]["width"].Float();	 | ||||||
|  | 	mapHeader->twoLevel = !levels["underground"].isNull(); | ||||||
|  |  | ||||||
|  | 	mapHeader->name = header["name"].String(); | ||||||
|  | 	mapHeader->description = header["description"].String(); | ||||||
|  | 	 | ||||||
|  | 	//todo: support arbitrary percentage | ||||||
|  | 	 | ||||||
|  | 	static const std::map<std::string, ui8> difficultyMap = | ||||||
|  | 	{ | ||||||
|  | 		{"EASY", 0}, | ||||||
|  | 		{"NORMAL", 1},  | ||||||
|  | 		{"HARD", 2},  | ||||||
|  | 		{"EXPERT", 3}, | ||||||
|  | 		{"IMPOSSIBLE", 4}		 | ||||||
|  | 	}; | ||||||
|  | 	 | ||||||
|  | 	mapHeader->difficulty = difficultyMap.at(header["difficulty"].String());  | ||||||
|  | 	mapHeader->levelLimit = header["levelLimit"].Float(); | ||||||
|  | 	 | ||||||
|  |  | ||||||
|  | //	std::vector<bool> allowedHeroes; | ||||||
|  | //	std::vector<ui16> placeholdedHeroes;	 | ||||||
|  | 	 | ||||||
|  | 	readTriggeredEvents(header); | ||||||
| 	readPlayerInfo(); | 	readPlayerInfo(); | ||||||
| 	//TODO: readHeader | 	//TODO: readHeader | ||||||
| } | } | ||||||
|  |  | ||||||
| void CMapLoaderJson::readPlayerInfo() | void CMapLoaderJson::readPlayerInfo() | ||||||
| { | { | ||||||
|  | 	//ui8 howManyTeams; | ||||||
| 	//TODO: readPlayerInfo | 	//TODO: readPlayerInfo | ||||||
| } | } | ||||||
|  |  | ||||||
| ///CMapSaverJson | ///CMapSaverJson | ||||||
| CMapSaverJson::CMapSaverJson(CInputOutputStream * stream): | CMapSaverJson::CMapSaverJson(CInputOutputStream * stream): | ||||||
| 	output(stream), | 	CMapFormatZip(stream), | ||||||
| 	ioApi(new CProxyIOApi(output)), |  | ||||||
| 	saver(ioApi, "_") | 	saver(ioApi, "_") | ||||||
| { | { | ||||||
| 	 | 	 | ||||||
| @@ -202,14 +255,48 @@ void CMapSaverJson::saveMap(const std::unique_ptr<CMap>& map) | |||||||
| { | { | ||||||
| 	//TODO: saveMap | 	//TODO: saveMap | ||||||
| 	this->map = map.get(); | 	this->map = map.get(); | ||||||
|  | 	saveHeader();	 | ||||||
|  | 	 | ||||||
| } | } | ||||||
|  |  | ||||||
| void CMapSaverJson::saveHeader() | void CMapSaverJson::saveHeader() | ||||||
| { | { | ||||||
| 	JsonNode header; | 	JsonNode header; | ||||||
| 	//TODO: save header | 	header["versionMajor"].Float() = VERSION_MAJOR; | ||||||
|  | 	header["versionMinor"].Float() = VERSION_MINOR;	 | ||||||
|  | 	 | ||||||
|  | 	//todo: multilevel map save support	 | ||||||
|  | 	JsonNode levels = header["mapLevels"]; | ||||||
|  | 	levels["surface"]["height"].Float() = map->height;	 | ||||||
|  | 	levels["surface"]["width"].Float() = map->width; | ||||||
|  | 	levels["surface"]["index"].Float() = 0; | ||||||
|  | 	 | ||||||
|  | 	if(map->twoLevel) | ||||||
|  | 	{ | ||||||
|  | 		levels["underground"]["height"].Float() = map->height;	 | ||||||
|  | 		levels["underground"]["width"].Float() = map->width;	 | ||||||
|  | 		levels["underground"]["index"].Float() = 1; | ||||||
|  | 	} | ||||||
| 	 | 	 | ||||||
| 	header["name"].String() = map->name; | 	header["name"].String() = map->name; | ||||||
|  | 	header["description"].String() = map->description; | ||||||
|  | 	 | ||||||
|  | 	 | ||||||
|  | 	//todo: support arbitrary percentage	 | ||||||
|  | 	static const std::map<ui8, std::string> difficultyMap = | ||||||
|  | 	{ | ||||||
|  | 		{0, "EASY"}, | ||||||
|  | 		{1, "NORMAL"}, | ||||||
|  | 		{2, "HARD"}, | ||||||
|  | 		{3, "EXPERT"}, | ||||||
|  | 		{4, "IMPOSSIBLE"} | ||||||
|  | 	}; | ||||||
|  | 	 | ||||||
|  | 	header["difficulty"].String() = difficultyMap.at(map->difficulty);	 | ||||||
|  | 	header["levelLimit"].Float() = map->levelLimit; | ||||||
|  | 	 | ||||||
|  | 	//todo:	allowedHeroes; | ||||||
|  | 	//todo: placeholdedHeroes;	 | ||||||
| 	 | 	 | ||||||
| 	std::ostringstream out; | 	std::ostringstream out; | ||||||
| 	out << header; | 	out << header; | ||||||
| @@ -217,7 +304,7 @@ void CMapSaverJson::saveHeader() | |||||||
| 	 | 	 | ||||||
| 	{ | 	{ | ||||||
| 		auto s = out.str(); | 		auto s = out.str(); | ||||||
| 		auto stream = saver.addFile(HEADER_FILE_NAME); | 		std::unique_ptr<COutputStream> stream = saver.addFile(HEADER_FILE_NAME); | ||||||
| 		 | 		 | ||||||
| 		stream->write((const ui8*)s.c_str(), s.size()); | 		stream->write((const ui8*)s.c_str(), s.size()); | ||||||
| 	}	 | 	}	 | ||||||
|   | |||||||
| @@ -22,6 +22,9 @@ class TriggeredEvent; | |||||||
| class DLL_LINKAGE CMapFormatJson | class DLL_LINKAGE CMapFormatJson | ||||||
| { | { | ||||||
| public:	 | public:	 | ||||||
|  | 	static const int VERSION_MAJOR; | ||||||
|  | 	static const int VERSION_MINOR;	 | ||||||
|  | 	 | ||||||
| 	static const std::string HEADER_FILE_NAME; | 	static const std::string HEADER_FILE_NAME; | ||||||
|  |  | ||||||
| protected: | protected: | ||||||
| @@ -73,7 +76,16 @@ private: | |||||||
| 	const JsonNode input;	 | 	const JsonNode input;	 | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class DLL_LINKAGE CMapLoaderJson : public CMapFormatJson, public IMapLoader | class DLL_LINKAGE CMapFormatZip : public CMapFormatJson | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	CMapFormatZip(CInputOutputStream * stream); | ||||||
|  | protected: | ||||||
|  | 	CInputOutputStream * buffer; | ||||||
|  | 	std::shared_ptr<CIOApi> ioApi; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class DLL_LINKAGE CMapLoaderJson : public CMapFormatZip, public IMapLoader | ||||||
| { | { | ||||||
| public: | public: | ||||||
| 	/** | 	/** | ||||||
| @@ -81,7 +93,7 @@ public: | |||||||
| 	 * | 	 * | ||||||
| 	 * @param stream a stream containing the map data | 	 * @param stream a stream containing the map data | ||||||
| 	 */ | 	 */ | ||||||
| 	CMapLoaderJson(CInputStream * stream); | 	CMapLoaderJson(CInputOutputStream * stream); | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| 	 * Loads the VCMI/Json map file. | 	 * Loads the VCMI/Json map file. | ||||||
| @@ -112,12 +124,11 @@ private: | |||||||
| 	 * Reads player information. | 	 * Reads player information. | ||||||
| 	 */ | 	 */ | ||||||
| 	void readPlayerInfo(); | 	void readPlayerInfo(); | ||||||
|  | 	 | ||||||
|  | 	CZipLoader loader; | ||||||
| 	CInputStream * input; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class DLL_LINKAGE CMapSaverJson : public CMapFormatJson, public IMapSaver | class DLL_LINKAGE CMapSaverJson : public CMapFormatZip, public IMapSaver | ||||||
| { | { | ||||||
| public: | public: | ||||||
| 	/** | 	/** | ||||||
| @@ -136,8 +147,6 @@ public: | |||||||
| 	void saveMap(const std::unique_ptr<CMap> & map) override;	 | 	void saveMap(const std::unique_ptr<CMap> & map) override;	 | ||||||
| private: | private: | ||||||
| 	void saveHeader(); | 	void saveHeader(); | ||||||
| 	 | 		 | ||||||
| 	CInputOutputStream * output; |  | ||||||
| 	std::shared_ptr<CIOApi> ioApi;		 |  | ||||||
| 	CZipSaver saver;	 | 	CZipSaver saver;	 | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -40,6 +40,7 @@ public: | |||||||
| 		CMapGenerator gen; | 		CMapGenerator gen; | ||||||
| 		 | 		 | ||||||
| 		initialMap = gen.generate(&opt, TEST_RANDOM_SEED); | 		initialMap = gen.generate(&opt, TEST_RANDOM_SEED); | ||||||
|  | 		initialMap->name = "Test"; | ||||||
| 	}; | 	}; | ||||||
| 	~CMapTestFixture() | 	~CMapTestFixture() | ||||||
| 	{ | 	{ | ||||||
| @@ -53,20 +54,26 @@ BOOST_AUTO_TEST_CASE(CMapFormatVCMI_Simple) | |||||||
| { | { | ||||||
| 	try | 	try | ||||||
| 	{ | 	{ | ||||||
|  | 		logGlobal->info("CMapFormatVCMI_Simple start"); | ||||||
| 		CMemoryBuffer serializeBuffer; | 		CMemoryBuffer serializeBuffer; | ||||||
| 		CMapSaverJson saver(&serializeBuffer); | 		{ | ||||||
| 		saver.saveMap(initialMap); | 			CMapSaverJson saver(&serializeBuffer); | ||||||
| 		 | 			saver.saveMap(initialMap); | ||||||
| 		CMapLoaderJson loader(&serializeBuffer); | 		} | ||||||
| 		serializeBuffer.seek(0);		 | 		serializeBuffer.seek(0); | ||||||
| 		std::unique_ptr<CMap> serialized = loader.loadMap(); | 		{ | ||||||
| 		 | 			CMapLoaderJson loader(&serializeBuffer); | ||||||
| 		 | 			std::unique_ptr<CMap> serialized = loader.loadMap(); | ||||||
| 		MapComparer c;		 |  | ||||||
| 		c(initialMap, serialized); | 			MapComparer c; | ||||||
|  | 			c(initialMap, serialized); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		logGlobal->info("CMapFormatVCMI_Simple finish"); | ||||||
| 	} | 	} | ||||||
| 	catch(const std::exception & e) | 	catch(const std::exception & e) | ||||||
| 	{ | 	{ | ||||||
|  | 		logGlobal->info("CMapFormatVCMI_Simple crash"); | ||||||
| 		logGlobal-> errorStream() << e.what(); | 		logGlobal-> errorStream() << e.what(); | ||||||
| 		throw; | 		throw; | ||||||
| 	} | 	} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user