<commit_before>#include "Http.hpp"
#include "CLog.hpp"
#include "version.hpp"

#include <boost/asio/system_timer.hpp>
#include <boost/spirit/include/qi.hpp>

Http::Http(std::string token) :
	m_SslContext(asio::ssl::context::sslv23),
	m_Token(token),
	m_NetworkThreadRunning(true),
	m_NetworkThread(std::bind(&Http::NetworkThreadFunc, this))
{
}

Http::~Http()
{
	m_NetworkThreadRunning = false;
	m_NetworkThread.join();
}

void Http::NetworkThreadFunc()
{
	std::unordered_map<std::string, TimePoint_t> path_ratelimit;
	unsigned int retry_counter = 0;
	unsigned int const MaxRetries = 3;
	bool skip_entry = false;

	if (!Connect())
		return;

	while (m_NetworkThreadRunning)
	{
		TimePoint_t current_time = std::chrono::system_clock::now();

		m_QueueMutex.lock();
		auto it = m_Queue.begin();
		while (it != m_Queue.end())
		{
			auto const &entry = *it;

			// check if we're rate-limited
			auto pr_it = path_ratelimit.find(entry->Request->target().to_string());
			if (pr_it != path_ratelimit.end())
			{
				// rate-limit for this path exists
				// are we still within the rate-limit timepoint?
				if (current_time < pr_it->second)
				{
					// yes, ignore this request for now
					it++;
					continue;
				}

				// no, delete rate-limit and go on
				path_ratelimit.erase(pr_it);
			}

			boost::system::error_code error_code;
			Response_t response;
			Streambuf_t sb;
			retry_counter = 0;
			skip_entry = false;
			do
			{
				bool do_reconnect = false;
				beast::http::write(*m_SslStream, *entry->Request, error_code);
				if (error_code)
				{
					CLog::Get()->Log(LogLevel::ERROR, "Error while sending HTTP {} request to '{}': {}",
						entry->Request->method_string().to_string(),
						entry->Request->target().to_string(),
						error_code.message());

					do_reconnect = true;
				}
				else
				{
					beast::http::read(*m_SslStream, sb, response, error_code);
					if (error_code)
					{
						CLog::Get()->Log(LogLevel::ERROR, "Error while retrieving HTTP {} response from '{}': {}",
							entry->Request->method_string().to_string(),
							entry->Request->target().to_string(),
							error_code.message());

						do_reconnect = true;
					}
				}

				if (do_reconnect)
				{
					if (retry_counter++ >= MaxRetries || !ReconnectRetry())
					{
						// we failed to reconnect, discard this request
						CLog::Get()->Log(LogLevel::WARNING, "Failed to send request, discarding");
						it = m_Queue.erase(it);
						skip_entry = true;
						break; // break out of do-while loop
					}
				}
			} while (error_code);
			if (skip_entry)
				continue; // continue queue loop

			auto it_r = response.find("X-RateLimit-Remaining");
			if (it_r != response.end())
			{
				if (it_r->value().compare("0") == 0)
				{
					// we're now officially rate-limited
					// the next call to this path will fail
					std::string limited_url = entry->Request->target().to_string();
					auto lit = path_ratelimit.find(limited_url);
					if (lit != path_ratelimit.end())
					{
						CLog::Get()->Log(LogLevel::ERROR,
							"Error while processing rate-limit: already rate-limited path '{}'",
							limited_url);

						// skip this request, but leave it in our list to retry later
						it++;
						continue;
					}

					it_r = response.find("X-RateLimit-Reset");
					if (it_r != response.end())
					{
						TimePoint_t current_time = std::chrono::system_clock::now();
						CLog::Get()->Log(LogLevel::DEBUG, "rate-limiting path {} until {} (current time: {})",
							limited_url,
							it_r->value().to_string(),
							std::chrono::duration_cast<std::chrono::seconds>(
								current_time.time_since_epoch()).count());

						string const &reset_time_str = it_r->value().to_string();
						long long reset_time_secs = 0;
						boost::spirit::qi::parse(reset_time_str.begin(), reset_time_str.end(),
							boost::spirit::qi::any_int_parser<long long>(),
							reset_time_secs);
						TimePoint_t reset_time{ std::chrono::seconds(reset_time_secs) };

						path_ratelimit.insert({ limited_url, reset_time });
					}
				}
			}

			m_QueueMutex.unlock(); // allow requests to be queued from within callback
			if (entry->Callback)
				entry->Callback(sb, response);
			m_QueueMutex.lock();

			it = m_Queue.erase(it);
		}
		m_QueueMutex.unlock();

		std::this_thread::sleep_for(std::chrono::milliseconds(10));
	}

	Disconnect();
}

bool Http::Connect()
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::Connect");

	// connect to REST API
	asio::ip::tcp::resolver r{ m_IoService };
	boost::system::error_code error;
	auto target = r.resolve({ "discordapp.com", "https" }, error);
	if (error)
	{
		CLog::Get()->Log(LogLevel::ERROR, "Can't resolve Discord API URL: {} ({})",
			error.message(), error.value());
		return false;
	}

	m_SslStream.reset(new SslStream_t(m_IoService, m_SslContext));
	asio::connect(m_SslStream->lowest_layer(), target, error);
	if (error)
	{
		CLog::Get()->Log(LogLevel::ERROR, "Can't connect to Discord API: {} ({})",
			error.message(), error.value());
		return false;
	}

	// SSL handshake
	m_SslStream->set_verify_mode(asio::ssl::verify_none, error);
	if (error)
	{
		CLog::Get()->Log(LogLevel::ERROR,
			"Can't configure SSL stream peer verification mode for Discord API: {} ({})",
			error.message(), error.value());
		return false;
	}

	m_SslStream->handshake(asio::ssl::stream_base::client, error);
	if (error)
	{
		CLog::Get()->Log(LogLevel::ERROR, "Can't establish secured connection to Discord API: {} ({})",
			error.message(), error.value());
		return false;
	}

	return true;
}

void Http::Disconnect()
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::Disconnect");

	boost::system::error_code error;
	m_SslStream->shutdown(error);
	if (error && error != boost::asio::error::eof)
	{
		CLog::Get()->Log(LogLevel::WARNING, "Error while shutting down SSL on HTTP connection: {} ({})",
			error.message(), error.value());
	}

	m_SslStream->lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, error);
	if (error && error != boost::asio::error::eof)
	{
		CLog::Get()->Log(LogLevel::WARNING, "Error while shutting down HTTP connection: {} ({})",
			error.message(), error.value());
	}

	m_SslStream->lowest_layer().close(error);
	if (error)
	{
		CLog::Get()->Log(LogLevel::WARNING, "Error while closing HTTP connection: {} ({})",
			error.message(), error.value());
	}
}

bool Http::ReconnectRetry()
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::ReconnectRetry");

	unsigned int reconnect_counter = 0;
	do
	{
		CLog::Get()->Log(LogLevel::INFO, "trying reconnect #{}...", reconnect_counter + 1);

		Disconnect();
		if (Connect())
		{
			CLog::Get()->Log(LogLevel::INFO, "reconnect succeeded, resending request");
			return true;
		}
		else
		{
			unsigned int seconds_to_wait = static_cast<unsigned int>(std::pow(2U, reconnect_counter));
			CLog::Get()->Log(LogLevel::WARNING, "reconnect failed, waiting {} seconds...", seconds_to_wait);
			std::this_thread::sleep_for(std::chrono::seconds(seconds_to_wait));
		}
	} while (++reconnect_counter < 3);

	CLog::Get()->Log(LogLevel::ERROR, "Could not reconnect to Discord");
	return false;
}

Http::SharedRequest_t Http::PrepareRequest(beast::http::verb const method,
	std::string const &url, std::string const &content)
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::PrepareRequest");

	auto req = std::make_shared<Request_t>();
	req->method(method);
	req->target("/api/v6" + url);
	req->version(11);
	req->insert("Host", "discordapp.com");
	req->insert("User-Agent", "DiscordBot (github.com/maddinat0r/samp-discord-connector, " PLUGIN_VERSION ")");
	if (!content.empty())
		req->insert("Content-Type", "application/json");
	req->insert("Authorization", "Bot " + m_Token);
	req->body() = content;

	req->prepare_payload();

	return req;
}

void Http::SendRequest(beast::http::verb const method, std::string const &url,
	std::string const &content, ResponseCallback_t &&callback)
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::SendRequest");

	SharedRequest_t req = PrepareRequest(method, url, content);

	std::lock_guard<std::mutex> lock_guard(m_QueueMutex);
	m_Queue.push_back(std::make_shared<QueueEntry>(req, std::move(callback)));
}

void Http::Get(std::string const &url, GetCallback_t &&callback)
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::Get");

	SendRequest(beast::http::verb::get, url, "", [callback](Streambuf_t &sb, Response_t &resp)
	{
		std::stringstream ss_body, ss_data;
		ss_body << beast::buffers(resp.body().data());
		ss_data << beast::buffers(sb.data());
		callback({ resp.result_int(), resp.reason().to_string(), ss_body.str(), ss_data.str() });
	});
}

void Http::Post(std::string const &url, std::string const &content)
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::Post");

	SendRequest(beast::http::verb::post, url, content, nullptr);
}
<commit_msg>add keep-alive http header on all requests<commit_after>#include "Http.hpp"
#include "CLog.hpp"
#include "version.hpp"

#include <boost/asio/system_timer.hpp>
#include <boost/spirit/include/qi.hpp>

Http::Http(std::string token) :
	m_SslContext(asio::ssl::context::sslv23),
	m_Token(token),
	m_NetworkThreadRunning(true),
	m_NetworkThread(std::bind(&Http::NetworkThreadFunc, this))
{
}

Http::~Http()
{
	m_NetworkThreadRunning = false;
	m_NetworkThread.join();
}

void Http::NetworkThreadFunc()
{
	std::unordered_map<std::string, TimePoint_t> path_ratelimit;
	unsigned int retry_counter = 0;
	unsigned int const MaxRetries = 3;
	bool skip_entry = false;

	if (!Connect())
		return;

	while (m_NetworkThreadRunning)
	{
		TimePoint_t current_time = std::chrono::system_clock::now();

		m_QueueMutex.lock();
		auto it = m_Queue.begin();
		while (it != m_Queue.end())
		{
			auto const &entry = *it;

			// check if we're rate-limited
			auto pr_it = path_ratelimit.find(entry->Request->target().to_string());
			if (pr_it != path_ratelimit.end())
			{
				// rate-limit for this path exists
				// are we still within the rate-limit timepoint?
				if (current_time < pr_it->second)
				{
					// yes, ignore this request for now
					it++;
					continue;
				}

				// no, delete rate-limit and go on
				path_ratelimit.erase(pr_it);
			}

			boost::system::error_code error_code;
			Response_t response;
			Streambuf_t sb;
			retry_counter = 0;
			skip_entry = false;
			do
			{
				bool do_reconnect = false;
				beast::http::write(*m_SslStream, *entry->Request, error_code);
				if (error_code)
				{
					CLog::Get()->Log(LogLevel::ERROR, "Error while sending HTTP {} request to '{}': {}",
						entry->Request->method_string().to_string(),
						entry->Request->target().to_string(),
						error_code.message());

					do_reconnect = true;
				}
				else
				{
					beast::http::read(*m_SslStream, sb, response, error_code);
					if (error_code)
					{
						CLog::Get()->Log(LogLevel::ERROR, "Error while retrieving HTTP {} response from '{}': {}",
							entry->Request->method_string().to_string(),
							entry->Request->target().to_string(),
							error_code.message());

						do_reconnect = true;
					}
				}

				if (do_reconnect)
				{
					if (retry_counter++ >= MaxRetries || !ReconnectRetry())
					{
						// we failed to reconnect, discard this request
						CLog::Get()->Log(LogLevel::WARNING, "Failed to send request, discarding");
						it = m_Queue.erase(it);
						skip_entry = true;
						break; // break out of do-while loop
					}
				}
			} while (error_code);
			if (skip_entry)
				continue; // continue queue loop

			auto it_r = response.find("X-RateLimit-Remaining");
			if (it_r != response.end())
			{
				if (it_r->value().compare("0") == 0)
				{
					// we're now officially rate-limited
					// the next call to this path will fail
					std::string limited_url = entry->Request->target().to_string();
					auto lit = path_ratelimit.find(limited_url);
					if (lit != path_ratelimit.end())
					{
						CLog::Get()->Log(LogLevel::ERROR,
							"Error while processing rate-limit: already rate-limited path '{}'",
							limited_url);

						// skip this request, but leave it in our list to retry later
						it++;
						continue;
					}

					it_r = response.find("X-RateLimit-Reset");
					if (it_r != response.end())
					{
						TimePoint_t current_time = std::chrono::system_clock::now();
						CLog::Get()->Log(LogLevel::DEBUG, "rate-limiting path {} until {} (current time: {})",
							limited_url,
							it_r->value().to_string(),
							std::chrono::duration_cast<std::chrono::seconds>(
								current_time.time_since_epoch()).count());

						string const &reset_time_str = it_r->value().to_string();
						long long reset_time_secs = 0;
						boost::spirit::qi::parse(reset_time_str.begin(), reset_time_str.end(),
							boost::spirit::qi::any_int_parser<long long>(),
							reset_time_secs);
						TimePoint_t reset_time{ std::chrono::seconds(reset_time_secs) };

						path_ratelimit.insert({ limited_url, reset_time });
					}
				}
			}

			m_QueueMutex.unlock(); // allow requests to be queued from within callback
			if (entry->Callback)
				entry->Callback(sb, response);
			m_QueueMutex.lock();

			it = m_Queue.erase(it);
		}
		m_QueueMutex.unlock();

		std::this_thread::sleep_for(std::chrono::milliseconds(10));
	}

	Disconnect();
}

