<commit_before>#include "game/state/savemanager.h"
#include "framework/configfile.h"
#include "framework/filesystem.h"
#include "framework/framework.h"
#include "framework/serialization/serialize.h"
#include "framework/trace.h"
#include "game/state/gamestate.h"
#include "version.h"
#include <algorithm>
#include <sstream>

// Disable automatic #pragma linking for boost - only enabled in msvc and that should provide boost
// symbols as part of the module that uses it
#define BOOST_ALL_NO_LIB

// boost uuid for generating temporary identifier for new save
#include <boost/uuid/uuid_generators.hpp> // generators
#include <boost/uuid/uuid_io.hpp>         // conversion to string

namespace uuids = boost::uuids;

namespace OpenApoc
{
const UString saveManifestName = "save_manifest";
const UString saveFileExtension = ".save";

ConfigOptionString saveDirOption("Game.Save", "Directory", "Directory containing saved games",
                                 "./saves");
ConfigOptionBool packSaveOption("Game.Save", "Pack", "Pack saved games into a zip", true);

SaveManager::SaveManager() : saveDirectory(saveDirOption.get()) {}

UString SaveManager::createSavePath(const UString &name) const
{
	UString result;
	result += saveDirectory;
	result += "/";
	result += name;
	result += saveFileExtension;
	return result;
}

static std::map<SaveType, UString> saveTypeNames{{SaveType::Manual, "New saved game"},
                                                 {SaveType::Quick, "Quicksave"},
                                                 {SaveType::Auto, "Autosave"}};

std::shared_future<void> SaveManager::loadGame(const SaveMetadata &metadata,
                                               sp<GameState> state) const
{
	return loadGame(metadata.getFile(), state);
}

std::shared_future<void> SaveManager::loadGame(const UString &savePath, sp<GameState> state) const
{
	UString saveArchiveLocation = savePath;
	auto loadTask = fw().threadPoolEnqueue([saveArchiveLocation, state]() -> void {
		if (!state->loadGame(saveArchiveLocation))
		{
			LogError("Failed to load '%s'", saveArchiveLocation);
			return;
		}
		state->initState();
		return;
	});

	return loadTask;
}

std::shared_future<void> SaveManager::loadSpecialSave(const SaveType type,
                                                      sp<GameState> state) const
{
	if (type == SaveType::Manual)
	{
		LogError("Cannot load automatic save for type %i", static_cast<int>(type));
		return std::async(std::launch::deferred, []() -> void { return; });
	}

	UString saveName;

	try
	{
		saveName = saveTypeNames.at(type);
	}
	catch (std::out_of_range)
	{
		LogError("Cannot find name of save type %i", static_cast<int>(type));
		return std::async(std::launch::deferred, []() -> void { return; });
	}

	return loadGame(createSavePath(saveName), state);
}

bool writeArchiveWithBackup(SerializationArchive *archive, const UString &path, bool pack)
{
	fs::path savePath = path.str();
	fs::path tempPath;
	bool shouldCleanup = false;
	try
	{
		if (!fs::exists(savePath))
		{
			return archive->write(path, pack);
		}

		// WARNING! Dragons live here! Specifically dragon named miniz who hates windows paths
		// (or paths not starting with dot)
		// therefore I'm doing gymnasitcs here to backup and still pass original path string to
		// archive write
		// that is really bad, because if user clicks exit, save will be renamed to some random
		// junk
		// however it will still function as regular save file, so maybe not that bad?
		fs::path saveDirectory = savePath.parent_path();
		bool haveNewName = false;
		for (int retries = 5; retries > 0; retries--)
		{
			{
				tempPath = saveDirectory /
				           (boost::uuids::to_string(uuids::random_generator()()) + ".save");
				if (!fs::exists(tempPath))
				{
					haveNewName = true;
					break;
				}
			}
		}

		if (!haveNewName)
		{
			LogError("Unable to create temporary file at \"%s\"", tempPath.string());
			return false;
		}

		fs::rename(savePath, tempPath);
		shouldCleanup = true;
		bool saveSuccess = archive->write(path, pack);
		shouldCleanup = false;

		if (saveSuccess)
		{
			fs::remove_all(tempPath);
			return true;
		}
		else
		{
			if (fs::exists(savePath))
			{
				fs::remove_all(savePath);
			}

			fs::rename(tempPath, savePath);
		}
	}
	catch (fs::filesystem_error exception)
	{
		if (shouldCleanup)
		{
			if (fs::exists(savePath))
			{
				fs::remove_all(savePath);
			}
			fs::rename(tempPath, savePath);
		}

		LogError("Unable to save game: \"%s\"", exception.what());
	}

	return false;
}

bool SaveManager::findFreePath(UString &path, const UString &name) const
{
	path = createSavePath("save_" + name);
	if (fs::exists(path.str()))
	{
		for (int retries = 5; retries > 0; retries--)
		{
			path = createSavePath("save_" + name + std::to_string(rand()));
			if (!fs::exists(path.str()))
			{
				return true;
			}
		}

		LogError("Unable to generate filename for save %s", name);
		return false;
	}

	return true;
}

bool SaveManager::newSaveGame(const UString &name, const sp<GameState> gameState) const
{
	UString path;
	if (!findFreePath(path, name))
	{
		return false;
	}

	SaveMetadata manifest(name, path, time(nullptr), SaveType::Manual, gameState);
	return saveGame(manifest, gameState);
}

bool SaveManager::overrideGame(const SaveMetadata &metadata, const UString &newName,
                               const sp<GameState> gameState) const
{
	SaveMetadata updatedMetadata(newName, metadata.getFile(), time(nullptr), metadata.getType(),
	                             gameState);
	bool result = saveGame(updatedMetadata, gameState);
	if (result && newName != metadata.getName().str())
	{
		// if renamed file move to path with new name
		UString newFile;
		if (findFreePath(newFile, newName))
		{
			try
			{
				fs::rename(metadata.getFile().str(), newFile.str());
			}
			catch (fs::filesystem_error error)
			{
				LogWarning("Error while removing renamed save: \"%s\"", error.what());
			}
		}
	}

	return result;
}

bool SaveManager::saveGame(const SaveMetadata &metadata, const sp<GameState> gameState) const
{
	bool pack = packSaveOption.get();
	const UString path = metadata.getFile();
	TRACE_FN_ARGS1("path", path);
	auto archive = SerializationArchive::createArchive();
	if (gameState->serialize(archive.get()) && metadata.serializeManifest(archive.get()))
	{
		return writeArchiveWithBackup(archive.get(), path, pack);
	}

	return false;
}

bool SaveManager::specialSaveGame(SaveType type, const sp<GameState> gameState) const
{
	if (type == SaveType::Manual)
	{
		LogError("Cannot create automatic save for type %i", static_cast<int>(type));
		return false;
	}

	UString saveName;
	try
	{
		saveName = saveTypeNames.at(type);
	}
	catch (std::out_of_range)
	{
		LogError("Cannot find name of save type %i", static_cast<int>(type));
		return false;
	}

	SaveMetadata manifest(saveName, createSavePath(saveName), time(nullptr), type, gameState);
	return saveGame(manifest, gameState);
}

std::vector<SaveMetadata> SaveManager::getSaveList() const
{
	auto dirString = saveDirOption.get();
	fs::path saveDirectory = dirString.str();
	std::vector<SaveMetadata> saveList;
	try
	{
		fs::path currentPath = fs::current_path().string();
		if (!fs::exists(saveDirectory) && !fs::create_directories(saveDirectory))
		{
			LogWarning("Save directory \"%s\" not found, and could not be created!", saveDirectory);
			return saveList;
		}

		for (auto i = fs::directory_iterator(currentPath / saveDirectory);
		     i != fs::directory_iterator(); ++i)
		{
			if (i->path().extension().string() != saveFileExtension.str())
			{
				continue;
			}

			std::string saveFileName = i->path().filename().string();
			// miniz can't read paths not starting with dor or with windows slashes
			UString savePath = saveDirectory.string() + "/" + saveFileName;
			if (auto archive = SerializationArchive::readArchive(savePath))
			{
				SaveMetadata metadata;
				if (metadata.deserializeManifest(archive.get(), savePath))
				{
					saveList.push_back(metadata);
				}
				else // accept saves with missing manifest if extension is correct
				{
					saveList.push_back(SaveMetadata("Unknown(Missing manifest)", savePath, 0,
					                                SaveType::Manual, nullptr));
				}
			}
		}
	}
	catch (fs::filesystem_error er)
	{
		LogError("Error while enumerating directory: \"%s\"", er.what());
	}

	sort(saveList.begin(), saveList.end(), [](const SaveMetadata &lhs, const SaveMetadata &rhs) {
		return lhs.getCreationDate() > rhs.getCreationDate();
	});

	return saveList;
}

bool SaveManager::deleteGame(const sp<SaveMetadata> &slot) const
{
	try
	{
		if (!fs::exists(slot->getFile().str()))
		{
			LogWarning("Attempt to delete not existing file");
			return false;
		}

		fs::remove_all(slot->getFile().str());
		return true;
	}
	catch (fs::filesystem_error exception)
	{
		LogError("Unable to delete saved gane: \"%s\"", exception.what());
		return false;
	}
}

bool SaveMetadata::deserializeManifest(SerializationArchive *archive, const UString &saveFileName)
{
	auto root = archive->getRoot("", saveManifestName);
	if (!root)
	{
		return false;
	}

	auto nameNode = root->getNodeOpt("name");
	if (!nameNode)
	{
		return false;
	}
	this->name = nameNode->getValue();

	auto difficultyNode = root->getNodeOpt("difficulty");
	if (difficultyNode)
	{
		this->difficulty = difficultyNode->getValue();
	}

	auto saveDateNode = root->getNodeOpt("save_date");
	if (saveDateNode)
	{
		std::istringstream stream(saveDateNode->getValue().str());
		time_t timestamp;
		stream >> timestamp;
		this->creationDate = timestamp;
	}

	auto gameTicksNode = root->getNodeOpt("game_ticks");
	if (gameTicksNode)
	{
		gameTicks = gameTicksNode->getValueUInt();
	}

	auto typeNode = root->getNodeOpt("type");
	if (typeNode)
	{
		this->type = static_cast<SaveType>(Strings::toInteger(typeNode->getValue()));
	}
	else
	{
		this->type = SaveType::Manual;
	}

	this->file = saveFileName;
	return true;
}

bool SaveMetadata::serializeManifest(SerializationArchive *archive) const
{
	auto root = archive->newRoot("", saveManifestName);
	if (!root)
	{
		return false;
	}
	auto nameNode = root->addNode("name");
	nameNode->setValue(this->getName());

	auto difficultyNode = root->addNode("difficulty");
	difficultyNode->setValue(this->getDifficulty());

	auto savedateNode = root->addNode("save_date");
	savedateNode->setValue(std::to_string(time(nullptr)));

	auto gameTicksNode = root->addNode("game_ticks");
	gameTicksNode->setValue(std::to_string(getGameTicks()));

	auto versionNode = root->addNode("game_version");
	versionNode->setValue(OPENAPOC_VERSION);

	if (this->type != SaveType::Manual)
	{
		auto typeNode = root->addNode("type");
		typeNode->setValue(Strings::fromInteger(static_cast<unsigned>(this->type)));
	}

	return true;
}

time_t SaveMetadata::getCreationDate() const { return creationDate; }

SaveMetadata::SaveMetadata() : creationDate(0), type(), gameTicks(0){};
SaveMetadata::~SaveMetadata() = default;
;
SaveMetadata::SaveMetadata(UString name, UString file, time_t creationDate, SaveType type,
                           const sp<GameState> gameState)
    : name(name), file(file), creationDate(creationDate), type(type)
{
	if (gameState)
	{
		gameTicks = gameState->gameTime.getTicks();
		// this->difficulty = gameState->difficulty; ?
	}
}
SaveMetadata::SaveMetadata(const SaveMetadata &metdata, time_t creationDate,
                           const sp<GameState> gameState)
    : name(metdata.name), file(metdata.file), creationDate(creationDate), type(metdata.type)
{
	if (gameState)
	{
		gameTicks = gameState->gameTime.getTicks();
		// this->difficulty = gameState->difficulty; ?
	}
}
const UString &SaveMetadata::getName() const { return name; }
const UString &SaveMetadata::getFile() const { return file; }
const UString &SaveMetadata::getDifficulty() const { return difficulty; }
const SaveType &SaveMetadata::getType() const { return type; }
uint64_t SaveMetadata::getGameTicks() const { return gameTicks; }
} // namespace OpenApoc
<commit_msg>fix save manager not allowing absolute paths<commit_after>#include "game/state/savemanager.h"
#include "framework/configfile.h"
#include "framework/filesystem.h"
#include "framework/framework.h"
#include "framework/serialization/serialize.h"
#include "framework/trace.h"
#include "game/state/gamestate.h"
#include "version.h"
#include <algorithm>
#include <sstream>

