<commit_before>#include "log.h"
#include "format.h"
#include "../constants.h"
#include "../service_locator/service_locator.h"
#include "../configuration/configuration_wrapper.h"
#include "../utils/null_deleter.h"

#include <sstream>
#include <fstream>
#include <chrono>
#include <cstdio>

#include <boost/log/trivial.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/utility/setup/file.hpp>
#include <boost/log/utility/setup/console.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/sources/channel_logger.hpp>
#include <boost/log/sources/severity_channel_logger.hpp>
#include <boost/log/attributes/function.hpp>
#include <boost/log/attributes/current_thread_id.hpp>
#include <boost/log/attributes/current_process_id.hpp>
#include <boost/log/attributes/mutable_constant.hpp>
#include <boost/log/expressions/formatters/date_time.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/log/support/date_time.hpp>

/*
 * Thread-safety notes:
 * - if you want to use a thread-safe sink frontend (in the sense that it takes
 * care of synchronizing the access to the backend), use the synchronous_sink
 * - as far as I can see, all boost-log sink backends need synchronization
 * in the frontend, i.e. does not implement synchronization on their own
 * - if you want to access to a backend in a thread-safe manner, you
 * can get it through the locked_backend member function, provided by
 * all sink frontends
 * -  there are two versions of loggers provided by the library: the thread-safe ones
 * and the non-thread-safe ones. For the non-thread-safe loggers it is safe for different
 * threads to write logs through different instances of loggers and thus there should
 * be a separate logger for each thread that writes logs. The thread-safe counterparts
 * can be accessed from different threads concurrently, but this will involve locking and
 * may slow things down in case of intense logging. The thread-safe logger types
 * have the _mt suffix in their name.
 *
 * - boost is not letting me using the unlocked_sink with the text_ostream_backend!!!!
 */

namespace
{
static constexpr const char* hypen = "-";
static constexpr const char* p = "p";
static constexpr const char* dot = ".";
}