bool Http::Connect()
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::Connect");

	// connect to REST API
	asio::ip::tcp::resolver r{ m_IoService };
	boost::system::error_code error;
	auto target = r.resolve({ "discordapp.com", "https" }, error);
	if (error)
	{
		CLog::Get()->Log(LogLevel::ERROR, "Can't resolve Discord API URL: {} ({})",
			error.message(), error.value());
		return false;
	}

	m_SslStream.reset(new SslStream_t(m_IoService, m_SslContext));
	asio::connect(m_SslStream->lowest_layer(), target, error);
	if (error)
	{
		CLog::Get()->Log(LogLevel::ERROR, "Can't connect to Discord API: {} ({})",
			error.message(), error.value());
		return false;
	}

	// SSL handshake
	m_SslStream->set_verify_mode(asio::ssl::verify_none, error);
	if (error)
	{
		CLog::Get()->Log(LogLevel::ERROR,
			"Can't configure SSL stream peer verification mode for Discord API: {} ({})",
			error.message(), error.value());
		return false;
	}

	m_SslStream->handshake(asio::ssl::stream_base::client, error);
	if (error)
	{
		CLog::Get()->Log(LogLevel::ERROR, "Can't establish secured connection to Discord API: {} ({})",
			error.message(), error.value());
		return false;
	}

	return true;
}

void Http::Disconnect()
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::Disconnect");

	boost::system::error_code error;
	m_SslStream->shutdown(error);
	if (error && error != boost::asio::error::eof)
	{
		CLog::Get()->Log(LogLevel::WARNING, "Error while shutting down SSL on HTTP connection: {} ({})",
			error.message(), error.value());
	}

	m_SslStream->lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, error);
	if (error && error != boost::asio::error::eof)
	{
		CLog::Get()->Log(LogLevel::WARNING, "Error while shutting down HTTP connection: {} ({})",
			error.message(), error.value());
	}

	m_SslStream->lowest_layer().close(error);
	if (error)
	{
		CLog::Get()->Log(LogLevel::WARNING, "Error while closing HTTP connection: {} ({})",
			error.message(), error.value());
	}
}

bool Http::ReconnectRetry()
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::ReconnectRetry");

	unsigned int reconnect_counter = 0;
	do
	{
		CLog::Get()->Log(LogLevel::INFO, "trying reconnect #{}...", reconnect_counter + 1);

		Disconnect();
		if (Connect())
		{
			CLog::Get()->Log(LogLevel::INFO, "reconnect succeeded, resending request");
			return true;
		}
		else
		{
			unsigned int seconds_to_wait = static_cast<unsigned int>(std::pow(2U, reconnect_counter));
			CLog::Get()->Log(LogLevel::WARNING, "reconnect failed, waiting {} seconds...", seconds_to_wait);
			std::this_thread::sleep_for(std::chrono::seconds(seconds_to_wait));
		}
	} while (++reconnect_counter < 3);

	CLog::Get()->Log(LogLevel::ERROR, "Could not reconnect to Discord");
	return false;
}

Http::SharedRequest_t Http::PrepareRequest(beast::http::verb const method,
	std::string const &url, std::string const &content)
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::PrepareRequest");

	auto req = std::make_shared<Request_t>();
	req->method(method);
	req->target("/api/v6" + url);
	req->version(11);
	req->insert("Connection", "keep-alive");
	req->insert("Host", "discordapp.com");
	req->insert("User-Agent", "DiscordBot (github.com/maddinat0r/samp-discord-connector, " PLUGIN_VERSION ")");
	if (!content.empty())
		req->insert("Content-Type", "application/json");
	req->insert("Authorization", "Bot " + m_Token);
	req->body() = content;

	req->prepare_payload();

	return req;
}

void Http::SendRequest(beast::http::verb const method, std::string const &url,
	std::string const &content, ResponseCallback_t &&callback)
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::SendRequest");

	SharedRequest_t req = PrepareRequest(method, url, content);

	std::lock_guard<std::mutex> lock_guard(m_QueueMutex);
	m_Queue.push_back(std::make_shared<QueueEntry>(req, std::move(callback)));
}

void Http::Get(std::string const &url, GetCallback_t &&callback)
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::Get");

	SendRequest(beast::http::verb::get, url, "", [callback](Streambuf_t &sb, Response_t &resp)
	{
		std::stringstream ss_body, ss_data;
		ss_body << beast::buffers(resp.body().data());
		ss_data << beast::buffers(sb.data());
		callback({ resp.result_int(), resp.reason().to_string(), ss_body.str(), ss_data.str() });
	});
}

void Http::Post(std::string const &url, std::string const &content)
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::Post");

	SendRequest(beast::http::verb::post, url, content, nullptr);
}
<|endoftext|>


<commit_before>#include "Http.hpp"
#include "CLog.hpp"
#include "version.hpp"

#include <boost/asio/system_timer.hpp>
#include <boost/spirit/include/qi.hpp>

Http::Http(std::string token) :
	m_SslContext(asio::ssl::context::sslv23),
	m_SslStream(m_IoService, m_SslContext),
	m_Token(token),
	m_NetworkThreadRunning(true),
	m_NetworkThread(std::bind(&Http::NetworkThreadFunc, this))
{
	if (!Connect())
		return;
}

Http::~Http()
{
	m_NetworkThreadRunning = false;
	m_NetworkThread.join();
	Disconnect();
}

void Http::NetworkThreadFunc()
{
	std::unordered_map<std::string, TimePoint_t> path_ratelimit;
	unsigned int retry_counter = 0;
	unsigned int const MaxRetries = 3;
	bool skip_entry = false;

	while (m_NetworkThreadRunning)
	{
		TimePoint_t current_time = std::chrono::system_clock::now();

		m_QueueMutex.lock();
		auto it = m_Queue.begin();
		while (it != m_Queue.end())
		{
			auto const &entry = *it;

			// check if we're rate-limited
			auto pr_it = path_ratelimit.find(entry->Request->target().to_string());
			if (pr_it != path_ratelimit.end())
			{
				// rate-limit for this path exists
				// are we still within the rate-limit timepoint?
				if (current_time < pr_it->second)
				{
					// yes, ignore this request for now
					it++;
					continue;
				}

				// no, delete rate-limit and go on
				path_ratelimit.erase(pr_it);
			}

			boost::system::error_code error_code;
			retry_counter = 0;
			skip_entry = false;
			do
			{
				beast::http::write(m_SslStream, *entry->Request, error_code);
				if (error_code)
				{
					CLog::Get()->Log(LogLevel::ERROR, "Error while sending HTTP {} request to '{}': {}",
						entry->Request->method_string().to_string(),
						entry->Request->target().to_string(),
						error_code.message());

					// try reconnecting
					if (retry_counter++ >= MaxRetries || !ReconnectRetry())
					{
						// we failed to reconnect, discard this request
						CLog::Get()->Log(LogLevel::WARNING, "Failed to send request, discarding");
						it = m_Queue.erase(it);
						skip_entry = true;
						break; // break out of do-while loop
					}
				}
			} while (error_code);
			if (skip_entry)
				continue; // continue queue loop

			Streambuf_t sb;
			Response_t response;
			retry_counter = 0;
			skip_entry = false;
			do
			{
				beast::http::read(m_SslStream, sb, response, error_code);
				if (error_code)
				{
					CLog::Get()->Log(LogLevel::ERROR, "Error while retrieving HTTP {} response from '{}': {}",
						entry->Request->method_string().to_string(),
						entry->Request->target().to_string(),
						error_code.message());

					// try reconnecting
					if (retry_counter++ >= MaxRetries || !ReconnectRetry())
					{
						// we failed to reconnect, discard this request
						CLog::Get()->Log(LogLevel::WARNING, "Failed to read response, discarding");
						it = m_Queue.erase(it);
						skip_entry = true;
						break; // break out of do-while loop
					}
				}
			} while (error_code);
			if (skip_entry)
				continue; // continue queue loop

			auto it_r = response.find("X-RateLimit-Remaining");
			if (it_r != response.end())
			{
				if (it_r->value().compare("0") == 0)
				{
					// we're now officially rate-limited
					// the next call to this path will fail
					std::string limited_url = entry->Request->target().to_string();
					auto lit = path_ratelimit.find(limited_url);
					if (lit != path_ratelimit.end())
					{
						CLog::Get()->Log(LogLevel::ERROR,
							"Error while processing rate-limit: already rate-limited path '{}'",
							limited_url);

						// skip this request, but leave it in our list to retry later
						it++;
						continue;
					}

					it_r = response.find("X-RateLimit-Reset");
					if (it_r != response.end())
					{
						TimePoint_t current_time = std::chrono::system_clock::now();
						CLog::Get()->Log(LogLevel::DEBUG, "rate-limiting path {} until {} (current time: {})",
							limited_url,
							it_r->value().to_string(),
							std::chrono::duration_cast<std::chrono::seconds>(
								current_time.time_since_epoch()).count());

						string const &reset_time_str = it_r->value().to_string();
						long long reset_time_secs = 0;
						boost::spirit::qi::parse(reset_time_str.begin(), reset_time_str.end(),
							boost::spirit::qi::any_int_parser<long long>(),
							reset_time_secs);
						TimePoint_t reset_time{ std::chrono::seconds(reset_time_secs) };

						path_ratelimit.insert({ limited_url, reset_time });
					}
				}
			}

			m_QueueMutex.unlock(); // allow requests to be queued from within callback
			if (entry->Callback)
				entry->Callback(sb, response);
			m_QueueMutex.lock();

			it = m_Queue.erase(it);
		}
		m_QueueMutex.unlock();

		std::this_thread::sleep_for(std::chrono::milliseconds(10));
	}

}

bool Http::Connect()
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::Connect");

	// connect to REST API
	asio::ip::tcp::resolver r{ m_IoService };
	boost::system::error_code error;
	auto target = r.resolve({ "discordapp.com", "https" }, error);
	if (error)
	{
		CLog::Get()->Log(LogLevel::ERROR, "Can't resolve Discord API URL: {} ({})",
			error.message(), error.value());
		return false;
	}

	asio::connect(m_SslStream.lowest_layer(), target, error);
	if (error)
	{
		CLog::Get()->Log(LogLevel::ERROR, "Can't connect to Discord API: {} ({})",
			error.message(), error.value());
		return false;
	}

	// SSL handshake
	m_SslStream.set_verify_mode(asio::ssl::verify_none, error);
	if (error)
	{
		CLog::Get()->Log(LogLevel::ERROR,
			"Can't configure SSL stream peer verification mode for Discord API: {} ({})",
			error.message(), error.value());
		return false;
	}

	m_SslStream.handshake(asio::ssl::stream_base::client, error);
	if (error)
	{
		CLog::Get()->Log(LogLevel::ERROR, "Can't establish secured connection to Discord API: {} ({})",
			error.message(), error.value());
		return false;
	}

	return true;
}

void Http::Disconnect()
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::Disconnect");

	boost::system::error_code error;
	m_SslStream.shutdown(error);
	if (error && error != boost::asio::error::eof)
	{
		CLog::Get()->Log(LogLevel::WARNING, "Error while shutting down SSL on HTTP connection: {} ({})",
			error.message(), error.value());
	}

	m_SslStream.lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, error);
	if (error && error != boost::asio::error::eof)
	{
		CLog::Get()->Log(LogLevel::WARNING, "Error while shutting down HTTP connection: {} ({})",
			error.message(), error.value());
	}

	m_SslStream.lowest_layer().close(error);
	if (error)
	{
		CLog::Get()->Log(LogLevel::WARNING, "Error while closing HTTP connection: {} ({})",
			error.message(), error.value());
	}
}