// Disable automatic #pragma linking for boost - only enabled in msvc and that should provide boost
// symbols as part of the module that uses it
#define BOOST_ALL_NO_LIB

// boost uuid for generating temporary identifier for new save
#include <boost/uuid/uuid_generators.hpp> // generators
#include <boost/uuid/uuid_io.hpp>         // conversion to string

namespace uuids = boost::uuids;

namespace OpenApoc
{
const UString saveManifestName = "save_manifest";
const UString saveFileExtension = ".save";

ConfigOptionString saveDirOption("Game.Save", "Directory", "Directory containing saved games",
                                 "./saves");
ConfigOptionBool packSaveOption("Game.Save", "Pack", "Pack saved games into a zip", true);

SaveManager::SaveManager() : saveDirectory(saveDirOption.get()) {}

UString SaveManager::createSavePath(const UString &name) const
{
	UString result;
	result += saveDirectory;
	result += "/";
	result += name;
	result += saveFileExtension;
	return result;
}

static std::map<SaveType, UString> saveTypeNames{{SaveType::Manual, "New saved game"},
                                                 {SaveType::Quick, "Quicksave"},
                                                 {SaveType::Auto, "Autosave"}};

std::shared_future<void> SaveManager::loadGame(const SaveMetadata &metadata,
                                               sp<GameState> state) const
{
	return loadGame(metadata.getFile(), state);
}

std::shared_future<void> SaveManager::loadGame(const UString &savePath, sp<GameState> state) const
{
	UString saveArchiveLocation = savePath;
	auto loadTask = fw().threadPoolEnqueue([saveArchiveLocation, state]() -> void {
		if (!state->loadGame(saveArchiveLocation))
		{
			LogError("Failed to load '%s'", saveArchiveLocation);
			return;
		}
		state->initState();
		return;
	});

	return loadTask;
}

std::shared_future<void> SaveManager::loadSpecialSave(const SaveType type,
                                                      sp<GameState> state) const
{
	if (type == SaveType::Manual)
	{
		LogError("Cannot load automatic save for type %i", static_cast<int>(type));
		return std::async(std::launch::deferred, []() -> void { return; });
	}

	UString saveName;

	try
	{
		saveName = saveTypeNames.at(type);
	}
	catch (std::out_of_range)
	{
		LogError("Cannot find name of save type %i", static_cast<int>(type));
		return std::async(std::launch::deferred, []() -> void { return; });
	}

	return loadGame(createSavePath(saveName), state);
}

bool writeArchiveWithBackup(SerializationArchive *archive, const UString &path, bool pack)
{
	fs::path savePath = path.str();
	fs::path tempPath;
	bool shouldCleanup = false;
	try
	{
		if (!fs::exists(savePath))
		{
			return archive->write(path, pack);
		}

		// WARNING! Dragons live here! Specifically dragon named miniz who hates windows paths
		// (or paths not starting with dot)
		// therefore I'm doing gymnasitcs here to backup and still pass original path string to
		// archive write
		// that is really bad, because if user clicks exit, save will be renamed to some random
		// junk
		// however it will still function as regular save file, so maybe not that bad?
		fs::path saveDirectory = savePath.parent_path();
		bool haveNewName = false;
		for (int retries = 5; retries > 0; retries--)
		{
			{
				tempPath = saveDirectory /
				           (boost::uuids::to_string(uuids::random_generator()()) + ".save");
				if (!fs::exists(tempPath))
				{
					haveNewName = true;
					break;
				}
			}
		}

		if (!haveNewName)
		{
			LogError("Unable to create temporary file at \"%s\"", tempPath.string());
			return false;
		}

		fs::rename(savePath, tempPath);
		shouldCleanup = true;
		bool saveSuccess = archive->write(path, pack);
		shouldCleanup = false;

		if (saveSuccess)
		{
			fs::remove_all(tempPath);
			return true;
		}
		else
		{
			if (fs::exists(savePath))
			{
				fs::remove_all(savePath);
			}

			fs::rename(tempPath, savePath);
		}
	}
	catch (fs::filesystem_error exception)
	{
		if (shouldCleanup)
		{
			if (fs::exists(savePath))
			{
				fs::remove_all(savePath);
			}
			fs::rename(tempPath, savePath);
		}

		LogError("Unable to save game: \"%s\"", exception.what());
	}

	return false;
}

bool SaveManager::findFreePath(UString &path, const UString &name) const
{
	path = createSavePath("save_" + name);
	if (fs::exists(path.str()))
	{
		for (int retries = 5; retries > 0; retries--)
		{
			path = createSavePath("save_" + name + std::to_string(rand()));
			if (!fs::exists(path.str()))
			{
				return true;
			}
		}

		LogError("Unable to generate filename for save %s", name);
		return false;
	}

	return true;
}

bool SaveManager::newSaveGame(const UString &name, const sp<GameState> gameState) const
{
	UString path;
	if (!findFreePath(path, name))
	{
		return false;
	}

	SaveMetadata manifest(name, path, time(nullptr), SaveType::Manual, gameState);
	return saveGame(manifest, gameState);
}

bool SaveManager::overrideGame(const SaveMetadata &metadata, const UString &newName,
                               const sp<GameState> gameState) const
{
	SaveMetadata updatedMetadata(newName, metadata.getFile(), time(nullptr), metadata.getType(),
	                             gameState);
	bool result = saveGame(updatedMetadata, gameState);
	if (result && newName != metadata.getName().str())
	{
		// if renamed file move to path with new name
		UString newFile;
		if (findFreePath(newFile, newName))
		{
			try
			{
				fs::rename(metadata.getFile().str(), newFile.str());
			}
			catch (fs::filesystem_error error)
			{
				LogWarning("Error while removing renamed save: \"%s\"", error.what());
			}
		}
	}

	return result;
}

bool SaveManager::saveGame(const SaveMetadata &metadata, const sp<GameState> gameState) const
{
	bool pack = packSaveOption.get();
	const UString path = metadata.getFile();
	TRACE_FN_ARGS1("path", path);
	auto archive = SerializationArchive::createArchive();
	if (gameState->serialize(archive.get()) && metadata.serializeManifest(archive.get()))
	{
		return writeArchiveWithBackup(archive.get(), path, pack);
	}

	return false;
}

bool SaveManager::specialSaveGame(SaveType type, const sp<GameState> gameState) const
{
	if (type == SaveType::Manual)
	{
		LogError("Cannot create automatic save for type %i", static_cast<int>(type));
		return false;
	}

	UString saveName;
	try
	{
		saveName = saveTypeNames.at(type);
	}
	catch (std::out_of_range)
	{
		LogError("Cannot find name of save type %i", static_cast<int>(type));
		return false;
	}

	SaveMetadata manifest(saveName, createSavePath(saveName), time(nullptr), type, gameState);
	return saveGame(manifest, gameState);
}

std::vector<SaveMetadata> SaveManager::getSaveList() const
{
	auto dirString = saveDirOption.get();
	fs::path saveDirectory = dirString.str();
	std::vector<SaveMetadata> saveList;
	try
	{
		if (!fs::exists(saveDirectory) && !fs::create_directories(saveDirectory))
		{
			LogWarning("Save directory \"%s\" not found, and could not be created!", saveDirectory);
			return saveList;
		}

		for (auto i = fs::directory_iterator(saveDirectory); i != fs::directory_iterator(); ++i)
		{
			if (i->path().extension().string() != saveFileExtension.str())
			{
				continue;
			}

			std::string saveFileName = i->path().filename().string();
			// miniz can't read paths not starting with dor or with windows slashes
			UString savePath = saveDirectory.string() + "/" + saveFileName;
			if (auto archive = SerializationArchive::readArchive(savePath))
			{
				SaveMetadata metadata;
				if (metadata.deserializeManifest(archive.get(), savePath))
				{
					saveList.push_back(metadata);
				}
				else // accept saves with missing manifest if extension is correct
				{
					saveList.push_back(SaveMetadata("Unknown(Missing manifest)", savePath, 0,
					                                SaveType::Manual, nullptr));
				}
			}
		}
	}
	catch (fs::filesystem_error er)
	{
		LogError("Error while enumerating directory: \"%s\"", er.what());
	}

	sort(saveList.begin(), saveList.end(), [](const SaveMetadata &lhs, const SaveMetadata &rhs) {
		return lhs.getCreationDate() > rhs.getCreationDate();
	});

	return saveList;
}

bool SaveManager::deleteGame(const sp<SaveMetadata> &slot) const
{
	try
	{
		if (!fs::exists(slot->getFile().str()))
		{
			LogWarning("Attempt to delete not existing file");
			return false;
		}

		fs::remove_all(slot->getFile().str());
		return true;
	}
	catch (fs::filesystem_error exception)
	{
		LogError("Unable to delete saved gane: \"%s\"", exception.what());
		return false;
	}
}

bool SaveMetadata::deserializeManifest(SerializationArchive *archive, const UString &saveFileName)
{
	auto root = archive->getRoot("", saveManifestName);
	if (!root)
	{
		return false;
	}

	auto nameNode = root->getNodeOpt("name");
	if (!nameNode)
	{
		return false;
	}
	this->name = nameNode->getValue();

	auto difficultyNode = root->getNodeOpt("difficulty");
	if (difficultyNode)
	{
		this->difficulty = difficultyNode->getValue();
	}

	auto saveDateNode = root->getNodeOpt("save_date");
	if (saveDateNode)
	{
		std::istringstream stream(saveDateNode->getValue().str());
		time_t timestamp;
		stream >> timestamp;
		this->creationDate = timestamp;
	}

	auto gameTicksNode = root->getNodeOpt("game_ticks");
	if (gameTicksNode)
	{
		gameTicks = gameTicksNode->getValueUInt();
	}

	auto typeNode = root->getNodeOpt("type");
	if (typeNode)
	{
		this->type = static_cast<SaveType>(Strings::toInteger(typeNode->getValue()));
	}
	else
	{
		this->type = SaveType::Manual;
	}

	this->file = saveFileName;
	return true;
}

bool SaveMetadata::serializeManifest(SerializationArchive *archive) const
{
	auto root = archive->newRoot("", saveManifestName);
	if (!root)
	{
		return false;
	}
	auto nameNode = root->addNode("name");
	nameNode->setValue(this->getName());

	auto difficultyNode = root->addNode("difficulty");
	difficultyNode->setValue(this->getDifficulty());

	auto savedateNode = root->addNode("save_date");
	savedateNode->setValue(std::to_string(time(nullptr)));

	auto gameTicksNode = root->addNode("game_ticks");
	gameTicksNode->setValue(std::to_string(getGameTicks()));

	auto versionNode = root->addNode("game_version");
	versionNode->setValue(OPENAPOC_VERSION);

	if (this->type != SaveType::Manual)
	{
		auto typeNode = root->addNode("type");
		typeNode->setValue(Strings::fromInteger(static_cast<unsigned>(this->type)));
	}

	return true;
}

time_t SaveMetadata::getCreationDate() const { return creationDate; }

SaveMetadata::SaveMetadata() : creationDate(0), type(), gameTicks(0){};
SaveMetadata::~SaveMetadata() = default;
;
SaveMetadata::SaveMetadata(UString name, UString file, time_t creationDate, SaveType type,
                           const sp<GameState> gameState)
    : name(name), file(file), creationDate(creationDate), type(type)
{
	if (gameState)
	{
		gameTicks = gameState->gameTime.getTicks();
		// this->difficulty = gameState->difficulty; ?
	}
}
SaveMetadata::SaveMetadata(const SaveMetadata &metdata, time_t creationDate,
                           const sp<GameState> gameState)
    : name(metdata.name), file(metdata.file), creationDate(creationDate), type(metdata.type)
{
	if (gameState)
	{
		gameTicks = gameState->gameTime.getTicks();
		// this->difficulty = gameState->difficulty; ?
	}
}
const UString &SaveMetadata::getName() const { return name; }
const UString &SaveMetadata::getFile() const { return file; }
const UString &SaveMetadata::getDifficulty() const { return difficulty; }
const SaveType &SaveMetadata::getType() const { return type; }
uint64_t SaveMetadata::getGameTicks() const { return gameTicks; }
} // namespace OpenApoc
<|endoftext|>