namespace logging
{

BOOST_LOG_ATTRIBUTE_KEYWORD(timestamp_attribute, "TimeStamp", std::string)

BOOST_LOG_ATTRIBUTE_KEYWORD(hostname_attribute, "HostName", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(logname_attribute, "LogName", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(user_attribute, "User", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(req_attribute, "Req1stLine", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(status_attribute, "Status", int)
BOOST_LOG_ATTRIBUTE_KEYWORD(size_attribute, "ResponseSize", size_t)
BOOST_LOG_ATTRIBUTE_KEYWORD(referrer_attribute, "Referrer", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(useragent_attribute, "User-agent", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(requesttime_attribute, "RequestTime", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(pipe_attribute, "Pipe", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(requestlength_attribute, "RequestLength", size_t)
BOOST_LOG_ATTRIBUTE_KEYWORD(microserver_attribute, "Microserver", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(host_attribute, "Host", std::string)

// ----------------------------------------------------

struct filelogger_base
{
	using sink_backend_t = boost::log::sinks::text_file_backend;
	using sink_frontend_t = boost::log::sinks::synchronous_sink<sink_backend_t>;

	boost::shared_ptr<sink_frontend_t> log_frtnd_;

	filelogger_base(const std::string& log_dir, const std::string& file_prefix, const std::string& channel)
		: log_frtnd_{boost::make_shared<sink_frontend_t>(
						 boost::make_shared<sink_backend_t>(
							boost::log::keywords::file_name = build_path(log_dir, file_prefix) + ".log",
							boost::log::keywords::rotation_size = 1 * 1024 * 1024,
							boost::log::keywords::open_mode = std::ios_base::app
			))}
	{
		using namespace boost::log;

		// minimum scope to make release the locked_ptr
		{
			auto log_bcknd = log_frtnd_->locked_backend();
			// manage log file collection and avoid overwriting existing log files at startup
			log_bcknd->set_file_collector(sinks::file::make_collector(
				keywords::target = build_path(log_dir, file_prefix) + "_old",
				keywords::max_size = 200 * 1024 * 1024, // max total size of collected log files
				keywords::min_free_space = 1024 * 1024 * 1024));
			log_bcknd->scan_for_files();
			log_bcknd->auto_flush(true);
		}

		// set up filtering for local / remote logging
		log_frtnd_->set_filter(expressions::attr<std::string>("Channel") == channel);

		// register the sink to the core
		core::get()->add_sink(log_frtnd_);
	}

	~filelogger_base() noexcept
	{
		/* ensure that sinks are flushed (couldn't find in the
		 * documentation that sinks are flushed at destruction time) */
		log_frtnd_->flush();

		// detach sinks from the core, otherwise they will go on logging...
		boost::log::core::get()->remove_sink(log_frtnd_);
	}

	void flush()
	{
		log_frtnd_->flush();
	}

	static std::string build_path(const std::string& log_dir, const std::string& file_name)
	{
		std::string ret = file_name.empty() ? std::string("log") : file_name;
		if(log_dir.empty()) return ret;

		if(log_dir.back() == '/')
			return log_dir + (ret.front() == '/' ? ret.substr(1) : ret);
		else
			return log_dir + (ret.front() == '/' ? ret : ret.insert(0, "/"));
	}
};

// -----------------------------------------------------------------------------------
// access log class implementation
// -----------------------------------------------------------------------------------

class access_log::impl : public filelogger_base
{
public:
	impl(const std::string& log_dir, const std::string& file_prefix);
	impl(const impl&) = delete;
	impl(impl&& l) = default;
	impl& operator=(const impl&) = delete;
	impl& operator=(impl&& l) = default;

	void log(const access_record& r);

private:
	static const std::string channel_name;
};

access_log::impl::impl(const std::string& log_dir, const std::string& file_prefix)
	: filelogger_base{log_dir, file_prefix, channel_name}
{
	using namespace boost::log;

	// set up log formatting
	log_frtnd_->set_formatter
	(
		// 127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326
		// Access log attributes: "%h %l %u %t \"%r\" %>s %b" common
		expressions::format(R"out(%1% %2% %3% [%4%] "%5%" %6% %7% "%8%" "%9%" %10% %11% %12% "%13%" "%14%")out")
			% hostname_attribute
			% logname_attribute
			% user_attribute
			% timestamp_attribute
			% req_attribute
			% status_attribute
			% size_attribute
			% referrer_attribute
			% useragent_attribute
			% requesttime_attribute
			% pipe_attribute
			% requestlength_attribute
			% microserver_attribute
			% host_attribute
	);
}

void access_log::impl::log(const access_record& r)
{
	static thread_local boost::log::sources::channel_logger<std::string> logger_{boost::log::keywords::channel = channel_name};
	static thread_local boost::log::attributes::mutable_constant<std::string> hostname_attr{""};
	static thread_local boost::log::attributes::mutable_constant<std::string> logname_attr{""};
	static thread_local boost::log::attributes::mutable_constant<std::string> user_attr{""};
	static thread_local boost::log::attributes::mutable_constant<std::string> req_attr{""};
	static thread_local boost::log::attributes::mutable_constant<int> status_attr{0};
	static thread_local boost::log::attributes::mutable_constant<size_t> size_attr{0};
	static thread_local boost::log::attributes::mutable_constant<std::string> referrer_attribute{""};
	static thread_local boost::log::attributes::mutable_constant<std::string> useragent_attribute{""};
	static thread_local boost::log::attributes::mutable_constant<std::string> requesttime_attribute{""};
	static thread_local boost::log::attributes::mutable_constant<std::string> pipe_attribute{""};
	static thread_local boost::log::attributes::mutable_constant<size_t> requestlength_attribute{0};
	static thread_local boost::log::attributes::mutable_constant<std::string> microserver_attribute{""};
	static thread_local boost::log::attributes::mutable_constant<std::string> host_attribute{""};

	static thread_local bool init = true;
	if(init)
	{
		init = false;
		logger_.add_attribute("HostName", hostname_attr);
		logger_.add_attribute("LogName", logname_attr);
		logger_.add_attribute("User", user_attr);
		logger_.add_attribute("Req1stLine", req_attr);
		logger_.add_attribute("Status", status_attr);
		logger_.add_attribute("ResponseSize", size_attr);
		logger_.add_attribute("TimeStamp", boost::log::attributes::make_function([]{
			return format::apache_access_time(std::chrono::system_clock::now());
		}));
		logger_.add_attribute("Referrer", referrer_attribute);
		logger_.add_attribute("User-agent", useragent_attribute);
		logger_.add_attribute("RequestTime", requesttime_attribute);
		logger_.add_attribute("Pipe", pipe_attribute);
		logger_.add_attribute("RequestLength", requestlength_attribute);
		logger_.add_attribute("Microserver", microserver_attribute);
		logger_.add_attribute("Host", host_attribute);
	}

	// duration _zero_ must be printed as "0.000"... we must do it by ourselves! thanks boost::log...
	static thread_local std::string req_time_str(16, 'x');
	{
		using namespace std::chrono;
		auto dur = duration_cast<std::chrono::duration<double,std::ratio<1,1>>>(r.end - r.start).count();
		size_t n = snprintf(const_cast<char*>(req_time_str.data()), req_time_str.capacity(), "%.3f", dur);
		req_time_str.resize(n);
		if(n >= req_time_str.capacity())
			req_time_str.assign(req_time_str.size(), 'x');
	}

	hostname_attr.set(r.remote_addr.empty() ? hypen : static_cast<std::string>( r.remote_addr ) );
	logname_attr.set(hypen);
	user_attr.set(r.remote_user.empty() ? hypen : static_cast<std::string>( r.remote_user) );
	req_attr.set( static_cast<std::string>( r.req ) );
	status_attr.set( r.status );
	size_attr.set(r.body_bytes_sent);
	referrer_attribute.set(r.referer.empty() ? hypen : static_cast<std::string>( r.referer ) );
	useragent_attribute.set(r.user_agent.empty() ? hypen : static_cast<std::string>( r.user_agent) );
	requesttime_attribute.set(req_time_str);
	pipe_attribute.set(r.pipe ? p : dot);
	requestlength_attribute.set(r.req_length);
	microserver_attribute.set(r.x_debug_cyn_ip.empty() ? hypen : static_cast<std::string>( r.x_debug_cyn_ip) ) ;
	host_attribute.set(r.host.empty() ? hypen : static_cast<std::string>( r.host ) );

	BOOST_LOG(logger_);
}

std::ostream& operator<<(std::ostream& os, severity level)
{
	switch(level)
	{
	case severity::emerg:
		os << "emerg";
		break;
	case severity::alert:
		os << "alert";
		break;
	case severity::crit:
		os << "crit";
		break;
	case severity::error:
		os << "error";
		break;
	case severity::warn:
		os << "warn";
		break;
	case severity::notice:
		os << "notice";
		break;
	case severity::info:
		os << "info";
		break;
	case severity::debug:
		os << "debug";
		break;
	case severity::trace1:
		os << "trace1";
		break;
	case severity::trace2:
		os << "trace2";
		break;
	case severity::trace3:
		os << "trace3";
		break;
	case severity::trace4:
		os << "trace4";
		break;
	case severity::trace5:
		os << "trace5";
		break;
	case severity::trace6:
		os << "trace6";
		break;
	case severity::trace7:
		os << "trace7";
		break;
	case severity::trace8:
		os << "trace8";
		break;
	}

	return os;
}

// ----- static vars initialization
const std::string access_log::impl::channel_name = "access";

// *************************** access log ********************************
access_log::access_log(const std::string& log_dir, const std::string& file_prefix)
	: pImpl{new impl{log_dir, file_prefix}}
{}

void access_log::log(const access_record& r) noexcept
{
	pImpl->log(r);
}

void access_log::flush()
{
	pImpl->flush();
}

access_log::~access_log() {}

}
<commit_msg>Fixed destruction of access_log sink<commit_after>#include "log.h"
#include "format.h"
#include "../constants.h"
#include "../service_locator/service_locator.h"
#include "../configuration/configuration_wrapper.h"
#include "../utils/null_deleter.h"

#include <sstream>
#include <fstream>
#include <chrono>
#include <cstdio>

#include <boost/log/trivial.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/utility/setup/file.hpp>
#include <boost/log/utility/setup/console.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/sources/channel_logger.hpp>
#include <boost/log/sources/severity_channel_logger.hpp>
#include <boost/log/attributes/function.hpp>
#include <boost/log/attributes/current_thread_id.hpp>
#include <boost/log/attributes/current_process_id.hpp>
#include <boost/log/attributes/mutable_constant.hpp>
#include <boost/log/expressions/formatters/date_time.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/log/support/date_time.hpp>

/*
 * Thread-safety notes:
 * - if you want to use a thread-safe sink frontend (in the sense that it takes
 * care of synchronizing the access to the backend), use the synchronous_sink
 * - as far as I can see, all boost-log sink backends need synchronization
 * in the frontend, i.e. does not implement synchronization on their own
 * - if you want to access to a backend in a thread-safe manner, you
 * can get it through the locked_backend member function, provided by
 * all sink frontends
 * -  there are two versions of loggers provided by the library: the thread-safe ones
 * and the non-thread-safe ones. For the non-thread-safe loggers it is safe for different
 * threads to write logs through different instances of loggers and thus there should
 * be a separate logger for each thread that writes logs. The thread-safe counterparts
 * can be accessed from different threads concurrently, but this will involve locking and
 * may slow things down in case of intense logging. The thread-safe logger types
 * have the _mt suffix in their name.
 *
 * - boost is not letting me using the unlocked_sink with the text_ostream_backend!!!!
 */

namespace
{
static constexpr const char* hypen = "-";
static constexpr const char* p = "p";
static constexpr const char* dot = ".";
}

namespace logging
{

BOOST_LOG_ATTRIBUTE_KEYWORD(timestamp_attribute, "TimeStamp", std::string)

BOOST_LOG_ATTRIBUTE_KEYWORD(hostname_attribute, "HostName", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(logname_attribute, "LogName", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(user_attribute, "User", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(req_attribute, "Req1stLine", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(status_attribute, "Status", int)
BOOST_LOG_ATTRIBUTE_KEYWORD(size_attribute, "ResponseSize", size_t)
BOOST_LOG_ATTRIBUTE_KEYWORD(referrer_attribute, "Referrer", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(useragent_attribute, "User-agent", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(requesttime_attribute, "RequestTime", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(pipe_attribute, "Pipe", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(requestlength_attribute, "RequestLength", size_t)
BOOST_LOG_ATTRIBUTE_KEYWORD(microserver_attribute, "Microserver", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(host_attribute, "Host", std::string)

// ----------------------------------------------------

struct filelogger_base
{
	using sink_backend_t = boost::log::sinks::text_file_backend;
	using sink_frontend_t = boost::log::sinks::synchronous_sink<sink_backend_t>;

	boost::shared_ptr<sink_frontend_t> log_frtnd_;

	filelogger_base(const std::string& log_dir, const std::string& file_prefix, const std::string& channel)
		: log_frtnd_{boost::make_shared<sink_frontend_t>(
						 boost::make_shared<sink_backend_t>(
							boost::log::keywords::file_name = build_path(log_dir, file_prefix) + ".log",
							boost::log::keywords::rotation_size = 1 * 1024 * 1024,
							boost::log::keywords::open_mode = std::ios_base::app
			))}
	{
		using namespace boost::log;

		// minimum scope to make release the locked_ptr
		{
			auto log_bcknd = log_frtnd_->locked_backend();
			// manage log file collection and avoid overwriting existing log files at startup
			log_bcknd->set_file_collector(sinks::file::make_collector(
				keywords::target = build_path(log_dir, file_prefix) + "_old",
				keywords::max_size = 200 * 1024 * 1024, // max total size of collected log files
				keywords::min_free_space = 1024 * 1024 * 1024));
			log_bcknd->scan_for_files();
			log_bcknd->auto_flush(true);
		}

		// set up filtering for local / remote logging
		log_frtnd_->set_filter(expressions::attr<std::string>("Channel") == channel);

		// register the sink to the core
		core::get()->add_sink(log_frtnd_);
	}

	~filelogger_base() noexcept
	{
		// detach sinks from the core, otherwise they will go on logging...
		boost::log::core::get()->remove_sink(log_frtnd_);

		/* ensure that sinks are flushed (couldn't find in the
		 * documentation that sinks are flushed at destruction time) */
		log_frtnd_->flush();
	}

	void flush()
	{
		log_frtnd_->flush();
	}

	static std::string build_path(const std::string& log_dir, const std::string& file_name)
	{
		std::string ret = file_name.empty() ? std::string("log") : file_name;
		if(log_dir.empty()) return ret;

		if(log_dir.back() == '/')
			return log_dir + (ret.front() == '/' ? ret.substr(1) : ret);
		else
			return log_dir + (ret.front() == '/' ? ret : ret.insert(0, "/"));
	}
};

// -----------------------------------------------------------------------------------
// access log class implementation
// -----------------------------------------------------------------------------------

class access_log::impl : public filelogger_base
{
public:
	impl(const std::string& log_dir, const std::string& file_prefix);
	impl(const impl&) = delete;
	impl(impl&& l) = default;
	impl& operator=(const impl&) = delete;
	impl& operator=(impl&& l) = default;

	void log(const access_record& r);

private:
	static const std::string channel_name;
};

access_log::impl::impl(const std::string& log_dir, const std::string& file_prefix)
	: filelogger_base{log_dir, file_prefix, channel_name}
{
	using namespace boost::log;

	// set up log formatting
	log_frtnd_->set_formatter
	(
		// 127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326
		// Access log attributes: "%h %l %u %t \"%r\" %>s %b" common
		expressions::format(R"out(%1% %2% %3% [%4%] "%5%" %6% %7% "%8%" "%9%" %10% %11% %12% "%13%" "%14%")out")
			% hostname_attribute
			% logname_attribute
			% user_attribute
			% timestamp_attribute
			% req_attribute
			% status_attribute
			% size_attribute
			% referrer_attribute
			% useragent_attribute
			% requesttime_attribute
			% pipe_attribute
			% requestlength_attribute
			% microserver_attribute
			% host_attribute
	);
}

void access_log::impl::log(const access_record& r)
{
	static thread_local boost::log::sources::channel_logger<std::string> logger_{boost::log::keywords::channel = channel_name};
	static thread_local boost::log::attributes::mutable_constant<std::string> hostname_attr{""};
	static thread_local boost::log::attributes::mutable_constant<std::string> logname_attr{""};
	static thread_local boost::log::attributes::mutable_constant<std::string> user_attr{""};
	static thread_local boost::log::attributes::mutable_constant<std::string> req_attr{""};
	static thread_local boost::log::attributes::mutable_constant<int> status_attr{0};
	static thread_local boost::log::attributes::mutable_constant<size_t> size_attr{0};
	static thread_local boost::log::attributes::mutable_constant<std::string> referrer_attribute{""};
	static thread_local boost::log::attributes::mutable_constant<std::string> useragent_attribute{""};
	static thread_local boost::log::attributes::mutable_constant<std::string> requesttime_attribute{""};
	static thread_local boost::log::attributes::mutable_constant<std::string> pipe_attribute{""};
	static thread_local boost::log::attributes::mutable_constant<size_t> requestlength_attribute{0};
	static thread_local boost::log::attributes::mutable_constant<std::string> microserver_attribute{""};
	static thread_local boost::log::attributes::mutable_constant<std::string> host_attribute{""};

	static thread_local bool init = true;
	if(init)
	{
		init = false;
		logger_.add_attribute("HostName", hostname_attr);
		logger_.add_attribute("LogName", logname_attr);
		logger_.add_attribute("User", user_attr);
		logger_.add_attribute("Req1stLine", req_attr);
		logger_.add_attribute("Status", status_attr);
		logger_.add_attribute("ResponseSize", size_attr);
		logger_.add_attribute("TimeStamp", boost::log::attributes::make_function([]{
			return format::apache_access_time(std::chrono::system_clock::now());
		}));
		logger_.add_attribute("Referrer", referrer_attribute);
		logger_.add_attribute("User-agent", useragent_attribute);
		logger_.add_attribute("RequestTime", requesttime_attribute);
		logger_.add_attribute("Pipe", pipe_attribute);
		logger_.add_attribute("RequestLength", requestlength_attribute);
		logger_.add_attribute("Microserver", microserver_attribute);
		logger_.add_attribute("Host", host_attribute);
	}

	// duration _zero_ must be printed as "0.000"... we must do it by ourselves! thanks boost::log...
	static thread_local std::string req_time_str(16, 'x');
	{
		using namespace std::chrono;
		auto dur = duration_cast<std::chrono::duration<double,std::ratio<1,1>>>(r.end - r.start).count();
		size_t n = snprintf(const_cast<char*>(req_time_str.data()), req_time_str.capacity(), "%.3f", dur);
		req_time_str.resize(n);
		if(n >= req_time_str.capacity())
			req_time_str.assign(req_time_str.size(), 'x');
	}

	hostname_attr.set(r.remote_addr.empty() ? hypen : static_cast<std::string>( r.remote_addr ) );
	logname_attr.set(hypen);
	user_attr.set(r.remote_user.empty() ? hypen : static_cast<std::string>( r.remote_user) );
	req_attr.set( static_cast<std::string>( r.req ) );
	status_attr.set( r.status );
	size_attr.set(r.body_bytes_sent);
	referrer_attribute.set(r.referer.empty() ? hypen : static_cast<std::string>( r.referer ) );
	useragent_attribute.set(r.user_agent.empty() ? hypen : static_cast<std::string>( r.user_agent) );
	requesttime_attribute.set(req_time_str);
	pipe_attribute.set(r.pipe ? p : dot);
	requestlength_attribute.set(r.req_length);
	microserver_attribute.set(r.x_debug_cyn_ip.empty() ? hypen : static_cast<std::string>( r.x_debug_cyn_ip) ) ;
	host_attribute.set(r.host.empty() ? hypen : static_cast<std::string>( r.host ) );

	BOOST_LOG(logger_);
}

std::ostream& operator<<(std::ostream& os, severity level)
{
	switch(level)
	{
	case severity::emerg:
		os << "emerg";
		break;
	case severity::alert:
		os << "alert";
		break;
	case severity::crit:
		os << "crit";
		break;
	case severity::error:
		os << "error";
		break;
	case severity::warn:
		os << "warn";
		break;
	case severity::notice:
		os << "notice";
		break;
	case severity::info:
		os << "info";
		break;
	case severity::debug:
		os << "debug";
		break;
	case severity::trace1:
		os << "trace1";
		break;
	case severity::trace2:
		os << "trace2";
		break;
	case severity::trace3:
		os << "trace3";
		break;
	case severity::trace4:
		os << "trace4";
		break;
	case severity::trace5:
		os << "trace5";
		break;
	case severity::trace6:
		os << "trace6";
		break;
	case severity::trace7:
		os << "trace7";
		break;
	case severity::trace8:
		os << "trace8";
		break;
	}

	return os;
}

// ----- static vars initialization
const std::string access_log::impl::channel_name = "access";

// *************************** access log ********************************
access_log::access_log(const std::string& log_dir, const std::string& file_prefix)
	: pImpl{new impl{log_dir, file_prefix}}
{}

void access_log::log(const access_record& r) noexcept
{
	pImpl->log(r);
}

void access_log::flush()
{
	pImpl->flush();
}

access_log::~access_log() {}

}
<|endoftext|>


<commit_before>//
// Aspia Project
// Copyright (C) 2018 Dmitry Chapyshev <dmitry@aspia.ru>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//

#include "host/input_injector.h"

#include <QDebug>

#include <set>
#include <qt_windows.h>
#include <sas.h>

#include "base/win/registry.h"
#include "base/errno_logging.h"
#include "base/keycode_converter.h"
#include "desktop_capture/win/scoped_thread_desktop.h"
#include "desktop_capture/desktop_geometry.h"

namespace aspia {

namespace {

const wchar_t kSoftwareSASGenerationPath[] =
    L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System";

const wchar_t kSoftwareSASGeneration[] = L"SoftwareSASGeneration";

const uint32_t kUsbCodeDelete = 0x07004c;
const uint32_t kUsbCodeLeftCtrl = 0x0700e0;
const uint32_t kUsbCodeRightCtrl = 0x0700e4;
const uint32_t kUsbCodeLeftAlt = 0x0700e2;
const uint32_t kUsbCodeRightAlt = 0x0700e6;

void sendKeyboardScancode(WORD scancode, DWORD flags)
{
    INPUT input;
    memset(&input, 0, sizeof(input));

    input.type       = INPUT_KEYBOARD;
    input.ki.dwFlags = flags;
    input.ki.wScan   = scancode;

    if (!(flags & KEYEVENTF_UNICODE))
    {
        input.ki.wScan &= 0xFF;

        if ((scancode & 0xFF00) != 0x0000)
            input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
    }

    // Do the keyboard event.
    if (!SendInput(1, &input, sizeof(input)))
        qWarningErrno("SendInput failed");
}

void sendKeyboardVirtualKey(WORD key_code, DWORD flags)
{
    INPUT input;
    memset(&input, 0, sizeof(input));

    input.type       = INPUT_KEYBOARD;
    input.ki.wVk     = key_code;
    input.ki.dwFlags = flags;
    input.ki.wScan   = static_cast<WORD>(MapVirtualKeyW(key_code, MAPVK_VK_TO_VSC));

    // Do the keyboard event.
    if (!SendInput(1, &input, sizeof(input)))
        qWarningErrno("SendInput failed");
}

class InputInjectorImpl
{
public:
    InputInjectorImpl(bool block_input);
    ~InputInjectorImpl();

    void injectPointerEvent(const proto::desktop::PointerEvent& event);
    void injectKeyEvent(const proto::desktop::KeyEvent& event);

private:
    void switchToInputDesktop();
    void injectSAS();
    bool isCtrlAndAltPressed();

    ScopedThreadDesktop desktop_;

    const bool block_input_;

    std::set<uint32_t> pressed_keys_;
    DesktopPoint prev_mouse_pos_;

    uint32_t prev_mouse_button_mask_ = 0;

    DISALLOW_COPY_AND_ASSIGN(InputInjectorImpl);
};

InputInjectorImpl::InputInjectorImpl(bool block_input)
    : block_input_(block_input)
{
    switchToInputDesktop();

    if (block_input_)
        BlockInput(TRUE);
}

InputInjectorImpl::~InputInjectorImpl()
{
    for (auto usb_keycode : pressed_keys_)
    {
        int scancode = KeycodeConverter::usbKeycodeToNativeKeycode(usb_keycode);
        if (scancode != KeycodeConverter::invalidNativeKeycode())
        {
            sendKeyboardScancode(static_cast<WORD>(scancode),
                                 KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP);
        }
    }
}

void InputInjectorImpl::injectPointerEvent(const proto::desktop::PointerEvent& event)
{
    switchToInputDesktop();

    DesktopSize screen_size(GetSystemMetrics(SM_CXVIRTUALSCREEN),
                            GetSystemMetrics(SM_CYVIRTUALSCREEN));
    if (screen_size.isEmpty())
        return;

    int x = std::max(0, std::min(screen_size.width(), event.x()));
    int y = std::max(0, std::min(screen_size.height(), event.y()));

    // Translate the coordinates of the cursor into the coordinates of the virtual screen.
    DesktopPoint pos((x * 65535) / (screen_size.width() - 1),
                     (y * 65535) / (screen_size.height() - 1));

    DWORD flags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK;
    DWORD wheel_movement = 0;

    if (pos != prev_mouse_pos_)
    {
        flags |= MOUSEEVENTF_MOVE;
        prev_mouse_pos_ = pos;
    }

    uint32_t mask = event.mask();

    bool prev = (prev_mouse_button_mask_ & proto::desktop::PointerEvent::LEFT_BUTTON) != 0;
    bool curr = (mask & proto::desktop::PointerEvent::LEFT_BUTTON) != 0;

    if (curr != prev)
    {
        flags |= (curr ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP);
    }

    prev = (prev_mouse_button_mask_ & proto::desktop::PointerEvent::MIDDLE_BUTTON) != 0;
    curr = (mask & proto::desktop::PointerEvent::MIDDLE_BUTTON) != 0;

    if (curr != prev)
    {
        flags |= (curr ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP);
    }

    prev = (prev_mouse_button_mask_ & proto::desktop::PointerEvent::RIGHT_BUTTON) != 0;
    curr = (mask & proto::desktop::PointerEvent::RIGHT_BUTTON) != 0;

    if (curr != prev)
    {
        flags |= (curr ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP);
    }

    if (mask & proto::desktop::PointerEvent::WHEEL_UP)
    {
        flags |= MOUSEEVENTF_WHEEL;
        wheel_movement = static_cast<DWORD>(WHEEL_DELTA);
    }
    else if (mask & proto::desktop::PointerEvent::WHEEL_DOWN)
    {
        flags |= MOUSEEVENTF_WHEEL;
        wheel_movement = static_cast<DWORD>(-WHEEL_DELTA);
    }

    INPUT input;
    memset(&input, 0, sizeof(input));

    input.type         = INPUT_MOUSE;
    input.mi.dx        = pos.x();
    input.mi.dy        = pos.y();
    input.mi.mouseData = wheel_movement;
    input.mi.dwFlags   = flags;

    // Do the mouse event.
    if (!SendInput(1, &input, sizeof(input)))
        qWarningErrno("SendInput failed");

    prev_mouse_button_mask_ = mask;
}

void InputInjectorImpl::injectKeyEvent(const proto::desktop::KeyEvent& event)
{
    if (event.flags() & proto::desktop::KeyEvent::PRESSED)
    {
        pressed_keys_.insert(event.usb_keycode());

        if (event.usb_keycode() == kUsbCodeDelete && isCtrlAndAltPressed())
        {
            injectSAS();
            return;
        }
    }
    else
    {
        pressed_keys_.erase(event.usb_keycode());
    }

    int scancode = KeycodeConverter::usbKeycodeToNativeKeycode(event.usb_keycode());
    if (scancode == KeycodeConverter::invalidNativeKeycode())
        return;

    switchToInputDesktop();

    bool prev_state = GetKeyState(VK_CAPITAL) != 0;
    bool curr_state = (event.flags() & proto::desktop::KeyEvent::CAPSLOCK) != 0;

    if (prev_state != curr_state)
    {
        sendKeyboardVirtualKey(VK_CAPITAL, 0);
        sendKeyboardVirtualKey(VK_CAPITAL, KEYEVENTF_KEYUP);
    }

    prev_state = GetKeyState(VK_NUMLOCK) != 0;
    curr_state = (event.flags() & proto::desktop::KeyEvent::NUMLOCK) != 0;

    if (prev_state != curr_state)
    {
        sendKeyboardVirtualKey(VK_NUMLOCK, 0);
        sendKeyboardVirtualKey(VK_NUMLOCK, KEYEVENTF_KEYUP);
    }

    DWORD flags = KEYEVENTF_SCANCODE;

    if (!(event.flags() & proto::desktop::KeyEvent::PRESSED))
        flags |= KEYEVENTF_KEYUP;

    sendKeyboardScancode(static_cast<WORD>(scancode), flags);
}

void InputInjectorImpl::switchToInputDesktop()
{
    Desktop input_desktop(Desktop::inputDesktop());

    if (input_desktop.isValid() && !desktop_.isSame(input_desktop))
        desktop_.setThreadDesktop(std::move(input_desktop));

    if (block_input_)
        BlockInput(TRUE);

    // We send a notification to the system that it is used to prevent
    // the screen saver, going into hibernation mode, etc.
    SetThreadExecutionState(ES_SYSTEM_REQUIRED);
}

void InputInjectorImpl::injectSAS()
{
    static const DWORD kNone = 0;
    static const DWORD kApplications = 2;

    RegistryKey key;
    DWORD old_state = kNone;

    LONG status = key.create(HKEY_LOCAL_MACHINE, kSoftwareSASGenerationPath, KEY_READ | KEY_WRITE);
    if (status != ERROR_SUCCESS)
    {
        qWarning() << "key.create failed" << errnoToString(status);
    }
    else
    {
        status = key.readValueDW(kSoftwareSASGeneration, &old_state);
        if (status != ERROR_SUCCESS)
        {
            // The previous state is not defined.
            // Consider that the software generation of SAS has been disabled.
            old_state = kNone;
        }

        if (old_state < kApplications)
        {
            status = key.writeValue(kSoftwareSASGeneration, kApplications);
            if (status != ERROR_SUCCESS)
            {
                qWarning() << "key.writeValue failed" << errnoToString(status);
                key.close();
            }
        }
    }

    SendSAS(FALSE);

    if (key.isValid())
    {
        LONG status = key.writeValue(kSoftwareSASGeneration, old_state);
        if (status != ERROR_SUCCESS)
        {
            qWarning() << "key.writeValue failed" << errnoToString(status);
        }
    }
}

bool InputInjectorImpl::isCtrlAndAltPressed()
{
    bool ctrl_pressed = false;
    bool alt_pressed = false;

    for (const auto& key : pressed_keys_)
    {
        if (key == kUsbCodeLeftCtrl || key == kUsbCodeRightCtrl)
            ctrl_pressed = true;

        if (key == kUsbCodeLeftAlt || key == kUsbCodeRightAlt)
            alt_pressed = true;
    }

    return ctrl_pressed && alt_pressed;
}

} // namespace

InputInjector::InputInjector(QObject* parent, bool block_input)
    : QThread(parent),
      block_input_(block_input)
{
    start(QThread::HighPriority);
}

InputInjector::~InputInjector()
{
    {
        std::scoped_lock lock(input_queue_lock_);
        terminate_ = true;
        input_event_.notify_one();
    }

    wait();
}

void InputInjector::injectPointerEvent(const proto::desktop::PointerEvent& event)
{
    std::scoped_lock lock(input_queue_lock_);
    incoming_input_queue_.emplace(event);
    input_event_.notify_one();
}

void InputInjector::injectKeyEvent(const proto::desktop::KeyEvent& event)
{
    std::scoped_lock lock(input_queue_lock_);
    incoming_input_queue_.emplace(event);
    input_event_.notify_one();
}

void InputInjector::run()
{
    InputInjectorImpl impl(block_input_);

    while (true)
    {
        std::queue<InputEvent> work_input_queue;

        {
            std::unique_lock lock(input_queue_lock_);

            while (incoming_input_queue_.empty() && !terminate_)
                input_event_.wait(lock);

            if (terminate_)
                return;

            work_input_queue.swap(incoming_input_queue_);
        }

        while (!work_input_queue.empty())
        {
            const InputEvent& input_event = work_input_queue.front();

            if (std::holds_alternative<proto::desktop::KeyEvent>(input_event))
            {
                impl.injectKeyEvent(std::get<proto::desktop::KeyEvent>(input_event));
            }
            else if (std::holds_alternative<proto::desktop::PointerEvent>(input_event))
            {
                impl.injectPointerEvent(std::get<proto::desktop::PointerEvent>(input_event));
            }

            work_input_queue.pop();
        }
    }
}

} // namespace aspia
<commit_msg>- Correct processing of the swapped right and left mouse buttons.<commit_after>//
// Aspia Project
// Copyright (C) 2018 Dmitry Chapyshev <dmitry@aspia.ru>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//

#include "host/input_injector.h"

#include <QDebug>

#include <set>
#include <qt_windows.h>
#include <sas.h>

#include "base/win/registry.h"
#include "base/errno_logging.h"
#include "base/keycode_converter.h"
#include "desktop_capture/win/scoped_thread_desktop.h"
#include "desktop_capture/desktop_geometry.h"

namespace aspia {

namespace {

const wchar_t kSoftwareSASGenerationPath[] =
    L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System";

const wchar_t kSoftwareSASGeneration[] = L"SoftwareSASGeneration";

const uint32_t kUsbCodeDelete = 0x07004c;
const uint32_t kUsbCodeLeftCtrl = 0x0700e0;
const uint32_t kUsbCodeRightCtrl = 0x0700e4;
const uint32_t kUsbCodeLeftAlt = 0x0700e2;
const uint32_t kUsbCodeRightAlt = 0x0700e6;

void sendKeyboardScancode(WORD scancode, DWORD flags)
{
    INPUT input;
    memset(&input, 0, sizeof(input));

    input.type       = INPUT_KEYBOARD;
    input.ki.dwFlags = flags;
    input.ki.wScan   = scancode;

    if (!(flags & KEYEVENTF_UNICODE))
    {
        input.ki.wScan &= 0xFF;

        if ((scancode & 0xFF00) != 0x0000)
            input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
    }

    // Do the keyboard event.
    if (!SendInput(1, &input, sizeof(input)))
        qWarningErrno("SendInput failed");
}

void sendKeyboardVirtualKey(WORD key_code, DWORD flags)
{
    INPUT input;
    memset(&input, 0, sizeof(input));

    input.type       = INPUT_KEYBOARD;
    input.ki.wVk     = key_code;
    input.ki.dwFlags = flags;
    input.ki.wScan   = static_cast<WORD>(MapVirtualKeyW(key_code, MAPVK_VK_TO_VSC));

    // Do the keyboard event.
    if (!SendInput(1, &input, sizeof(input)))
        qWarningErrno("SendInput failed");
}

class InputInjectorImpl
{
public:
    InputInjectorImpl(bool block_input);
    ~InputInjectorImpl();

    void injectPointerEvent(const proto::desktop::PointerEvent& event);
    void injectKeyEvent(const proto::desktop::KeyEvent& event);

private:
    void switchToInputDesktop();
    void injectSAS();
    bool isCtrlAndAltPressed();

    ScopedThreadDesktop desktop_;

    const bool block_input_;

    std::set<uint32_t> pressed_keys_;
    DesktopPoint prev_mouse_pos_;

    uint32_t prev_mouse_button_mask_ = 0;

    DISALLOW_COPY_AND_ASSIGN(InputInjectorImpl);
};

InputInjectorImpl::InputInjectorImpl(bool block_input)
    : block_input_(block_input)
{
    switchToInputDesktop();

    if (block_input_)
        BlockInput(TRUE);
}

InputInjectorImpl::~InputInjectorImpl()
{
    for (auto usb_keycode : pressed_keys_)
    {
        int scancode = KeycodeConverter::usbKeycodeToNativeKeycode(usb_keycode);
        if (scancode != KeycodeConverter::invalidNativeKeycode())
        {
            sendKeyboardScancode(static_cast<WORD>(scancode),
                                 KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP);
        }
    }
}

void InputInjectorImpl::injectPointerEvent(const proto::desktop::PointerEvent& event)
{
    switchToInputDesktop();

    DesktopSize screen_size(GetSystemMetrics(SM_CXVIRTUALSCREEN),
                            GetSystemMetrics(SM_CYVIRTUALSCREEN));
    if (screen_size.isEmpty())
        return;

    int x = std::max(0, std::min(screen_size.width(), event.x()));
    int y = std::max(0, std::min(screen_size.height(), event.y()));

    // Translate the coordinates of the cursor into the coordinates of the virtual screen.
    DesktopPoint pos((x * 65535) / (screen_size.width() - 1),
                     (y * 65535) / (screen_size.height() - 1));

    DWORD flags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK;
    DWORD wheel_movement = 0;

    if (pos != prev_mouse_pos_)
    {
        flags |= MOUSEEVENTF_MOVE;
        prev_mouse_pos_ = pos;
    }

    uint32_t mask = event.mask();

    // If the host is configured to swap left & right buttons.
    bool swap_buttons = !!GetSystemMetrics(SM_SWAPBUTTON);

    bool prev = (prev_mouse_button_mask_ & proto::desktop::PointerEvent::LEFT_BUTTON) != 0;
    bool curr = (mask & proto::desktop::PointerEvent::LEFT_BUTTON) != 0;
    if (curr != prev)
    {
        if (!swap_buttons)
            flags |= (curr ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP);
        else
            flags |= (curr ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP);
    }

    prev = (prev_mouse_button_mask_ & proto::desktop::PointerEvent::MIDDLE_BUTTON) != 0;
    curr = (mask & proto::desktop::PointerEvent::MIDDLE_BUTTON) != 0;
    if (curr != prev)
    {
        flags |= (curr ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP);
    }

    prev = (prev_mouse_button_mask_ & proto::desktop::PointerEvent::RIGHT_BUTTON) != 0;
    curr = (mask & proto::desktop::PointerEvent::RIGHT_BUTTON) != 0;
    if (curr != prev)
    {
        if (!swap_buttons)
            flags |= (curr ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP);
        else
            flags |= (curr ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP);
    }

    if (mask & proto::desktop::PointerEvent::WHEEL_UP)
    {
        flags |= MOUSEEVENTF_WHEEL;
        wheel_movement = static_cast<DWORD>(WHEEL_DELTA);
    }
    else if (mask & proto::desktop::PointerEvent::WHEEL_DOWN)
    {
        flags |= MOUSEEVENTF_WHEEL;
        wheel_movement = static_cast<DWORD>(-WHEEL_DELTA);
    }

    INPUT input;
    memset(&input, 0, sizeof(input));

    input.type         = INPUT_MOUSE;
    input.mi.dx        = pos.x();
    input.mi.dy        = pos.y();
    input.mi.mouseData = wheel_movement;
    input.mi.dwFlags   = flags;

    // Do the mouse event.
    if (!SendInput(1, &input, sizeof(input)))
        qWarningErrno("SendInput failed");

    prev_mouse_button_mask_ = mask;
}

void InputInjectorImpl::injectKeyEvent(const proto::desktop::KeyEvent& event)
{
    if (event.flags() & proto::desktop::KeyEvent::PRESSED)
    {
        pressed_keys_.insert(event.usb_keycode());

        if (event.usb_keycode() == kUsbCodeDelete && isCtrlAndAltPressed())
        {
            injectSAS();
            return;
        }
    }
    else
    {
        pressed_keys_.erase(event.usb_keycode());
    }

    int scancode = KeycodeConverter::usbKeycodeToNativeKeycode(event.usb_keycode());
    if (scancode == KeycodeConverter::invalidNativeKeycode())
        return;

    switchToInputDesktop();

    bool prev_state = GetKeyState(VK_CAPITAL) != 0;
    bool curr_state = (event.flags() & proto::desktop::KeyEvent::CAPSLOCK) != 0;

    if (prev_state != curr_state)
    {
        sendKeyboardVirtualKey(VK_CAPITAL, 0);
        sendKeyboardVirtualKey(VK_CAPITAL, KEYEVENTF_KEYUP);
    }

    prev_state = GetKeyState(VK_NUMLOCK) != 0;
    curr_state = (event.flags() & proto::desktop::KeyEvent::NUMLOCK) != 0;

    if (prev_state != curr_state)
    {
        sendKeyboardVirtualKey(VK_NUMLOCK, 0);
        sendKeyboardVirtualKey(VK_NUMLOCK, KEYEVENTF_KEYUP);
    }

    DWORD flags = KEYEVENTF_SCANCODE;

    if (!(event.flags() & proto::desktop::KeyEvent::PRESSED))
        flags |= KEYEVENTF_KEYUP;

    sendKeyboardScancode(static_cast<WORD>(scancode), flags);
}

void InputInjectorImpl::switchToInputDesktop()
{
    Desktop input_desktop(Desktop::inputDesktop());

    if (input_desktop.isValid() && !desktop_.isSame(input_desktop))
        desktop_.setThreadDesktop(std::move(input_desktop));

    if (block_input_)
        BlockInput(TRUE);

    // We send a notification to the system that it is used to prevent
    // the screen saver, going into hibernation mode, etc.
    SetThreadExecutionState(ES_SYSTEM_REQUIRED);
}

void InputInjectorImpl::injectSAS()
{
    static const DWORD kNone = 0;
    static const DWORD kApplications = 2;

    RegistryKey key;
    DWORD old_state = kNone;

    LONG status = key.create(HKEY_LOCAL_MACHINE, kSoftwareSASGenerationPath, KEY_READ | KEY_WRITE);
    if (status != ERROR_SUCCESS)
    {
        qWarning() << "key.create failed" << errnoToString(status);
    }
    else
    {
        status = key.readValueDW(kSoftwareSASGeneration, &old_state);
        if (status != ERROR_SUCCESS)
        {
            // The previous state is not defined.
            // Consider that the software generation of SAS has been disabled.
            old_state = kNone;
        }

        if (old_state < kApplications)
        {
            status = key.writeValue(kSoftwareSASGeneration, kApplications);
            if (status != ERROR_SUCCESS)
            {
                qWarning() << "key.writeValue failed" << errnoToString(status);
                key.close();
            }
        }
    }

    SendSAS(FALSE);

    if (key.isValid())
    {
        LONG status = key.writeValue(kSoftwareSASGeneration, old_state);
        if (status != ERROR_SUCCESS)
        {
            qWarning() << "key.writeValue failed" << errnoToString(status);
        }
    }
}

bool InputInjectorImpl::isCtrlAndAltPressed()
{
    bool ctrl_pressed = false;
    bool alt_pressed = false;

    for (const auto& key : pressed_keys_)
    {
        if (key == kUsbCodeLeftCtrl || key == kUsbCodeRightCtrl)
            ctrl_pressed = true;

        if (key == kUsbCodeLeftAlt || key == kUsbCodeRightAlt)
            alt_pressed = true;
    }

    return ctrl_pressed && alt_pressed;
}

} // namespace

InputInjector::InputInjector(QObject* parent, bool block_input)
    : QThread(parent),
      block_input_(block_input)
{
    start(QThread::HighPriority);
}

InputInjector::~InputInjector()
{
    {
        std::scoped_lock lock(input_queue_lock_);
        terminate_ = true;
        input_event_.notify_one();
    }

    wait();
}

void InputInjector::injectPointerEvent(const proto::desktop::PointerEvent& event)
{
    std::scoped_lock lock(input_queue_lock_);
    incoming_input_queue_.emplace(event);
    input_event_.notify_one();
}

void InputInjector::injectKeyEvent(const proto::desktop::KeyEvent& event)
{
    std::scoped_lock lock(input_queue_lock_);
    incoming_input_queue_.emplace(event);
    input_event_.notify_one();
}

void InputInjector::run()
{
    InputInjectorImpl impl(block_input_);

    while (true)
    {
        std::queue<InputEvent> work_input_queue;

        {
            std::unique_lock lock(input_queue_lock_);

            while (incoming_input_queue_.empty() && !terminate_)
                input_event_.wait(lock);

            if (terminate_)
                return;

            work_input_queue.swap(incoming_input_queue_);
        }

        while (!work_input_queue.empty())
        {
            const InputEvent& input_event = work_input_queue.front();

            if (std::holds_alternative<proto::desktop::KeyEvent>(input_event))
            {
                impl.injectKeyEvent(std::get<proto::desktop::KeyEvent>(input_event));
            }
            else if (std::holds_alternative<proto::desktop::PointerEvent>(input_event))
            {
                impl.injectPointerEvent(std::get<proto::desktop::PointerEvent>(input_event));
            }

            work_input_queue.pop();
        }
    }
}

} // namespace aspia
<|endoftext|>


<commit_before>//@author A0097630B
#include "stdafx.h"
#include "query_executor.h"
#include "../exceptions/context_index_out_of_range_exception.h"
#include "../result.h"
#include "query_executor_builder_visitor.h"

namespace You {
namespace Controller {
namespace Internal {

using You::NLP::TaskField;
using You::NLP::TaskPriority;
using You::NLP::QUERY;
using You::NLP::ADD_QUERY;
using You::NLP::SHOW_QUERY;
using You::NLP::EDIT_QUERY;
using You::NLP::DELETE_QUERY;
using You::NLP::UNDO_QUERY;

QueryExecutorBuilderVisitor::QueryExecutorBuilderVisitor(
	const Controller::Context& context)
	: context(context) {
}

std::unique_ptr<QueryExecutor>
QueryExecutorBuilderVisitor::build(const ADD_QUERY& query) {
	class AddTaskQueryExecutor : public QueryExecutor {
	public:
		explicit AddTaskQueryExecutor(
			std::unique_ptr<QueryEngine::Query>&& query)
			: QueryExecutor(std::move(query)) {
		}

		virtual ~AddTaskQueryExecutor() = default;

	protected:
		Result processResponse(
			const You::QueryEngine::Response& response) override {
			return ADD_RESULT {
				boost::get<Task>(response)
			};
		}
	};

	return std::unique_ptr<QueryExecutor>(
		new AddTaskQueryExecutor(
			QueryEngine::AddTask(
				query.description,
				query.deadline ? query.deadline.get() : Task::DEFAULT_DEADLINE,
				query.priority == TaskPriority::HIGH ?
					Task::Priority::HIGH : Task::Priority::NORMAL,
				Task::Dependencies(),
				Task::Subtasks()
			)
		)
	);
}

std::unique_ptr<QueryExecutor>
QueryExecutorBuilderVisitor::build(const SHOW_QUERY& query) {
	class ShowTaskQueryExecutor : public QueryExecutor {
	public:
		explicit ShowTaskQueryExecutor(
			std::unique_ptr<QueryEngine::Query>&& query)
			: QueryExecutor(std::move(query)) {
		}

		virtual ~ShowTaskQueryExecutor() = default;

	protected:
		Result processResponse(
			const You::QueryEngine::Response& response) override {
			return SHOW_RESULT {
				boost::get<TaskList>(response)
			};
		}
	};

	using You::QueryEngine::Filter;
	using You::QueryEngine::Comparator;
	Filter filter = Filter::anyTask();
	Comparator comparator(Comparator::notSorted());

	std::for_each(begin(query.predicates), end(query.predicates),
		[&filter](const SHOW_QUERY::FIELD_FILTER& field) {
			std::function<bool(const Task&)> currentFilter;
			switch (field.field) {
			case TaskField::DESCRIPTION:
				assert(boost::get<std::wstring>(&field.value));
				currentFilter = buildComparator(&Task::getDescription,
					field.predicate,
					boost::get<std::wstring>(field.value));
				break;
			case TaskField::DEADLINE:
				assert(boost::get<boost::posix_time::ptime>(&field.value));
				currentFilter = buildComparator(&Task::getDeadline,
					field.predicate,
					boost::get<boost::posix_time::ptime>(field.value));
				break;
			case TaskField::COMPLETE:
				assert(boost::get<bool>(&field.value));
				currentFilter = buildComparator(&Task::isCompleted,
					field.predicate,
					boost::get<bool>(field.value));
				break;
			case TaskField::PRIORITY:
				assert(boost::get<TaskPriority>(&field.value));
				currentFilter = buildComparator(&Task::getPriority,
					field.predicate,
					Controller::nlpToQueryEnginePriority(
						boost::get<TaskPriority>(field.value)));
				break;
			default:
				assert(false); abort();
			}

			filter = filter && Filter(currentFilter);
		});

	std::for_each(begin(query.order), end(query.order),
		[&comparator](const SHOW_QUERY::FIELD_ORDER& field) {
			Comparator comp(Comparator::notSorted());
			switch (field.field) {
			case TaskField::DESCRIPTION:
				comp = Comparator::byDescription();
				break;
			case TaskField::DEADLINE:
				comp = Comparator::byDeadline();
				break;
			case TaskField::PRIORITY:
				comp = Comparator::byPriority();
				break;
			case TaskField::COMPLETE:
			default:
				assert(false); abort();
			}

			if (field.order == SHOW_QUERY::Order::ASCENDING) {
				comp = comp.ascending();
			} else {
				comp = comp.descending();
			}

			comparator = comparator && comp;
		});

	return std::unique_ptr<QueryExecutor>(
		new ShowTaskQueryExecutor(
			QueryEngine::GetTask(filter, comparator)
		)
	);
}

template<typename TValue>
std::function<bool(const Task&)>
QueryExecutorBuilderVisitor::buildComparator(
	TValue(QueryEngine::Task::*selector)() const,
	SHOW_QUERY::Predicate predicate,
	const TValue& value) {
	switch (predicate) {
	case SHOW_QUERY::Predicate::EQ:
		return std::bind(std::equal_to<TValue>(),
			std::bind(selector, std::placeholders::_1),
			value);
	case SHOW_QUERY::Predicate::NOT_EQ:
		return std::bind(std::not_equal_to<TValue>(),
			std::bind(selector, std::placeholders::_1),
			value);
	case SHOW_QUERY::Predicate::LESS_THAN:
		return std::bind(std::less<TValue>(),
			std::bind(selector, std::placeholders::_1),
			value);
	case SHOW_QUERY::Predicate::LESS_THAN_EQ:
		return std::bind(std::less_equal<TValue>(),
			std::bind(selector, std::placeholders::_1),
			value);
	case SHOW_QUERY::Predicate::GREATER_THAN:
		return std::bind(std::greater<TValue>(),
			std::bind(selector, std::placeholders::_1),
			value);
	case SHOW_QUERY::Predicate::GREATER_THAN_EQ:
		return std::bind(std::greater_equal<TValue>(),
			std::bind(selector, std::placeholders::_1),
			value);
	default:
		assert(false); abort();
	}
}

std::unique_ptr<QueryExecutor>
QueryExecutorBuilderVisitor::build(const EDIT_QUERY& query) const {
	class EditTaskQueryExecutor : public QueryExecutor {
	public:
		explicit EditTaskQueryExecutor(
			std::unique_ptr<QueryEngine::Query>&& query)
			: QueryExecutor(std::move(query)) {
		}

		virtual ~EditTaskQueryExecutor() = default;

	protected:
		Result processResponse(
			const You::QueryEngine::Response& response) override {
			return EDIT_RESULT {
				boost::get<Task>(response)
			};
		}
	};

	try {
		Task::ID task = context.at(query.taskID).getID();
		You::Utils::Option<Task::Priority> priority;
		if (query.priority) {
			priority = Controller::nlpToQueryEnginePriority(
				query.priority.get());
		}

		if (query.childTask) {
			assert(!query.description &&
				!query.deadline &&
				!priority &&
				!query.complete &&
				!query.dependingTask &&
				"Cannot change subtasks with other properties");
			int childTask = query.childTask;
			if (childTask < 0) {
				return std::unique_ptr<QueryExecutor>(
					new EditTaskQueryExecutor(
						QueryEngine::RemoveSubtask(task,
							context.at(-childTask).getID())));
			} else {
				return std::unique_ptr<QueryExecutor>(
					new EditTaskQueryExecutor(
						QueryEngine::AddSubtask(task,
							context.at(childTask).getID())));
			}
		}

		You::Utils::Option<Task::Dependencies> dependencies;
		if (query.dependingTask) {
			assert(!query.description &&
				!query.deadline &&
				!priority &&
				!query.complete &&
				!query.childTask &&
				"Cannot change dependencies with other properties");
			Task::ID dependentTask = task;
			int dependingTask = query.dependingTask;
			if (dependingTask < 0) {
				return std::unique_ptr<QueryExecutor>(
					new EditTaskQueryExecutor(
						QueryEngine::RemoveDependency(
							context.at(-dependingTask).getID(),
							dependentTask)));
			} else {
				return std::unique_ptr<QueryExecutor>(
					new EditTaskQueryExecutor(
						QueryEngine::AddDependency(
							context.at(dependingTask).getID(),
							dependentTask)));
			}
		}

		return std::unique_ptr<QueryExecutor>(
			new EditTaskQueryExecutor(
				QueryEngine::UpdateTask(
					task,
					query.description,
					query.deadline,
					priority,
					boost::none,
					query.complete,
					boost::none,
					boost::none)));
	} catch (std::out_of_range& e) {
		throw ContextIndexOutOfRangeException(e);
	}
}

std::unique_ptr<QueryExecutor>
QueryExecutorBuilderVisitor::build(const DELETE_QUERY& query) const {
	class DeleteTaskQueryExecutor : public QueryExecutor {
	public:
		explicit DeleteTaskQueryExecutor(
			std::unique_ptr<QueryEngine::Query>&& query)
			: QueryExecutor(std::move(query)) {
		}

		virtual ~DeleteTaskQueryExecutor() = default;

	protected:
		Result processResponse(
			const You::QueryEngine::Response& response) override {
			return DELETE_RESULT {
				boost::get<Task::ID>(response)
			};
		}
	};

	try {
		const Task& task = context.at(query.taskID);

		return std::unique_ptr<QueryExecutor>(
			new DeleteTaskQueryExecutor(
				QueryEngine::DeleteTask(
					task.getID())));
	} catch (std::out_of_range& e) {
		throw ContextIndexOutOfRangeException(e);
	}
}

std::unique_ptr<QueryExecutor>
QueryExecutorBuilderVisitor::build(const UNDO_QUERY& /*query*/) const {
	class UndoTaskQueryExecutor : public QueryExecutor {
	public:
		explicit UndoTaskQueryExecutor(
			std::unique_ptr<QueryEngine::Query>&& query)
			: QueryExecutor(std::move(query)) {
		}

		virtual ~UndoTaskQueryExecutor() = default;

	protected:
		Result processResponse(
			const You::QueryEngine::Response& response) override {
			return UNDO_RESULT {
				boost::get<TaskList>(response)
			};
		}
	};

	return std::unique_ptr<QueryExecutor>(
		new UndoTaskQueryExecutor(
			QueryEngine::Undo()));
}

}  // namespace Internal
}  // namespace Controller
}  // namespace You
<commit_msg>Implement adding attachments.<commit_after>//@author A0097630B
#include "stdafx.h"
#include "query_executor.h"
#include "../exceptions/context_index_out_of_range_exception.h"
#include "../result.h"
#include "query_executor_builder_visitor.h"

namespace You {
namespace Controller {
namespace Internal {

using You::NLP::TaskField;
using You::NLP::TaskPriority;
using You::NLP::QUERY;
using You::NLP::ADD_QUERY;
using You::NLP::SHOW_QUERY;
using You::NLP::EDIT_QUERY;
using You::NLP::DELETE_QUERY;
using You::NLP::UNDO_QUERY;

QueryExecutorBuilderVisitor::QueryExecutorBuilderVisitor(
	const Controller::Context& context)
	: context(context) {
}

std::unique_ptr<QueryExecutor>
QueryExecutorBuilderVisitor::build(const ADD_QUERY& query) {
	class AddTaskQueryExecutor : public QueryExecutor {
	public:
		explicit AddTaskQueryExecutor(
			std::unique_ptr<QueryEngine::Query>&& query)
			: QueryExecutor(std::move(query)) {
		}

		virtual ~AddTaskQueryExecutor() = default;

	protected:
		Result processResponse(
			const You::QueryEngine::Response& response) override {
			return ADD_RESULT {
				boost::get<Task>(response)
			};
		}
	};

	return std::unique_ptr<QueryExecutor>(
		new AddTaskQueryExecutor(
			QueryEngine::AddTask(
				query.description,
				query.deadline ? query.deadline.get() : Task::DEFAULT_DEADLINE,
				query.priority == TaskPriority::HIGH ?
					Task::Priority::HIGH : Task::Priority::NORMAL,
				Task::Dependencies(),
				Task::Subtasks()
			)
		)
	);
}

std::unique_ptr<QueryExecutor>
QueryExecutorBuilderVisitor::build(const SHOW_QUERY& query) {
	class ShowTaskQueryExecutor : public QueryExecutor {
	public:
		explicit ShowTaskQueryExecutor(
			std::unique_ptr<QueryEngine::Query>&& query)
			: QueryExecutor(std::move(query)) {
		}

		virtual ~ShowTaskQueryExecutor() = default;

	protected:
		Result processResponse(
			const You::QueryEngine::Response& response) override {
			return SHOW_RESULT {
				boost::get<TaskList>(response)
			};
		}
	};

	using You::QueryEngine::Filter;
	using You::QueryEngine::Comparator;
	Filter filter = Filter::anyTask();
	Comparator comparator(Comparator::notSorted());

	std::for_each(begin(query.predicates), end(query.predicates),
		[&filter](const SHOW_QUERY::FIELD_FILTER& field) {
			std::function<bool(const Task&)> currentFilter;
			switch (field.field) {
			case TaskField::DESCRIPTION:
				assert(boost::get<std::wstring>(&field.value));
				currentFilter = buildComparator(&Task::getDescription,
					field.predicate,
					boost::get<std::wstring>(field.value));
				break;
			case TaskField::DEADLINE:
				assert(boost::get<boost::posix_time::ptime>(&field.value));
				currentFilter = buildComparator(&Task::getDeadline,
					field.predicate,
					boost::get<boost::posix_time::ptime>(field.value));
				break;
			case TaskField::COMPLETE:
				assert(boost::get<bool>(&field.value));
				currentFilter = buildComparator(&Task::isCompleted,
					field.predicate,
					boost::get<bool>(field.value));
				break;
			case TaskField::PRIORITY:
				assert(boost::get<TaskPriority>(&field.value));
				currentFilter = buildComparator(&Task::getPriority,
					field.predicate,
					Controller::nlpToQueryEnginePriority(
						boost::get<TaskPriority>(field.value)));
				break;
			default:
				assert(false); abort();
			}

			filter = filter && Filter(currentFilter);
		});

	std::for_each(begin(query.order), end(query.order),
		[&comparator](const SHOW_QUERY::FIELD_ORDER& field) {
			Comparator comp(Comparator::notSorted());
			switch (field.field) {
			case TaskField::DESCRIPTION:
				comp = Comparator::byDescription();
				break;
			case TaskField::DEADLINE:
				comp = Comparator::byDeadline();
				break;
			case TaskField::PRIORITY:
				comp = Comparator::byPriority();
				break;
			case TaskField::COMPLETE:
			default:
				assert(false); abort();
			}

			if (field.order == SHOW_QUERY::Order::ASCENDING) {
				comp = comp.ascending();
			} else {
				comp = comp.descending();
			}

			comparator = comparator && comp;
		});

	return std::unique_ptr<QueryExecutor>(
		new ShowTaskQueryExecutor(
			QueryEngine::GetTask(filter, comparator)
		)
	);
}

template<typename TValue>
std::function<bool(const Task&)>
QueryExecutorBuilderVisitor::buildComparator(
	TValue(QueryEngine::Task::*selector)() const,
	SHOW_QUERY::Predicate predicate,
	const TValue& value) {
	switch (predicate) {
	case SHOW_QUERY::Predicate::EQ:
		return std::bind(std::equal_to<TValue>(),
			std::bind(selector, std::placeholders::_1),
			value);
	case SHOW_QUERY::Predicate::NOT_EQ:
		return std::bind(std::not_equal_to<TValue>(),
			std::bind(selector, std::placeholders::_1),
			value);
	case SHOW_QUERY::Predicate::LESS_THAN:
		return std::bind(std::less<TValue>(),
			std::bind(selector, std::placeholders::_1),
			value);
	case SHOW_QUERY::Predicate::LESS_THAN_EQ:
		return std::bind(std::less_equal<TValue>(),
			std::bind(selector, std::placeholders::_1),
			value);
	case SHOW_QUERY::Predicate::GREATER_THAN:
		return std::bind(std::greater<TValue>(),
			std::bind(selector, std::placeholders::_1),
			value);
	case SHOW_QUERY::Predicate::GREATER_THAN_EQ:
		return std::bind(std::greater_equal<TValue>(),
			std::bind(selector, std::placeholders::_1),
			value);
	default:
		assert(false); abort();
	}
}

std::unique_ptr<QueryExecutor>
QueryExecutorBuilderVisitor::build(const EDIT_QUERY& query) const {
	class EditTaskQueryExecutor : public QueryExecutor {
	public:
		explicit EditTaskQueryExecutor(
			std::unique_ptr<QueryEngine::Query>&& query)
			: QueryExecutor(std::move(query)) {
		}

		virtual ~EditTaskQueryExecutor() = default;

	protected:
		Result processResponse(
			const You::QueryEngine::Response& response) override {
			return EDIT_RESULT {
				boost::get<Task>(response)
			};
		}
	};

	try {
		Task::ID task = context.at(query.taskID).getID();
		You::Utils::Option<Task::Priority> priority;
		if (query.priority) {
			priority = Controller::nlpToQueryEnginePriority(
				query.priority.get());
		}

		if (query.childTask) {
			assert(!query.description &&
				!query.deadline &&
				!priority &&
				!query.complete &&
				!query.dependingTask &&
				!query.attachments &&
				"Cannot change subtasks with other properties");
			int childTask = query.childTask;
			if (childTask < 0) {
				return std::unique_ptr<QueryExecutor>(
					new EditTaskQueryExecutor(
						QueryEngine::RemoveSubtask(task,
							context.at(-childTask).getID())));
			} else {
				return std::unique_ptr<QueryExecutor>(
					new EditTaskQueryExecutor(
						QueryEngine::AddSubtask(task,
							context.at(childTask).getID())));
			}
		}

		You::Utils::Option<Task::Dependencies> dependencies;
		if (query.dependingTask) {
			assert(!query.description &&
				!query.deadline &&
				!priority &&
				!query.complete &&
				!query.childTask &&
				!query.attachments &&
				"Cannot change dependencies with other properties");
			Task::ID dependentTask = task;
			int dependingTask = query.dependingTask;
			if (dependingTask < 0) {
				return std::unique_ptr<QueryExecutor>(
					new EditTaskQueryExecutor(
						QueryEngine::RemoveDependency(
							context.at(-dependingTask).getID(),
							dependentTask)));
			} else {
				return std::unique_ptr<QueryExecutor>(
					new EditTaskQueryExecutor(
						QueryEngine::AddDependency(
							context.at(dependingTask).getID(),
							dependentTask)));
			}
		}

		You::Utils::Option<Task::Attachment> attachment;
		if (!query.attachments.empty()) {
			assert(!query.description &&
				!query.deadline &&
				!priority &&
				!query.complete &&
				!query.childTask &&
				!query.dependingTask &&
				"Cannot modify attachments with other properties");
			assert(query.attachments.size() == 1 &&
				"Controller currently only supports modifying one attachment "
				"at a time");
			if (query.attachments[0].add) {
				attachment = query.attachments[0].path;
			}
		}

		return std::unique_ptr<QueryExecutor>(
			new EditTaskQueryExecutor(
				QueryEngine::UpdateTask(
					task,
					query.description,
					query.deadline,
					priority,
					boost::none,
					query.complete,
					boost::none,
					boost::none)));
	} catch (std::out_of_range& e) {
		throw ContextIndexOutOfRangeException(e);
	}
}

std::unique_ptr<QueryExecutor>
QueryExecutorBuilderVisitor::build(const DELETE_QUERY& query) const {
	class DeleteTaskQueryExecutor : public QueryExecutor {
	public:
		explicit DeleteTaskQueryExecutor(
			std::unique_ptr<QueryEngine::Query>&& query)
			: QueryExecutor(std::move(query)) {
		}

		virtual ~DeleteTaskQueryExecutor() = default;

	protected:
		Result processResponse(
			const You::QueryEngine::Response& response) override {
			return DELETE_RESULT {
				boost::get<Task::ID>(response)
			};
		}
	};

	try {
		const Task& task = context.at(query.taskID);

		return std::unique_ptr<QueryExecutor>(
			new DeleteTaskQueryExecutor(
				QueryEngine::DeleteTask(
					task.getID())));
	} catch (std::out_of_range& e) {
		throw ContextIndexOutOfRangeException(e);
	}
}

std::unique_ptr<QueryExecutor>
QueryExecutorBuilderVisitor::build(const UNDO_QUERY& /*query*/) const {
	class UndoTaskQueryExecutor : public QueryExecutor {
	public:
		explicit UndoTaskQueryExecutor(
			std::unique_ptr<QueryEngine::Query>&& query)
			: QueryExecutor(std::move(query)) {
		}

		virtual ~UndoTaskQueryExecutor() = default;

	protected:
		Result processResponse(
			const You::QueryEngine::Response& response) override {
			return UNDO_RESULT {
				boost::get<TaskList>(response)
			};
		}
	};

	return std::unique_ptr<QueryExecutor>(
		new UndoTaskQueryExecutor(
			QueryEngine::Undo()));
}

}  // namespace Internal
}  // namespace Controller
}  // namespace You
<|endoftext|>


<commit_before>#include "raptor_api.h"
#include "type/pb_converter.h"
#include "boost/date_time/posix_time/posix_time.hpp"
namespace navitia { namespace routing { namespace raptor {

std::string iso_string(const nt::Data & d, int date, int hour){
    boost::posix_time::ptime date_time(d.meta.production_date.begin() + boost::gregorian::days(date));
    date_time += boost::posix_time::seconds(hour);
    return boost::posix_time::to_iso_string(date_time);
}

pbnavitia::Response make_pathes(const std::vector<navitia::routing::Path> &paths, const nt::Data & d) {
    pbnavitia::Response pb_response;
    pb_response.set_requested_api(pbnavitia::PLANNER);

    for(Path path : paths) {
        pbnavitia::Journey * pb_journey = pb_response.mutable_planner()->add_journey();
        pb_journey->set_duration(path.duration);
        pb_journey->set_nb_transfers(path.nb_changes);
        for(PathItem & item : path.items){
            pbnavitia::Section * pb_section = pb_journey->add_section();
            pb_section->set_arrival_date_time(iso_string(d, item.arrival.date(), item.arrival.hour()));
            pb_section->set_departure_date_time(iso_string(d, item.departure.date(), item.departure.hour()));
            if(item.type == public_transport)
                pb_section->set_type(pbnavitia::PUBLIC_TRANSPORT);
            else
                pb_section->set_type(pbnavitia::TRANSFER);
            if(item.type == public_transport && item.vj_idx != type::invalid_idx){
                const type::VehicleJourney & vj = d.pt_data.vehicle_journeys[item.vj_idx];
                const type::Route & route = d.pt_data.routes[vj.route_idx];
                const type::Line & line = d.pt_data.lines[route.line_idx];
                fill_pb_object(line.idx, d, pb_section->mutable_line());
            }
            for(navitia::type::idx_t stop_point : item.stop_points){
                fill_pb_object(stop_point, d, pb_section->add_stop_point());
            }
            if(item.stop_points.size() >= 2) {
                pbnavitia::PlaceMark * origin_place_mark = pb_section->mutable_origin();
                origin_place_mark->set_type(pbnavitia::STOPAREA);
                fill_pb_object(d.pt_data.stop_points[item.stop_points.front()].stop_area_idx, d, origin_place_mark->mutable_stop_area());
                pbnavitia::PlaceMark * destination_place_mark = pb_section->mutable_destination();
                destination_place_mark->set_type(pbnavitia::STOPAREA);
                fill_pb_object(d.pt_data.stop_points[item.stop_points.back()].stop_area_idx, d, destination_place_mark->mutable_stop_area());
            }

        }
    }

    return pb_response;
}

std::vector<std::pair<type::idx_t, double> > get_stop_points(const type::EntryPoint &ep, const type::Data & data, streetnetwork::StreetNetworkWorker & worker){
    std::vector<std::pair<type::idx_t, double> > result;

    switch(ep.type) {
    case navitia::type::Type_e::eStopArea:
    {
        auto it = data.pt_data.stop_area_map.find(ep.external_code);
        if(it!= data.pt_data.stop_area_map.end()) {
            for(auto spidx : data.pt_data.stop_areas[it->second].stop_point_list) {
                result.push_back(std::make_pair(spidx, 0));
            }
        }
    } break;
    case type::Type_e::eStopPoint: {
        auto it = data.pt_data.stop_point_map.find(ep.external_code);
        if(it != data.pt_data.stop_point_map.end()){
            result.push_back(std::make_pair(data.pt_data.stop_points[it->second].idx, 0));
        }
    } break;
    case type::Type_e::eCoord: {
        result = worker.find_nearest(ep.coordinates, data.pt_data.stop_point_proximity_list, 300);
    } break;
    default: break;
    }
    return result;
}

vector_idxretour to_idxretour(std::vector<std::pair<type::idx_t, double> > elements, int hour, int day){
    vector_idxretour result;
    for(auto item : elements) {
        int temps = hour + (item.second / 80);
        int jour;
        if(temps > 86400) {
            temps = temps % 86400;
            jour = day + 1;
        } else {
            jour = day;
        }
        result.push_back(std::make_pair(item.first, type_retour(navitia::type::invalid_idx, DateTime(jour, temps), 0, (item.second / 80))));
    }
    return result;
}

pbnavitia::Response make_response(RAPTOR &raptor, const type::EntryPoint &origin, const type::EntryPoint &destination,
                                  const boost::posix_time::ptime &datetime, bool clockwise,
                                  streetnetwork::StreetNetworkWorker & worker) {
    pbnavitia::Response response;

    if(!raptor.data.meta.production_date.contains(datetime.date())) {
        response.set_error("Date not in the production period");
        return response;
    }

    auto departures = get_stop_points(origin, raptor.data, worker);
    if(departures.size() == 0){
        response.set_error("Departure point not found");
        return response;
    }

    auto destinations = get_stop_points(destination, raptor.data, worker);
    if(destinations.size() == 0){
        response.set_error("Destination point not found");
        return response;
    }

    std::vector<Path> result;

    int day = (datetime.date() - raptor.data.meta.production_date.begin()).days();
    int time = datetime.time_of_day().total_seconds();

    if(clockwise)
        result = raptor.compute_all(to_idxretour(departures, time, day), to_idxretour(destinations, time, day));
    else
        result = raptor.compute_reverse_all(to_idxretour(departures, time, day), to_idxretour(destinations, time, day));

    return make_pathes(result, raptor.data);
}

pbnavitia::Response make_response(RAPTOR &raptor, const type::EntryPoint &origin, const type::EntryPoint &destination,
                                  const std::vector<std::string> &datetimes_str, bool clockwise,
                                  streetnetwork::StreetNetworkWorker & worker) {
    pbnavitia::Response response;

    std::vector<boost::posix_time::ptime> datetimes;
    for(std::string datetime: datetimes_str){
        try {
            boost::posix_time::ptime ptime;
            ptime = boost::posix_time::from_iso_string(datetime);
            if(!raptor.data.meta.production_date.contains(ptime.date())) {
                response.set_error("Date not in the production period");
                return response;
            }
            datetimes.push_back(ptime);
        } catch(...){
            response.set_error("Impossible to parse date " + datetime);
            return response;
        }
    }
    if(clockwise)
        std::sort(datetimes.begin(), datetimes.end(),
                  [](boost::posix_time::ptime dt1, boost::posix_time::ptime dt2){return dt1 > dt2;});
    else
        std::sort(datetimes.begin(), datetimes.end(),
                  [](boost::posix_time::ptime dt1, boost::posix_time::ptime dt2){return dt1 < dt2;});

    auto departures = get_stop_points(origin, raptor.data, worker);
    if(departures.size() == 0){
        response.set_error("Departure point not found");
        return response;
    }

    auto destinations = get_stop_points(destination, raptor.data, worker);
    if(destinations.size() == 0){
        response.set_error("Destination point not found");
        return response;
    }

    std::vector<Path> result;

    DateTime borne;
    if(!clockwise)
        borne = DateTime::min;

    for(boost::posix_time::ptime datetime : datetimes){
        std::vector<Path> tmp;
        int day = (datetime.date() - raptor.data.meta.production_date.begin()).days();
        int time = datetime.time_of_day().total_seconds();

        if(clockwise)
            tmp = raptor.compute_all(to_idxretour(departures, time, day), to_idxretour(destinations, time, day), borne);
        else
            tmp = raptor.compute_reverse_all(to_idxretour(departures, time, day), to_idxretour(destinations, time, day), borne);
        if(tmp.size() > 0) {
            result.push_back(tmp.back());
            borne = tmp.back().items.back().arrival;
        }
        else
            result.push_back(Path());
    }
    return make_pathes(result, raptor.data);
}

}}}
<commit_msg>raptor_api : On retourne les itinéraires dans l'ordre croissant des horaires<commit_after>#include "raptor_api.h"
#include "type/pb_converter.h"
#include "boost/date_time/posix_time/posix_time.hpp"
namespace navitia { namespace routing { namespace raptor {

std::string iso_string(const nt::Data & d, int date, int hour){
    boost::posix_time::ptime date_time(d.meta.production_date.begin() + boost::gregorian::days(date));
    date_time += boost::posix_time::seconds(hour);
    return boost::posix_time::to_iso_string(date_time);
}

pbnavitia::Response make_pathes(const std::vector<navitia::routing::Path> &paths, const nt::Data & d) {
    pbnavitia::Response pb_response;
    pb_response.set_requested_api(pbnavitia::PLANNER);

    for(Path path : paths) {
        pbnavitia::Journey * pb_journey = pb_response.mutable_planner()->add_journey();
        pb_journey->set_duration(path.duration);
        pb_journey->set_nb_transfers(path.nb_changes);
        for(PathItem & item : path.items){
            pbnavitia::Section * pb_section = pb_journey->add_section();
            pb_section->set_arrival_date_time(iso_string(d, item.arrival.date(), item.arrival.hour()));
            pb_section->set_departure_date_time(iso_string(d, item.departure.date(), item.departure.hour()));
            if(item.type == public_transport)
                pb_section->set_type(pbnavitia::PUBLIC_TRANSPORT);
            else
                pb_section->set_type(pbnavitia::TRANSFER);
            if(item.type == public_transport && item.vj_idx != type::invalid_idx){
                const type::VehicleJourney & vj = d.pt_data.vehicle_journeys[item.vj_idx];
                const type::Route & route = d.pt_data.routes[vj.route_idx];
                const type::Line & line = d.pt_data.lines[route.line_idx];
                fill_pb_object(line.idx, d, pb_section->mutable_line());
            }
            for(navitia::type::idx_t stop_point : item.stop_points){
                fill_pb_object(stop_point, d, pb_section->add_stop_point());
            }
            if(item.stop_points.size() >= 2) {
                pbnavitia::PlaceMark * origin_place_mark = pb_section->mutable_origin();
                origin_place_mark->set_type(pbnavitia::STOPAREA);
                fill_pb_object(d.pt_data.stop_points[item.stop_points.front()].stop_area_idx, d, origin_place_mark->mutable_stop_area());
                pbnavitia::PlaceMark * destination_place_mark = pb_section->mutable_destination();
                destination_place_mark->set_type(pbnavitia::STOPAREA);
                fill_pb_object(d.pt_data.stop_points[item.stop_points.back()].stop_area_idx, d, destination_place_mark->mutable_stop_area());
            }

        }
    }

    return pb_response;
}

std::vector<std::pair<type::idx_t, double> > get_stop_points(const type::EntryPoint &ep, const type::Data & data, streetnetwork::StreetNetworkWorker & worker){
    std::vector<std::pair<type::idx_t, double> > result;

    switch(ep.type) {
    case navitia::type::Type_e::eStopArea:
    {
        auto it = data.pt_data.stop_area_map.find(ep.external_code);
        if(it!= data.pt_data.stop_area_map.end()) {
            for(auto spidx : data.pt_data.stop_areas[it->second].stop_point_list) {
                result.push_back(std::make_pair(spidx, 0));
            }
        }
    } break;
    case type::Type_e::eStopPoint: {
        auto it = data.pt_data.stop_point_map.find(ep.external_code);
        if(it != data.pt_data.stop_point_map.end()){
            result.push_back(std::make_pair(data.pt_data.stop_points[it->second].idx, 0));
        }
    } break;
    case type::Type_e::eCoord: {
        result = worker.find_nearest(ep.coordinates, data.pt_data.stop_point_proximity_list, 300);
    } break;
    default: break;
    }
    return result;
}

vector_idxretour to_idxretour(std::vector<std::pair<type::idx_t, double> > elements, int hour, int day){
    vector_idxretour result;
    for(auto item : elements) {
        int temps = hour + (item.second / 80);
        int jour;
        if(temps > 86400) {
            temps = temps % 86400;
            jour = day + 1;
        } else {
            jour = day;
        }
        result.push_back(std::make_pair(item.first, type_retour(navitia::type::invalid_idx, DateTime(jour, temps), 0, (item.second / 80))));
    }
    return result;
}

pbnavitia::Response make_response(RAPTOR &raptor, const type::EntryPoint &origin, const type::EntryPoint &destination,
                                  const boost::posix_time::ptime &datetime, bool clockwise,
                                  streetnetwork::StreetNetworkWorker & worker) {
    pbnavitia::Response response;

    if(!raptor.data.meta.production_date.contains(datetime.date())) {
        response.set_error("Date not in the production period");
        return response;
    }

    auto departures = get_stop_points(origin, raptor.data, worker);
    if(departures.size() == 0){
        response.set_error("Departure point not found");
        return response;
    }

    auto destinations = get_stop_points(destination, raptor.data, worker);
    if(destinations.size() == 0){
        response.set_error("Destination point not found");
        return response;
    }

    std::vector<Path> result;

    int day = (datetime.date() - raptor.data.meta.production_date.begin()).days();
    int time = datetime.time_of_day().total_seconds();

    if(clockwise)
        result = raptor.compute_all(to_idxretour(departures, time, day), to_idxretour(destinations, time, day));
    else
        result = raptor.compute_reverse_all(to_idxretour(departures, time, day), to_idxretour(destinations, time, day));

    return make_pathes(result, raptor.data);
}

pbnavitia::Response make_response(RAPTOR &raptor, const type::EntryPoint &origin, const type::EntryPoint &destination,
                                  const std::vector<std::string> &datetimes_str, bool clockwise,
                                  streetnetwork::StreetNetworkWorker & worker) {
    pbnavitia::Response response;

    std::vector<boost::posix_time::ptime> datetimes;
    for(std::string datetime: datetimes_str){
        try {
            boost::posix_time::ptime ptime;
            ptime = boost::posix_time::from_iso_string(datetime);
            if(!raptor.data.meta.production_date.contains(ptime.date())) {
                response.set_error("Date not in the production period");
                return response;
            }
            datetimes.push_back(ptime);
        } catch(...){
            response.set_error("Impossible to parse date " + datetime);
            return response;
        }
    }
    if(clockwise)
        std::sort(datetimes.begin(), datetimes.end(),
                  [](boost::posix_time::ptime dt1, boost::posix_time::ptime dt2){return dt1 > dt2;});
    else
        std::sort(datetimes.begin(), datetimes.end(),
                  [](boost::posix_time::ptime dt1, boost::posix_time::ptime dt2){return dt1 < dt2;});

    auto departures = get_stop_points(origin, raptor.data, worker);
    if(departures.size() == 0){
        response.set_error("Departure point not found");
        return response;
    }

    auto destinations = get_stop_points(destination, raptor.data, worker);
    if(destinations.size() == 0){
        response.set_error("Destination point not found");
        return response;
    }

    std::vector<Path> result;

    DateTime borne;
    if(!clockwise)
        borne = DateTime::min;

    for(boost::posix_time::ptime datetime : datetimes){
        std::vector<Path> tmp;
        int day = (datetime.date() - raptor.data.meta.production_date.begin()).days();
        int time = datetime.time_of_day().total_seconds();

        if(clockwise)
            tmp = raptor.compute_all(to_idxretour(departures, time, day), to_idxretour(destinations, time, day), borne);
        else
            tmp = raptor.compute_reverse_all(to_idxretour(departures, time, day), to_idxretour(destinations, time, day), borne);
        if(tmp.size() > 0) {
            result.push_back(tmp.back());
            borne = tmp.back().items.back().arrival;
        }
        else
            result.push_back(Path());
    }
    if(clockwise)
        std::reverse(result.begin(), result.end());

    return make_pathes(result, raptor.data);
}

}}}
<|endoftext|>


<commit_before>//@author A0097630B
#include "stdafx.h"
#include "query_executor.h"
#include "../exceptions/context_index_out_of_range_exception.h"
#include "../result.h"
#include "query_executor_builder_visitor.h"

namespace You {
namespace Controller {
namespace Internal {

using You::NLP::TaskField;
using You::NLP::TaskPriority;
using You::NLP::QUERY;
using You::NLP::ADD_QUERY;
using You::NLP::SHOW_QUERY;
using You::NLP::EDIT_QUERY;
using You::NLP::DELETE_QUERY;
using You::NLP::UNDO_QUERY;

QueryExecutorBuilderVisitor::QueryExecutorBuilderVisitor(
	const Controller::Context& context)
	: context(context) {
}

std::unique_ptr<QueryExecutor>
QueryExecutorBuilderVisitor::build(const ADD_QUERY& query) {
	class AddTaskQueryExecutor : public QueryExecutor {
	public:
		explicit AddTaskQueryExecutor(
			std::unique_ptr<QueryEngine::Query>&& query)
			: QueryExecutor(std::move(query)) {
		}

		virtual ~AddTaskQueryExecutor() = default;

	protected:
		Result processResponse(
			const You::QueryEngine::Response& response) override {
			return ADD_RESULT {
				boost::get<Task>(response)
			};
		}
	};

	return std::unique_ptr<QueryExecutor>(
		new AddTaskQueryExecutor(
			QueryEngine::AddTask(
				query.description,
				query.deadline ? query.deadline.get() : Task::DEFAULT_DEADLINE,
				query.priority == TaskPriority::HIGH ?
					Task::Priority::HIGH : Task::Priority::NORMAL,
				Task::Dependencies()
			)
		)
	);
}

std::unique_ptr<QueryExecutor>
QueryExecutorBuilderVisitor::build(const SHOW_QUERY& query) {
	class ShowTaskQueryExecutor : public QueryExecutor {
	public:
		explicit ShowTaskQueryExecutor(
			std::unique_ptr<QueryEngine::Query>&& query)
			: QueryExecutor(std::move(query)) {
		}

		virtual ~ShowTaskQueryExecutor() = default;

	protected:
		Result processResponse(
			const You::QueryEngine::Response& response) override {
			return SHOW_RESULT {
				boost::get<TaskList>(response)
			};
		}
	};

	using You::QueryEngine::Filter;
	using You::QueryEngine::Comparator;
	Filter filter = Filter::anyTask();
	Comparator comparator(Comparator::notSorted());

	std::for_each(begin(query.predicates), end(query.predicates),
		[&filter](const SHOW_QUERY::FIELD_FILTER& field) {
			std::function<bool(const Task&)> currentFilter;
			switch (field.field) {
			case TaskField::DESCRIPTION:
				assert(boost::get<std::wstring>(&field.value));
				currentFilter = buildComparator(&Task::getDescription,
					field.predicate,
					boost::get<std::wstring>(field.value));
				break;
			case TaskField::DEADLINE:
				assert(boost::get<boost::posix_time::ptime>(&field.value));
				currentFilter = buildComparator(&Task::getDeadline,
					field.predicate,
					boost::get<boost::posix_time::ptime>(field.value));
				break;
			case TaskField::COMPLETE:
				assert(boost::get<bool>(&field.value));
				// TODO(lowjoel): wait for completion flag.
				break;
			case TaskField::PRIORITY:
				assert(boost::get<TaskPriority>(&field.value));
				currentFilter = buildComparator(&Task::getPriority,
					field.predicate,
					Controller::nlpToQueryEnginePriority(
						boost::get<TaskPriority>(field.value)));
				break;
			default:
				assert(false); abort();
			}
		});

	std::for_each(begin(query.order), end(query.order),
		[&comparator](const SHOW_QUERY::FIELD_ORDER& field) {
			Comparator comp(Comparator::notSorted());
			switch (field.field) {
			case TaskField::DESCRIPTION:
				comp = Comparator::byDescription();
				break;
			case TaskField::DEADLINE:
				comp = Comparator::byDeadline();
				break;
			case TaskField::PRIORITY:
				comp = Comparator::byPriority();
				break;
			case TaskField::COMPLETE:
			default:
				assert(false);
			}

			if (field.order == SHOW_QUERY::Order::ASCENDING) {
				comp = comp.ascending();
			} else {
				comp = comp.descending();
			}

			comparator = comparator && comp;
		});

	return std::unique_ptr<QueryExecutor>(
		new ShowTaskQueryExecutor(
			QueryEngine::GetTask(filter, comparator)
		)
	);
}

template<typename TValue>
std::function<bool(const Task&)>
QueryExecutorBuilderVisitor::buildComparator(
	TValue(QueryEngine::Task::*selector)() const,
	SHOW_QUERY::Predicate predicate,
	const TValue& value) {
	switch (predicate) {
	case SHOW_QUERY::Predicate::EQ:
		return [selector, value](const Task& task) {
			return (task.*selector)() == value;
		};
	case SHOW_QUERY::Predicate::NOT_EQ:
		return [selector, value](const Task& task) {
			return (task.*selector)() != value;
		};
	case SHOW_QUERY::Predicate::LESS_THAN:
		return [selector, value](const Task& task) {
			return (task.*selector)() < value;
		};
	case SHOW_QUERY::Predicate::LESS_THAN_EQ:
		return [selector, value](const Task& task) {
			return (task.*selector)() <= value;
		};
	case SHOW_QUERY::Predicate::GREATER_THAN:
		return [selector, value](const Task& task) {
			return (task.*selector)() > value;
		};
	case SHOW_QUERY::Predicate::GREATER_THAN_EQ:
		return [selector, value](const Task& task) {
			return (task.*selector)() >= value;
		};
	default:
		assert(false);
		abort();
	}
}

std::unique_ptr<QueryExecutor>
QueryExecutorBuilderVisitor::build(const EDIT_QUERY& query) const {
	class EditTaskQueryExecutor : public QueryExecutor {
	public:
		explicit EditTaskQueryExecutor(
			std::unique_ptr<QueryEngine::Query>&& query)
			: QueryExecutor(std::move(query)) {
		}

		virtual ~EditTaskQueryExecutor() = default;

	protected:
		Result processResponse(
			const You::QueryEngine::Response& response) override {
			return EDIT_RESULT {
				boost::get<Task>(response)
			};
		}
	};

	try {
		const Task& task = context.at(query.taskID);
		std::wstring description = query.description ?
			query.description.get() :
			Task::DEFAULT_DESCRIPTION;
		boost::posix_time::ptime deadline = query.deadline ?
			query.deadline.get() :
			Task::DEFAULT_DEADLINE;
		Task::Priority priority = query.priority ?
			(query.priority == TaskPriority::HIGH ?
				Task::Priority::HIGH : Task::Priority::NORMAL) :
			Task::DEFAULT_PRIORITY;

		return std::unique_ptr<QueryExecutor>(
			new EditTaskQueryExecutor(
				QueryEngine::UpdateTask(
					task.getID(),
					description,
					deadline,
					priority,
					Task::Dependencies())));
	} catch (std::out_of_range& e) {
		throw ContextIndexOutOfRangeException(e);
	}
}

std::unique_ptr<QueryExecutor>
QueryExecutorBuilderVisitor::build(const DELETE_QUERY& query) const {
	class DeleteTaskQueryExecutor : public QueryExecutor {
	public:
		explicit DeleteTaskQueryExecutor(
			std::unique_ptr<QueryEngine::Query>&& query)
			: QueryExecutor(std::move(query)) {
		}

		virtual ~DeleteTaskQueryExecutor() = default;

	protected:
		Result processResponse(
			const You::QueryEngine::Response& response) override {
			return DELETE_RESULT {
				boost::get<Task::ID>(response)
			};
		}
	};

	try {
		const Task& task = context.at(query.taskID);

		return std::unique_ptr<QueryExecutor>(
			new DeleteTaskQueryExecutor(
				QueryEngine::DeleteTask(
					task.getID())));
	} catch (std::out_of_range& e) {
		throw ContextIndexOutOfRangeException(e);
	}
}

std::unique_ptr<QueryExecutor>
QueryExecutorBuilderVisitor::build(const UNDO_QUERY& query) const {
	class UndoTaskQueryExecutor : public QueryExecutor {
	public:
		explicit UndoTaskQueryExecutor(
			std::unique_ptr<QueryEngine::Query>&& query)
			: QueryExecutor(std::move(query)) {
		}

		virtual ~UndoTaskQueryExecutor() = default;

	protected:
		Result processResponse(
			const You::QueryEngine::Response& response) override {
			return UNDO_RESULT {
			};
		}
	};

	return std::unique_ptr<QueryExecutor>(
		new UndoTaskQueryExecutor(
			QueryEngine::Undo()));
}

}  // namespace Internal
}  // namespace Controller
}  // namespace You
<commit_msg>Support isCompleted.<commit_after>//@author A0097630B
#include "stdafx.h"
#include "query_executor.h"
#include "../exceptions/context_index_out_of_range_exception.h"
#include "../result.h"
#include "query_executor_builder_visitor.h"

namespace You {
namespace Controller {
namespace Internal {

using You::NLP::TaskField;
using You::NLP::TaskPriority;
using You::NLP::QUERY;
using You::NLP::ADD_QUERY;
using You::NLP::SHOW_QUERY;
using You::NLP::EDIT_QUERY;
using You::NLP::DELETE_QUERY;
using You::NLP::UNDO_QUERY;

QueryExecutorBuilderVisitor::QueryExecutorBuilderVisitor(
	const Controller::Context& context)
	: context(context) {
}

std::unique_ptr<QueryExecutor>
QueryExecutorBuilderVisitor::build(const ADD_QUERY& query) {
	class AddTaskQueryExecutor : public QueryExecutor {
	public:
		explicit AddTaskQueryExecutor(
			std::unique_ptr<QueryEngine::Query>&& query)
			: QueryExecutor(std::move(query)) {
		}

		virtual ~AddTaskQueryExecutor() = default;

	protected:
		Result processResponse(
			const You::QueryEngine::Response& response) override {
			return ADD_RESULT {
				boost::get<Task>(response)
			};
		}
	};

	return std::unique_ptr<QueryExecutor>(
		new AddTaskQueryExecutor(
			QueryEngine::AddTask(
				query.description,
				query.deadline ? query.deadline.get() : Task::DEFAULT_DEADLINE,
				query.priority == TaskPriority::HIGH ?
					Task::Priority::HIGH : Task::Priority::NORMAL,
				Task::Dependencies()
			)
		)
	);
}

std::unique_ptr<QueryExecutor>
QueryExecutorBuilderVisitor::build(const SHOW_QUERY& query) {
	class ShowTaskQueryExecutor : public QueryExecutor {
	public:
		explicit ShowTaskQueryExecutor(
			std::unique_ptr<QueryEngine::Query>&& query)
			: QueryExecutor(std::move(query)) {
		}

		virtual ~ShowTaskQueryExecutor() = default;

	protected:
		Result processResponse(
			const You::QueryEngine::Response& response) override {
			return SHOW_RESULT {
				boost::get<TaskList>(response)
			};
		}
	};

	using You::QueryEngine::Filter;
	using You::QueryEngine::Comparator;
	Filter filter = Filter::anyTask();
	Comparator comparator(Comparator::notSorted());

	std::for_each(begin(query.predicates), end(query.predicates),
		[&filter](const SHOW_QUERY::FIELD_FILTER& field) {
			std::function<bool(const Task&)> currentFilter;
			switch (field.field) {
			case TaskField::DESCRIPTION:
				assert(boost::get<std::wstring>(&field.value));
				currentFilter = buildComparator(&Task::getDescription,
					field.predicate,
					boost::get<std::wstring>(field.value));
				break;
			case TaskField::DEADLINE:
				assert(boost::get<boost::posix_time::ptime>(&field.value));
				currentFilter = buildComparator(&Task::getDeadline,
					field.predicate,
					boost::get<boost::posix_time::ptime>(field.value));
				break;
			case TaskField::COMPLETE:
				assert(boost::get<bool>(&field.value));
				currentFilter = buildComparator(&Task::isCompleted,
					field.predicate,
					boost::get<bool>(field.value));
				break;
			case TaskField::PRIORITY:
				assert(boost::get<TaskPriority>(&field.value));
				currentFilter = buildComparator(&Task::getPriority,
					field.predicate,
					Controller::nlpToQueryEnginePriority(
						boost::get<TaskPriority>(field.value)));
				break;
			default:
				assert(false); abort();
			}
		});

	std::for_each(begin(query.order), end(query.order),
		[&comparator](const SHOW_QUERY::FIELD_ORDER& field) {
			Comparator comp(Comparator::notSorted());
			switch (field.field) {
			case TaskField::DESCRIPTION:
				comp = Comparator::byDescription();
				break;
			case TaskField::DEADLINE:
				comp = Comparator::byDeadline();
				break;
			case TaskField::PRIORITY:
				comp = Comparator::byPriority();
				break;
			case TaskField::COMPLETE:
			default:
				assert(false);
			}

			if (field.order == SHOW_QUERY::Order::ASCENDING) {
				comp = comp.ascending();
			} else {
				comp = comp.descending();
			}

			comparator = comparator && comp;
		});

	return std::unique_ptr<QueryExecutor>(
		new ShowTaskQueryExecutor(
			QueryEngine::GetTask(filter, comparator)
		)
	);
}

template<typename TValue>
std::function<bool(const Task&)>
QueryExecutorBuilderVisitor::buildComparator(
	TValue(QueryEngine::Task::*selector)() const,
	SHOW_QUERY::Predicate predicate,
	const TValue& value) {
	switch (predicate) {
	case SHOW_QUERY::Predicate::EQ:
		return [selector, value](const Task& task) {
			return (task.*selector)() == value;
		};
	case SHOW_QUERY::Predicate::NOT_EQ:
		return [selector, value](const Task& task) {
			return (task.*selector)() != value;
		};
	case SHOW_QUERY::Predicate::LESS_THAN:
		return [selector, value](const Task& task) {
			return (task.*selector)() < value;
		};
	case SHOW_QUERY::Predicate::LESS_THAN_EQ:
		return [selector, value](const Task& task) {
			return (task.*selector)() <= value;
		};
	case SHOW_QUERY::Predicate::GREATER_THAN:
		return [selector, value](const Task& task) {
			return (task.*selector)() > value;
		};
	case SHOW_QUERY::Predicate::GREATER_THAN_EQ:
		return [selector, value](const Task& task) {
			return (task.*selector)() >= value;
		};
	default:
		assert(false);
		abort();
	}
}

std::unique_ptr<QueryExecutor>
QueryExecutorBuilderVisitor::build(const EDIT_QUERY& query) const {
	class EditTaskQueryExecutor : public QueryExecutor {
	public:
		explicit EditTaskQueryExecutor(
			std::unique_ptr<QueryEngine::Query>&& query)
			: QueryExecutor(std::move(query)) {
		}

		virtual ~EditTaskQueryExecutor() = default;

	protected:
		Result processResponse(
			const You::QueryEngine::Response& response) override {
			return EDIT_RESULT {
				boost::get<Task>(response)
			};
		}
	};

	try {
		const Task& task = context.at(query.taskID);
		std::wstring description = query.description ?
			query.description.get() :
			Task::DEFAULT_DESCRIPTION;
		boost::posix_time::ptime deadline = query.deadline ?
			query.deadline.get() :
			Task::DEFAULT_DEADLINE;
		Task::Priority priority = query.priority ?
			(query.priority == TaskPriority::HIGH ?
				Task::Priority::HIGH : Task::Priority::NORMAL) :
			Task::DEFAULT_PRIORITY;

		return std::unique_ptr<QueryExecutor>(
			new EditTaskQueryExecutor(
				QueryEngine::UpdateTask(
					task.getID(),
					description,
					deadline,
					priority,
					Task::Dependencies())));
	} catch (std::out_of_range& e) {
		throw ContextIndexOutOfRangeException(e);
	}
}

std::unique_ptr<QueryExecutor>
QueryExecutorBuilderVisitor::build(const DELETE_QUERY& query) const {
	class DeleteTaskQueryExecutor : public QueryExecutor {
	public:
		explicit DeleteTaskQueryExecutor(
			std::unique_ptr<QueryEngine::Query>&& query)
			: QueryExecutor(std::move(query)) {
		}

		virtual ~DeleteTaskQueryExecutor() = default;

	protected:
		Result processResponse(
			const You::QueryEngine::Response& response) override {
			return DELETE_RESULT {
				boost::get<Task::ID>(response)
			};
		}
	};

	try {
		const Task& task = context.at(query.taskID);

		return std::unique_ptr<QueryExecutor>(
			new DeleteTaskQueryExecutor(
				QueryEngine::DeleteTask(
					task.getID())));
	} catch (std::out_of_range& e) {
		throw ContextIndexOutOfRangeException(e);
	}
}

std::unique_ptr<QueryExecutor>
QueryExecutorBuilderVisitor::build(const UNDO_QUERY& query) const {
	class UndoTaskQueryExecutor : public QueryExecutor {
	public:
		explicit UndoTaskQueryExecutor(
			std::unique_ptr<QueryEngine::Query>&& query)
			: QueryExecutor(std::move(query)) {
		}

		virtual ~UndoTaskQueryExecutor() = default;

	protected:
		Result processResponse(
			const You::QueryEngine::Response& response) override {
			return UNDO_RESULT {
			};
		}
	};

	return std::unique_ptr<QueryExecutor>(
		new UndoTaskQueryExecutor(
			QueryEngine::Undo()));
}

}  // namespace Internal
}  // namespace Controller
}  // namespace You
<|endoftext|>