bool Http::ReconnectRetry()
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::ReconnectRetry");

	unsigned int reconnect_counter = 0;
	do
	{
		CLog::Get()->Log(LogLevel::INFO, "trying reconnect #{}...", reconnect_counter + 1);

		Disconnect();
		if (Connect())
		{
			CLog::Get()->Log(LogLevel::INFO, "reconnect succeeded, resending request");
			return true;
		}
		else
		{
			unsigned int seconds_to_wait = static_cast<unsigned int>(std::pow(2U, reconnect_counter));
			CLog::Get()->Log(LogLevel::WARNING, "reconnect failed, waiting {} seconds...", seconds_to_wait);
			std::this_thread::sleep_for(std::chrono::seconds(seconds_to_wait));
		}
	} while (++reconnect_counter < 3);

	CLog::Get()->Log(LogLevel::ERROR, "Could not reconnect to Discord");
	return false;
}

Http::SharedRequest_t Http::PrepareRequest(beast::http::verb const method,
	std::string const &url, std::string const &content)
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::PrepareRequest");

	auto req = std::make_shared<Request_t>();
	req->method(method);
	req->target("/api/v6" + url);
	req->version(11);
	req->insert("Host", "discordapp.com");
	req->insert("User-Agent", "DiscordBot (github.com/maddinat0r/samp-discord-connector, " PLUGIN_VERSION ")");
	if (!content.empty())
		req->insert("Content-Type", "application/json");
	req->insert("Authorization", "Bot " + m_Token);
	req->body() = content;

	req->prepare_payload();

	return req;
}

void Http::SendRequest(beast::http::verb const method, std::string const &url,
	std::string const &content, ResponseCallback_t &&callback)
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::SendRequest");

	SharedRequest_t req = PrepareRequest(method, url, content);

	std::lock_guard<std::mutex> lock_guard(m_QueueMutex);
	m_Queue.push_back(std::make_shared<QueueEntry>(req, std::move(callback)));
}

void Http::Get(std::string const &url, GetCallback_t &&callback)
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::Get");

	SendRequest(beast::http::verb::get, url, "", [callback](Streambuf_t &sb, Response_t &resp)
	{
		std::stringstream ss_body, ss_data;
		ss_body << beast::buffers(resp.body().data());
		ss_data << beast::buffers(sb.data());
		callback({ resp.result_int(), resp.reason().to_string(), ss_body.str(), ss_data.str() });
	});
}

void Http::Post(std::string const &url, std::string const &content)
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::Post");

	SendRequest(beast::http::verb::post, url, content, nullptr);
}
<commit_msg>fix HTTP request retry logic<commit_after>#include "Http.hpp"
#include "CLog.hpp"
#include "version.hpp"

#include <boost/asio/system_timer.hpp>
#include <boost/spirit/include/qi.hpp>

Http::Http(std::string token) :
	m_SslContext(asio::ssl::context::sslv23),
	m_SslStream(m_IoService, m_SslContext),
	m_Token(token),
	m_NetworkThreadRunning(true),
	m_NetworkThread(std::bind(&Http::NetworkThreadFunc, this))
{
	if (!Connect())
		return;
}

Http::~Http()
{
	m_NetworkThreadRunning = false;
	m_NetworkThread.join();
	Disconnect();
}

void Http::NetworkThreadFunc()
{
	std::unordered_map<std::string, TimePoint_t> path_ratelimit;
	unsigned int retry_counter = 0;
	unsigned int const MaxRetries = 3;
	bool skip_entry = false;

	while (m_NetworkThreadRunning)
	{
		TimePoint_t current_time = std::chrono::system_clock::now();

		m_QueueMutex.lock();
		auto it = m_Queue.begin();
		while (it != m_Queue.end())
		{
			auto const &entry = *it;

			// check if we're rate-limited
			auto pr_it = path_ratelimit.find(entry->Request->target().to_string());
			if (pr_it != path_ratelimit.end())
			{
				// rate-limit for this path exists
				// are we still within the rate-limit timepoint?
				if (current_time < pr_it->second)
				{
					// yes, ignore this request for now
					it++;
					continue;
				}

				// no, delete rate-limit and go on
				path_ratelimit.erase(pr_it);
			}

			boost::system::error_code error_code;
			Response_t response;
			Streambuf_t sb;
			retry_counter = 0;
			skip_entry = false;
			do
			{
				bool do_reconnect = false;
				beast::http::write(m_SslStream, *entry->Request, error_code);
				if (error_code)
				{
					CLog::Get()->Log(LogLevel::ERROR, "Error while sending HTTP {} request to '{}': {}",
						entry->Request->method_string().to_string(),
						entry->Request->target().to_string(),
						error_code.message());

					do_reconnect = true;
				}
				else
				{
					beast::http::read(m_SslStream, sb, response, error_code);
					if (error_code)
					{
						CLog::Get()->Log(LogLevel::ERROR, "Error while retrieving HTTP {} response from '{}': {}",
							entry->Request->method_string().to_string(),
							entry->Request->target().to_string(),
							error_code.message());

						do_reconnect = true;
					}
				}

				if (do_reconnect)
				{
					if (retry_counter++ >= MaxRetries || !ReconnectRetry())
					{
						// we failed to reconnect, discard this request
						CLog::Get()->Log(LogLevel::WARNING, "Failed to send request, discarding");
						it = m_Queue.erase(it);
						skip_entry = true;
						break; // break out of do-while loop
					}
				}
			} while (error_code);
			if (skip_entry)
				continue; // continue queue loop

			auto it_r = response.find("X-RateLimit-Remaining");
			if (it_r != response.end())
			{
				if (it_r->value().compare("0") == 0)
				{
					// we're now officially rate-limited
					// the next call to this path will fail
					std::string limited_url = entry->Request->target().to_string();
					auto lit = path_ratelimit.find(limited_url);
					if (lit != path_ratelimit.end())
					{
						CLog::Get()->Log(LogLevel::ERROR,
							"Error while processing rate-limit: already rate-limited path '{}'",
							limited_url);

						// skip this request, but leave it in our list to retry later
						it++;
						continue;
					}

					it_r = response.find("X-RateLimit-Reset");
					if (it_r != response.end())
					{
						TimePoint_t current_time = std::chrono::system_clock::now();
						CLog::Get()->Log(LogLevel::DEBUG, "rate-limiting path {} until {} (current time: {})",
							limited_url,
							it_r->value().to_string(),
							std::chrono::duration_cast<std::chrono::seconds>(
								current_time.time_since_epoch()).count());

						string const &reset_time_str = it_r->value().to_string();
						long long reset_time_secs = 0;
						boost::spirit::qi::parse(reset_time_str.begin(), reset_time_str.end(),
							boost::spirit::qi::any_int_parser<long long>(),
							reset_time_secs);
						TimePoint_t reset_time{ std::chrono::seconds(reset_time_secs) };

						path_ratelimit.insert({ limited_url, reset_time });
					}
				}
			}

			m_QueueMutex.unlock(); // allow requests to be queued from within callback
			if (entry->Callback)
				entry->Callback(sb, response);
			m_QueueMutex.lock();

			it = m_Queue.erase(it);
		}
		m_QueueMutex.unlock();

		std::this_thread::sleep_for(std::chrono::milliseconds(10));
	}

}

bool Http::Connect()
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::Connect");

	// connect to REST API
	asio::ip::tcp::resolver r{ m_IoService };
	boost::system::error_code error;
	auto target = r.resolve({ "discordapp.com", "https" }, error);
	if (error)
	{
		CLog::Get()->Log(LogLevel::ERROR, "Can't resolve Discord API URL: {} ({})",
			error.message(), error.value());
		return false;
	}

	asio::connect(m_SslStream.lowest_layer(), target, error);
	if (error)
	{
		CLog::Get()->Log(LogLevel::ERROR, "Can't connect to Discord API: {} ({})",
			error.message(), error.value());
		return false;
	}

	// SSL handshake
	m_SslStream.set_verify_mode(asio::ssl::verify_none, error);
	if (error)
	{
		CLog::Get()->Log(LogLevel::ERROR,
			"Can't configure SSL stream peer verification mode for Discord API: {} ({})",
			error.message(), error.value());
		return false;
	}

	m_SslStream.handshake(asio::ssl::stream_base::client, error);
	if (error)
	{
		CLog::Get()->Log(LogLevel::ERROR, "Can't establish secured connection to Discord API: {} ({})",
			error.message(), error.value());
		return false;
	}

	return true;
}

void Http::Disconnect()
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::Disconnect");

	boost::system::error_code error;
	m_SslStream.shutdown(error);
	if (error && error != boost::asio::error::eof)
	{
		CLog::Get()->Log(LogLevel::WARNING, "Error while shutting down SSL on HTTP connection: {} ({})",
			error.message(), error.value());
	}

	m_SslStream.lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, error);
	if (error && error != boost::asio::error::eof)
	{
		CLog::Get()->Log(LogLevel::WARNING, "Error while shutting down HTTP connection: {} ({})",
			error.message(), error.value());
	}

	m_SslStream.lowest_layer().close(error);
	if (error)
	{
		CLog::Get()->Log(LogLevel::WARNING, "Error while closing HTTP connection: {} ({})",
			error.message(), error.value());
	}
}

bool Http::ReconnectRetry()
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::ReconnectRetry");

	unsigned int reconnect_counter = 0;
	do
	{
		CLog::Get()->Log(LogLevel::INFO, "trying reconnect #{}...", reconnect_counter + 1);

		Disconnect();
		if (Connect())
		{
			CLog::Get()->Log(LogLevel::INFO, "reconnect succeeded, resending request");
			return true;
		}
		else
		{
			unsigned int seconds_to_wait = static_cast<unsigned int>(std::pow(2U, reconnect_counter));
			CLog::Get()->Log(LogLevel::WARNING, "reconnect failed, waiting {} seconds...", seconds_to_wait);
			std::this_thread::sleep_for(std::chrono::seconds(seconds_to_wait));
		}
	} while (++reconnect_counter < 3);

	CLog::Get()->Log(LogLevel::ERROR, "Could not reconnect to Discord");
	return false;
}

Http::SharedRequest_t Http::PrepareRequest(beast::http::verb const method,
	std::string const &url, std::string const &content)
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::PrepareRequest");

	auto req = std::make_shared<Request_t>();
	req->method(method);
	req->target("/api/v6" + url);
	req->version(11);
	req->insert("Host", "discordapp.com");
	req->insert("User-Agent", "DiscordBot (github.com/maddinat0r/samp-discord-connector, " PLUGIN_VERSION ")");
	if (!content.empty())
		req->insert("Content-Type", "application/json");
	req->insert("Authorization", "Bot " + m_Token);
	req->body() = content;

	req->prepare_payload();

	return req;
}

void Http::SendRequest(beast::http::verb const method, std::string const &url,
	std::string const &content, ResponseCallback_t &&callback)
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::SendRequest");

	SharedRequest_t req = PrepareRequest(method, url, content);

	std::lock_guard<std::mutex> lock_guard(m_QueueMutex);
	m_Queue.push_back(std::make_shared<QueueEntry>(req, std::move(callback)));
}

void Http::Get(std::string const &url, GetCallback_t &&callback)
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::Get");

	SendRequest(beast::http::verb::get, url, "", [callback](Streambuf_t &sb, Response_t &resp)
	{
		std::stringstream ss_body, ss_data;
		ss_body << beast::buffers(resp.body().data());
		ss_data << beast::buffers(sb.data());
		callback({ resp.result_int(), resp.reason().to_string(), ss_body.str(), ss_data.str() });
	});
}

void Http::Post(std::string const &url, std::string const &content)
{
	CLog::Get()->Log(LogLevel::DEBUG, "Http::Post");

	SendRequest(beast::http::verb::post, url, content, nullptr);
}
<|endoftext|>


<commit_before>#include <windows.h>

#include <gl\gl.h>

#ifdef _MSC_VER
#define _USE_MATH_DEFINES
#endif
#include <math.h>

#include "Window.h"
#include "Body.h"
#include "Error.h"
#include "Info.h"

#define NAME_FONT_NAME "Arial"
#define NAME_FONT_SIZE_AT_H600 24
#define NAME_FONT_SIZE (NAME_FONT_SIZE_AT_H600*scrheight/600)

#define NAME_TEXT_COLOR_R 1.00f
#define NAME_TEXT_COLOR_G 1.00f
#define NAME_TEXT_COLOR_B 1.00f
#define NAME_TEXT_COLOR_A 0.50f

#define INFO_FONT_NAME "Arial"
#define INFO_FONT_SIZE_AT_H600 16
#define INFO_FONT_SIZE (INFO_FONT_SIZE_AT_H600*scrheight/600)

#define INFO_TEXT_COLOR_R 1.00f
#define INFO_TEXT_COLOR_G 1.00f
#define INFO_TEXT_COLOR_B 1.00f
#define INFO_TEXT_COLOR_A 0.50f