<commit_before>/***************************************************************************
 *   Copyright (C) 2006 by Till Adam <adam@kde.org>                        *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library General Public License as       *
 *   published by the Free Software Foundation; either version 2 of the    *
 *   License, or (at your option) any later version.                       *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this program; if not, write to the                 *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.             *
 ***************************************************************************/
#include "akonadiconnection.h"

#include <QtCore/QDebug>
#include <QtCore/QEventLoop>
#include <QtCore/QLatin1String>

#include "storage/datastore.h"
#include "handler.h"
#include "response.h"
#include "tracer.h"

#include "libs/imapparser_p.h"
#include "imapstreamparser.h"
#include "shared/akdebug.h"
#include "shared/akcrash.h"

#include <assert.h>

using namespace Akonadi;

AkonadiConnection::AkonadiConnection( quintptr socketDescriptor, QObject *parent )
    : QThread(parent)
    , m_socketDescriptor(socketDescriptor)
    , m_socket( 0 )
    , m_currentHandler( 0 )
    , m_connectionState( NonAuthenticated )
    , m_backend( 0 )
    , m_selectedConnection( 0 )
{
    m_identifier.sprintf( "%p", static_cast<void*>( this ) );
    Tracer::self()->beginConnection( m_identifier, QString() );
    m_parser = new ImapParser;
    m_streamParser = 0;
}

DataStore * Akonadi::AkonadiConnection::storageBackend()
{
    if ( !m_backend )
      m_backend = DataStore::self();
    return m_backend;
}

AkonadiConnection::~AkonadiConnection()
{
    Tracer::self()->endConnection( m_identifier, QString() );

    delete m_parser;
}

void AkonadiConnection::run()
{
    m_socket = new QLocalSocket();

    if ( !m_socket->setSocketDescriptor( m_socketDescriptor ) ) {
        qWarning() << "AkonadiConnection(" << m_identifier
                 << ")::run: failed to set socket descriptor: "
                 << m_socket->error() << "(" << m_socket->errorString() << ")";
        return;
    }

    /* Start a local event loop and start processing incoming data. Whenever
     * a full command has been read, it is delegated to the responsible
     * handler and processed by that. If that command needs to do something
     * asynchronous such as ask the db for data, it returns and the input
     * queue can continue to be processed. Whenever there is something to
     * be sent back to the user it is queued in the form of a Response object.
     * All this is meant to make it possible to process large incoming or
     * outgoing data transfers in a streaming manner, without having to
     * hold them in memory 'en gros'. */

    connect( m_socket, SIGNAL( readyRead() ),
             this, SLOT( slotNewData() ), Qt::DirectConnection );
    connect( m_socket, SIGNAL( disconnected() ),
             this, SLOT( slotDisconnected() ), Qt::DirectConnection );

    writeOut( "* OK Akonadi Almost IMAP Server [PROTOCOL 7]");

    exec();
    delete m_socket;
    m_socket = 0;
}

void AkonadiConnection::slotDisconnected()
{
    quit();
}

void AkonadiConnection::slotNewData()
{
  QByteArray prependBuffer;

  while ( m_socket->bytesAvailable() > 0 || !prependBuffer.isEmpty() ) {
    if ( !m_streamParser && m_parser->continuationSize() > 1 ) {
      const QByteArray data = m_socket->read( qMin( m_socket->bytesAvailable(), m_parser->continuationSize() - 1 ) );
      Tracer::self()->connectionInput( m_identifier, QLatin1String("[binary data]") );
      m_parser->parseBlock( data );
    } else if ( m_socket->canReadLine() || !prependBuffer.isEmpty() ) {
      QByteArray line = prependBuffer;
      if ( m_socket->canReadLine() )
        line.append( m_socket->readLine() );
      prependBuffer.clear();

      Tracer::self()->connectionInput( m_identifier, QString::fromUtf8( line ) );

      if ( m_parser->parseNextLine( line ) ) {
        // parse the command
          QByteArray command;
          ImapParser::parseString( m_parser->data(), command );

          m_currentHandler = findHandlerForCommand( command );
          m_currentHandler->setTag( m_parser->tag() );
          qDebug() << "Handler found, data is fully available: " << m_currentHandler->metaObject()->className();
          assert( m_currentHandler );
          connect( m_currentHandler, SIGNAL( responseAvailable( const Response & ) ),
                   this, SLOT( slotResponseAvailable( const Response & ) ), Qt::DirectConnection );
          connect( m_currentHandler, SIGNAL( connectionStateChange( ConnectionState ) ),
                   this, SLOT( slotConnectionStateChange( ConnectionState ) ),
                               Qt::DirectConnection );
          try {
          // FIXME: remove the tag, it's only there for backward compatibility with the handlers!
            if ( m_currentHandler->handleLine( m_parser->tag() + ' ' + m_parser->data() ) )
              m_currentHandler = 0;
          } catch ( const Akonadi::HandlerException &e ) {
            m_currentHandler->failureResponse( e.what() );
            m_currentHandler->deleteLater();
          } catch ( const Akonadi::Exception &e ) {
            m_currentHandler->failureResponse( QString::fromLatin1( e.type() )
                + QLatin1String( ": " ) + QString::fromLatin1( e.what()  ) );
            m_currentHandler->deleteLater();
          } catch ( ... ) {
            akError() << "Unknown exception caught: " << akBacktrace();
            delete m_currentHandler;
            m_currentHandler = 0;
          }
          m_parser->reset();

      } else {
        if ( !m_currentHandler ) { //find a handler, even if the full data is not yet available
          QByteArray command;
          ImapParser::parseString( m_parser->data(), command );

          m_currentHandler = findHandlerForCommand( command );
          assert( m_currentHandler );
          m_currentHandler->setTag( m_parser->tag() );
          connect( m_currentHandler, SIGNAL( responseAvailable( const Response & ) ),
                   this, SLOT( slotResponseAvailable( const Response & ) ), Qt::DirectConnection );
          connect( m_currentHandler, SIGNAL( connectionStateChange( ConnectionState ) ),
                   this, SLOT( slotConnectionStateChange( ConnectionState ) ),
                               Qt::DirectConnection );
          prependBuffer.clear();
          if ( m_currentHandler->supportsStreamParser() )
          {
             qDebug() << "Handler found, data is NOT fully available: " << m_currentHandler->metaObject()->className();
             m_streamParser = new ImapStreamParser( m_socket );
             m_streamParser->setData(m_parser->data());
             m_currentHandler->setStreamParser( m_streamParser );
             try {
              if ( m_currentHandler->parseStream() ) {
                prependBuffer = m_streamParser->readRemainingData();
              } else {
                prependBuffer = m_parser->tag() + ' ' + m_parser->data();
              }
             } catch ( const Akonadi::HandlerException &e ) {
               m_currentHandler->failureResponse( e.what() );
             }catch ( const Akonadi::Exception &e ) {
               m_currentHandler->failureResponse( QString::fromLatin1( e.type() )
                   + QLatin1String( ": " ) + QString::fromLatin1( e.what()  ) );
             } catch ( ... ) {
               akError() << "Unknown exception caught: " << akBacktrace();
             }
             delete m_streamParser;
             m_streamParser = 0;
             m_parser->reset();
          }
          delete m_currentHandler;
          m_currentHandler = 0;
        }
        if ( m_parser->continuationStarted() ) {
          Response response;
          response.setContinuation();
          response.setString( "Ready for literal data (expecting " +
              QByteArray::number( m_parser->continuationSize() ) + " bytes)" );
          slotResponseAvailable( response );
        }
      }
    } else {
      break; // nothing we can do for now with the available data
    }
  }
}

void AkonadiConnection::writeOut( const QByteArray &data )
{
    QByteArray block = data + "\r\n";
    m_socket->write(block);
    m_socket->waitForBytesWritten();

    Tracer::self()->connectionOutput( m_identifier, QString::fromUtf8( block ) );
}

Handler * AkonadiConnection::findHandlerForCommand( const QByteArray & command )
{
    Handler * handler = Handler::findHandlerForCommandAlwaysAllowed( command );
    if ( handler ) return handler;

    switch ( m_connectionState ) {
        case NonAuthenticated:
            handler =  Handler::findHandlerForCommandNonAuthenticated( command );            break;
        case Authenticated:
            handler =  Handler::findHandlerForCommandAuthenticated( command );
            break;
        case Selected:
            break;
        case LoggingOut:
            break;
    }
    // we didn't have a handler for this, let the default one do its thing
    if ( !handler ) handler = new Handler();
    handler->setConnection( this );
    return handler;
}

void AkonadiConnection::slotResponseAvailable( const Response& response )
{
    // FIXME handle reentrancy in the presence of continuation. Something like:
    // "if continuation pending, queue responses, once continuation is done, replay them"
    writeOut( response.asString() );
}

void AkonadiConnection::slotConnectionStateChange( ConnectionState state )
{
    if ( state == m_connectionState ) return;
    m_connectionState = state;
    switch ( m_connectionState ) {
        case NonAuthenticated:
            assert( 0 ); // can't happen, it's only the initial state, we can't go back to it
            break;
        case Authenticated:
            break;
        case Selected:
            break;
        case LoggingOut:
            m_socket->disconnectFromServer();
            break;
    }
}

qint64 Akonadi::AkonadiConnection::selectedCollectionId( ) const
{
    return m_selectedConnection;
}

void Akonadi::AkonadiConnection::setSelectedCollection( qint64 collection )
{
    m_selectedConnection = collection;
}

const Collection Akonadi::AkonadiConnection::selectedCollection()
{
  return Collection::retrieveById( selectedCollectionId() );
}

void Akonadi::AkonadiConnection::addStatusMessage( const QByteArray& msg )
{
    m_statusMessageQueue.append( msg );
}

void Akonadi::AkonadiConnection::flushStatusMessageQueue()
{
    for ( int i = 0; i < m_statusMessageQueue.count(); ++i ) {
      Response response;
      response.setUntagged();
      response.setString( m_statusMessageQueue[ i ] );

      slotResponseAvailable( response );
    }

    m_statusMessageQueue.clear();
}

void AkonadiConnection::setSessionId(const QByteArray &id)
{
  m_sessionId = id;
  storageBackend()->setSessionId( id );
  storageBackend()->notificationCollector()->setSessionId( id );
}

QByteArray AkonadiConnection::sessionId() const
{
  return m_sessionId;
}

#include "akonadiconnection.moc"
<commit_msg>Add the posibility to use the streaming parser for handlers (see the comment in the commit).<commit_after>/***************************************************************************
 *   Copyright (C) 2006 by Till Adam <adam@kde.org>                        *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library General Public License as       *
 *   published by the Free Software Foundation; either version 2 of the    *
 *   License, or (at your option) any later version.                       *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this program; if not, write to the                 *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.             *
 ***************************************************************************/
#include "akonadiconnection.h"

#include <QtCore/QDebug>
#include <QtCore/QEventLoop>
#include <QtCore/QLatin1String>

#include "storage/datastore.h"
#include "handler.h"
#include "response.h"
#include "tracer.h"

#include "libs/imapparser_p.h"
#include "imapstreamparser.h"
#include "shared/akdebug.h"
#include "shared/akcrash.h"

#include <assert.h>

using namespace Akonadi;

AkonadiConnection::AkonadiConnection( quintptr socketDescriptor, QObject *parent )
    : QThread(parent)
    , m_socketDescriptor(socketDescriptor)
    , m_socket( 0 )
    , m_currentHandler( 0 )
    , m_connectionState( NonAuthenticated )
    , m_backend( 0 )
    , m_selectedConnection( 0 )
{
    m_identifier.sprintf( "%p", static_cast<void*>( this ) );
    Tracer::self()->beginConnection( m_identifier, QString() );
    m_parser = new ImapParser;
    m_streamParser = 0;
}

DataStore * Akonadi::AkonadiConnection::storageBackend()
{
    if ( !m_backend )
      m_backend = DataStore::self();
    return m_backend;
}

AkonadiConnection::~AkonadiConnection()
{
    Tracer::self()->endConnection( m_identifier, QString() );

    delete m_parser;
}

void AkonadiConnection::run()
{
    m_socket = new QLocalSocket();

    if ( !m_socket->setSocketDescriptor( m_socketDescriptor ) ) {
        qWarning() << "AkonadiConnection(" << m_identifier
                 << ")::run: failed to set socket descriptor: "
                 << m_socket->error() << "(" << m_socket->errorString() << ")";
        return;
    }

    /* Start a local event loop and start processing incoming data. Whenever
     * a full command has been read, it is delegated to the responsible
     * handler and processed by that. If that command needs to do something
     * asynchronous such as ask the db for data, it returns and the input
     * queue can continue to be processed. Whenever there is something to
     * be sent back to the user it is queued in the form of a Response object.
     * All this is meant to make it possible to process large incoming or
     * outgoing data transfers in a streaming manner, without having to
     * hold them in memory 'en gros'. */

    connect( m_socket, SIGNAL( readyRead() ),
             this, SLOT( slotNewData() ), Qt::DirectConnection );
    connect( m_socket, SIGNAL( disconnected() ),
             this, SLOT( slotDisconnected() ), Qt::DirectConnection );

    writeOut( "* OK Akonadi Almost IMAP Server [PROTOCOL 7]");

    exec();
    delete m_socket;
    m_socket = 0;
}

void AkonadiConnection::slotDisconnected()
{
    quit();
}

void AkonadiConnection::slotNewData()
{
  QByteArray prependBuffer;

  while ( m_socket->bytesAvailable() > 0 || !prependBuffer.isEmpty() ) {
    if ( !m_streamParser && m_parser->continuationSize() > 1 ) {
      const QByteArray data = m_socket->read( qMin( m_socket->bytesAvailable(), m_parser->continuationSize() - 1 ) );
      Tracer::self()->connectionInput( m_identifier, QLatin1String("[binary data]") );
      m_parser->parseBlock( data );
    } else if ( m_socket->canReadLine() || !prependBuffer.isEmpty() ) {
      QByteArray line = prependBuffer;
      if ( m_socket->canReadLine() )
        line.append( m_socket->readLine() );
      prependBuffer.clear();

      Tracer::self()->connectionInput( m_identifier, QString::fromUtf8( line ) );

      if ( m_parser->parseNextLine( line ) ) {
        // parse the command
          QByteArray command;
          ImapParser::parseString( m_parser->data(), command );

          m_currentHandler = findHandlerForCommand( command );
          m_currentHandler->setTag( m_parser->tag() );
          qDebug() << "Handler found, data is fully available: " << m_currentHandler->metaObject()->className();
          assert( m_currentHandler );
          connect( m_currentHandler, SIGNAL( responseAvailable( const Response & ) ),
                   this, SLOT( slotResponseAvailable( const Response & ) ), Qt::DirectConnection );
          connect( m_currentHandler, SIGNAL( connectionStateChange( ConnectionState ) ),
                   this, SLOT( slotConnectionStateChange( ConnectionState ) ),
                               Qt::DirectConnection );
          try {
            //Rempve 0 && to enable the streaming parser usage
            if ( 0 && m_currentHandler->supportsStreamParser() )
            {
              qDebug() << "Using the streaming parser";
              m_streamParser = new ImapStreamParser( m_socket );
              m_streamParser->setData(m_parser->data());
              m_currentHandler->setStreamParser( m_streamParser );
              m_currentHandler->parseStream();
            } else {
          // FIXME: remove the tag, it's only there for backward compatibility with the handlers!
              if ( m_currentHandler->handleLine( m_parser->tag() + ' ' + m_parser->data() ) )
                m_currentHandler = 0;
            }
          } catch ( const Akonadi::HandlerException &e ) {
            m_currentHandler->failureResponse( e.what() );
            m_currentHandler->deleteLater();
          } catch ( const Akonadi::Exception &e ) {
            m_currentHandler->failureResponse( QString::fromLatin1( e.type() )
                + QLatin1String( ": " ) + QString::fromLatin1( e.what()  ) );
            m_currentHandler->deleteLater();
          } catch ( ... ) {
            akError() << "Unknown exception caught: " << akBacktrace();
            delete m_currentHandler;
            m_currentHandler = 0;
          }
          m_parser->reset();

      } else {
        if ( !m_currentHandler ) { //find a handler, even if the full data is not yet available
          QByteArray command;
          ImapParser::parseString( m_parser->data(), command );

          m_currentHandler = findHandlerForCommand( command );
          assert( m_currentHandler );
          m_currentHandler->setTag( m_parser->tag() );
          connect( m_currentHandler, SIGNAL( responseAvailable( const Response & ) ),
                   this, SLOT( slotResponseAvailable( const Response & ) ), Qt::DirectConnection );
          connect( m_currentHandler, SIGNAL( connectionStateChange( ConnectionState ) ),
                   this, SLOT( slotConnectionStateChange( ConnectionState ) ),
                               Qt::DirectConnection );
          prependBuffer.clear();
          if ( m_currentHandler->supportsStreamParser() )
          {
             qDebug() << "Handler found, data is NOT fully available: " << m_currentHandler->metaObject()->className();
             m_streamParser = new ImapStreamParser( m_socket );
             m_streamParser->setData(m_parser->data());
             m_currentHandler->setStreamParser( m_streamParser );
             try {
              if ( m_currentHandler->parseStream() ) {
                prependBuffer = m_streamParser->readRemainingData();
              } else {
                prependBuffer = m_parser->tag() + ' ' + m_parser->data();
              }
             } catch ( const Akonadi::HandlerException &e ) {
               m_currentHandler->failureResponse( e.what() );
             }catch ( const Akonadi::Exception &e ) {
               m_currentHandler->failureResponse( QString::fromLatin1( e.type() )
                   + QLatin1String( ": " ) + QString::fromLatin1( e.what()  ) );
             } catch ( ... ) {
               akError() << "Unknown exception caught: " << akBacktrace();
             }
             delete m_streamParser;
             m_streamParser = 0;
             m_parser->reset();
          }
          delete m_currentHandler;
          m_currentHandler = 0;
        }
        if ( m_parser->continuationStarted() ) {
          Response response;
          response.setContinuation();
          response.setString( "Ready for literal data (expecting " +
              QByteArray::number( m_parser->continuationSize() ) + " bytes)" );
          slotResponseAvailable( response );
        }
      }
    } else {
      break; // nothing we can do for now with the available data
    }
  }
}