#define SPACING_COEF 1.13f
#define LINES_AFTER_NAME 1.00f

#define WINDOW_COLOR_R 0.50f
#define WINDOW_COLOR_G 0.50f
#define WINDOW_COLOR_B 0.50f
#define WINDOW_COLOR_A 0.25f

#define MAX_FADE_TIME 3.0f
#define FADE_TIME_RATIO 0.10f
#define FADE_TIME(totaltime) min(MAX_FADE_TIME, (totaltime)*FADE_TIME_RATIO)

#define WINDOW_BORDER_REL 0.0125f
#define WINDOW_BORDER (WINDOW_BORDER_REL*scrheight)
#define WINDOW_WIDTH_REL_Y (0.3075f*4.0f/3.0f)
#define WINDOW_WIDTH (WINDOW_WIDTH_REL_Y*scrheight)
#define WINDOW_HEIGHT_REL 0.25f
#define WINDOW_HEIGHT (WINDOW_HEIGHT_REL*scrheight)

#define WINDOW_POS_X1 (WINDOW_BORDER)
#define WINDOW_POS_Y1 (WINDOW_BORDER)
#define WINDOW_POS_X2 (WINDOW_POS_X1+WINDOW_WIDTH)
#define WINDOW_POS_Y2 (WINDOW_POS_Y1+WINDOW_HEIGHT)

#define MARGIN_TOP_REL 0.100f
#define MARGIN_LEFT_REL 0.075f
#define MARGIN_WIDTH (WINDOW_WIDTH*MARGIN_LEFT_REL)
#define MARGIN_HEIGHT (WINDOW_HEIGHT*MARGIN_TOP_REL)

CInfo::CInfo()
{
	Init();
}

CInfo::~CInfo()
{
	Free();
}

void CInfo::Init()
{
	loaded=false;
	scrwidth=0;
	scrheight=0;
	winlist=0;
	namelist=infolist=0;
	time=0;
	starttime=endtime=0;
	fadetime=1;
	alpha=0;
}

void CInfo::Free()
{
	nametext.Free();
	infotext.Free();
	if (winlist)
	{
		if (glIsList(winlist))
			glDeleteLists(winlist,3);
	}
	Init();
}

bool CInfo::Load()
{
	Free();

	scrwidth=CWindow::GetWidth();
	scrheight=CWindow::GetHeight();

	winlist=glGenLists(3);
	if (!winlist)
	{
		CError::LogError(WARNING_CODE,"Unable to load planet info - failed to generate display lists.");
		Free();
		return false;
	}
	namelist=winlist+1;
	infolist=namelist+1;

	MakeWindow(winlist);

	loaded=true;
	loaded&=nametext.BuildFTFont(NAME_FONT_NAME,(int)NAME_FONT_SIZE);
	loaded&=infotext.BuildFTFont(INFO_FONT_NAME,(int)INFO_FONT_SIZE);
	if (!loaded)
	{
		CError::LogError(WARNING_CODE,"Unable to load planet info - failed to load font.");
		Free();
	}

	return loaded;
}

void CInfo::MakeWindow(int list)
{
	glNewList(list,GL_COMPILE);
	{
		int l=(int)WINDOW_POS_X1;
		int r=(int)WINDOW_POS_X2;
		int b=(int)WINDOW_POS_Y1;
		int t=(int)WINDOW_POS_Y2;
		glDisable(GL_TEXTURE_2D);
		glLoadIdentity();
		glBegin(GL_QUADS);
		{
			glVertex2f((float)l,(float)b);
			glVertex2f((float)r,(float)b);
			glVertex2f((float)r,(float)t);
			glVertex2f((float)l,(float)t);
		}
		glEnd();
	}
	glEndList();
}

void CInfo::GetNameCoords(const char *text, int *x, int *y)
{
	float tw;
	nametext.GetTextSize(text,&tw,NULL);
	int th=(int)NAME_FONT_SIZE;
	if (x) *x=(int)(WINDOW_POS_X1+(WINDOW_WIDTH-tw)*0.5f);
	if (y) *y=(int)(WINDOW_POS_Y2-MARGIN_HEIGHT)-th;
}

void CInfo::GetInfoCoords(int linenum, int *x, int *y)
{
	int namey;
	GetNameCoords(" ",NULL,&namey);
	float nameadd;
	nametext.GetTextSize(" ",NULL,&nameadd);
	nameadd*=(SPACING_COEF*LINES_AFTER_NAME);

	float th;
	infotext.GetTextSize(" ",NULL,&th);
	th*=SPACING_COEF;
	int thi=(int)th*(linenum-1);

	if (x) *x=(int)(WINDOW_POS_X1+MARGIN_WIDTH);
	if (y) *y=namey-(int)nameadd-thi;
}

void CInfo::MakeName(int list, char *targetname)
{
	if (!targetname) return;
	if (*targetname==' ') targetname++;
	int x,y;
	GetNameCoords(targetname,&x,&y);
	glNewList(list,GL_COMPILE);
	{
		glEnable(GL_TEXTURE_2D);
		glLoadIdentity();
		glTranslatef((float)x,(float)y,0);
		nametext.Print(targetname);
	}
	glEndList();
}

void CInfo::MakeInfoLine(int linenum, char *line)
{
	int x,y;
	GetInfoCoords(linenum,&x,&y);
	glPushMatrix();
	glTranslatef((float)x,(float)y,0);
	infotext.Print(line);
	glPopMatrix();
}

void CInfo::MakeInfo(int list, CBody *targetbody)
{
	bool star=(targetbody==NULL);
	if (star) targetbody=CBody::bodycache[0];
	glNewList(list,GL_COMPILE);
	{
		int n=0;
		glEnable(GL_TEXTURE_2D);
		glLoadIdentity();
		if (star)
		{
			char line[64];
			n++;
			sprintf(line,"star name: %s",targetbody->name);
			MakeInfoLine(n,line);
		}
		for (int i=0;i<targetbody->info.numlines;i++)
		{
			if (targetbody->info.textlines[i][0]=='/') continue;
			n++;
			MakeInfoLine(n,targetbody->info.textlines[i]);
		}
	}
	glEndList();
}

void CInfo::Start(float seconds, float duration, char *targetname, CBody *targetbody)
{
	if (!loaded)
		return;
	char name[32];
	strcpy(name,targetname);
	int l=strlen(name);
	for (int i=0;i<l;i++)
		if (name[i]=='_')
			name[i]=' ';
	starttime=seconds;
	endtime=starttime+duration;
	fadetime=FADE_TIME(duration);
	MakeName(namelist,name);
	MakeInfo(infolist,targetbody);
}

void CInfo::Update(float seconds)
{
	time=seconds;
	if (time<(endtime-fadetime))
		alpha=min(1,(time-starttime)/fadetime);
	else
		alpha=max(0,(endtime-time)/fadetime);
}

void CInfo::Restart()
{
	starttime-=time;
	endtime-=time;
	time=0;
}

void CInfo::Draw()
{
	if (!alpha || !loaded)
		return;

	glDisable(GL_LIGHTING);
	glDisable(GL_DEPTH_TEST);
	glDisable(GL_CULL_FACE);
	glEnable(GL_BLEND);

	glColor4f(WINDOW_COLOR_R,WINDOW_COLOR_G,WINDOW_COLOR_B,WINDOW_COLOR_A*alpha);
	glCallList(winlist);

	glColor4f(NAME_TEXT_COLOR_R,NAME_TEXT_COLOR_G,NAME_TEXT_COLOR_B,NAME_TEXT_COLOR_A*alpha);
	glCallList(namelist);

	glColor4f(INFO_TEXT_COLOR_R,INFO_TEXT_COLOR_G,INFO_TEXT_COLOR_B,INFO_TEXT_COLOR_A*alpha);
	glCallList(infolist);
}
<commit_msg>Corrections to info text coordinate calculation.<commit_after>#include <windows.h>

#include <gl\gl.h>

#ifdef _MSC_VER
#define _USE_MATH_DEFINES
#endif
#include <math.h>

#include "Window.h"
#include "Body.h"
#include "Error.h"
#include "Info.h"

#define NAME_FONT_NAME "Arial"
#define NAME_FONT_SIZE_AT_H600 24
#define NAME_FONT_SIZE (NAME_FONT_SIZE_AT_H600*scrheight/600)

#define NAME_TEXT_COLOR_R 1.00f
#define NAME_TEXT_COLOR_G 1.00f
#define NAME_TEXT_COLOR_B 1.00f
#define NAME_TEXT_COLOR_A 0.50f

#define INFO_FONT_NAME "Arial"
#define INFO_FONT_SIZE_AT_H600 16
#define INFO_FONT_SIZE (INFO_FONT_SIZE_AT_H600*scrheight/600)

#define INFO_TEXT_COLOR_R 1.00f
#define INFO_TEXT_COLOR_G 1.00f
#define INFO_TEXT_COLOR_B 1.00f
#define INFO_TEXT_COLOR_A 0.50f

#define SPACING_COEF 1.15f
#define LINES_AFTER_NAME 1.00f

#define WINDOW_COLOR_R 0.50f
#define WINDOW_COLOR_G 0.50f
#define WINDOW_COLOR_B 0.50f
#define WINDOW_COLOR_A 0.25f

#define MAX_FADE_TIME 3.0f
#define FADE_TIME_RATIO 0.10f
#define FADE_TIME(totaltime) min(MAX_FADE_TIME, (totaltime)*FADE_TIME_RATIO)

#define WINDOW_BORDER_REL 0.0125f
#define WINDOW_BORDER (WINDOW_BORDER_REL*scrheight)
#define WINDOW_WIDTH_REL_Y (0.3075f*4.0f/3.0f)
#define WINDOW_WIDTH (WINDOW_WIDTH_REL_Y*scrheight)
#define WINDOW_HEIGHT_REL 0.25f
#define WINDOW_HEIGHT (WINDOW_HEIGHT_REL*scrheight)

#define WINDOW_POS_X1 (WINDOW_BORDER)
#define WINDOW_POS_Y1 (WINDOW_BORDER)
#define WINDOW_POS_X2 (WINDOW_POS_X1+WINDOW_WIDTH)
#define WINDOW_POS_Y2 (WINDOW_POS_Y1+WINDOW_HEIGHT)

#define MARGIN_TOP_REL 0.100f
#define MARGIN_LEFT_REL 0.075f
#define MARGIN_WIDTH (WINDOW_WIDTH*MARGIN_LEFT_REL)
#define MARGIN_HEIGHT (WINDOW_HEIGHT*MARGIN_TOP_REL)

CInfo::CInfo()
{
	Init();
}

CInfo::~CInfo()
{
	Free();
}

void CInfo::Init()
{
	loaded=false;
	scrwidth=0;
	scrheight=0;
	winlist=0;
	namelist=infolist=0;
	time=0;
	starttime=endtime=0;
	fadetime=1;
	alpha=0;
}

void CInfo::Free()
{
	nametext.Free();
	infotext.Free();
	if (winlist)
	{
		if (glIsList(winlist))
			glDeleteLists(winlist,3);
	}
	Init();
}

bool CInfo::Load()
{
	Free();

	scrwidth=CWindow::GetWidth();
	scrheight=CWindow::GetHeight();

	winlist=glGenLists(3);
	if (!winlist)
	{
		CError::LogError(WARNING_CODE,"Unable to load planet info - failed to generate display lists.");
		Free();
		return false;
	}
	namelist=winlist+1;
	infolist=namelist+1;

	MakeWindow(winlist);

	loaded=true;
	loaded&=nametext.BuildFTFont(NAME_FONT_NAME,(int)NAME_FONT_SIZE);
	loaded&=infotext.BuildFTFont(INFO_FONT_NAME,(int)INFO_FONT_SIZE);
	if (!loaded)
	{
		CError::LogError(WARNING_CODE,"Unable to load planet info - failed to load font.");
		Free();
	}

	return loaded;
}

void CInfo::MakeWindow(int list)
{
	glNewList(list,GL_COMPILE);
	{
		int l=(int)WINDOW_POS_X1;
		int r=(int)WINDOW_POS_X2;
		int b=(int)WINDOW_POS_Y1;
		int t=(int)WINDOW_POS_Y2;
		glDisable(GL_TEXTURE_2D);
		glLoadIdentity();
		glBegin(GL_QUADS);
		{
			glVertex2f((float)l,(float)b);
			glVertex2f((float)r,(float)b);
			glVertex2f((float)r,(float)t);
			glVertex2f((float)l,(float)t);
		}
		glEnd();
	}
	glEndList();
}