void AkonadiConnection::writeOut( const QByteArray &data )
{
    QByteArray block = data + "\r\n";
    m_socket->write(block);
    m_socket->waitForBytesWritten();

    Tracer::self()->connectionOutput( m_identifier, QString::fromUtf8( block ) );
}

Handler * AkonadiConnection::findHandlerForCommand( const QByteArray & command )
{
    Handler * handler = Handler::findHandlerForCommandAlwaysAllowed( command );
    if ( handler ) return handler;

    switch ( m_connectionState ) {
        case NonAuthenticated:
            handler =  Handler::findHandlerForCommandNonAuthenticated( command );            break;
        case Authenticated:
            handler =  Handler::findHandlerForCommandAuthenticated( command );
            break;
        case Selected:
            break;
        case LoggingOut:
            break;
    }
    // we didn't have a handler for this, let the default one do its thing
    if ( !handler ) handler = new Handler();
    handler->setConnection( this );
    return handler;
}

void AkonadiConnection::slotResponseAvailable( const Response& response )
{
    // FIXME handle reentrancy in the presence of continuation. Something like:
    // "if continuation pending, queue responses, once continuation is done, replay them"
    writeOut( response.asString() );
}

void AkonadiConnection::slotConnectionStateChange( ConnectionState state )
{
    if ( state == m_connectionState ) return;
    m_connectionState = state;
    switch ( m_connectionState ) {
        case NonAuthenticated:
            assert( 0 ); // can't happen, it's only the initial state, we can't go back to it
            break;
        case Authenticated:
            break;
        case Selected:
            break;
        case LoggingOut:
            m_socket->disconnectFromServer();
            break;
    }
}

qint64 Akonadi::AkonadiConnection::selectedCollectionId( ) const
{
    return m_selectedConnection;
}

void Akonadi::AkonadiConnection::setSelectedCollection( qint64 collection )
{
    m_selectedConnection = collection;
}

const Collection Akonadi::AkonadiConnection::selectedCollection()
{
  return Collection::retrieveById( selectedCollectionId() );
}

void Akonadi::AkonadiConnection::addStatusMessage( const QByteArray& msg )
{
    m_statusMessageQueue.append( msg );
}

void Akonadi::AkonadiConnection::flushStatusMessageQueue()
{
    for ( int i = 0; i < m_statusMessageQueue.count(); ++i ) {
      Response response;
      response.setUntagged();
      response.setString( m_statusMessageQueue[ i ] );

      slotResponseAvailable( response );
    }

    m_statusMessageQueue.clear();
}

void AkonadiConnection::setSessionId(const QByteArray &id)
{
  m_sessionId = id;
  storageBackend()->setSessionId( id );
  storageBackend()->notificationCollector()->setSessionId( id );
}

QByteArray AkonadiConnection::sessionId() const
{
  return m_sessionId;
}

#include "akonadiconnection.moc"
<|endoftext|>


<commit_before>/*************************************************************************
 *
 *  OpenOffice.org - a multi-platform office productivity suite
 *
 *  $RCSfile: TableDesignControl.cxx,v $
 *
 *  $Revision: 1.9 $
 *
 *  last change: $Author: hr $ $Date: 2006-06-20 03:32:45 $
 *
 *  The Contents of this file are made available subject to
 *  the terms of GNU Lesser General Public License Version 2.1.
 *
 *
 *    GNU Lesser General Public License Version 2.1
 *    =============================================
 *    Copyright 2005 by Sun Microsystems, Inc.
 *    901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License version 2.1, as published by the Free Software Foundation.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with this library; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *    MA  02111-1307  USA
 *
 ************************************************************************/

#ifndef DBAUI_TABLEDESIGNCONTROL_HXX
#include "TableDesignControl.hxx"
#endif
#ifndef _DBU_TBL_HRC_
#include "dbu_tbl.hrc"
#endif
#ifndef _DBAUI_MODULE_DBU_HXX_
#include "moduledbu.hxx"
#endif
#ifndef DBAUI_TABLEDESIGNVIEW_HXX
#include "TableDesignView.hxx"
#endif
#ifndef DBUI_TABLECONTROLLER_HXX
#include "TableController.hxx"
#endif
#ifndef DBACCESS_UI_BROWSER_ID_HXX
#include "browserids.hxx"
#endif
#ifndef _COM_SUN_STAR_UTIL_URL_HPP_
#include <com/sun/star/util/URL.hpp>
#endif
#ifndef _COM_SUN_STAR_BEANS_PROPERTYVALUE_HPP_
#include <com/sun/star/beans/PropertyValue.hpp>
#endif
#ifndef _DBA_DBACCESS_HELPID_HRC_
#include "dbaccess_helpid.hrc"
#endif

using namespace ::dbaui;
using namespace ::svt;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::util;
//--- Defines
#define HANDLE_ID 0

DBG_NAME(OTableRowView)
//------------------------------------------------------------------------
OTableRowView::OTableRowView(Window* pParent)
    :EditBrowseBox(pParent, ModuleRes(RID_DB_TAB_EDITOR),EBBF_NONE,
                    BROWSER_COLUMNSELECTION | BROWSER_MULTISELECTION | BROWSER_AUTOSIZE_LASTCOL |
                    BROWSER_KEEPSELECTION | BROWSER_HLINESFULL | BROWSER_VLINESFULL)
    ,m_nDataPos(-1)
    ,m_nCurrentPos(-1)
    ,m_nCurUndoActId(0)
    ,m_bCurrentModified(FALSE)
    ,m_bUpdatable(FALSE)
    ,m_bClipboardFilled(FALSE)
{
    DBG_CTOR(OTableRowView,NULL);

}

//------------------------------------------------------------------------
OTableRowView::~OTableRowView()
{

    DBG_DTOR(OTableRowView,NULL);
}

//------------------------------------------------------------------------
void OTableRowView::Init()
{
    EditBrowseBox::Init();

//  SetMapMode( MapMode(MAP_TWIP) );
//  GetDataWindow().SetMapMode( GetMapMode() );

    Font aFont( GetDataWindow().GetFont() );
    aFont.SetWeight( WEIGHT_NORMAL );
    GetDataWindow().SetFont( aFont );

    // Font fuer die Ueberschriften auf Light setzen
    aFont = GetFont();
    aFont.SetWeight( WEIGHT_LIGHT );
    SetFont(aFont);

    // HandleColumn, fuer maximal fuenf Ziffern einrichten
    InsertHandleColumn(static_cast<USHORT>(GetTextWidth('0') * 4)/*, TRUE */);

    BrowserMode nMode = BROWSER_COLUMNSELECTION | BROWSER_MULTISELECTION | BROWSER_KEEPSELECTION |
                        BROWSER_HLINESFULL | BROWSER_VLINESFULL | BROWSER_AUTOSIZE_LASTCOL;
    if (IsUpdatable())
        nMode |= BROWSER_HIDECURSOR;

    SetMode(nMode);
}

//------------------------------------------------------------------------
void OTableRowView::KeyInput( const KeyEvent& rEvt )
{
    if (IsDeleteAllowed(0))
    {
        if (rEvt.GetKeyCode().GetCode() == KEY_DELETE &&    // Delete rows
            !rEvt.GetKeyCode().IsShift() &&
            !rEvt.GetKeyCode().IsMod1())
        {
            DeleteRows();
            return;
        }
        if( rEvt.GetKeyCode().GetCode() == KEY_F2 )
        {
            ::com::sun::star::util::URL aUrl;
            aUrl.Complete =::rtl::OUString::createFromAscii(".uno:DSBEditDoc");
            GetView()->getController()->dispatch( aUrl,Sequence< PropertyValue >() );
        }
    }
    EditBrowseBox::KeyInput(rEvt);
}

//------------------------------------------------------------------------
void OTableRowView::SetUpdatable( BOOL bUpdate )
{
    m_bUpdatable = bUpdate;

}

//------------------------------------------------------------------------
void OTableRowView::Command(const CommandEvent& rEvt)
{

    switch (rEvt.GetCommand())
    {
        case COMMAND_CONTEXTMENU:
        {
            if (!rEvt.IsMouseEvent())
            {
                EditBrowseBox::Command(rEvt);
                return;
            }

            USHORT nColId = GetColumnAtXPosPixel(rEvt.GetMousePosPixel().X());
            long   nRow = GetRowAtYPosPixel(rEvt.GetMousePosPixel().Y());

            if ( nColId == HANDLE_ID )
            {
                PopupMenu aContextMenu(ModuleRes(RID_TABLEDESIGNROWPOPUPMENU));
                long nSelectRowCount = GetSelectRowCount();
                aContextMenu.EnableItem( SID_CUT, nSelectRowCount != 0);
                aContextMenu.EnableItem( SID_COPY, nSelectRowCount  != 0);
                aContextMenu.EnableItem( SID_PASTE, m_bClipboardFilled );
                aContextMenu.EnableItem( SID_DELETE, IsUpdatable() && nSelectRowCount != 0 );
                switch (aContextMenu.Execute(this, rEvt.GetMousePosPixel()))
                {
                    case SID_CUT:
                        cut();
                        break;
                    case SID_COPY:
                        copy();
                        break;
                    case SID_PASTE:
                        Paste( nRow );
                        SetNoSelection();
                        GoToRow( nRow );
                        SeekRow( nRow );
                        break;

                    case SID_DELETE:
                        DeleteRows();
                        break;
                    case SID_TABLEDESIGN_INSERTROWS:
                        InsertNewRows( nRow );
                        SetNoSelection();
                        GoToRow( nRow );
                        SeekRow( nRow );
                        break;
                    default:
                        break;
                }
            }

        }
        default:
            EditBrowseBox::Command(rEvt);
    }

}

//------------------------------------------------------------------------------
void OTableRowView::cut()
{
    CopyRows();
    DeleteRows();
}

//------------------------------------------------------------------------------
void OTableRowView::copy()
{
    CopyRows();
}

//------------------------------------------------------------------------------
void OTableRowView::paste()
{
    OSL_ENSURE(0,"OTableRowView::Paste : (pseudo-) abstract method called !");
}

//------------------------------------------------------------------------------
void OTableRowView::Paste( long nRow )
{
    InsertRows( nRow );
}

//------------------------------------------------------------------------------
EditBrowseBox::RowStatus OTableRowView::GetRowStatus(long nRow) const
{
    if (nRow >= 0 && m_nDataPos == nRow)
        return CURRENT;
    else
        return CLEAN;
}

<commit_msg>INTEGRATION: CWS pchfix02 (1.9.50); FILE MERGED 2006/09/01 17:24:41 kaib 1.9.50.1: #i68856# Added header markers and pch files<commit_after>/*************************************************************************
 *
 *  OpenOffice.org - a multi-platform office productivity suite
 *
 *  $RCSfile: TableDesignControl.cxx,v $
 *
 *  $Revision: 1.10 $
 *
 *  last change: $Author: obo $ $Date: 2006-09-17 07:29:44 $
 *
 *  The Contents of this file are made available subject to
 *  the terms of GNU Lesser General Public License Version 2.1.
 *
 *
 *    GNU Lesser General Public License Version 2.1
 *    =============================================
 *    Copyright 2005 by Sun Microsystems, Inc.
 *    901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License version 2.1, as published by the Free Software Foundation.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with this library; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *    MA  02111-1307  USA
 *
 ************************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_dbaccess.hxx"

#ifndef DBAUI_TABLEDESIGNCONTROL_HXX
#include "TableDesignControl.hxx"
#endif
#ifndef _DBU_TBL_HRC_
#include "dbu_tbl.hrc"
#endif
#ifndef _DBAUI_MODULE_DBU_HXX_
#include "moduledbu.hxx"
#endif
#ifndef DBAUI_TABLEDESIGNVIEW_HXX
#include "TableDesignView.hxx"
#endif
#ifndef DBUI_TABLECONTROLLER_HXX
#include "TableController.hxx"
#endif
#ifndef DBACCESS_UI_BROWSER_ID_HXX
#include "browserids.hxx"
#endif
#ifndef _COM_SUN_STAR_UTIL_URL_HPP_
#include <com/sun/star/util/URL.hpp>
#endif
#ifndef _COM_SUN_STAR_BEANS_PROPERTYVALUE_HPP_
#include <com/sun/star/beans/PropertyValue.hpp>
#endif
#ifndef _DBA_DBACCESS_HELPID_HRC_
#include "dbaccess_helpid.hrc"
#endif

using namespace ::dbaui;
using namespace ::svt;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::util;
//--- Defines
#define HANDLE_ID 0

DBG_NAME(OTableRowView)
//------------------------------------------------------------------------
OTableRowView::OTableRowView(Window* pParent)
    :EditBrowseBox(pParent, ModuleRes(RID_DB_TAB_EDITOR),EBBF_NONE,
                    BROWSER_COLUMNSELECTION | BROWSER_MULTISELECTION | BROWSER_AUTOSIZE_LASTCOL |
                    BROWSER_KEEPSELECTION | BROWSER_HLINESFULL | BROWSER_VLINESFULL)
    ,m_nDataPos(-1)
    ,m_nCurrentPos(-1)
    ,m_nCurUndoActId(0)
    ,m_bCurrentModified(FALSE)
    ,m_bUpdatable(FALSE)
    ,m_bClipboardFilled(FALSE)
{
    DBG_CTOR(OTableRowView,NULL);

}

//------------------------------------------------------------------------
OTableRowView::~OTableRowView()
{

    DBG_DTOR(OTableRowView,NULL);
}

//------------------------------------------------------------------------
void OTableRowView::Init()
{
    EditBrowseBox::Init();

//  SetMapMode( MapMode(MAP_TWIP) );
//  GetDataWindow().SetMapMode( GetMapMode() );

    Font aFont( GetDataWindow().GetFont() );
    aFont.SetWeight( WEIGHT_NORMAL );
    GetDataWindow().SetFont( aFont );

    // Font fuer die Ueberschriften auf Light setzen
    aFont = GetFont();
    aFont.SetWeight( WEIGHT_LIGHT );
    SetFont(aFont);

    // HandleColumn, fuer maximal fuenf Ziffern einrichten
    InsertHandleColumn(static_cast<USHORT>(GetTextWidth('0') * 4)/*, TRUE */);

    BrowserMode nMode = BROWSER_COLUMNSELECTION | BROWSER_MULTISELECTION | BROWSER_KEEPSELECTION |
                        BROWSER_HLINESFULL | BROWSER_VLINESFULL | BROWSER_AUTOSIZE_LASTCOL;
    if (IsUpdatable())
        nMode |= BROWSER_HIDECURSOR;

    SetMode(nMode);
}

//------------------------------------------------------------------------
void OTableRowView::KeyInput( const KeyEvent& rEvt )
{
    if (IsDeleteAllowed(0))
    {
        if (rEvt.GetKeyCode().GetCode() == KEY_DELETE &&    // Delete rows
            !rEvt.GetKeyCode().IsShift() &&
            !rEvt.GetKeyCode().IsMod1())
        {
            DeleteRows();
            return;
        }
        if( rEvt.GetKeyCode().GetCode() == KEY_F2 )
        {
            ::com::sun::star::util::URL aUrl;
            aUrl.Complete =::rtl::OUString::createFromAscii(".uno:DSBEditDoc");
            GetView()->getController()->dispatch( aUrl,Sequence< PropertyValue >() );
        }
    }
    EditBrowseBox::KeyInput(rEvt);
}

//------------------------------------------------------------------------
void OTableRowView::SetUpdatable( BOOL bUpdate )
{
    m_bUpdatable = bUpdate;

}

//------------------------------------------------------------------------
void OTableRowView::Command(const CommandEvent& rEvt)
{

    switch (rEvt.GetCommand())
    {
        case COMMAND_CONTEXTMENU:
        {
            if (!rEvt.IsMouseEvent())
            {
                EditBrowseBox::Command(rEvt);
                return;
            }

            USHORT nColId = GetColumnAtXPosPixel(rEvt.GetMousePosPixel().X());
            long   nRow = GetRowAtYPosPixel(rEvt.GetMousePosPixel().Y());

            if ( nColId == HANDLE_ID )
            {
                PopupMenu aContextMenu(ModuleRes(RID_TABLEDESIGNROWPOPUPMENU));
                long nSelectRowCount = GetSelectRowCount();
                aContextMenu.EnableItem( SID_CUT, nSelectRowCount != 0);
                aContextMenu.EnableItem( SID_COPY, nSelectRowCount  != 0);
                aContextMenu.EnableItem( SID_PASTE, m_bClipboardFilled );
                aContextMenu.EnableItem( SID_DELETE, IsUpdatable() && nSelectRowCount != 0 );
                switch (aContextMenu.Execute(this, rEvt.GetMousePosPixel()))
                {
                    case SID_CUT:
                        cut();
                        break;
                    case SID_COPY:
                        copy();
                        break;
                    case SID_PASTE:
                        Paste( nRow );
                        SetNoSelection();
                        GoToRow( nRow );
                        SeekRow( nRow );
                        break;

                    case SID_DELETE:
                        DeleteRows();
                        break;
                    case SID_TABLEDESIGN_INSERTROWS:
                        InsertNewRows( nRow );
                        SetNoSelection();
                        GoToRow( nRow );
                        SeekRow( nRow );
                        break;
                    default:
                        break;
                }
            }

        }
        default:
            EditBrowseBox::Command(rEvt);
    }

}

//------------------------------------------------------------------------------
void OTableRowView::cut()
{
    CopyRows();
    DeleteRows();
}

//------------------------------------------------------------------------------
void OTableRowView::copy()
{
    CopyRows();
}

//------------------------------------------------------------------------------
void OTableRowView::paste()
{
    OSL_ENSURE(0,"OTableRowView::Paste : (pseudo-) abstract method called !");
}

//------------------------------------------------------------------------------
void OTableRowView::Paste( long nRow )
{
    InsertRows( nRow );
}

//------------------------------------------------------------------------------
EditBrowseBox::RowStatus OTableRowView::GetRowStatus(long nRow) const
{
    if (nRow >= 0 && m_nDataPos == nRow)
        return CURRENT;
    else
        return CLEAN;
}

<|endoftext|>


<commit_before>//------------------------------------------------------------------------------
// CLING - the C++ LLVM-based InterpreterG :)
// author:  Vassil Vassilev <vasil.georgiev.vasilev@cern.ch>
//
// This file is dual-licensed: you can choose to license it under the University
// of Illinois Open Source License or the GNU Lesser General Public License. See
// LICENSE.TXT for details.
//------------------------------------------------------------------------------

#include "cling/Interpreter/ClangInternalState.h"

#include "clang/AST/ASTContext.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/SourceManager.h"

#include "llvm/ADT/SmallString.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/Signals.h"

#include <cstdio>
#include <string>
#include <time.h>

#ifdef WIN32
#define popen _popen
#define pclose _pclose
#endif

using namespace clang;

namespace cling {

  ClangInternalState::ClangInternalState(ASTContext& AC, Preprocessor& PP,
                                         llvm::Module* M, const std::string& name)
    : m_ASTContext(AC), m_Preprocessor(PP), m_Module(M),
      m_DiffCommand("diff -u --text "), m_Name(name) {
    store();
  }

  ClangInternalState::~ClangInternalState() {
    // cleanup the temporary files:
    remove(m_LookupTablesFile.c_str());
    remove(m_IncludedFilesFile.c_str());
    remove(m_ASTFile.c_str());
    remove(m_LLVMModuleFile.c_str());
    remove(m_MacrosFile.c_str());
  }

  void ClangInternalState::store() {
    // Cannot use the stack (private copy ctor)
    llvm::OwningPtr<llvm::raw_fd_ostream> m_LookupTablesOS;
    llvm::OwningPtr<llvm::raw_fd_ostream> m_IncludedFilesOS;
    llvm::OwningPtr<llvm::raw_fd_ostream> m_ASTOS;
    llvm::OwningPtr<llvm::raw_fd_ostream> m_LLVMModuleOS;
    llvm::OwningPtr<llvm::raw_fd_ostream> m_MacrosOS;

    m_LookupTablesOS.reset(createOutputFile("lookup",
                                            &m_LookupTablesFile));
    m_IncludedFilesOS.reset(createOutputFile("included",
                                             &m_IncludedFilesFile));
    m_ASTOS.reset(createOutputFile("ast", &m_ASTFile));
    m_LLVMModuleOS.reset(createOutputFile("module", &m_LLVMModuleFile));
    m_MacrosOS.reset(createOutputFile("macros", &m_MacrosFile));

    printLookupTables(*m_LookupTablesOS.get(), m_ASTContext);
    printIncludedFiles(*m_IncludedFilesOS.get(),
                       m_ASTContext.getSourceManager());
    printAST(*m_ASTOS.get(), m_ASTContext);
    if (m_Module)
      printLLVMModule(*m_LLVMModuleOS.get(), *m_Module);
    printMacroDefinitions(*m_MacrosOS.get(), m_Preprocessor);
  }
  namespace {
    std::string getCurrentTimeAsString() {
      time_t rawtime;
      struct tm * timeinfo;
      char buffer [80];

      time (&rawtime);
      timeinfo = localtime (&rawtime);

      strftime (buffer, 80, "%I_%M_%S", timeinfo);
      return buffer;
    }
  }