void CInfo::GetNameCoords(const char *text, int *x, int *y)
{
	float tw;
	nametext.GetTextSize(text,&tw,NULL);
	int th=(int)NAME_FONT_SIZE;

	if (x) *x=(int)(WINDOW_POS_X1+(WINDOW_WIDTH-tw)*0.5f);
	if (y) *y=(int)(WINDOW_POS_Y2-MARGIN_HEIGHT)-th;
}

void CInfo::GetInfoCoords(int linenum, int *x, int *y)
{
	int ymargin=(int)(WINDOW_POS_Y2-MARGIN_HEIGHT);

	float nameheight;
	nametext.GetTextSize("",NULL,&nameheight);

	float nameadd;
	nameadd=nameheight*SPACING_COEF*LINES_AFTER_NAME;

	int ioffset=(int)INFO_FONT_SIZE;

	float th;
	infotext.GetTextSize("",NULL,&th);
	th*=SPACING_COEF;
	int thi=(int)th*(linenum-1);

	if (x) *x=(int)(WINDOW_POS_X1+MARGIN_WIDTH);
	if (y) *y=ymargin-(int)nameadd-ioffset-thi;
}

void CInfo::MakeName(int list, char *targetname)
{
	if (!targetname) return;
	if (*targetname==' ') targetname++;
	int x,y;
	GetNameCoords(targetname,&x,&y);
	glNewList(list,GL_COMPILE);
	{
		glEnable(GL_TEXTURE_2D);
		glLoadIdentity();
		glTranslatef((float)x,(float)y,0);
		nametext.Print(targetname);
	}
	glEndList();
}

void CInfo::MakeInfoLine(int linenum, char *line)
{
	int x,y;
	GetInfoCoords(linenum,&x,&y);
	glPushMatrix();
	glTranslatef((float)x,(float)y,0);
	infotext.Print(line);
	glPopMatrix();
}

void CInfo::MakeInfo(int list, CBody *targetbody)
{
	bool star=(targetbody==NULL);
	if (star) targetbody=CBody::bodycache[0];
	glNewList(list,GL_COMPILE);
	{
		int n=0;
		glEnable(GL_TEXTURE_2D);
		glLoadIdentity();
		if (star)
		{
			char line[64];
			n++;
			sprintf(line,"star name: %s",targetbody->name);
			MakeInfoLine(n,line);
		}
		for (int i=0;i<targetbody->info.numlines;i++)
		{
			if (targetbody->info.textlines[i][0]=='/') continue;
			n++;
			MakeInfoLine(n,targetbody->info.textlines[i]);
		}
	}
	glEndList();
}

void CInfo::Start(float seconds, float duration, char *targetname, CBody *targetbody)
{
	if (!loaded)
		return;
	char name[32];
	strcpy(name,targetname);
	int l=strlen(name);
	for (int i=0;i<l;i++)
		if (name[i]=='_')
			name[i]=' ';
	starttime=seconds;
	endtime=starttime+duration;
	fadetime=FADE_TIME(duration);
	MakeName(namelist,name);
	MakeInfo(infolist,targetbody);
}

void CInfo::Update(float seconds)
{
	time=seconds;
	if (time<(endtime-fadetime))
		alpha=min(1,(time-starttime)/fadetime);
	else
		alpha=max(0,(endtime-time)/fadetime);
}

void CInfo::Restart()
{
	starttime-=time;
	endtime-=time;
	time=0;
}

void CInfo::Draw()
{
	if (!alpha || !loaded)
		return;

	glDisable(GL_LIGHTING);
	glDisable(GL_DEPTH_TEST);
	glDisable(GL_CULL_FACE);
	glEnable(GL_BLEND);

	glColor4f(WINDOW_COLOR_R,WINDOW_COLOR_G,WINDOW_COLOR_B,WINDOW_COLOR_A*alpha);
	glCallList(winlist);

	glColor4f(NAME_TEXT_COLOR_R,NAME_TEXT_COLOR_G,NAME_TEXT_COLOR_B,NAME_TEXT_COLOR_A*alpha);
	glCallList(namelist);

	glColor4f(INFO_TEXT_COLOR_R,INFO_TEXT_COLOR_G,INFO_TEXT_COLOR_B,INFO_TEXT_COLOR_A*alpha);
	glCallList(infolist);
}
<|endoftext|>


<commit_before>/*
 * Global Presence - wraps calls to set and get presence for all accounts.
 *
 * Copyright (C) 2011 David Edmundson <kde@davidedmundson.co.uk>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "global-presence.h"

#include "presence.h"

#include <TelepathyQt/AccountSet>
#include <TelepathyQt/Account>
#include <TelepathyQt/PendingReady>

#include "ktp-debug.h"

namespace KTp
{

GlobalPresence::GlobalPresence(QObject *parent)
    : QObject(parent),
      m_connectionStatus(Tp::ConnectionStatusDisconnected),
      m_changingPresence(false)
{
    Tp::Presence unknown;
    unknown.setStatus(Tp::ConnectionPresenceTypeUnknown, QLatin1String("unknown"), QString());

    m_requestedPresence = KTp::Presence(unknown);
    m_currentPresence = KTp::Presence(unknown);
}

void GlobalPresence::setAccountManager(const Tp::AccountManagerPtr &accountManager)
{
    if (! accountManager->isReady()) {
        qCWarning(KTP_COMMONINTERNALS) << "GlobalPresence used with unready account manager";
    }

    m_enabledAccounts = accountManager->enabledAccounts();
    m_onlineAccounts = accountManager->onlineAccounts();

    Q_FOREACH(const Tp::AccountPtr &account, m_enabledAccounts->accounts()) {
        onAccountAdded(account);
    }

    onCurrentPresenceChanged();
    onRequestedPresenceChanged();
    onChangingPresence();
    onConnectionStatusChanged();

    connect(m_enabledAccounts.data(), SIGNAL(accountAdded(Tp::AccountPtr)), SLOT(onAccountAdded(Tp::AccountPtr)));
    connect(m_enabledAccounts.data(), SIGNAL(accountRemoved(Tp::AccountPtr)), this, SIGNAL(enabledAccountsChanged()));
}

void GlobalPresence::addAccountManager(const Tp::AccountManagerPtr &accountManager)
{
    m_accountManager = accountManager;
    connect(m_accountManager->becomeReady(), SIGNAL(finished(Tp::PendingOperation*)),
            this, SLOT(onAccountManagerReady(Tp::PendingOperation*)));
}

Tp::AccountManagerPtr GlobalPresence::accountManager() const
{
    return m_accountManager;
}

void GlobalPresence::onAccountManagerReady(Tp::PendingOperation* op)
{
    if (op->isError()) {
        qCDebug(KTP_COMMONINTERNALS) << op->errorName();
        qCDebug(KTP_COMMONINTERNALS) << op->errorMessage();

        //TODO: Create signal to send to client
        qCDebug(KTP_COMMONINTERNALS) << "Something unexpected happened to the core part of your Instant Messaging system "
                 << "and it couldn't be initialized. Try restarting the client.";

        return;
    }

    setAccountManager(m_accountManager);
    Q_EMIT(accountManagerReady());
}

Tp::ConnectionStatus GlobalPresence::connectionStatus() const
{
    return m_connectionStatus;
}

Presence GlobalPresence::currentPresence() const
{
    return m_currentPresence;
}

QString GlobalPresence::currentPresenceMessage() const
{
    KTp::Presence p = currentPresence();
    return p.statusMessage();
}

QIcon GlobalPresence::currentPresenceIcon() const
{
    return currentPresence().icon();
}

QString GlobalPresence::currentPresenceIconName() const
{
    return currentPresence().iconName();
}

QString GlobalPresence::currentPresenceName() const
{
    return currentPresence().displayString();
}

GlobalPresence::ConnectionPresenceType GlobalPresence::currentPresenceType() const
{
    KTp::Presence p = currentPresence();
    switch(p.type()) {
        case Tp::ConnectionPresenceTypeAvailable:
            return GlobalPresence::Available;
        case Tp::ConnectionPresenceTypeBusy:
            return GlobalPresence::Busy;
        case Tp::ConnectionPresenceTypeAway:
            return GlobalPresence::Away;
        case Tp::ConnectionPresenceTypeExtendedAway:
            return GlobalPresence::ExtendedAway;
        case Tp::ConnectionPresenceTypeHidden:
            return GlobalPresence::Hidden;
        case Tp::ConnectionPresenceTypeOffline:
            return GlobalPresence::Offline;
        default:
            return GlobalPresence::Unknown;
    }
}

Presence GlobalPresence::requestedPresence() const
{
    return m_requestedPresence;
}

QString GlobalPresence::requestedPresenceName() const
{
    return m_requestedPresence.displayString();
}

bool GlobalPresence::isChangingPresence() const
{
    return connectionStatus() == Tp::ConnectionStatusConnecting;
}

void GlobalPresence::setPresence(const KTp::Presence &presence)
{
    if (m_enabledAccounts.isNull()) {
        qCWarning(KTP_COMMONINTERNALS) << "Requested presence change on empty accounts set";
        return;
    }

    Q_FOREACH(const Tp::AccountPtr &account, m_enabledAccounts->accounts()) {
        account->setRequestedPresence(presence);
    }
}

void GlobalPresence::setPresence(GlobalPresence::ConnectionPresenceType p, QString message)
{
    switch (p) {
    case GlobalPresence::Available:
        setPresence(Tp::Presence::available(message));
        break;
    case GlobalPresence::Busy:
        setPresence(Tp::Presence::busy(message));
        break;
    case GlobalPresence::Away:
        setPresence(Tp::Presence::away(message));
        break;
    case GlobalPresence::ExtendedAway:
        setPresence(Tp::Presence::xa(message));
        break;
    case GlobalPresence::Hidden:
        setPresence(Tp::Presence::hidden(message));
        break;
    case GlobalPresence::Offline:
        setPresence(Tp::Presence::offline(message));
        break;
    default:
        qCDebug(KTP_COMMONINTERNALS) << "You should not be here!";
    }
}

void GlobalPresence::onAccountAdded(const Tp::AccountPtr &account)
{
    connect(account.data(), SIGNAL(connectionStatusChanged(Tp::ConnectionStatus)), SLOT(onConnectionStatusChanged()));
    connect(account.data(), SIGNAL(requestedPresenceChanged(Tp::Presence)), SLOT(onRequestedPresenceChanged()));
    connect(account.data(), SIGNAL(currentPresenceChanged(Tp::Presence)), SLOT(onCurrentPresenceChanged()));

    Q_EMIT enabledAccountsChanged();
}

void GlobalPresence::onCurrentPresenceChanged()
{
    /* basic idea of choosing global presence it to make it reflects the presence
     * over all accounts, usually this is used to indicates user the whole system
     * status.
     *
     * If there isn't any account, currentPresence should be offline, since there is nothing
     * online.
     * If there's only one account, then currentPresence should represent the presence
     * of this account.
     * If there're more than one accounts, the situation is more complicated.
     * There can be some accounts is still connecting (thus it's offline), and there can be
     * some accounts doesn't support the presence you're choosing. The user-chosen presence
     * priority will be higher than standard presence order.
     *
     * Example:
     * user choose to be online, 1 account online, 1 account offline, current presence
     * should be online, since online priority is higher than offline.
     * user chooses a presence supported by part of the account, current presence will be
     * the one chosen by user, to indicate there is at least one account supports it.
     * user choose a presence supported by no account, current presence will be chosen
     * from all accounts based on priority, and it also indicates there is no account support
     * the user-chosen presence.
     */
    Tp::Presence highestCurrentPresence = Tp::Presence::offline();
    Q_FOREACH(const Tp::AccountPtr &account, m_enabledAccounts->accounts()) {
        if (account->currentPresence().type() == m_requestedPresence.type()) {
            highestCurrentPresence = account->currentPresence();
            break;
        }

        if (Presence::sortPriority(account->currentPresence().type()) < Presence::sortPriority(highestCurrentPresence.type())) {
            highestCurrentPresence = account->currentPresence();
        }
    }

    qCDebug(KTP_COMMONINTERNALS) << "Current presence changed";

    if (highestCurrentPresence.type() != m_currentPresence.type() ||
            highestCurrentPresence.status() != m_currentPresence.status() ||
            highestCurrentPresence.statusMessage() != m_currentPresence.statusMessage()) {

        m_currentPresence = Presence(highestCurrentPresence);
        Q_EMIT currentPresenceChanged(m_currentPresence);
    }

    onChangingPresence();
}