  // Copied with modifications from CompilerInstance.cpp
  llvm::raw_fd_ostream*
  ClangInternalState::createOutputFile(llvm::StringRef OutFile,
                                       std::string *TempPathName/*=0*/,
                                       bool RemoveFileOnSignal/*=true*/) {
    llvm::OwningPtr<llvm::raw_fd_ostream> OS;
    std::string OSFile;
    llvm::SmallString<256> OutputPath;
    llvm::sys::path::system_temp_directory(/*erasedOnReboot*/false, OutputPath);

    // Only create the temporary if the parent directory exists (or create
    // missing directories is true) and we can actually write to OutPath,
    // otherwise we want to fail early.
    llvm::SmallString<256> TempPath(OutputPath);
    llvm::sys::fs::make_absolute(TempPath);
    assert(llvm::sys::fs::is_directory(TempPath.str()) && "Must be a folder.");
    // Create a temporary file.
    llvm::sys::path::append(TempPath, OutFile);
    TempPath += "-" + getCurrentTimeAsString();
    TempPath += "-%%%%%%%%";
    int fd;
    if (llvm::sys::fs::createUniqueFile(TempPath.str(), fd, TempPath)
        == llvm::errc::success) {
      OS.reset(new llvm::raw_fd_ostream(fd, /*shouldClose=*/true));
      OSFile = TempPath.str();
    }

    // Make sure the out stream file gets removed if we crash.
    if (RemoveFileOnSignal)
      llvm::sys::RemoveFileOnSignal(OSFile);

    if (TempPathName)
      *TempPathName = OSFile;

    return OS.take();
  }

  void ClangInternalState::compare(ClangInternalState& other) {
    std::string differences = "";
    // Ignore the builtins
    typedef llvm::SmallVector<const char*, 1024> Builtins;
    Builtins builtinNames;
    m_ASTContext.BuiltinInfo.GetBuiltinNames(builtinNames);
    for (Builtins::iterator I = builtinNames.begin();
         I != builtinNames.end();) {
      if (llvm::StringRef(*I).startswith("__builtin"))
        I = builtinNames.erase(I);
      else
        ++I;
    }

    builtinNames.push_back(".*__builtin.*");

    if (differentContent(m_LookupTablesFile, other.m_LookupTablesFile,
                         differences, &builtinNames)) {
      llvm::errs() << "Differences in the lookup tables\n";
      llvm::errs() << differences << "\n";
      differences = "";
    }

    if (differentContent(m_IncludedFilesFile, other.m_IncludedFilesFile,
                         differences)) {
      llvm::errs() << "Differences in the included files\n";
      llvm::errs() << differences << "\n";
      differences = "";
    }
    if (differentContent(m_ASTFile, other.m_ASTFile, differences)) {
      llvm::errs() << "Differences in the AST \n";
      llvm::errs() << differences << "\n";
      differences = "";
    }

    if (differentContent(m_LLVMModuleFile, other.m_LLVMModuleFile, differences)){
      llvm::errs() << "Differences in the llvm Module \n";
      llvm::errs() << differences << "\n";
      differences = "";
    }
    if (differentContent(m_MacrosFile, other.m_MacrosFile, differences)){
      llvm::errs() << "Differences in the Macro Definitions \n";
      llvm::errs() << differences << "\n";
      differences = "";
    }
  }

  bool ClangInternalState::differentContent(const std::string& file1,
                                            const std::string& file2,
                                            std::string& differences,
                const llvm::SmallVectorImpl<const char*>* ignores/*=0*/) const {
    std::string diffCall = m_DiffCommand;
    if (ignores) {
      for (size_t i = 0, e = ignores->size(); i < e; ++i) {
        diffCall += " --ignore-matching-lines=\".*";
        diffCall += (*ignores)[i];
        diffCall += ".*\"";
      }
    }

    FILE* pipe = popen((diffCall + " " + file1 + " " + file2).c_str() , "r");
    assert(pipe && "Error creating the pipe");
    assert(differences.empty() && "Must be empty");

    char buffer[128];
    while(!feof(pipe)) {
      if(fgets(buffer, 128, pipe) != NULL)
        differences += buffer;
    }
    pclose(pipe);

    if (!differences.empty())
      llvm::errs() << diffCall << " " << file1 << " " << file2 << "\n";

    return !differences.empty();
  }

  class DumpLookupTables : public RecursiveASTVisitor<DumpLookupTables> {
  private:
    llvm::raw_ostream& m_OS;
  public:
    DumpLookupTables(llvm::raw_ostream& OS) : m_OS(OS) { }
    bool VisitDecl(Decl* D) {
      if (DeclContext* DC = dyn_cast<DeclContext>(D))
        VisitDeclContext(DC);
      return true;
    }

    bool VisitDeclContext(DeclContext* DC) {
      // If the lookup is pending for building, force its creation.
      if (DC == DC->getPrimaryContext() && !DC->getLookupPtr())
        DC->buildLookup();
      DC->dumpLookups(m_OS);
      return true;
    }
  };

  void ClangInternalState::printLookupTables(llvm::raw_ostream& Out,
                                             ASTContext& C) {
    DumpLookupTables dumper(Out);
    dumper.TraverseDecl(C.getTranslationUnitDecl());
  }

  void ClangInternalState::printIncludedFiles(llvm::raw_ostream& Out,
                                              SourceManager& SM) {
    for (clang::SourceManager::fileinfo_iterator I = SM.fileinfo_begin(),
           E = SM.fileinfo_end(); I != E; ++I) {
      const clang::SrcMgr::ContentCache &C = *I->second;
      const clang::FileEntry *FE = C.OrigEntry;
      // Our error recovery purges the cache of the FileEntry, but keeps
      // the FileEntry's pointer so that if it was used by smb (like the
      // SourceManager) it wouldn't be dangling. In that case we shouldn't
      // print the FileName, because semantically it is not there.
      if (!FE->getSize() && !FE->getModificationTime())
        continue;
      std::string fileName(FE->getName());
      if (!(fileName.compare(0, 5, "/usr/") == 0 &&
            fileName.find("/bits/") != std::string::npos)) {
        Out << fileName << '\n';
      }
    }
  }

  void ClangInternalState::printAST(llvm::raw_ostream& Out, ASTContext& C) {
    TranslationUnitDecl* TU = C.getTranslationUnitDecl();
    unsigned Indentation = 0;
    bool PrintInstantiation = false;
    std::string ErrMsg;
    clang::PrintingPolicy policy = C.getPrintingPolicy();
    TU->print(Out, policy, Indentation, PrintInstantiation);
    // TODO: For future when we relpace the bump allocation with slab.
    //
    //Out << "Allocated memory: " << C.getAllocatedMemory();
    //Out << "Side table allocated memory: " << C.getSideTableAllocatedMemory();
    Out.flush();
  }

  void ClangInternalState::printLLVMModule(llvm::raw_ostream& Out,
                                           llvm::Module& M) {
    M.print(Out, /*AssemblyAnnotationWriter*/ 0);
  }

  void ClangInternalState::printMacroDefinitions(llvm::raw_ostream& Out,
                            clang::Preprocessor& PP) {
    PP.printMacros(Out);
  }
} // end namespace cling
<commit_msg>Blanks.<commit_after>//------------------------------------------------------------------------------
// CLING - the C++ LLVM-based InterpreterG :)
// author:  Vassil Vassilev <vasil.georgiev.vasilev@cern.ch>
//
// This file is dual-licensed: you can choose to license it under the University
// of Illinois Open Source License or the GNU Lesser General Public License. See
// LICENSE.TXT for details.
//------------------------------------------------------------------------------

#include "cling/Interpreter/ClangInternalState.h"

#include "clang/AST/ASTContext.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/SourceManager.h"

#include "llvm/ADT/SmallString.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/Signals.h"

#include <cstdio>
#include <string>
#include <time.h>

#ifdef WIN32
#define popen _popen
#define pclose _pclose
#endif

using namespace clang;

namespace cling {

  ClangInternalState::ClangInternalState(ASTContext& AC, Preprocessor& PP,
                                         llvm::Module* M, const std::string& name)
    : m_ASTContext(AC), m_Preprocessor(PP), m_Module(M),
      m_DiffCommand("diff -u --text "), m_Name(name) {
    store();
  }

  ClangInternalState::~ClangInternalState() {
    // cleanup the temporary files:
    remove(m_LookupTablesFile.c_str());
    remove(m_IncludedFilesFile.c_str());
    remove(m_ASTFile.c_str());
    remove(m_LLVMModuleFile.c_str());
    remove(m_MacrosFile.c_str());
  }

  void ClangInternalState::store() {
    // Cannot use the stack (private copy ctor)
    llvm::OwningPtr<llvm::raw_fd_ostream> m_LookupTablesOS;
    llvm::OwningPtr<llvm::raw_fd_ostream> m_IncludedFilesOS;
    llvm::OwningPtr<llvm::raw_fd_ostream> m_ASTOS;
    llvm::OwningPtr<llvm::raw_fd_ostream> m_LLVMModuleOS;
    llvm::OwningPtr<llvm::raw_fd_ostream> m_MacrosOS;

    m_LookupTablesOS.reset(createOutputFile("lookup",
                                            &m_LookupTablesFile));
    m_IncludedFilesOS.reset(createOutputFile("included",
                                             &m_IncludedFilesFile));
    m_ASTOS.reset(createOutputFile("ast", &m_ASTFile));
    m_LLVMModuleOS.reset(createOutputFile("module", &m_LLVMModuleFile));
    m_MacrosOS.reset(createOutputFile("macros", &m_MacrosFile));

    printLookupTables(*m_LookupTablesOS.get(), m_ASTContext);
    printIncludedFiles(*m_IncludedFilesOS.get(),
                       m_ASTContext.getSourceManager());
    printAST(*m_ASTOS.get(), m_ASTContext);
    if (m_Module)
      printLLVMModule(*m_LLVMModuleOS.get(), *m_Module);
    printMacroDefinitions(*m_MacrosOS.get(), m_Preprocessor);
  }
  namespace {
    std::string getCurrentTimeAsString() {
      time_t rawtime;
      struct tm * timeinfo;
      char buffer [80];

      time (&rawtime);
      timeinfo = localtime (&rawtime);

      strftime (buffer, 80, "%I_%M_%S", timeinfo);
      return buffer;
    }
  }

  // Copied with modifications from CompilerInstance.cpp
  llvm::raw_fd_ostream*
  ClangInternalState::createOutputFile(llvm::StringRef OutFile,
                                       std::string *TempPathName/*=0*/,
                                       bool RemoveFileOnSignal/*=true*/) {
    llvm::OwningPtr<llvm::raw_fd_ostream> OS;
    std::string OSFile;
    llvm::SmallString<256> OutputPath;
    llvm::sys::path::system_temp_directory(/*erasedOnReboot*/false, OutputPath);

    // Only create the temporary if the parent directory exists (or create
    // missing directories is true) and we can actually write to OutPath,
    // otherwise we want to fail early.
    llvm::SmallString<256> TempPath(OutputPath);
    llvm::sys::fs::make_absolute(TempPath);
    assert(llvm::sys::fs::is_directory(TempPath.str()) && "Must be a folder.");
    // Create a temporary file.
    llvm::sys::path::append(TempPath, OutFile);
    TempPath += "-" + getCurrentTimeAsString();
    TempPath += "-%%%%%%%%";
    int fd;
    if (llvm::sys::fs::createUniqueFile(TempPath.str(), fd, TempPath)
        == llvm::errc::success) {
      OS.reset(new llvm::raw_fd_ostream(fd, /*shouldClose=*/true));
      OSFile = TempPath.str();
    }

    // Make sure the out stream file gets removed if we crash.
    if (RemoveFileOnSignal)
      llvm::sys::RemoveFileOnSignal(OSFile);

    if (TempPathName)
      *TempPathName = OSFile;

    return OS.take();
  }

  void ClangInternalState::compare(ClangInternalState& other) {
    std::string differences = "";
    // Ignore the builtins
    typedef llvm::SmallVector<const char*, 1024> Builtins;
    Builtins builtinNames;
    m_ASTContext.BuiltinInfo.GetBuiltinNames(builtinNames);
    for (Builtins::iterator I = builtinNames.begin();
         I != builtinNames.end();) {
      if (llvm::StringRef(*I).startswith("__builtin"))
        I = builtinNames.erase(I);
      else
        ++I;
    }

    builtinNames.push_back(".*__builtin.*");

    if (differentContent(m_LookupTablesFile, other.m_LookupTablesFile,
                         differences, &builtinNames)) {
      llvm::errs() << "Differences in the lookup tables\n";
      llvm::errs() << differences << "\n";
      differences = "";
    }

    if (differentContent(m_IncludedFilesFile, other.m_IncludedFilesFile,
                         differences)) {
      llvm::errs() << "Differences in the included files\n";
      llvm::errs() << differences << "\n";
      differences = "";
    }
    if (differentContent(m_ASTFile, other.m_ASTFile, differences)) {
      llvm::errs() << "Differences in the AST \n";
      llvm::errs() << differences << "\n";
      differences = "";
    }

    if (differentContent(m_LLVMModuleFile, other.m_LLVMModuleFile, differences)){
      llvm::errs() << "Differences in the llvm Module \n";
      llvm::errs() << differences << "\n";
      differences = "";
    }
    if (differentContent(m_MacrosFile, other.m_MacrosFile, differences)){
      llvm::errs() << "Differences in the Macro Definitions \n";
      llvm::errs() << differences << "\n";
      differences = "";
    }
  }

  bool ClangInternalState::differentContent(const std::string& file1,
                                            const std::string& file2,
                                            std::string& differences,
                const llvm::SmallVectorImpl<const char*>* ignores/*=0*/) const {
    std::string diffCall = m_DiffCommand;
    if (ignores) {
      for (size_t i = 0, e = ignores->size(); i < e; ++i) {
        diffCall += " --ignore-matching-lines=\".*";
        diffCall += (*ignores)[i];
        diffCall += ".*\"";
      }
    }

    FILE* pipe = popen((diffCall + " " + file1 + " " + file2).c_str() , "r");
    assert(pipe && "Error creating the pipe");
    assert(differences.empty() && "Must be empty");

    char buffer[128];
    while(!feof(pipe)) {
      if(fgets(buffer, 128, pipe) != NULL)
        differences += buffer;
    }
    pclose(pipe);

    if (!differences.empty())
      llvm::errs() << diffCall << " " << file1 << " " << file2 << "\n";

    return !differences.empty();
  }

  class DumpLookupTables : public RecursiveASTVisitor<DumpLookupTables> {
  private:
    llvm::raw_ostream& m_OS;
  public:
    DumpLookupTables(llvm::raw_ostream& OS) : m_OS(OS) { }
    bool VisitDecl(Decl* D) {
      if (DeclContext* DC = dyn_cast<DeclContext>(D))
        VisitDeclContext(DC);
      return true;
    }

    bool VisitDeclContext(DeclContext* DC) {
      // If the lookup is pending for building, force its creation.
      if (DC == DC->getPrimaryContext() && !DC->getLookupPtr())
        DC->buildLookup();
      DC->dumpLookups(m_OS);
      return true;
    }
  };

  void ClangInternalState::printLookupTables(llvm::raw_ostream& Out,
                                             ASTContext& C) {
    DumpLookupTables dumper(Out);
    dumper.TraverseDecl(C.getTranslationUnitDecl());
  }

  void ClangInternalState::printIncludedFiles(llvm::raw_ostream& Out,
                                              SourceManager& SM) {
    for (clang::SourceManager::fileinfo_iterator I = SM.fileinfo_begin(),
           E = SM.fileinfo_end(); I != E; ++I) {
      const clang::SrcMgr::ContentCache &C = *I->second;
      const clang::FileEntry *FE = C.OrigEntry;
      // Our error recovery purges the cache of the FileEntry, but keeps
      // the FileEntry's pointer so that if it was used by smb (like the
      // SourceManager) it wouldn't be dangling. In that case we shouldn't
      // print the FileName, because semantically it is not there.
      if (!FE->getSize() && !FE->getModificationTime())
        continue;
      std::string fileName(FE->getName());
      if (!(fileName.compare(0, 5, "/usr/") == 0 &&
            fileName.find("/bits/") != std::string::npos)) {
        Out << fileName << '\n';
      }
    }
  }

  void ClangInternalState::printAST(llvm::raw_ostream& Out, ASTContext& C) {
    TranslationUnitDecl* TU = C.getTranslationUnitDecl();
    unsigned Indentation = 0;
    bool PrintInstantiation = false;
    std::string ErrMsg;
    clang::PrintingPolicy policy = C.getPrintingPolicy();
    TU->print(Out, policy, Indentation, PrintInstantiation);
    // TODO: For future when we relpace the bump allocation with slab.
    //
    //Out << "Allocated memory: " << C.getAllocatedMemory();
    //Out << "Side table allocated memory: " << C.getSideTableAllocatedMemory();
    Out.flush();
  }

  void ClangInternalState::printLLVMModule(llvm::raw_ostream& Out,
                                           llvm::Module& M) {
    M.print(Out, /*AssemblyAnnotationWriter*/ 0);
  }

  void ClangInternalState::printMacroDefinitions(llvm::raw_ostream& Out,
                            clang::Preprocessor& PP) {
    PP.printMacros(Out);
  }
} // end namespace cling
<|endoftext|>


<commit_before>//------------------------------------------------------------------------------
// CLING - the C++ LLVM-based InterpreterG :)
//
// This file is dual-licensed: you can choose to license it under the University
// of Illinois Open Source License or the GNU Lesser General Public License. See
// LICENSE.TXT for details.
//------------------------------------------------------------------------------

// RUN: cat %s | %cling | FileCheck %s
int a = 12;
a // CHECK: (int) 12

const char* b = "b" // CHECK: (const char *) "b"
   const char* n = 0 // CHECK: (const char *) nullptr

struct C {int d;} E = {22};
E // CHECK: (struct C &) @0x{{[0-9A-Fa-f]{6,12}}}
E.d // CHECK: (int) 22

#include <string>
std::string s("xyz")
// CHECK: (std::string &) "xyz"

#include <limits.h>
class Outer {
public:
  struct Inner {
    enum E{
      A = INT_MAX,
      B = 2,
      C = 2,
      D = INT_MIN
    } ABC;
  };
};
Outer::Inner::C
// CHECK: (Outer::Inner::E::B) ? (Outer::Inner::E::C) : (int) 2
Outer::Inner::D
// CHECK: (Outer::Inner::E::D) : (int) -{{[0-9].*}}

// Put an enum on the global scope
enum E{ e1 = -12, e2, e3=33, e4, e5 = 33};
e2
// CHECK: (E::e2) : (int) -11
::e1
// CHECK: (E::e1) : (int) -12

// Arrays:
float farr[] = {0.,1.,2.,3.,4.,5.} // CHECK: (float [6]) { 0.00000f, 1.00000f, 2.00000f, 3.00000f, 4.00000f, 5.00000f }
std::string sarr[3] = {"A", "B", "C"} // CHECK: (std::string [3]) { "A", "B", "C" }

.rawInput
typedef void (*F_t)(int);
.rawInput
F_t fp = 0;
fp // CHECK: (F_t) Function @0x0
#include <stdio.h>
fp = (F_t)printf // CHECK: (F_t) Function @0x{{[0-9A-Fa-f]{6,12}}}
.q
<commit_msg>The "." is needed for lit to parse the regex; require 5*hex.<commit_after>//------------------------------------------------------------------------------
// CLING - the C++ LLVM-based InterpreterG :)
//
// This file is dual-licensed: you can choose to license it under the University
// of Illinois Open Source License or the GNU Lesser General Public License. See
// LICENSE.TXT for details.
//------------------------------------------------------------------------------

// RUN: cat %s | %cling | FileCheck %s
int a = 12;
a // CHECK: (int) 12

const char* b = "b" // CHECK: (const char *) "b"
   const char* n = 0 // CHECK: (const char *) nullptr

struct C {int d;} E = {22};
E // CHECK: (struct C &) @0x{{[0-9A-Fa-f]{5,12}.}}
E.d // CHECK: (int) 22

#include <string>
std::string s("xyz")
// CHECK: (std::string &) "xyz"

#include <limits.h>
class Outer {
public:
  struct Inner {
    enum E{
      A = INT_MAX,
      B = 2,
      C = 2,
      D = INT_MIN
    } ABC;
  };
};
Outer::Inner::C
// CHECK: (Outer::Inner::E::B) ? (Outer::Inner::E::C) : (int) 2
Outer::Inner::D
// CHECK: (Outer::Inner::E::D) : (int) -{{[0-9].*}}

// Put an enum on the global scope
enum E{ e1 = -12, e2, e3=33, e4, e5 = 33};
e2
// CHECK: (E::e2) : (int) -11
::e1
// CHECK: (E::e1) : (int) -12

// Arrays:
float farr[] = {0.,1.,2.,3.,4.,5.} // CHECK: (float [6]) { 0.00000f, 1.00000f, 2.00000f, 3.00000f, 4.00000f, 5.00000f }
std::string sarr[3] = {"A", "B", "C"} // CHECK: (std::string [3]) { "A", "B", "C" }

.rawInput
typedef void (*F_t)(int);
.rawInput
F_t fp = 0;
fp // CHECK: (F_t) Function @0x0
#include <stdio.h>
fp = (F_t)printf // CHECK: (F_t) Function @0x{{[0-9A-Fa-f]{5,12}.}}
.q
<|endoftext|>