void GlobalPresence::onRequestedPresenceChanged()
{
    Tp::Presence highestRequestedPresence = Tp::Presence::offline();
    Q_FOREACH(const Tp::AccountPtr &account, m_enabledAccounts->accounts()) {
        if (Presence::sortPriority(account->requestedPresence().type()) < Presence::sortPriority(highestRequestedPresence.type())) {
            highestRequestedPresence = account->requestedPresence();
        }
    }

    if (highestRequestedPresence.type() != m_requestedPresence.type() ||
            highestRequestedPresence.status() != m_requestedPresence.status() ||
            highestRequestedPresence.statusMessage() != m_requestedPresence.statusMessage()) {
        m_requestedPresence = Presence(highestRequestedPresence);
        // current presence priority is affected by requested presence
        onCurrentPresenceChanged();
        Q_EMIT requestedPresenceChanged(m_requestedPresence);
    }

    onChangingPresence();
}

void GlobalPresence::onChangingPresence()
{
    bool isChangingPresence = false;
    Q_FOREACH(const Tp::AccountPtr &account, m_enabledAccounts->accounts()) {
        if (account->requestedPresence().type() != account->currentPresence().type()) {
            isChangingPresence = true;
        }
    }

    if (isChangingPresence != m_changingPresence) {
        m_changingPresence = isChangingPresence;
        Q_EMIT changingPresence(m_changingPresence);
    }
}

void GlobalPresence::onConnectionStatusChanged()
{
    Tp::ConnectionStatus connectionStatus = Tp::ConnectionStatusDisconnected;

    Q_FOREACH(const Tp::AccountPtr &account, m_enabledAccounts->accounts()) {
        switch (account->connectionStatus()) {
        case Tp::ConnectionStatusConnecting:
            //connecting is the highest state, use this always
            connectionStatus = Tp::ConnectionStatusConnecting;
            break;
        case Tp::ConnectionStatusConnected:
            //only set to connected if we're not at connecting
            if (connectionStatus == Tp::ConnectionStatusDisconnected) {
                connectionStatus = Tp::ConnectionStatusConnected;
            }
            break;
        default:
            break;
        }
    }

    if (connectionStatus != m_connectionStatus) {
        m_connectionStatus = connectionStatus;
        Q_EMIT connectionStatusChanged(m_connectionStatus);
    }
}

bool GlobalPresence::hasEnabledAccounts() const
{
    if (m_enabledAccounts->accounts().isEmpty()) {
        return false;
    }

    return true;
}

void GlobalPresence::saveCurrentPresence()
{
    qCDebug(KTP_COMMONINTERNALS) << "Saving presence with message:" << m_currentPresence.statusMessage();
    m_savedPresence = m_currentPresence;
}

void GlobalPresence::restoreSavedPresence()
{
    qCDebug(KTP_COMMONINTERNALS) << m_savedPresence.statusMessage();
    setPresence(m_savedPresence);
}

Tp::AccountSetPtr GlobalPresence::onlineAccounts() const
{
    return m_onlineAccounts;
}

}

#include "global-presence.moc"
<commit_msg>Fix crash when m_enabledAccounts is still null.<commit_after>/*
 * Global Presence - wraps calls to set and get presence for all accounts.
 *
 * Copyright (C) 2011 David Edmundson <kde@davidedmundson.co.uk>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "global-presence.h"

#include "presence.h"

#include <TelepathyQt/AccountSet>
#include <TelepathyQt/Account>
#include <TelepathyQt/PendingReady>

#include "ktp-debug.h"

namespace KTp
{

GlobalPresence::GlobalPresence(QObject *parent)
    : QObject(parent),
      m_connectionStatus(Tp::ConnectionStatusDisconnected),
      m_changingPresence(false)
{
    Tp::Presence unknown;
    unknown.setStatus(Tp::ConnectionPresenceTypeUnknown, QLatin1String("unknown"), QString());

    m_requestedPresence = KTp::Presence(unknown);
    m_currentPresence = KTp::Presence(unknown);
}

void GlobalPresence::setAccountManager(const Tp::AccountManagerPtr &accountManager)
{
    if (! accountManager->isReady()) {
        qCWarning(KTP_COMMONINTERNALS) << "GlobalPresence used with unready account manager";
    }

    m_enabledAccounts = accountManager->enabledAccounts();
    m_onlineAccounts = accountManager->onlineAccounts();

    Q_FOREACH(const Tp::AccountPtr &account, m_enabledAccounts->accounts()) {
        onAccountAdded(account);
    }

    onCurrentPresenceChanged();
    onRequestedPresenceChanged();
    onChangingPresence();
    onConnectionStatusChanged();

    connect(m_enabledAccounts.data(), SIGNAL(accountAdded(Tp::AccountPtr)), SLOT(onAccountAdded(Tp::AccountPtr)));
    connect(m_enabledAccounts.data(), SIGNAL(accountRemoved(Tp::AccountPtr)), this, SIGNAL(enabledAccountsChanged()));
}

void GlobalPresence::addAccountManager(const Tp::AccountManagerPtr &accountManager)
{
    m_accountManager = accountManager;
    connect(m_accountManager->becomeReady(), SIGNAL(finished(Tp::PendingOperation*)),
            this, SLOT(onAccountManagerReady(Tp::PendingOperation*)));
}

Tp::AccountManagerPtr GlobalPresence::accountManager() const
{
    return m_accountManager;
}

void GlobalPresence::onAccountManagerReady(Tp::PendingOperation* op)
{
    if (op->isError()) {
        qCDebug(KTP_COMMONINTERNALS) << op->errorName();
        qCDebug(KTP_COMMONINTERNALS) << op->errorMessage();

        //TODO: Create signal to send to client
        qCDebug(KTP_COMMONINTERNALS) << "Something unexpected happened to the core part of your Instant Messaging system "
                 << "and it couldn't be initialized. Try restarting the client.";

        return;
    }

    setAccountManager(m_accountManager);
    Q_EMIT(accountManagerReady());
}

Tp::ConnectionStatus GlobalPresence::connectionStatus() const
{
    return m_connectionStatus;
}

Presence GlobalPresence::currentPresence() const
{
    return m_currentPresence;
}

QString GlobalPresence::currentPresenceMessage() const
{
    KTp::Presence p = currentPresence();
    return p.statusMessage();
}

QIcon GlobalPresence::currentPresenceIcon() const
{
    return currentPresence().icon();
}

QString GlobalPresence::currentPresenceIconName() const
{
    return currentPresence().iconName();
}

QString GlobalPresence::currentPresenceName() const
{
    return currentPresence().displayString();
}

GlobalPresence::ConnectionPresenceType GlobalPresence::currentPresenceType() const
{
    KTp::Presence p = currentPresence();
    switch(p.type()) {
        case Tp::ConnectionPresenceTypeAvailable:
            return GlobalPresence::Available;
        case Tp::ConnectionPresenceTypeBusy:
            return GlobalPresence::Busy;
        case Tp::ConnectionPresenceTypeAway:
            return GlobalPresence::Away;
        case Tp::ConnectionPresenceTypeExtendedAway:
            return GlobalPresence::ExtendedAway;
        case Tp::ConnectionPresenceTypeHidden:
            return GlobalPresence::Hidden;
        case Tp::ConnectionPresenceTypeOffline:
            return GlobalPresence::Offline;
        default:
            return GlobalPresence::Unknown;
    }
}

Presence GlobalPresence::requestedPresence() const
{
    return m_requestedPresence;
}

QString GlobalPresence::requestedPresenceName() const
{
    return m_requestedPresence.displayString();
}

bool GlobalPresence::isChangingPresence() const
{
    return connectionStatus() == Tp::ConnectionStatusConnecting;
}

void GlobalPresence::setPresence(const KTp::Presence &presence)
{
    if (m_enabledAccounts.isNull()) {
        qCWarning(KTP_COMMONINTERNALS) << "Requested presence change on empty accounts set";
        return;
    }

    Q_FOREACH(const Tp::AccountPtr &account, m_enabledAccounts->accounts()) {
        account->setRequestedPresence(presence);
    }
}

void GlobalPresence::setPresence(GlobalPresence::ConnectionPresenceType p, QString message)
{
    switch (p) {
    case GlobalPresence::Available:
        setPresence(Tp::Presence::available(message));
        break;
    case GlobalPresence::Busy:
        setPresence(Tp::Presence::busy(message));
        break;
    case GlobalPresence::Away:
        setPresence(Tp::Presence::away(message));
        break;
    case GlobalPresence::ExtendedAway:
        setPresence(Tp::Presence::xa(message));
        break;
    case GlobalPresence::Hidden:
        setPresence(Tp::Presence::hidden(message));
        break;
    case GlobalPresence::Offline:
        setPresence(Tp::Presence::offline(message));
        break;
    default:
        qCDebug(KTP_COMMONINTERNALS) << "You should not be here!";
    }
}

void GlobalPresence::onAccountAdded(const Tp::AccountPtr &account)
{
    connect(account.data(), SIGNAL(connectionStatusChanged(Tp::ConnectionStatus)), SLOT(onConnectionStatusChanged()));
    connect(account.data(), SIGNAL(requestedPresenceChanged(Tp::Presence)), SLOT(onRequestedPresenceChanged()));
    connect(account.data(), SIGNAL(currentPresenceChanged(Tp::Presence)), SLOT(onCurrentPresenceChanged()));

    Q_EMIT enabledAccountsChanged();
}

void GlobalPresence::onCurrentPresenceChanged()
{
    /* basic idea of choosing global presence it to make it reflects the presence
     * over all accounts, usually this is used to indicates user the whole system
     * status.
     *
     * If there isn't any account, currentPresence should be offline, since there is nothing
     * online.
     * If there's only one account, then currentPresence should represent the presence
     * of this account.
     * If there're more than one accounts, the situation is more complicated.
     * There can be some accounts is still connecting (thus it's offline), and there can be
     * some accounts doesn't support the presence you're choosing. The user-chosen presence
     * priority will be higher than standard presence order.
     *
     * Example:
     * user choose to be online, 1 account online, 1 account offline, current presence
     * should be online, since online priority is higher than offline.
     * user chooses a presence supported by part of the account, current presence will be
     * the one chosen by user, to indicate there is at least one account supports it.
     * user choose a presence supported by no account, current presence will be chosen
     * from all accounts based on priority, and it also indicates there is no account support
     * the user-chosen presence.
     */
    Tp::Presence highestCurrentPresence = Tp::Presence::offline();
    Q_FOREACH(const Tp::AccountPtr &account, m_enabledAccounts->accounts()) {
        if (account->currentPresence().type() == m_requestedPresence.type()) {
            highestCurrentPresence = account->currentPresence();
            break;
        }

        if (Presence::sortPriority(account->currentPresence().type()) < Presence::sortPriority(highestCurrentPresence.type())) {
            highestCurrentPresence = account->currentPresence();
        }
    }

    qCDebug(KTP_COMMONINTERNALS) << "Current presence changed";

    if (highestCurrentPresence.type() != m_currentPresence.type() ||
            highestCurrentPresence.status() != m_currentPresence.status() ||
            highestCurrentPresence.statusMessage() != m_currentPresence.statusMessage()) {

        m_currentPresence = Presence(highestCurrentPresence);
        Q_EMIT currentPresenceChanged(m_currentPresence);
    }

    onChangingPresence();
}

void GlobalPresence::onRequestedPresenceChanged()
{
    Tp::Presence highestRequestedPresence = Tp::Presence::offline();
    Q_FOREACH(const Tp::AccountPtr &account, m_enabledAccounts->accounts()) {
        if (Presence::sortPriority(account->requestedPresence().type()) < Presence::sortPriority(highestRequestedPresence.type())) {
            highestRequestedPresence = account->requestedPresence();
        }
    }

    if (highestRequestedPresence.type() != m_requestedPresence.type() ||
            highestRequestedPresence.status() != m_requestedPresence.status() ||
            highestRequestedPresence.statusMessage() != m_requestedPresence.statusMessage()) {
        m_requestedPresence = Presence(highestRequestedPresence);
        // current presence priority is affected by requested presence
        onCurrentPresenceChanged();
        Q_EMIT requestedPresenceChanged(m_requestedPresence);
    }

    onChangingPresence();
}

void GlobalPresence::onChangingPresence()
{
    bool isChangingPresence = false;
    Q_FOREACH(const Tp::AccountPtr &account, m_enabledAccounts->accounts()) {
        if (account->requestedPresence().type() != account->currentPresence().type()) {
            isChangingPresence = true;
        }
    }

    if (isChangingPresence != m_changingPresence) {
        m_changingPresence = isChangingPresence;
        Q_EMIT changingPresence(m_changingPresence);
    }
}

void GlobalPresence::onConnectionStatusChanged()
{
    Tp::ConnectionStatus connectionStatus = Tp::ConnectionStatusDisconnected;

    Q_FOREACH(const Tp::AccountPtr &account, m_enabledAccounts->accounts()) {
        switch (account->connectionStatus()) {
        case Tp::ConnectionStatusConnecting:
            //connecting is the highest state, use this always
            connectionStatus = Tp::ConnectionStatusConnecting;
            break;
        case Tp::ConnectionStatusConnected:
            //only set to connected if we're not at connecting
            if (connectionStatus == Tp::ConnectionStatusDisconnected) {
                connectionStatus = Tp::ConnectionStatusConnected;
            }
            break;
        default:
            break;
        }
    }

    if (connectionStatus != m_connectionStatus) {
        m_connectionStatus = connectionStatus;
        Q_EMIT connectionStatusChanged(m_connectionStatus);
    }
}

bool GlobalPresence::hasEnabledAccounts() const
{
    if (m_enabledAccounts.isNull() || m_enabledAccounts->accounts().isEmpty()) {
        return false;
    }

    return true;
}

void GlobalPresence::saveCurrentPresence()
{
    qCDebug(KTP_COMMONINTERNALS) << "Saving presence with message:" << m_currentPresence.statusMessage();
    m_savedPresence = m_currentPresence;
}

void GlobalPresence::restoreSavedPresence()
{
    qCDebug(KTP_COMMONINTERNALS) << m_savedPresence.statusMessage();
    setPresence(m_savedPresence);
}

Tp::AccountSetPtr GlobalPresence::onlineAccounts() const
{
    return m_onlineAccounts;
}

}

#include "global-presence.moc"
<|endoftext|>


<commit_before>#include <Utilities/Common.h>

#include <string>
#include <iostream>
#include <fstream>
#include <vector>
#include <utility>

using namespace std;

struct coord {
	word x;
	word y;
};

class grid {
	word width;
	word height;
	coord start;
	coord end;

	vector<vector<word>> cells;

	vector<coord> get_neighbors(coord c);
	void walk(coord next, word previous_lowest_cost);

	public:
		grid(istream& stream);

		void print_input(ostream& stream);
		void print_output(ostream& stream);

		word find_path();
};

grid::grid(istream& stream) {
	stream >> this->width >> this->height;
	stream >> this->start.x >> this->start.y;
	stream >> this->end.x >> this->end.y;

	this->cells.resize(this->width);

	for (word x = 0; x < this->width; x++)
		this->cells[x].resize(this->height);

	for (word y = 0; y < this->height; y++)
		for (word x = 0; x < this->width; x++)
			stream >> this->cells[x][y];
}

void grid::print_input(ostream& stream) {
	for (word y = 0; y < this->height; y++) {
		for (word x = 0; x < this->width; x++)
			stream << this->cells[x][y] << " ";

		stream << endl;
	}
}

void grid::print_output(ostream& stream) {

}

vector<coord> grid::get_neighbors(coord c) {
	vector<coord> neighbors;

	if (c.x == 0) {
		neighbors.push_back(coord { 1, 0 });
	}
	else if (c.x == this->width - 1) {
		neighbors.push_back(coord { this->width - 2, 0 });
	}
	else {
		neighbors.push_back(coord { c.x - 1, c.y });
		neighbors.push_back(coord { c.x + 1, c.y });
	}

	if (c.y == 0) {
		neighbors.push_back(coord { 0, 1 });
	}
	else if (c.y == this->height - 1) {
		neighbors.push_back(coord { 0, this->height - 2 });
	}
	else {
		neighbors.push_back(coord { c.x, c.y - 1 });
		neighbors.push_back(coord { c.x, c.y + 1 });
	}

	return neighbors;
}

word grid::find_path() {
	this->walk(this->start, 0);

	return 0;
}

void grid::walk(coord current, word previous_lowest_cost) {
	auto neighbors = this->get_neighbors(current);
	auto current_cost = this->cells[current.x][current.y];
	word lowest_cost = 0;

	for (auto i : neighbors) {
		auto& neighbor_cost = this->cells[i.x][i.y];

		if (neighbor_cost != 0)
			neighbor_cost += current_cost;

		if (neighbor_cost < lowest_cost)
			lowest_cost = neighbor_cost;
	}
}

int main() {
	string path;

	cout << "Filename: ";
	cin >> path;

	ifstream file(path);

	grid g(file);

	auto cost = g.find_path();

	cout << "Input: " << endl;
	g.print_input(cout);

	cout << "Output: " << endl;
	cout << "Cost: " << cost << endl;
	g.print_output(cout);

	return 0;
}
<commit_msg>Updated a dependency name.<commit_after>#include <ArkeIndustries.CPPUtilities/Common.h>

#include <string>
#include <iostream>
#include <fstream>
#include <vector>
#include <utility>

using namespace std;

struct coord {
	word x;
	word y;
};

class grid {
	word width;
	word height;
	coord start;
	coord end;

	vector<vector<word>> cells;

	vector<coord> get_neighbors(coord c);
	void walk(coord next, word previous_lowest_cost);

	public:
		grid(istream& stream);

		void print_input(ostream& stream);
		void print_output(ostream& stream);

		word find_path();
};

grid::grid(istream& stream) {
	stream >> this->width >> this->height;
	stream >> this->start.x >> this->start.y;
	stream >> this->end.x >> this->end.y;

	this->cells.resize(this->width);

	for (word x = 0; x < this->width; x++)
		this->cells[x].resize(this->height);

	for (word y = 0; y < this->height; y++)
		for (word x = 0; x < this->width; x++)
			stream >> this->cells[x][y];
}

void grid::print_input(ostream& stream) {
	for (word y = 0; y < this->height; y++) {
		for (word x = 0; x < this->width; x++)
			stream << this->cells[x][y] << " ";

		stream << endl;
	}
}

void grid::print_output(ostream& stream) {

}

vector<coord> grid::get_neighbors(coord c) {
	vector<coord> neighbors;

	if (c.x == 0) {
		neighbors.push_back(coord { 1, 0 });
	}
	else if (c.x == this->width - 1) {
		neighbors.push_back(coord { this->width - 2, 0 });
	}
	else {
		neighbors.push_back(coord { c.x - 1, c.y });
		neighbors.push_back(coord { c.x + 1, c.y });
	}

	if (c.y == 0) {
		neighbors.push_back(coord { 0, 1 });
	}
	else if (c.y == this->height - 1) {
		neighbors.push_back(coord { 0, this->height - 2 });
	}
	else {
		neighbors.push_back(coord { c.x, c.y - 1 });
		neighbors.push_back(coord { c.x, c.y + 1 });
	}

	return neighbors;
}

word grid::find_path() {
	this->walk(this->start, 0);

	return 0;
}

void grid::walk(coord current, word previous_lowest_cost) {
	auto neighbors = this->get_neighbors(current);
	auto current_cost = this->cells[current.x][current.y];
	word lowest_cost = 0;

	for (auto i : neighbors) {
		auto& neighbor_cost = this->cells[i.x][i.y];

		if (neighbor_cost != 0)
			neighbor_cost += current_cost;

		if (neighbor_cost < lowest_cost)
			lowest_cost = neighbor_cost;
	}
}

int main() {
	string path;

	cout << "Filename: ";
	cin >> path;

	ifstream file(path);

	grid g(file);

	auto cost = g.find_path();

	cout << "Input: " << endl;
	g.print_input(cout);

	cout << "Output: " << endl;
	cout << "Cost: " << cost << endl;
	g.print_output(cout);

	return 0;
}
<|endoftext|>


<commit_before>#include "Ruby.h"
#include "RubyObject.h"
#include "common.h"
#include <cstring>

#include <iostream>
using namespace std;

using namespace v8;

Persistent<Function> Ruby::s_getCtor;
VALUE Ruby::BLOCK_WRAPPER_CLASS;

void Ruby::Init(Handle<Object> module)
{
  int argc = 0;
  char** argv = NULL;

  // TODO: Do we need to call this?
  ruby_sysinit(&argc, &argv);
  RUBY_INIT_STACK;
  ruby_init();
  ruby_init_loadpath();

  BLOCK_WRAPPER_CLASS = rb_define_class("BlockWrapper", rb_cObject);

  node::AtExit(Cleanup);

  module->Set(NanNew<String>("exports"),
              NanNew<FunctionTemplate>(New)->GetFunction());
}

void Ruby::Cleanup(void*)
{
  log("Cleaning up!" << endl);
  RubyObject::Cleanup();
  ruby_cleanup(0);
}

NAN_METHOD(Ruby::New)
{
  NanScope();

  assert(args[0]->IsFunction());
  NanAssignPersistent(s_getCtor, args[0].As<Function>());

  Local<Object> bindings = NanNew<Object>();
  NODE_SET_METHOD(bindings, "_getClass", GetClass);
  NODE_SET_METHOD(bindings, "_gcStart", GCStart);
  NODE_SET_METHOD(bindings, "_defineClass", DefineClass);
  NODE_SET_METHOD(bindings, "require", Require);
  NODE_SET_METHOD(bindings, "eval", Eval);
  // TODO: Right name?
  NODE_SET_METHOD(bindings, "getFunction", GetFunction);
  // TODO: Maybe we should load the constants here and place them in an object?
  NODE_SET_METHOD(bindings, "getConstant", GetConstant);

  NanReturnValue(bindings);
}

Local<Function> Ruby::GetCtor(Local<Function> rubyClass)
{
  NanEscapableScope();

  Local<Function> getCtor = NanNew<Function>(s_getCtor);
  Handle<Value> argv[] = { rubyClass };
  return NanEscapeScope(NanMakeCallback(NanGetCurrentContext()->Global(),
                                        getCtor, 1, argv).As<Function>());
}

struct ConstGetter
{
  ConstGetter(Handle<Value> nv) : nameVal(nv) {}
  VALUE operator()() const
  {
    NanScope();

    Local<String> constName = nameVal->ToString();
    String::Utf8Value constStr(constName);

    ID id;
    VALUE mod;
    const char* split = std::strstr(*constStr, "::");
    if (split) {
      id = rb_intern(split + 2);
      mod = rb_const_get(rb_cObject, rb_intern2(*constStr, split - *constStr));
    }
    else {
      id = rb_intern2(*constStr, constStr.length());
      mod = rb_cObject;
    }

    return rb_const_get(mod, id);
  }

  Handle<Value> nameVal;
};

// TODO: Should/could we combine this with RubyObject::GetClass?
NAN_METHOD(Ruby::GetClass)
{
  NanScope();

  VALUE klass;
  SAFE_RUBY_CALL(klass, ConstGetter(args[0]));

  if (TYPE(klass) != T_CLASS) {
    std::string msg(*String::Utf8Value(args[0]));
    msg.append(" is not a class");
    // TODO: TypeError?
    NanThrowError(msg.c_str());
    NanReturnUndefined();
  }

  NanReturnValue(RubyObject::GetClass(klass));
}

NAN_METHOD(Ruby::GCStart)
{
  NanScope();
  rb_gc_start();

  NanReturnUndefined();
}

struct ClassDefiner
{
  ClassDefiner(Handle<Value> nv, VALUE s) : nameVal(nv), super(s) {}
  VALUE operator()() const
  {
    NanScope();

    Local<String> className = nameVal->ToString();
    return rb_define_class(*String::Utf8Value(className), super);
  }

  Handle<Value> nameVal;
  VALUE super;
};

NAN_METHOD(Ruby::DefineClass)
{
  NanScope();

  Local<String> name = args[0]->ToString();
  log("Inherit called for " << *String::Utf8Value(name) << endl);

  VALUE super;
  SAFE_RUBY_CALL(super, ConstGetter(args[1]));
  VALUE klass;
  SAFE_RUBY_CALL(klass, ClassDefiner(args[0], super));

  NanReturnValue(RubyObject::GetClass(klass));
}

struct RequireCaller
{
  RequireCaller(const char* n) : name(n) {}
  VALUE operator()() const
  {
    return rb_require(name);
  }

  const char* name;
};

NAN_METHOD(Ruby::Require)
{
  NanScope();

  Local<String> name = args[0]->ToString();
  VALUE res;
  SAFE_RUBY_CALL(res, RequireCaller(*String::Utf8Value(name)));

  NanReturnValue(rubyToV8(res));
}

struct EvalCaller
{
  EvalCaller(const char* s) : str(s) {}
  VALUE operator()() const
  {
    return rb_eval_string(str);
  }

  const char* str;
};

NAN_METHOD(Ruby::Eval)
{
  NanScope();

  Local<String> str = args[0]->ToString();
  VALUE res;
  SAFE_RUBY_CALL(res, EvalCaller(*String::Utf8Value(str)));

  NanReturnValue(rubyToV8(res));
}

// TODO: Can this be combined with RubyObject::CallMethod? Maybe rename?
NAN_METHOD(CallMethod)
{
  NanScope();
  NanReturnValue(CallRubyFromV8(rb_cObject, args));
}

// TODO: Should this throw immediately if the function doesnt exist?
NAN_METHOD(Ruby::GetFunction)
{
  NanScope();

  Local<String> name = args[0]->ToString();
  ID methodID = rb_intern(*String::Utf8Value(name));
  Local<Function> func =
    NanNew<FunctionTemplate>(CallMethod, EXTERNAL_WRAP((void*)methodID))->GetFunction();
  func->SetName(name);

  NanReturnValue(func);
}

NAN_METHOD(Ruby::GetConstant)
{
  NanScope();

  VALUE constant;
  SAFE_RUBY_CALL(constant, ConstGetter(args[0]));

  // TODO: Should we allow getting classes this way? Maybe throw an exception?
  NanReturnValue(rubyToV8(constant));
}
<commit_msg>Properly initialize Ruby<commit_after>#include "Ruby.h"
#include "RubyObject.h"
#include "common.h"
#include <cstring>

#include <iostream>
using namespace std;

using namespace v8;

Persistent<Function> Ruby::s_getCtor;
VALUE Ruby::BLOCK_WRAPPER_CLASS;

void Ruby::Init(Handle<Object> module)
{
  static char* argv[] = { (char*)"norby", (char*)"-e", (char*)"" };

  RUBY_INIT_STACK;
  ruby_init();
  ruby_options(3, argv);

  BLOCK_WRAPPER_CLASS = rb_define_class("BlockWrapper", rb_cObject);

  node::AtExit(Cleanup);

  module->Set(NanNew<String>("exports"),
              NanNew<FunctionTemplate>(New)->GetFunction());
}

void Ruby::Cleanup(void*)
{
  log("Cleaning up!" << endl);
  RubyObject::Cleanup();
  ruby_cleanup(0);
}

NAN_METHOD(Ruby::New)
{
  NanScope();

  assert(args[0]->IsFunction());
  NanAssignPersistent(s_getCtor, args[0].As<Function>());

  Local<Object> bindings = NanNew<Object>();
  NODE_SET_METHOD(bindings, "_getClass", GetClass);
  NODE_SET_METHOD(bindings, "_gcStart", GCStart);
  NODE_SET_METHOD(bindings, "_defineClass", DefineClass);
  NODE_SET_METHOD(bindings, "require", Require);
  NODE_SET_METHOD(bindings, "eval", Eval);
  // TODO: Right name?
  NODE_SET_METHOD(bindings, "getFunction", GetFunction);
  // TODO: Maybe we should load the constants here and place them in an object?
  NODE_SET_METHOD(bindings, "getConstant", GetConstant);

  NanReturnValue(bindings);
}

Local<Function> Ruby::GetCtor(Local<Function> rubyClass)
{
  NanEscapableScope();

  Local<Function> getCtor = NanNew<Function>(s_getCtor);
  Handle<Value> argv[] = { rubyClass };
  return NanEscapeScope(NanMakeCallback(NanGetCurrentContext()->Global(),
                                        getCtor, 1, argv).As<Function>());
}

struct ConstGetter
{
  ConstGetter(Handle<Value> nv) : nameVal(nv) {}
  VALUE operator()() const
  {
    NanScope();

    Local<String> constName = nameVal->ToString();
    String::Utf8Value constStr(constName);

    ID id;
    VALUE mod;
    const char* split = std::strstr(*constStr, "::");
    if (split) {
      id = rb_intern(split + 2);
      mod = rb_const_get(rb_cObject, rb_intern2(*constStr, split - *constStr));
    }
    else {
      id = rb_intern2(*constStr, constStr.length());
      mod = rb_cObject;
    }

    return rb_const_get(mod, id);
  }

  Handle<Value> nameVal;
};

// TODO: Should/could we combine this with RubyObject::GetClass?
NAN_METHOD(Ruby::GetClass)
{
  NanScope();

  VALUE klass;
  SAFE_RUBY_CALL(klass, ConstGetter(args[0]));

  if (TYPE(klass) != T_CLASS) {
    std::string msg(*String::Utf8Value(args[0]));
    msg.append(" is not a class");
    // TODO: TypeError?
    NanThrowError(msg.c_str());
    NanReturnUndefined();
  }

  NanReturnValue(RubyObject::GetClass(klass));
}

NAN_METHOD(Ruby::GCStart)
{
  NanScope();
  rb_gc_start();

  NanReturnUndefined();
}

struct ClassDefiner
{
  ClassDefiner(Handle<Value> nv, VALUE s) : nameVal(nv), super(s) {}
  VALUE operator()() const
  {
    NanScope();

    Local<String> className = nameVal->ToString();
    return rb_define_class(*String::Utf8Value(className), super);
  }

  Handle<Value> nameVal;
  VALUE super;
};

NAN_METHOD(Ruby::DefineClass)
{
  NanScope();

  Local<String> name = args[0]->ToString();
  log("Inherit called for " << *String::Utf8Value(name) << endl);

  VALUE super;
  SAFE_RUBY_CALL(super, ConstGetter(args[1]));
  VALUE klass;
  SAFE_RUBY_CALL(klass, ClassDefiner(args[0], super));

  NanReturnValue(RubyObject::GetClass(klass));
}

struct RequireCaller
{
  RequireCaller(const char* n) : name(n) {}
  VALUE operator()() const
  {
    return rb_require(name);
  }

  const char* name;
};

NAN_METHOD(Ruby::Require)
{
  NanScope();

  Local<String> name = args[0]->ToString();
  VALUE res;
  SAFE_RUBY_CALL(res, RequireCaller(*String::Utf8Value(name)));

  NanReturnValue(rubyToV8(res));
}

struct EvalCaller
{
  EvalCaller(const char* s) : str(s) {}
  VALUE operator()() const
  {
    return rb_eval_string(str);
  }

  const char* str;
};

NAN_METHOD(Ruby::Eval)
{
  NanScope();

  Local<String> str = args[0]->ToString();
  VALUE res;
  SAFE_RUBY_CALL(res, EvalCaller(*String::Utf8Value(str)));

  NanReturnValue(rubyToV8(res));
}

// TODO: Can this be combined with RubyObject::CallMethod? Maybe rename?
NAN_METHOD(CallMethod)
{
  NanScope();
  NanReturnValue(CallRubyFromV8(rb_cObject, args));
}

// TODO: Should this throw immediately if the function doesnt exist?
NAN_METHOD(Ruby::GetFunction)
{
  NanScope();

  Local<String> name = args[0]->ToString();
  ID methodID = rb_intern(*String::Utf8Value(name));
  Local<Function> func =
    NanNew<FunctionTemplate>(CallMethod, EXTERNAL_WRAP((void*)methodID))->GetFunction();
  func->SetName(name);

  NanReturnValue(func);
}

NAN_METHOD(Ruby::GetConstant)
{
  NanScope();

  VALUE constant;
  SAFE_RUBY_CALL(constant, ConstGetter(args[0]));

  // TODO: Should we allow getting classes this way? Maybe throw an exception?
  NanReturnValue(rubyToV8(constant));
}
<|endoftext|>


<commit_before>//------------------------------------------------------------------------------
// main.cpp
// Entry point for the primary driver program.
//
// File is under the MIT license; see LICENSE for details
//------------------------------------------------------------------------------

#include "compilation/Compilation.h"
#include "parsing/SyntaxTree.h"

#include "CLI11.hpp"
#include "json.hpp"

using namespace slang;

int main(int argc, char** argv)
try {
    std::vector<std::string> sourceFiles;

    CLI::App cmd("SystemVerilog compiler");
    cmd.add_option("files", sourceFiles, "Source files to compile");

    try {
        cmd.parse(argc, argv);
    }
    catch (const CLI::ParseError& e) {
        return cmd.exit(e);
    }

    // Initialize the source manager.
    SourceManager sourceManager;

    // Build the compilation out of each source file.
    Compilation compilation;
    bool anyErrors = false;
    for (const std::string& file : sourceFiles) {
        std::shared_ptr<SyntaxTree> tree = SyntaxTree::fromFile(file, sourceManager);
        if (!tree) {
            printf("error: no such file or directory: '%s'\n", file.c_str());
            anyErrors = true;
            continue;
        }

        compilation.addSyntaxTree(std::move(tree));
    }

    if (compilation.getSyntaxTrees().empty()) {
        printf("error: no input files\n");
        return 1;
    }

    // Report diagnostics.
    Diagnostics diagnostics = compilation.getAllDiagnostics();
    DiagnosticWriter writer(sourceManager);
    printf("%s\n", writer.report(diagnostics).c_str());

    return !anyErrors && diagnostics.empty() ? 0 : 1;
}
catch (const std::exception& e) {
    printf("internal compiler error (exception): %s\n", e.what());
    return 2;
}<commit_msg>Expand driver options, add mode to print preprocessed output<commit_after>//------------------------------------------------------------------------------
// main.cpp
// Entry point for the primary driver program.
//
// File is under the MIT license; see LICENSE for details
//------------------------------------------------------------------------------

#include "compilation/Compilation.h"
#include "parsing/SyntaxTree.h"

#include "CLI11.hpp"
#include "json.hpp"

using namespace slang;

bool runPreprocessor(SourceManager& sourceManager, const Bag& options,
                    const std::vector<SourceBuffer>& buffers) {
    BumpAllocator alloc;
    DiagnosticWriter writer(sourceManager);

    bool success = true;
    for (const SourceBuffer& buffer : buffers) {
        Diagnostics diagnostics;
        Preprocessor preprocessor(sourceManager, alloc, diagnostics, options);
        preprocessor.pushSource(buffer);

        SmallVectorSized<char, 32> output;
        while (true) {
            Token token = preprocessor.next();
            token.writeTo(output, SyntaxToStringFlags::IncludePreprocessed | SyntaxToStringFlags::IncludeTrivia);
            if (token.kind == TokenKind::EndOfFile)
                break;
        }

        if (diagnostics.empty())
            printf("%s:\n", std::string(sourceManager.getRawFileName(buffer.id)).c_str());
        else {
            printf("%s", writer.report(diagnostics).c_str());
            success = false;
        }

        printf("==============================\n%s\n", std::string(output.begin(), output.size()).c_str());
    }
    return success;
}

bool runCompiler(SourceManager& sourceManager, const Bag& options,
                 const std::vector<SourceBuffer>& buffers) {

    Compilation compilation;
    for (const SourceBuffer& buffer : buffers)
        compilation.addSyntaxTree(SyntaxTree::fromBuffer(buffer, sourceManager, options));

    Diagnostics diagnostics = compilation.getAllDiagnostics();
    DiagnosticWriter writer(sourceManager);
    printf("%s\n", writer.report(diagnostics).c_str());

    return diagnostics.empty();
}

int main(int argc, char** argv)
try {
    std::vector<std::string> sourceFiles;
    std::vector<std::string> includeDirs;
    std::vector<std::string> includeSystemDirs;
    std::vector<std::string> defines;
    std::vector<std::string> undefines;

    bool onlyPreprocess;

    CLI::App cmd("SystemVerilog compiler");
    cmd.add_option("files", sourceFiles, "Source files to compile");
    cmd.add_option("-I,--include-directory", includeDirs, "Additional include search paths");
    cmd.add_option("--include-system-directory", includeSystemDirs, "Additional system include search paths");
    cmd.add_option("-D,--define-macro", defines, "Define <macro>=<value> (or 1 if <value> ommitted) in all source files");
    cmd.add_option("-U,--undefine-macro", undefines, "Undefine macro name at the start of all source files");
    cmd.add_flag("-E,--preprocess", onlyPreprocess, "Only run the preprocessor (and print preprocessed files to stdout)");

    try {
        cmd.parse(argc, argv);
    }
    catch (const CLI::ParseError& e) {
        return cmd.exit(e);
    }

    SourceManager sourceManager;
    for (const std::string& dir : includeDirs)
        sourceManager.addUserDirectory(string_view(dir));

    for (const std::string& dir : includeSystemDirs)
        sourceManager.addSystemDirectory(string_view(dir));

    PreprocessorOptions ppoptions;
    ppoptions.predefines = defines;
    ppoptions.undefines = undefines;
    ppoptions.predefineSource = "<command-line>";

    Bag options;
    options.add(ppoptions);

    bool anyErrors = false;
    std::vector<SourceBuffer> buffers;
    for (const std::string& file : sourceFiles) {
        SourceBuffer buffer = sourceManager.readSource(file);
        if (!buffer) {
            printf("error: no such file or directory: '%s'\n", file.c_str());
            anyErrors = true;
            continue;
        }

        buffers.push_back(buffer);
    }

    if (buffers.empty()) {
        printf("error: no input files\n");
        return 1;
    }

    if (onlyPreprocess)
        anyErrors |= !runPreprocessor(sourceManager, options, buffers);
    else
        anyErrors |= !runCompiler(sourceManager, options, buffers);

    return anyErrors ? 1 : 0;
}
catch (const std::exception& e) {
    printf("internal compiler error (exception): %s\n", e.what());
    return 2;
}<|endoftext|>