<commit_before>/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Multisample interpolation state query tests
 *//*--------------------------------------------------------------------*/

#include "es31fShaderMultisampleInterpolationStateQueryTests.hpp"
#include "tcuTestLog.hpp"
#include "gluCallLogWrapper.hpp"
#include "gluContextInfo.hpp"
#include "gluRenderContext.hpp"
#include "glsStateQueryUtil.hpp"
#include "glwEnums.hpp"
#include "glwFunctions.hpp"

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

using namespace gls::StateQueryUtil;

class InterpolationOffsetCase : public TestCase
{
public:
	enum TestType
	{
		TEST_MIN_OFFSET = 0,
		TEST_MAX_OFFSET,

		TEST_LAST
	};

						InterpolationOffsetCase		(Context& context, const char* name, const char* desc, QueryType verifier, TestType testType);
						~InterpolationOffsetCase	(void);

	void				init						(void);
	IterateResult		iterate						(void);

private:
	const QueryType		m_verifier;
	const TestType		m_testType;
};

InterpolationOffsetCase::InterpolationOffsetCase (Context& context, const char* name, const char* desc, QueryType verifier, TestType testType)
	: TestCase		(context, name, desc)
	, m_verifier	(verifier)
	, m_testType	(testType)
{
	DE_ASSERT(m_testType < TEST_LAST);
}

InterpolationOffsetCase::~InterpolationOffsetCase (void)
{
}

void InterpolationOffsetCase::init (void)
{
	const bool supportsES32 = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2));

	if (!supportsES32 && !m_context.getContextInfo().isExtensionSupported("GL_OES_shader_multisample_interpolation"))
		throw tcu::NotSupportedError("Test requires GL_OES_shader_multisample_interpolation extension");
}

InterpolationOffsetCase::IterateResult InterpolationOffsetCase::iterate (void)
{
	glu::CallLogWrapper		gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
	tcu::ResultCollector	result	(m_testCtx.getLog(), " // ERROR: ");
	gl.enableLogging(true);

	if (m_testType == TEST_MAX_OFFSET)
		verifyStateFloatMin(result, gl, GL_MAX_FRAGMENT_INTERPOLATION_OFFSET, 0.5, m_verifier);
	else if (m_testType == TEST_MIN_OFFSET)
		verifyStateFloatMax(result, gl, GL_MIN_FRAGMENT_INTERPOLATION_OFFSET, -0.5, m_verifier);
	else
		DE_ASSERT(false);

	result.setTestContextResult(m_testCtx);
	return STOP;
}

class FragmentInterpolationOffsetBitsCase : public TestCase
{
public:
						FragmentInterpolationOffsetBitsCase		(Context& context, const char* name, const char* desc, QueryType verifier);
						~FragmentInterpolationOffsetBitsCase	(void);

	void				init									(void);
	IterateResult		iterate									(void);

private:
	const QueryType		m_verifier;
};

FragmentInterpolationOffsetBitsCase::FragmentInterpolationOffsetBitsCase (Context& context, const char* name, const char* desc, QueryType verifier)
	: TestCase		(context, name, desc)
	, m_verifier	(verifier)
{
}

FragmentInterpolationOffsetBitsCase::~FragmentInterpolationOffsetBitsCase (void)
{
}

void FragmentInterpolationOffsetBitsCase::init (void)
{
	const bool supportsES32 = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2));

	if (!supportsES32 && !m_context.getContextInfo().isExtensionSupported("GL_OES_shader_multisample_interpolation"))
		throw tcu::NotSupportedError("Test requires GL_OES_shader_multisample_interpolation extension");
}

FragmentInterpolationOffsetBitsCase::IterateResult FragmentInterpolationOffsetBitsCase::iterate (void)
{
	glu::CallLogWrapper		gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
	tcu::ResultCollector	result	(m_testCtx.getLog(), " // ERROR: ");
	gl.enableLogging(true);

	verifyStateIntegerMin(result, gl, GL_FRAGMENT_INTERPOLATION_OFFSET_BITS, 4, m_verifier);

	result.setTestContextResult(m_testCtx);
	return STOP;
}

} // anonymous

ShaderMultisampleInterpolationStateQueryTests::ShaderMultisampleInterpolationStateQueryTests (Context& context)
	: TestCaseGroup(context, "multisample_interpolation", "Test multisample interpolation states")
{
}

ShaderMultisampleInterpolationStateQueryTests::~ShaderMultisampleInterpolationStateQueryTests (void)
{
}

void ShaderMultisampleInterpolationStateQueryTests::init (void)
{
	static const struct Verifier
	{
		QueryType		verifier;
		const char*		name;
		const char*		desc;
	} verifiers[] =
	{
		{ QUERY_BOOLEAN,	"get_boolean",		"Test using getBoolean"		},
		{ QUERY_INTEGER,	"get_integer",		"Test using getInteger"		},
		{ QUERY_FLOAT,		"get_float",		"Test using getFloat"		},
		{ QUERY_INTEGER64,	"get_integer64",	"Test using getInteger64"	},
	};

	// .min_fragment_interpolation_offset
	{
		tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "min_fragment_interpolation_offset", "Test MIN_FRAGMENT_INTERPOLATION_OFFSET");
		addChild(group);

		for (int verifierNdx = 0; verifierNdx < DE_LENGTH_OF_ARRAY(verifiers); ++verifierNdx)
			group->addChild(new InterpolationOffsetCase(m_context, verifiers[verifierNdx].name, verifiers[verifierNdx].desc, verifiers[verifierNdx].verifier, InterpolationOffsetCase::TEST_MIN_OFFSET));
	}

	// .max_fragment_interpolation_offset
	{
		tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "max_fragment_interpolation_offset", "Test MAX_FRAGMENT_INTERPOLATION_OFFSET");
		addChild(group);

		for (int verifierNdx = 0; verifierNdx < DE_LENGTH_OF_ARRAY(verifiers); ++verifierNdx)
			group->addChild(new InterpolationOffsetCase(m_context, verifiers[verifierNdx].name, verifiers[verifierNdx].desc, verifiers[verifierNdx].verifier, InterpolationOffsetCase::TEST_MAX_OFFSET));
	}

	// .fragment_interpolation_offset_bits
	{
		tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "fragment_interpolation_offset_bits", "Test FRAGMENT_INTERPOLATION_OFFSET_BITS");
		addChild(group);

		for (int verifierNdx = 0; verifierNdx < DE_LENGTH_OF_ARRAY(verifiers); ++verifierNdx)
			group->addChild(new FragmentInterpolationOffsetBitsCase(m_context, verifiers[verifierNdx].name, verifiers[verifierNdx].desc, verifiers[verifierNdx].verifier));
	}
}

} // Functional
} // gles31
} // deqp
<commit_msg>Support GL 4.5 in the multisample interpolation states tests<commit_after>/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Multisample interpolation state query tests
 *//*--------------------------------------------------------------------*/

#include "es31fShaderMultisampleInterpolationStateQueryTests.hpp"
#include "tcuTestLog.hpp"
#include "gluCallLogWrapper.hpp"
#include "gluContextInfo.hpp"
#include "gluRenderContext.hpp"
#include "glsStateQueryUtil.hpp"
#include "glwEnums.hpp"
#include "glwFunctions.hpp"

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

using namespace gls::StateQueryUtil;

class InterpolationOffsetCase : public TestCase
{
public:
	enum TestType
	{
		TEST_MIN_OFFSET = 0,
		TEST_MAX_OFFSET,

		TEST_LAST
	};

						InterpolationOffsetCase		(Context& context, const char* name, const char* desc, QueryType verifier, TestType testType);
						~InterpolationOffsetCase	(void);

	void				init						(void);
	IterateResult		iterate						(void);

private:
	const QueryType		m_verifier;
	const TestType		m_testType;
};

InterpolationOffsetCase::InterpolationOffsetCase (Context& context, const char* name, const char* desc, QueryType verifier, TestType testType)
	: TestCase		(context, name, desc)
	, m_verifier	(verifier)
	, m_testType	(testType)
{
	DE_ASSERT(m_testType < TEST_LAST);
}

InterpolationOffsetCase::~InterpolationOffsetCase (void)
{
}

void InterpolationOffsetCase::init (void)
{
	auto		ctxType			= m_context.getRenderContext().getType();
	const bool	isES32orGL45	= glu::contextSupports(ctxType, glu::ApiType::es(3, 2)) ||
								  glu::contextSupports(ctxType, glu::ApiType::core(4, 5));

	if (!isES32orGL45 && !m_context.getContextInfo().isExtensionSupported("GL_OES_shader_multisample_interpolation"))
		throw tcu::NotSupportedError("Test requires GL_OES_shader_multisample_interpolation extension");
}

InterpolationOffsetCase::IterateResult InterpolationOffsetCase::iterate (void)
{
	glu::CallLogWrapper		gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
	tcu::ResultCollector	result	(m_testCtx.getLog(), " // ERROR: ");
	gl.enableLogging(true);

	if (m_testType == TEST_MAX_OFFSET)
		verifyStateFloatMin(result, gl, GL_MAX_FRAGMENT_INTERPOLATION_OFFSET, 0.5, m_verifier);
	else if (m_testType == TEST_MIN_OFFSET)
		verifyStateFloatMax(result, gl, GL_MIN_FRAGMENT_INTERPOLATION_OFFSET, -0.5, m_verifier);
	else
		DE_ASSERT(false);

	result.setTestContextResult(m_testCtx);
	return STOP;
}

class FragmentInterpolationOffsetBitsCase : public TestCase
{
public:
						FragmentInterpolationOffsetBitsCase		(Context& context, const char* name, const char* desc, QueryType verifier);
						~FragmentInterpolationOffsetBitsCase	(void);

	void				init									(void);
	IterateResult		iterate									(void);

private:
	const QueryType		m_verifier;
};

FragmentInterpolationOffsetBitsCase::FragmentInterpolationOffsetBitsCase (Context& context, const char* name, const char* desc, QueryType verifier)
	: TestCase		(context, name, desc)
	, m_verifier	(verifier)
{
}

FragmentInterpolationOffsetBitsCase::~FragmentInterpolationOffsetBitsCase (void)
{
}

void FragmentInterpolationOffsetBitsCase::init (void)
{
	auto		ctxType			= m_context.getRenderContext().getType();
	const bool	isES32orGL45	= glu::contextSupports(ctxType, glu::ApiType::es(3, 2)) ||
								  glu::contextSupports(ctxType, glu::ApiType::core(4, 5));

	if (!isES32orGL45 && !m_context.getContextInfo().isExtensionSupported("GL_OES_shader_multisample_interpolation"))
		throw tcu::NotSupportedError("Test requires GL_OES_shader_multisample_interpolation extension");
}

FragmentInterpolationOffsetBitsCase::IterateResult FragmentInterpolationOffsetBitsCase::iterate (void)
{
	glu::CallLogWrapper		gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
	tcu::ResultCollector	result	(m_testCtx.getLog(), " // ERROR: ");
	gl.enableLogging(true);

	verifyStateIntegerMin(result, gl, GL_FRAGMENT_INTERPOLATION_OFFSET_BITS, 4, m_verifier);

	result.setTestContextResult(m_testCtx);
	return STOP;
}

} // anonymous

ShaderMultisampleInterpolationStateQueryTests::ShaderMultisampleInterpolationStateQueryTests (Context& context)
	: TestCaseGroup(context, "multisample_interpolation", "Test multisample interpolation states")
{
}

ShaderMultisampleInterpolationStateQueryTests::~ShaderMultisampleInterpolationStateQueryTests (void)
{
}

void ShaderMultisampleInterpolationStateQueryTests::init (void)
{
	static const struct Verifier
	{
		QueryType		verifier;
		const char*		name;
		const char*		desc;
	} verifiers[] =
	{
		{ QUERY_BOOLEAN,	"get_boolean",		"Test using getBoolean"		},
		{ QUERY_INTEGER,	"get_integer",		"Test using getInteger"		},
		{ QUERY_FLOAT,		"get_float",		"Test using getFloat"		},
		{ QUERY_INTEGER64,	"get_integer64",	"Test using getInteger64"	},
	};

	// .min_fragment_interpolation_offset
	{
		tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "min_fragment_interpolation_offset", "Test MIN_FRAGMENT_INTERPOLATION_OFFSET");
		addChild(group);

		for (int verifierNdx = 0; verifierNdx < DE_LENGTH_OF_ARRAY(verifiers); ++verifierNdx)
			group->addChild(new InterpolationOffsetCase(m_context, verifiers[verifierNdx].name, verifiers[verifierNdx].desc, verifiers[verifierNdx].verifier, InterpolationOffsetCase::TEST_MIN_OFFSET));
	}

	// .max_fragment_interpolation_offset
	{
		tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "max_fragment_interpolation_offset", "Test MAX_FRAGMENT_INTERPOLATION_OFFSET");
		addChild(group);

		for (int verifierNdx = 0; verifierNdx < DE_LENGTH_OF_ARRAY(verifiers); ++verifierNdx)
			group->addChild(new InterpolationOffsetCase(m_context, verifiers[verifierNdx].name, verifiers[verifierNdx].desc, verifiers[verifierNdx].verifier, InterpolationOffsetCase::TEST_MAX_OFFSET));
	}

	// .fragment_interpolation_offset_bits
	{
		tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, "fragment_interpolation_offset_bits", "Test FRAGMENT_INTERPOLATION_OFFSET_BITS");
		addChild(group);

		for (int verifierNdx = 0; verifierNdx < DE_LENGTH_OF_ARRAY(verifiers); ++verifierNdx)
			group->addChild(new FragmentInterpolationOffsetBitsCase(m_context, verifiers[verifierNdx].name, verifiers[verifierNdx].desc, verifiers[verifierNdx].verifier));
	}
}

} // Functional
} // gles31
} // deqp
<|endoftext|>


<commit_before>/** Implementation of the pqxx::pipeline class.
 *
 * Throughput-optimized query interface.
 *
 * Copyright (c) 2000-2020, Jeroen T. Vermeulen.
 *
 * See COPYING for copyright license.  If you did not receive a file called
 * COPYING with this source code, please notify the distributor of this
 * mistake, or contact the author.
 */
#include "pqxx-source.hxx"

#include <iterator>

#include "pqxx/dbtransaction"
#include "pqxx/pipeline"
#include "pqxx/separated_list"

#include "pqxx/internal/gates/connection-pipeline.hxx"
#include "pqxx/internal/gates/result-creation.hxx"
#include "pqxx/internal/gates/result-pipeline.hxx"

namespace
{
const std::string theSeparator{"; "};
const std::string theDummyValue{"1"};
const std::string theDummyQuery{"SELECT " + theDummyValue + theSeparator};
} // namespace

void pqxx::pipeline::init()
{
  m_issuedrange = make_pair(m_queries.end(), m_queries.end());
  attach();
}

pqxx::pipeline::~pipeline() noexcept
{
  try
  {
    cancel();
  }
  catch (const std::exception &)
  {}
  detach();
}

void pqxx::pipeline::attach()
{
  if (not registered())
    register_me();
}

void pqxx::pipeline::detach()
{
  if (registered())
    unregister_me();
}

pqxx::pipeline::query_id pqxx::pipeline::insert(std::string_view q)
{
  attach();
  const query_id qid = generate_id();
  const auto i = m_queries.insert(std::make_pair(qid, Query(q))).first;

  if (m_issuedrange.second == m_queries.end())
  {
    m_issuedrange.second = i;
    if (m_issuedrange.first == m_queries.end())
      m_issuedrange.first = i;
  }
  m_num_waiting++;

  if (m_num_waiting > m_retain)
  {
    if (have_pending())
      receive_if_available();
    if (not have_pending())
      issue();
  }

  return qid;
}

void pqxx::pipeline::complete()
{
  if (have_pending())
    receive(m_issuedrange.second);
  if (m_num_waiting and (m_error == qid_limit()))
  {
    issue();
    receive(m_queries.end());
  }
  detach();
}

void pqxx::pipeline::flush()
{
  if (not m_queries.empty())
  {
    if (have_pending())
      receive(m_issuedrange.second);
    m_issuedrange.first = m_issuedrange.second = m_queries.end();
    m_num_waiting = 0;
    m_dummy_pending = false;
    m_queries.clear();
  }
  detach();
}

void pqxx::pipeline::cancel()
{
  while (have_pending())
  {
    pqxx::internal::gate::connection_pipeline(m_trans.conn()).cancel_query();
    auto canceled_query = m_issuedrange.first;
    ++m_issuedrange.first;
    m_queries.erase(canceled_query);
  }
}

bool pqxx::pipeline::is_finished(pipeline::query_id q) const
{
  if (m_queries.find(q) == m_queries.end())
    throw std::logic_error{"Requested status for unknown query '" +
                           to_string(q) + "'."};
  return (QueryMap::const_iterator(m_issuedrange.first) == m_queries.end()) or
         (q < m_issuedrange.first->first and q < m_error);
}

std::pair<pqxx::pipeline::query_id, pqxx::result> pqxx::pipeline::retrieve()
{
  if (m_queries.empty())
    throw std::logic_error{"Attempt to retrieve result from empty pipeline."};
  return retrieve(std::begin(m_queries));
}

int pqxx::pipeline::retain(int retain_max)
{
  if (retain_max < 0)
    throw range_error{"Attempt to make pipeline retain " +
                      to_string(retain_max) + " queries"};

  const int oldvalue = m_retain;
  m_retain = retain_max;

  if (m_num_waiting >= m_retain)
    resume();

  return oldvalue;
}

void pqxx::pipeline::resume()
{
  if (have_pending())
    receive_if_available();
  if (not have_pending() and m_num_waiting)
  {
    issue();
    receive_if_available();
  }
}

pqxx::pipeline::query_id pqxx::pipeline::generate_id()
{
  if (m_q_id == qid_limit())
    throw std::overflow_error{"Too many queries went through pipeline."};
  ++m_q_id;
  return m_q_id;
}

void pqxx::pipeline::issue()
{
  // TODO: Wrap in nested transaction if available, for extra "replayability"

  // Retrieve that null result for the last query, if needed
  obtain_result();

  // Don't issue anything if we've encountered an error
  if (m_error < qid_limit())
    return;

  // Start with oldest query (lowest id) not in previous issue range
  auto oldest = m_issuedrange.second;

  // Construct cumulative query string for entire batch
  std::string cum = separated_list(
    theSeparator, oldest, m_queries.end(),
    [](QueryMap::const_iterator i) { return i->second.get_query(); });
  const auto num_issued =
    QueryMap::size_type(std::distance(oldest, m_queries.end()));
  const bool prepend_dummy = (num_issued > 1);
  if (prepend_dummy)
    cum = theDummyQuery + cum;

  pqxx::internal::gate::connection_pipeline{m_trans.conn()}.start_exec(
    cum.c_str());

  // Since we managed to send out these queries, update state to reflect this
  m_dummy_pending = prepend_dummy;
  m_issuedrange.first = oldest;
  m_issuedrange.second = m_queries.end();
  m_num_waiting -= check_cast<int>(num_issued, "pipeline issue()");
}

void pqxx::pipeline::internal_error(const std::string &err)
{
  set_error_at(0);
  throw pqxx::internal_error{err};
}

bool pqxx::pipeline::obtain_result(bool expect_none)
{
  pqxx::internal::gate::connection_pipeline gate{m_trans.conn()};
  const auto r = gate.get_result();
  if (r == nullptr)
  {
    if (have_pending() and not expect_none)
    {
      set_error_at(m_issuedrange.first->first);
      m_issuedrange.second = m_issuedrange.first;
    }
    return false;
  }

  const result res = pqxx::internal::gate::result_creation::create(
    r, std::begin(m_queries)->second.get_query(),
    internal::enc_group(m_trans.conn().encoding_id()));

  if (not have_pending())
  {
    set_error_at(std::begin(m_queries)->first);
    throw std::logic_error{
      "Got more results from pipeline than there were queries."};
  }

  // Must be the result for the oldest pending query
  if (not m_issuedrange.first->second.get_result().empty())
    internal_error("Multiple results for one query.");

  m_issuedrange.first->second.set_result(res);
  ++m_issuedrange.first;

  return true;
}

void pqxx::pipeline::obtain_dummy()
{
  pqxx::internal::gate::connection_pipeline gate{m_trans.conn()};
  const auto r = gate.get_result();
  m_dummy_pending = false;

  if (r == nullptr)
    internal_error(
      "Pipeline got no result from backend when it expected one.");

  // Allocate once, re-use across invocations.
  static const auto text{
    std::make_shared<std::string>("[DUMMY PIPELINE QUERY]")};

  result R = pqxx::internal::gate::result_creation::create(
    r, text, internal::enc_group(m_trans.conn().encoding_id()));

  bool OK = false;
  try
  {
    pqxx::internal::gate::result_creation{R}.check_status();
    OK = true;
  }
  catch (const sql_error &)
  {}
  if (OK)
  {
    if (R.size() > 1)
      internal_error("Unexpected result for dummy query in pipeline.");

    if (R.at(0).at(0).as<std::string>() != theDummyValue)
      internal_error("Dummy query in pipeline returned unexpected value.");
    return;
  }

  /* Since none of the queries in the batch were actually executed, we can
   * afford to replay them one by one until we find the exact query that
   * caused the error.  This gives us not only a more specific error message
   * to report, but also tells us which query to report it for.
   */
  // First, give the whole batch the same syntax error message, in case all
  // else is going to fail.
  for (auto i = m_issuedrange.first; i != m_issuedrange.second; ++i)
    i->second.set_result(R);

  // Remember where the end of this batch was
  const auto stop = m_issuedrange.second;

  // Retrieve that null result for the last query, if needed
  obtain_result(true);

  // Reset internal state to forget botched batch attempt
  m_num_waiting += check_cast<int>(
    std::distance(m_issuedrange.first, stop), "pipeline obtain_dummy()");
  m_issuedrange.second = m_issuedrange.first;

  // Issue queries in failed batch one at a time.
  unregister_me();
  try
  {
    do
    {
      m_num_waiting--;
      const auto query{*m_issuedrange.first->second.get_query()};
      const result res{m_trans.exec(query)};
      m_issuedrange.first->second.set_result(res);
      pqxx::internal::gate::result_creation{res}.check_status();
      ++m_issuedrange.first;
    } while (m_issuedrange.first != stop);
  }
  catch (const std::exception &)
  {
    const query_id thud = m_issuedrange.first->first;
    ++m_issuedrange.first;
    m_issuedrange.second = m_issuedrange.first;
    auto q = m_issuedrange.first;
    set_error_at((q == m_queries.end()) ? thud + 1 : q->first);
  }
}

std::pair<pqxx::pipeline::query_id, pqxx::result>
pqxx::pipeline::retrieve(pipeline::QueryMap::iterator q)
{
  if (q == m_queries.end())
    throw std::logic_error{"Attempt to retrieve result for unknown query."};

  if (q->first >= m_error)
    throw std::runtime_error{
      "Could not complete query in pipeline due to error in earlier query."};

  // If query hasn't issued yet, do it now
  if (
    m_issuedrange.second != m_queries.end() and
    (q->first >= m_issuedrange.second->first))
  {
    if (have_pending())
      receive(m_issuedrange.second);
    if (m_error == qid_limit())
      issue();
  }

  // If result not in yet, get it; else get at least whatever's convenient
  if (have_pending())
  {
    if (q->first >= m_issuedrange.first->first)
    {
      auto suc = q;
      ++suc;
      receive(suc);
    }
    else
    {
      receive_if_available();
    }
  }

  if (q->first >= m_error)
    throw std::runtime_error{
      "Could not complete query in pipeline due to error in earlier query."};

  // Don't leave the backend idle if there are queries waiting to be issued
  if (m_num_waiting and not have_pending() and (m_error == qid_limit()))
    issue();

  const result R = q->second.get_result();
  const auto P = std::make_pair(q->first, R);

  m_queries.erase(q);

  pqxx::internal::gate::result_creation{R}.check_status();
  return P;
}

void pqxx::pipeline::get_further_available_results()
{
  pqxx::internal::gate::connection_pipeline gate{m_trans.conn()};
  while (not gate.is_busy() and obtain_result())
    if (not gate.consume_input())
      throw broken_connection{};
}

void pqxx::pipeline::receive_if_available()
{
  pqxx::internal::gate::connection_pipeline gate{m_trans.conn()};
  if (not gate.consume_input())
    throw broken_connection{};
  if (gate.is_busy())
    return;

  if (m_dummy_pending)
    obtain_dummy();
  if (have_pending())
    get_further_available_results();
}

void pqxx::pipeline::receive(pipeline::QueryMap::const_iterator stop)
{
  if (m_dummy_pending)
    obtain_dummy();

  while (obtain_result() and
         QueryMap::const_iterator{m_issuedrange.first} != stop)
    ;

  // Also haul in any remaining "targets of opportunity"
  if (QueryMap::const_iterator{m_issuedrange.first} == stop)
    get_further_available_results();
}
<commit_msg>Punctuate.<commit_after>/** Implementation of the pqxx::pipeline class.
 *
 * Throughput-optimized query interface.
 *
 * Copyright (c) 2000-2020, Jeroen T. Vermeulen.
 *
 * See COPYING for copyright license.  If you did not receive a file called
 * COPYING with this source code, please notify the distributor of this
 * mistake, or contact the author.
 */
#include "pqxx-source.hxx"

#include <iterator>

#include "pqxx/dbtransaction"
#include "pqxx/pipeline"
#include "pqxx/separated_list"

#include "pqxx/internal/gates/connection-pipeline.hxx"
#include "pqxx/internal/gates/result-creation.hxx"
#include "pqxx/internal/gates/result-pipeline.hxx"

namespace
{
const std::string theSeparator{"; "};
const std::string theDummyValue{"1"};
const std::string theDummyQuery{"SELECT " + theDummyValue + theSeparator};
} // namespace

void pqxx::pipeline::init()
{
  m_issuedrange = make_pair(m_queries.end(), m_queries.end());
  attach();
}

pqxx::pipeline::~pipeline() noexcept
{
  try
  {
    cancel();
  }
  catch (const std::exception &)
  {}
  detach();
}

void pqxx::pipeline::attach()
{
  if (not registered())
    register_me();
}

void pqxx::pipeline::detach()
{
  if (registered())
    unregister_me();
}

pqxx::pipeline::query_id pqxx::pipeline::insert(std::string_view q)
{
  attach();
  const query_id qid = generate_id();
  const auto i = m_queries.insert(std::make_pair(qid, Query(q))).first;

  if (m_issuedrange.second == m_queries.end())
  {
    m_issuedrange.second = i;
    if (m_issuedrange.first == m_queries.end())
      m_issuedrange.first = i;
  }
  m_num_waiting++;

  if (m_num_waiting > m_retain)
  {
    if (have_pending())
      receive_if_available();
    if (not have_pending())
      issue();
  }

  return qid;
}

void pqxx::pipeline::complete()
{
  if (have_pending())
    receive(m_issuedrange.second);
  if (m_num_waiting and (m_error == qid_limit()))
  {
    issue();
    receive(m_queries.end());
  }
  detach();
}

void pqxx::pipeline::flush()
{
  if (not m_queries.empty())
  {
    if (have_pending())
      receive(m_issuedrange.second);
    m_issuedrange.first = m_issuedrange.second = m_queries.end();
    m_num_waiting = 0;
    m_dummy_pending = false;
    m_queries.clear();
  }
  detach();
}

void pqxx::pipeline::cancel()
{
  while (have_pending())
  {
    pqxx::internal::gate::connection_pipeline(m_trans.conn()).cancel_query();
    auto canceled_query = m_issuedrange.first;
    ++m_issuedrange.first;
    m_queries.erase(canceled_query);
  }
}

bool pqxx::pipeline::is_finished(pipeline::query_id q) const
{
  if (m_queries.find(q) == m_queries.end())
    throw std::logic_error{"Requested status for unknown query '" +
                           to_string(q) + "'."};
  return (QueryMap::const_iterator(m_issuedrange.first) == m_queries.end()) or
         (q < m_issuedrange.first->first and q < m_error);
}

std::pair<pqxx::pipeline::query_id, pqxx::result> pqxx::pipeline::retrieve()
{
  if (m_queries.empty())
    throw std::logic_error{"Attempt to retrieve result from empty pipeline."};
  return retrieve(std::begin(m_queries));
}

int pqxx::pipeline::retain(int retain_max)
{
  if (retain_max < 0)
    throw range_error{"Attempt to make pipeline retain " +
                      to_string(retain_max) + " queries"};

  const int oldvalue = m_retain;
  m_retain = retain_max;

  if (m_num_waiting >= m_retain)
    resume();

  return oldvalue;
}

void pqxx::pipeline::resume()
{
  if (have_pending())
    receive_if_available();
  if (not have_pending() and m_num_waiting)
  {
    issue();
    receive_if_available();
  }
}

pqxx::pipeline::query_id pqxx::pipeline::generate_id()
{
  if (m_q_id == qid_limit())
    throw std::overflow_error{"Too many queries went through pipeline."};
  ++m_q_id;
  return m_q_id;
}

void pqxx::pipeline::issue()
{
  // Retrieve that null result for the last query, if needed.
  obtain_result();

  // Don't issue anything if we've encountered an error.
  if (m_error < qid_limit())
    return;

  // Start with oldest query (lowest id) not in previous issue range.
  auto oldest = m_issuedrange.second;

  // Construct cumulative query string for entire batch.
  std::string cum = separated_list(
    theSeparator, oldest, m_queries.end(),
    [](QueryMap::const_iterator i) { return i->second.get_query(); });
  const auto num_issued =
    QueryMap::size_type(std::distance(oldest, m_queries.end()));
  const bool prepend_dummy = (num_issued > 1);
  if (prepend_dummy)
    cum = theDummyQuery + cum;

  pqxx::internal::gate::connection_pipeline{m_trans.conn()}.start_exec(
    cum.c_str());

  // Since we managed to send out these queries, update state to reflect this.
  m_dummy_pending = prepend_dummy;
  m_issuedrange.first = oldest;
  m_issuedrange.second = m_queries.end();
  m_num_waiting -= check_cast<int>(num_issued, "pipeline issue()");
}

void pqxx::pipeline::internal_error(const std::string &err)
{
  set_error_at(0);
  throw pqxx::internal_error{err};
}

bool pqxx::pipeline::obtain_result(bool expect_none)
{
  pqxx::internal::gate::connection_pipeline gate{m_trans.conn()};
  const auto r = gate.get_result();
  if (r == nullptr)
  {
    if (have_pending() and not expect_none)
    {
      set_error_at(m_issuedrange.first->first);
      m_issuedrange.second = m_issuedrange.first;
    }
    return false;
  }

  const result res = pqxx::internal::gate::result_creation::create(
    r, std::begin(m_queries)->second.get_query(),
    internal::enc_group(m_trans.conn().encoding_id()));

  if (not have_pending())
  {
    set_error_at(std::begin(m_queries)->first);
    throw std::logic_error{
      "Got more results from pipeline than there were queries."};
  }

  // Must be the result for the oldest pending query
  if (not m_issuedrange.first->second.get_result().empty())
    internal_error("Multiple results for one query.");

  m_issuedrange.first->second.set_result(res);
  ++m_issuedrange.first;

  return true;
}

void pqxx::pipeline::obtain_dummy()
{
  pqxx::internal::gate::connection_pipeline gate{m_trans.conn()};
  const auto r = gate.get_result();
  m_dummy_pending = false;

  if (r == nullptr)
    internal_error(
      "Pipeline got no result from backend when it expected one.");

  // Allocate once, re-use across invocations.
  static const auto text{
    std::make_shared<std::string>("[DUMMY PIPELINE QUERY]")};

  result R = pqxx::internal::gate::result_creation::create(
    r, text, internal::enc_group(m_trans.conn().encoding_id()));

  bool OK = false;
  try
  {
    pqxx::internal::gate::result_creation{R}.check_status();
    OK = true;
  }
  catch (const sql_error &)
  {}
  if (OK)
  {
    if (R.size() > 1)
      internal_error("Unexpected result for dummy query in pipeline.");

    if (R.at(0).at(0).as<std::string>() != theDummyValue)
      internal_error("Dummy query in pipeline returned unexpected value.");
    return;
  }

  /* Since none of the queries in the batch were actually executed, we can
   * afford to replay them one by one until we find the exact query that
   * caused the error.  This gives us not only a more specific error message
   * to report, but also tells us which query to report it for.
   */
  // First, give the whole batch the same syntax error message, in case all
  // else is going to fail.
  for (auto i = m_issuedrange.first; i != m_issuedrange.second; ++i)
    i->second.set_result(R);

  // Remember where the end of this batch was
  const auto stop = m_issuedrange.second;

  // Retrieve that null result for the last query, if needed
  obtain_result(true);

  // Reset internal state to forget botched batch attempt
  m_num_waiting += check_cast<int>(
    std::distance(m_issuedrange.first, stop), "pipeline obtain_dummy()");
  m_issuedrange.second = m_issuedrange.first;

  // Issue queries in failed batch one at a time.
  unregister_me();
  try
  {
    do
    {
      m_num_waiting--;
      const auto query{*m_issuedrange.first->second.get_query()};
      const result res{m_trans.exec(query)};
      m_issuedrange.first->second.set_result(res);
      pqxx::internal::gate::result_creation{res}.check_status();
      ++m_issuedrange.first;
    } while (m_issuedrange.first != stop);
  }
  catch (const std::exception &)
  {
    const query_id thud = m_issuedrange.first->first;
    ++m_issuedrange.first;
    m_issuedrange.second = m_issuedrange.first;
    auto q = m_issuedrange.first;
    set_error_at((q == m_queries.end()) ? thud + 1 : q->first);
  }
}

std::pair<pqxx::pipeline::query_id, pqxx::result>
pqxx::pipeline::retrieve(pipeline::QueryMap::iterator q)
{
  if (q == m_queries.end())
    throw std::logic_error{"Attempt to retrieve result for unknown query."};

  if (q->first >= m_error)
    throw std::runtime_error{
      "Could not complete query in pipeline due to error in earlier query."};

  // If query hasn't issued yet, do it now
  if (
    m_issuedrange.second != m_queries.end() and
    (q->first >= m_issuedrange.second->first))
  {
    if (have_pending())
      receive(m_issuedrange.second);
    if (m_error == qid_limit())
      issue();
  }

  // If result not in yet, get it; else get at least whatever's convenient
  if (have_pending())
  {
    if (q->first >= m_issuedrange.first->first)
    {
      auto suc = q;
      ++suc;
      receive(suc);
    }
    else
    {
      receive_if_available();
    }
  }

  if (q->first >= m_error)
    throw std::runtime_error{
      "Could not complete query in pipeline due to error in earlier query."};

  // Don't leave the backend idle if there are queries waiting to be issued
  if (m_num_waiting and not have_pending() and (m_error == qid_limit()))
    issue();

  const result R = q->second.get_result();
  const auto P = std::make_pair(q->first, R);

  m_queries.erase(q);

  pqxx::internal::gate::result_creation{R}.check_status();
  return P;
}

void pqxx::pipeline::get_further_available_results()
{
  pqxx::internal::gate::connection_pipeline gate{m_trans.conn()};
  while (not gate.is_busy() and obtain_result())
    if (not gate.consume_input())
      throw broken_connection{};
}

void pqxx::pipeline::receive_if_available()
{
  pqxx::internal::gate::connection_pipeline gate{m_trans.conn()};
  if (not gate.consume_input())
    throw broken_connection{};
  if (gate.is_busy())
    return;

  if (m_dummy_pending)
    obtain_dummy();
  if (have_pending())
    get_further_available_results();
}

void pqxx::pipeline::receive(pipeline::QueryMap::const_iterator stop)
{
  if (m_dummy_pending)
    obtain_dummy();

  while (obtain_result() and
         QueryMap::const_iterator{m_issuedrange.first} != stop)
    ;

  // Also haul in any remaining "targets of opportunity"
  if (QueryMap::const_iterator{m_issuedrange.first} == stop)
    get_further_available_results();
}
<|endoftext|>


<commit_before>// SA:MP Profiler plugin
//
// Copyright (c) 2011 Zeex
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <list>
#include <numeric>
#include <string>
#include <vector>

#include "amxnamefinder.h"
#include "jump.h"
#include "plugincommon.h"
#include "profiler.h"

std::map<AMX*, AmxProfiler*> AmxProfiler::instances_;

AmxProfiler::AmxProfiler() {}

static void GetNatives(AMX *amx, std::vector<std::string> &names) {
    AMX_HEADER *hdr = reinterpret_cast<AMX_HEADER*>(amx->base);

    AMX_FUNCSTUBNT *natives = reinterpret_cast<AMX_FUNCSTUBNT*>(amx->base + hdr->natives);
    int numNatives = (hdr->libraries - hdr->natives) / hdr->defsize;

    for (int i = 0; i < numNatives; i++) {
        names.push_back(reinterpret_cast<char*>(amx->base + natives[i].nameofs));
    }
}

AmxProfiler::AmxProfiler(AMX *amx, AMX_DBG amxdbg)
    : amx_(amx),
      amxdbg_(amxdbg),
      debug_(amx->debug),
      callback_(amx->callback),
      active_(false),
      frame_(amx->stp)
{
    // Since PrintStats is done in AmxUnload and amx->base is already freed before
    // AmxUnload gets called, the native table is not accessible there thus natives'
    // names must be stored in some global place.
    GetNatives(amx, nativeNames_);
}

void AmxProfiler::Attach(AMX *amx, AMX_DBG amxdbg) {
    AmxProfiler *prof = new AmxProfiler(amx, amxdbg);
    instances_[amx] = prof;
    prof->Activate();
}

void AmxProfiler::Detach(AMX *amx) {
    AmxProfiler *prof = AmxProfiler::Get(amx);
    if (prof != 0) {
        prof->Deactivate();
        delete prof;
    }
    instances_.erase(amx);
}

AmxProfiler *AmxProfiler::Get(AMX *amx) {
    std::map<AMX*, AmxProfiler*>::iterator it = instances_.find(amx);
    if (it != instances_.end()) {
        return it->second;
    }
    return 0;
}

static int AMXAPI Debug(AMX *amx) {
    return AmxProfiler::Get(amx)->Debug();
}

static int AMXAPI Callback(AMX *amx, cell index, cell *result, cell *params) {
    return AmxProfiler::Get(amx)->Callback(index, result, params);
}

void AmxProfiler::Activate() {
    if (!active_) {
        active_ = true;
        amx_SetDebugHook(amx_, ::Debug);
        amx_SetCallback(amx_, ::Callback);
    }
}

bool AmxProfiler::IsActive() const {
    return active_;
}

void AmxProfiler::Deactivate() {
    if (active_) {
        active_ = false;
        amx_SetDebugHook(amx_, debug_);
        amx_SetCallback(amx_, callback_);
    }
}

void AmxProfiler::ResetStats() {
    counters_.clear();
}

static bool ByCalls(const std::pair<cell, AmxPerformanceCounter> &op1,
                         const std::pair<cell, AmxPerformanceCounter> &op2) {
    return op1.second.GetCalls() > op2.second.GetCalls();
}

static bool ByTime(const std::pair<cell, AmxPerformanceCounter> &op1,
                        const std::pair<cell, AmxPerformanceCounter> &op2) {
    return op1.second.GetTime() > op2.second.GetTime();
}

static bool ByTimePerCall(const std::pair<cell, AmxPerformanceCounter> &op1,
                               const std::pair<cell, AmxPerformanceCounter> &op2) {
    return static_cast<double>(op1.second.GetTime()) / static_cast<double>(op1.second.GetCalls())
         > static_cast<double>(op2.second.GetTime()) / static_cast<double>(op2.second.GetCalls());
}

bool AmxProfiler::PrintStats(const std::string &filename, StatsPrintOrder order) {
    std::ofstream stream(filename.c_str());

    if (stream.is_open()) {
        std::vector<std::pair<cell, AmxPerformanceCounter> > stats(counters_.begin(),
                                                     counters_.end());
        switch (order) {
            case ORDER_BY_CALLS:
                std::sort(stats.begin(), stats.end(), ByCalls);
                break;
            case ORDER_BY_TIME:
                std::sort(stats.begin(), stats.end(), ByTime);
                break;
            case ORDER_BY_TIME_PER_CALL:
                std::sort(stats.begin(), stats.end(), ByTimePerCall);
                break;
            default:
                // leave as is
                break;
        }

        stream << "
\n"
               << "\t
\n"
               << "\t\tFunction	\n"
               << "\t\tCalls	\n"
               << "\t\tTime per call, µs	\n"
               << "\t\tOverall time, µs	\n"
               << "\t\tOverall time, %	\n"
               << "\t
\n";

        platformstl::int64_t totalTime = 0;

        for (std::vector<std::pair<cell, AmxPerformanceCounter> >::iterator it = stats.begin();
             it != stats.end(); ++it)
        {
            totalTime += it->second.GetTime();
        }

        for (std::vector<std::pair<cell, AmxPerformanceCounter> >::iterator it = stats.begin();
             it != stats.end(); ++it)
        {
            stream << "\t
\n";

            if (it->first <= 0) {
                stream << "\t\t" << nativeNames_[-it->first] << "	\n";
            } else {
                const char *name;
                if (dbg_LookupFunction(&amxdbg_, it->first, &name) == AMX_ERR_NONE) {
                    //stream << "\t\t" << name << "@" << std::hex << it->first << std::dec << "	\n";
                    stream << "\t\t" << name << "	\n";
                } else {
                    stream << "\t\t" << std::hex << it->first << std::dec << "	\n";
                }
            }

            stream << "\t\t" << it->second.GetCalls() << "	\n"
                   << "\t\t" << std::fixed << std::setprecision(0)
                                 << static_cast<double>(it->second.GetTime()) /
                                        static_cast<double>(it->second.GetCalls()) << "	\n"
                   << "\t\t" << it->second.GetTime() << "	\n"
                   << "\t\t" << std::setprecision(2)
                                 << static_cast<double>(it->second.GetTime() * 100) /
                                        static_cast<double>(totalTime) << "	\n";
            stream << "\t
\n";
        }

        stream << "
\n";

        return true;
    }

    return false;
}

int AmxProfiler::Debug() {
    if (amx_->frm != frame_) {
        if (amx_->frm < frame_) {
            // Probably entered a function body (first BREAK after PROC)
            cell address = amx_->cip - 2*sizeof(cell);
            // Check if we have a PROC opcode behind us
            AMX_HEADER *hdr = reinterpret_cast<AMX_HEADER*>(amx_->base);
            cell op = *reinterpret_cast<cell*>(hdr->cod + amx_->base + address);
            if (op == 46) {
                callStack_.push(address);
                if (active_) {
                    counters_[address].Start();
                }
            }
        } else {
            // Left a function
            cell address = callStack_.top();
            if (active_) {
                counters_[address].Stop();
            }
            callStack_.pop();
            if (callStack_.empty()) {
                frame_ = amx_->stp;
            }
        }
        frame_ = amx_->frm;
    }

    if (debug_ != 0) {
        // Others could set their own debug hooks
        return debug_(amx_);
    }

    return AMX_ERR_NONE;
}

int AmxProfiler::Callback(cell index, cell *result, cell *params) {
    // The default AMX callback (amx_Callback) can replace SYSREQ.C opcodes
    // with SYSREQ.D for better performance.
    amx_->sysreq_d = 0;

    if (active_) {
        counters_[-index].Start();; // Notice negative index
    }

    // Call any previously set AMX callback (must not be null so we don't check)
    int error = callback_(amx_, index, result, params);

    if (active_) {
        counters_[-index].Stop();
    }

    return error;
}

static uint32_t amx_Exec_addr;
static unsigned char amx_Exec_code[5];

static int AMXAPI Exec(AMX *amx, cell *retval, int index) {
    memcpy(reinterpret_cast<void*>(::amx_Exec_addr), ::amx_Exec_code, 5);

    int error;

    // Check if this script has a profiler attached to it
    AmxProfiler *prof = AmxProfiler::Get(amx);
    if (prof != 0 && prof->IsActive()) {
        error =  prof->Exec(retval, index);
    } else {
        error = amx_Exec(amx, retval, index);
    }

    SetJump(reinterpret_cast<void*>(::amx_Exec_addr), (void*)::Exec, ::amx_Exec_code);

    return error;
}

int AmxProfiler::Exec(cell *retval, int index) {
    AMX_HEADER *hdr = reinterpret_cast<AMX_HEADER*>(amx_->base);
    AMX_FUNCSTUBNT *publics = reinterpret_cast<AMX_FUNCSTUBNT*>(amx_->base + hdr->publics);

    cell address = publics[index].address;
    callStack_.push(address);
    counters_[address].Start();

    int error = amx_Exec(amx_, retval, index);

    counters_[address].Stop();

    if (!callStack_.empty()) {
        callStack_.pop();
    }
    if (callStack_.empty()) {
        frame_ = amx_->stp;
    }

    return error;
}

PLUGIN_EXPORT unsigned int PLUGIN_CALL Supports() {
    return SUPPORTS_VERSION | SUPPORTS_AMX_NATIVES;
}

extern void *pAMXFunctions;

// Both x86 and x86-64 are Little Endian
static void *AMXAPI DummyAmxAlign(void *v) { return v; }

static std::list<std::string> profiledScripts;

PLUGIN_EXPORT bool PLUGIN_CALL Load(void **pluginData) {
    pAMXFunctions = pluginData[PLUGIN_DATA_AMX_EXPORTS];

    // The server does not export amx_Align16 and amx_Align32 for some reason
    ((void**)pAMXFunctions)[PLUGIN_AMX_EXPORT_Align16] = (void*)DummyAmxAlign;
    ((void**)pAMXFunctions)[PLUGIN_AMX_EXPORT_Align32] = (void*)DummyAmxAlign;

    ::amx_Exec_addr = reinterpret_cast<uint32_t>((static_cast<void**>(pAMXFunctions))[PLUGIN_AMX_EXPORT_Exec]);
    SetJump(reinterpret_cast<void*>(::amx_Exec_addr), (void*)::Exec, ::amx_Exec_code);

    // Get list of scripts to be profiled
    std::copy(std::istream_iterator<std::string>(std::ifstream("plugins/profiler.cfg")),
              std::istream_iterator<std::string>(),
              std::back_inserter(::profiledScripts));

    //std::copy(::profiledScripts.begin(), ::profiledScripts.end(), std::ostream_iterator<std::string>(std::cout));

    // Initialize the name finder
    AmxNameFinder::GetInstance()->AddSearchDir("gamemodes");
    AmxNameFinder::GetInstance()->AddSearchDir("filterscripts");

    return true;
}

PLUGIN_EXPORT void PLUGIN_CALL Unload() {
    return;
}

PLUGIN_EXPORT int PLUGIN_CALL AmxLoad(AMX *amx) {
    AmxNameFinder::GetInstance()->UpdateCache(); // Check if new files were added
    std::string filename = AmxNameFinder::GetInstance()->GetAmxName(amx);
    if (!filename.empty()) {
        // Use '/' as directory separator on both Windows and Linux
        std::replace(filename.begin(), filename.end(), '\\', '/');
        if (std::find(::profiledScripts.begin(),
                      ::profiledScripts.end(), filename) != ::profiledScripts.end())
        {
            FILE *fp = fopen(filename.c_str(), "rb");
            if (fp != 0) {
                AMX_DBG amxdbg;
                int error = dbg_LoadInfo(&amxdbg, fp);
                if (error == AMX_ERR_NONE) {
                    AmxProfiler::Attach(amx, amxdbg);
                } else {
                    // The server takes care of printing the error message
                    return error;
                }
                fclose(fp);
            }
        }
    }
    return AMX_ERR_NONE;
}

PLUGIN_EXPORT int PLUGIN_CALL AmxUnload(AMX *amx) {
    AmxProfiler *prof = AmxProfiler::Get(amx);
    std::string name = AmxNameFinder::GetInstance()->GetAmxName(amx);
    if (prof != 0 && !name.empty()) {
        prof->PrintStats(name + std::string(".prof"));
        AmxProfiler::Detach(amx);
    }
    return AMX_ERR_NONE;
}
<commit_msg>Fix bug when same public processed twice (by both Debug and Exec)<commit_after>// SA:MP Profiler plugin
//
// Copyright (c) 2011 Zeex
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <list>
#include <numeric>
#include <string>
#include <vector>

#include "amxnamefinder.h"
#include "jump.h"
#include "plugincommon.h"
#include "profiler.h"

std::map<AMX*, AmxProfiler*> AmxProfiler::instances_;

AmxProfiler::AmxProfiler() {}

static void GetNatives(AMX *amx, std::vector<std::string> &names) {
    AMX_HEADER *hdr = reinterpret_cast<AMX_HEADER*>(amx->base);

    AMX_FUNCSTUBNT *natives = reinterpret_cast<AMX_FUNCSTUBNT*>(amx->base + hdr->natives);
    int numNatives = (hdr->libraries - hdr->natives) / hdr->defsize;

    for (int i = 0; i < numNatives; i++) {
        names.push_back(reinterpret_cast<char*>(amx->base + natives[i].nameofs));
    }
}

AmxProfiler::AmxProfiler(AMX *amx, AMX_DBG amxdbg)
    : amx_(amx),
      amxdbg_(amxdbg),
      debug_(amx->debug),
      callback_(amx->callback),
      active_(false),
      frame_(amx->stp)
{
    // Since PrintStats is done in AmxUnload and amx->base is already freed before
    // AmxUnload gets called, the native table is not accessible there thus natives'
    // names must be stored in some global place.
    GetNatives(amx, nativeNames_);
}

void AmxProfiler::Attach(AMX *amx, AMX_DBG amxdbg) {
    AmxProfiler *prof = new AmxProfiler(amx, amxdbg);
    instances_[amx] = prof;
    prof->Activate();
}

void AmxProfiler::Detach(AMX *amx) {
    AmxProfiler *prof = AmxProfiler::Get(amx);
    if (prof != 0) {
        prof->Deactivate();
        delete prof;
    }
    instances_.erase(amx);
}

AmxProfiler *AmxProfiler::Get(AMX *amx) {
    std::map<AMX*, AmxProfiler*>::iterator it = instances_.find(amx);
    if (it != instances_.end()) {
        return it->second;
    }
    return 0;
}

static int AMXAPI Debug(AMX *amx) {
    return AmxProfiler::Get(amx)->Debug();
}

static int AMXAPI Callback(AMX *amx, cell index, cell *result, cell *params) {
    return AmxProfiler::Get(amx)->Callback(index, result, params);
}

void AmxProfiler::Activate() {
    if (!active_) {
        active_ = true;
        amx_SetDebugHook(amx_, ::Debug);
        amx_SetCallback(amx_, ::Callback);
    }
}

bool AmxProfiler::IsActive() const {
    return active_;
}

void AmxProfiler::Deactivate() {
    if (active_) {
        active_ = false;
        amx_SetDebugHook(amx_, debug_);
        amx_SetCallback(amx_, callback_);
    }
}

void AmxProfiler::ResetStats() {
    counters_.clear();
}

static bool ByCalls(const std::pair<cell, AmxPerformanceCounter> &op1,
                         const std::pair<cell, AmxPerformanceCounter> &op2) {
    return op1.second.GetCalls() > op2.second.GetCalls();
}

static bool ByTime(const std::pair<cell, AmxPerformanceCounter> &op1,
                        const std::pair<cell, AmxPerformanceCounter> &op2) {
    return op1.second.GetTime() > op2.second.GetTime();
}

static bool ByTimePerCall(const std::pair<cell, AmxPerformanceCounter> &op1,
                               const std::pair<cell, AmxPerformanceCounter> &op2) {
    return static_cast<double>(op1.second.GetTime()) / static_cast<double>(op1.second.GetCalls())
         > static_cast<double>(op2.second.GetTime()) / static_cast<double>(op2.second.GetCalls());
}

bool AmxProfiler::PrintStats(const std::string &filename, StatsPrintOrder order) {
    std::ofstream stream(filename.c_str());

    if (stream.is_open()) {
        std::vector<std::pair<cell, AmxPerformanceCounter> > stats(counters_.begin(),
                                                     counters_.end());
        switch (order) {
            case ORDER_BY_CALLS:
                std::sort(stats.begin(), stats.end(), ByCalls);
                break;
            case ORDER_BY_TIME:
                std::sort(stats.begin(), stats.end(), ByTime);
                break;
            case ORDER_BY_TIME_PER_CALL:
                std::sort(stats.begin(), stats.end(), ByTimePerCall);
                break;
            default:
                // leave as is
                break;
        }

        stream << "
\n"
               << "\t
\n"
               << "\t\tFunction	\n"
               << "\t\tCalls	\n"
               << "\t\tTime per call, µs	\n"
               << "\t\tOverall time, µs	\n"
               << "\t\tOverall time, %	\n"
               << "\t
\n";

        platformstl::int64_t totalTime = 0;

        for (std::vector<std::pair<cell, AmxPerformanceCounter> >::iterator it = stats.begin();
             it != stats.end(); ++it)
        {
            totalTime += it->second.GetTime();
        }

        for (std::vector<std::pair<cell, AmxPerformanceCounter> >::iterator it = stats.begin();
             it != stats.end(); ++it)
        {
            stream << "\t
\n";

            if (it->first <= 0) {
                stream << "\t\t" << nativeNames_[-it->first] << "	\n";
            } else {
                const char *name;
                if (dbg_LookupFunction(&amxdbg_, it->first, &name) == AMX_ERR_NONE) {
                    //stream << "\t\t" << name << "@" << std::hex << it->first << std::dec << "	\n";
                    stream << "\t\t" << name << "	\n";
                } else {
                    stream << "\t\t" << std::hex << it->first << std::dec << "	\n";
                }
            }

            stream << "\t\t" << it->second.GetCalls() << "	\n"
                   << "\t\t" << std::fixed << std::setprecision(0)
                                 << static_cast<double>(it->second.GetTime()) /
                                        static_cast<double>(it->second.GetCalls()) << "	\n"
                   << "\t\t" << it->second.GetTime() << "	\n"
                   << "\t\t" << std::setprecision(2)
                                 << static_cast<double>(it->second.GetTime() * 100) /
                                        static_cast<double>(totalTime) << "	\n";
            stream << "\t
\n";
        }

        stream << "
\n";

        return true;
    }

    return false;
}

int AmxProfiler::Debug() {
    if (amx_->frm != frame_ && frame_ > amx_->hea) {
        if (amx_->frm < frame_) {
            // Probably entered a function body (first BREAK after PROC)
            cell address = amx_->cip - 2*sizeof(cell);
            // Check if we have a PROC opcode behind us
            AMX_HEADER *hdr = reinterpret_cast<AMX_HEADER*>(amx_->base);
            cell op = *reinterpret_cast<cell*>(hdr->cod + amx_->base + address);
            if (op == 46) {
                callStack_.push(address);
                if (active_) {
                    counters_[address].Start();
                }
            }
        } else {
            // Left a function
            cell address = callStack_.top();
            if (active_) {
                counters_[address].Stop();
            }
            callStack_.pop();
            if (callStack_.empty()) {
                frame_ = amx_->stp;
            }
        }
        frame_ = amx_->frm;
    }

    if (debug_ != 0) {
        // Others could set their own debug hooks
        return debug_(amx_);
    }

    return AMX_ERR_NONE;
}

int AmxProfiler::Callback(cell index, cell *result, cell *params) {
    // The default AMX callback (amx_Callback) can replace SYSREQ.C opcodes
    // with SYSREQ.D for better performance.
    amx_->sysreq_d = 0;

    if (active_) {
        counters_[-index].Start();; // Notice negative index
    }

    // Call any previously set AMX callback (must not be null so we don't check)
    int error = callback_(amx_, index, result, params);

    if (active_) {
        counters_[-index].Stop();
    }

    return error;
}

static uint32_t amx_Exec_addr;
static unsigned char amx_Exec_code[5];

static int AMXAPI Exec(AMX *amx, cell *retval, int index) {
    memcpy(reinterpret_cast<void*>(::amx_Exec_addr), ::amx_Exec_code, 5);

    int error;

    // Check if this script has a profiler attached to it
    AmxProfiler *prof = AmxProfiler::Get(amx);
    if (prof != 0 && prof->IsActive()) {
        error =  prof->Exec(retval, index);
    } else {
        error = amx_Exec(amx, retval, index);
    }

    SetJump(reinterpret_cast<void*>(::amx_Exec_addr), (void*)::Exec, ::amx_Exec_code);

    return error;
}

int AmxProfiler::Exec(cell *retval, int index) {
    AMX_HEADER *hdr = reinterpret_cast<AMX_HEADER*>(amx_->base);
    AMX_FUNCSTUBNT *publics = reinterpret_cast<AMX_FUNCSTUBNT*>(amx_->base + hdr->publics);

    cell address = publics[index].address;
    callStack_.push(address);

    // Set frame_ to the value which amx_->frm will be set to
    // during amx_Exec. If we do this Debug() will think that
    // the frame stays the same and won't profile this call.
    frame_ = amx_->stk - 3*sizeof(cell);

    counters_[address].Start();
    int error = amx_Exec(amx_, retval, index);
    counters_[address].Stop();

    if (!callStack_.empty()) {
        callStack_.pop();
    } else {
        frame_ = amx_->stp;
    }

    return error;
}

PLUGIN_EXPORT unsigned int PLUGIN_CALL Supports() {
    return SUPPORTS_VERSION | SUPPORTS_AMX_NATIVES;
}

extern void *pAMXFunctions;

// Both x86 and x86-64 are Little Endian
static void *AMXAPI DummyAmxAlign(void *v) { return v; }

static std::list<std::string> profiledScripts;

PLUGIN_EXPORT bool PLUGIN_CALL Load(void **pluginData) {
    pAMXFunctions = pluginData[PLUGIN_DATA_AMX_EXPORTS];

    // The server does not export amx_Align16 and amx_Align32 for some reason
    ((void**)pAMXFunctions)[PLUGIN_AMX_EXPORT_Align16] = (void*)DummyAmxAlign;
    ((void**)pAMXFunctions)[PLUGIN_AMX_EXPORT_Align32] = (void*)DummyAmxAlign;

    // Hook amx_Execc
    ::amx_Exec_addr = reinterpret_cast<uint32_t>((static_cast<void**>(pAMXFunctions))[PLUGIN_AMX_EXPORT_Exec]);
    SetJump(reinterpret_cast<void*>(::amx_Exec_addr), (void*)::Exec, ::amx_Exec_code);

    // Get list of scripts to be profiled
    std::copy(std::istream_iterator<std::string>(std::ifstream("plugins/profiler.cfg")),
              std::istream_iterator<std::string>(),
              std::back_inserter(::profiledScripts));

    //std::copy(::profiledScripts.begin(), ::profiledScripts.end(), std::ostream_iterator<std::string>(std::cout));

    // Initialize the name finder
    AmxNameFinder::GetInstance()->AddSearchDir("gamemodes");
    AmxNameFinder::GetInstance()->AddSearchDir("filterscripts");

    return true;
}

PLUGIN_EXPORT void PLUGIN_CALL Unload() {
    return;
}

PLUGIN_EXPORT int PLUGIN_CALL AmxLoad(AMX *amx) {
    AmxNameFinder::GetInstance()->UpdateCache(); // Check if new files were added
    std::string filename = AmxNameFinder::GetInstance()->GetAmxName(amx);
    if (!filename.empty()) {
        // Use '/' as directory separator on both Windows and Linux
        std::replace(filename.begin(), filename.end(), '\\', '/');
        if (std::find(::profiledScripts.begin(),
                      ::profiledScripts.end(), filename) != ::profiledScripts.end())
        {
            FILE *fp = fopen(filename.c_str(), "rb");
            if (fp != 0) {
                AMX_DBG amxdbg;
                int error = dbg_LoadInfo(&amxdbg, fp);
                if (error == AMX_ERR_NONE) {
                    AmxProfiler::Attach(amx, amxdbg);
                } else {
                    return error;
                }
                fclose(fp);
            }
        }
    }
    return AMX_ERR_NONE;
}

PLUGIN_EXPORT int PLUGIN_CALL AmxUnload(AMX *amx) {
    AmxProfiler *prof = AmxProfiler::Get(amx);
    std::string name = AmxNameFinder::GetInstance()->GetAmxName(amx);
    if (prof != 0 && !name.empty()) {
        prof->PrintStats(name + std::string(".prof"));
        AmxProfiler::Detach(amx);
    }
    return AMX_ERR_NONE;
}
<|endoftext|>


<commit_before>// Copyright (c) 2011-2013 The Bitcoin developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "intro.h"
#include "ui_intro.h"

#include "util.h"

#include <boost/filesystem.hpp>
#include <QFileDialog>
#include <QSettings>
#include <QMessageBox>

/* Minimum free space (in bytes) needed for data directory */
static const uint64_t GB_BYTES = 1000000000LL;
static const uint64_t BLOCK_CHAIN_SIZE = 10LL * GB_BYTES;

/* Check free space asynchronously to prevent hanging the UI thread.

   Up to one request to check a path is in flight to this thread; when the check()
   function runs, the current path is requested from the associated Intro object.
   The reply is sent back through a signal.

   This ensures that no queue of checking requests is built up while the user is
   still entering the path, and that always the most recently entered path is checked as
   soon as the thread becomes available.
*/
class FreespaceChecker : public QObject
{
    Q_OBJECT

public:
    FreespaceChecker(Intro *intro);

    enum Status {
        ST_OK,
        ST_ERROR
    };

public slots:
    void check();

signals:
    void reply(int status, const QString &message, quint64 available);

private:
    Intro *intro;
};

#include "intro.moc"

FreespaceChecker::FreespaceChecker(Intro *intro)
{
    this->intro = intro;
}

void FreespaceChecker::check()
{
    namespace fs = boost::filesystem;
    QString dataDirStr = intro->getPathToCheck();
    fs::path dataDir = fs::path(dataDirStr.toStdString());
    uint64_t freeBytesAvailable = 0;
    int replyStatus = ST_OK;
    QString replyMessage = tr("A new data directory will be created.");

    /* Find first parent that exists, so that fs::space does not fail */
    fs::path parentDir = dataDir;
    fs::path parentDirOld = fs::path();
    while(parentDir.has_parent_path() && !fs::exists(parentDir))
    {
        parentDir = parentDir.parent_path();

        /* Check if we make any progress, break if not to prevent an infinite loop here */
        if (parentDirOld == parentDir)
            break;

        parentDirOld = parentDir;
    }

    try {
        freeBytesAvailable = fs::space(parentDir).available;
        if(fs::exists(dataDir))
        {
            if(fs::is_directory(dataDir))
            {
                QString separator = "" + QDir::toNativeSeparators("/") + tr("name") + "";
                replyStatus = ST_OK;
                replyMessage = tr("Directory already exists. Add %1 if you intend to create a new directory here.").arg(separator);
            } else {
                replyStatus = ST_ERROR;
                replyMessage = tr("Path already exists, and is not a directory.");
            }
        }
    } catch(fs::filesystem_error &e)
    {
        /* Parent directory does not exist or is not accessible */
        replyStatus = ST_ERROR;
        replyMessage = tr("Cannot create data directory here.");
    }
    emit reply(replyStatus, replyMessage, freeBytesAvailable);
}

Intro::Intro(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Intro),
    thread(0),
    signalled(false)
{
    ui->setupUi(this);
    ui->sizeWarningLabel->setText(ui->sizeWarningLabel->text().arg(BLOCK_CHAIN_SIZE/GB_BYTES));
    startThread();
}

Intro::~Intro()
{
    delete ui;
    /* Ensure thread is finished before it is deleted */
    emit stopThread();
    thread->wait();
}

QString Intro::getDataDirectory()
{
    return ui->dataDirectory->text();
}

void Intro::setDataDirectory(const QString &dataDir)
{
    ui->dataDirectory->setText(dataDir);
    if(dataDir == getDefaultDataDirectory())
    {
        ui->dataDirDefault->setChecked(true);
        ui->dataDirectory->setEnabled(false);
        ui->ellipsisButton->setEnabled(false);
    } else {
        ui->dataDirCustom->setChecked(true);
        ui->dataDirectory->setEnabled(true);
        ui->ellipsisButton->setEnabled(true);
    }
}

QString Intro::getDefaultDataDirectory()
{
    return QString::fromStdString(GetDefaultDataDir().string());
}

void Intro::pickDataDirectory(bool fIsTestnet)
{
    namespace fs = boost::filesystem;
    QSettings settings;
    /* If data directory provided on command line, no need to look at settings
       or show a picking dialog */
    if(!GetArg("-datadir", "").empty())
        return;
    /* 1) Default data directory for operating system */
    QString dataDir = getDefaultDataDirectory();
    /* 2) Allow QSettings to override default dir */
    dataDir = settings.value("strDataDir", dataDir).toString();

    if(!fs::exists(dataDir.toStdString()) || GetBoolArg("-choosedatadir", false))
    {
        /* If current default data directory does not exist, let the user choose one */
        Intro intro;
        intro.setDataDirectory(dataDir);
        if (!fIsTestnet)
            intro.setWindowIcon(QIcon(":icons/bitcoin"));
        else
            intro.setWindowIcon(QIcon(":icons/bitcoin_testnet"));

        while(true)
        {
            if(!intro.exec())
            {
                /* Cancel clicked */
                exit(0);
            }
            dataDir = intro.getDataDirectory();
            try {
                fs::create_directory(dataDir.toStdString());
                break;
            } catch(fs::filesystem_error &e) {
                QMessageBox::critical(0, tr("Bitcoin"),
                    tr("Error: Specified data directory \"%1\" can not be created.").arg(dataDir));
                /* fall through, back to choosing screen */
            }
        }

        settings.setValue("strDataDir", dataDir);
    }
    SoftSetArg("-datadir", dataDir.toStdString());
}

void Intro::setStatus(int status, const QString &message, quint64 bytesAvailable)
{
    switch(status)
    {
    case FreespaceChecker::ST_OK:
        ui->errorMessage->setText(message);
        ui->errorMessage->setStyleSheet("");
        break;
    case FreespaceChecker::ST_ERROR:
        ui->errorMessage->setText(tr("Error") + ": " + message);
        ui->errorMessage->setStyleSheet("QLabel { color: #800000 }");
        break;
    }
    /* Indicate number of bytes available */
    if(status == FreespaceChecker::ST_ERROR)
    {
        ui->freeSpace->setText("");
    } else {
        QString freeString = QString::number(bytesAvailable/GB_BYTES) + tr("GB of free space available");
        if(bytesAvailable < BLOCK_CHAIN_SIZE)
        {
            freeString += " " + tr("(of %1GB needed)").arg(BLOCK_CHAIN_SIZE/GB_BYTES);
            ui->freeSpace->setStyleSheet("QLabel { color: #800000 }");
        } else {
            ui->freeSpace->setStyleSheet("");
        }
        ui->freeSpace->setText(freeString + ".");
    }
    /* Don't allow confirm in ERROR state */
    ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status != FreespaceChecker::ST_ERROR);
}

void Intro::on_dataDirectory_textChanged(const QString &dataDirStr)
{
    /* Disable OK button until check result comes in */
    ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
    checkPath(dataDirStr);
}

void Intro::on_ellipsisButton_clicked()
{
    QString dir = QFileDialog::getExistingDirectory(0, "Choose data directory", ui->dataDirectory->text());
    if(!dir.isEmpty())
        ui->dataDirectory->setText(dir);
}

void Intro::on_dataDirDefault_clicked()
{
    setDataDirectory(getDefaultDataDirectory());
}

void Intro::on_dataDirCustom_clicked()
{
    ui->dataDirectory->setEnabled(true);
    ui->ellipsisButton->setEnabled(true);
}

void Intro::startThread()
{
    thread = new QThread(this);
    FreespaceChecker *executor = new FreespaceChecker(this);
    executor->moveToThread(thread);

    connect(executor, SIGNAL(reply(int,QString,quint64)), this, SLOT(setStatus(int,QString,quint64)));
    connect(this, SIGNAL(requestCheck()), executor, SLOT(check()));
    /*  make sure executor object is deleted in its own thread */
    connect(this, SIGNAL(stopThread()), executor, SLOT(deleteLater()));
    connect(this, SIGNAL(stopThread()), thread, SLOT(quit()));

    thread->start();
}

void Intro::checkPath(const QString &dataDir)
{
    mutex.lock();
    pathToCheck = dataDir;
    if(!signalled)
    {
        signalled = true;
        emit requestCheck();
    }
    mutex.unlock();
}

QString Intro::getPathToCheck()
{
    QString retval;
    mutex.lock();
    retval = pathToCheck;
    signalled = false; /* new request can be queued now */
    mutex.unlock();
    return retval;
}
<commit_msg>[Qt] display native dir separators in select dadatir dialog<commit_after>// Copyright (c) 2011-2013 The Bitcoin developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "intro.h"
#include "ui_intro.h"

#include "util.h"

#include <boost/filesystem.hpp>
#include <QFileDialog>
#include <QSettings>
#include <QMessageBox>

/* Minimum free space (in bytes) needed for data directory */
static const uint64_t GB_BYTES = 1000000000LL;
static const uint64_t BLOCK_CHAIN_SIZE = 10LL * GB_BYTES;

/* Check free space asynchronously to prevent hanging the UI thread.

   Up to one request to check a path is in flight to this thread; when the check()
   function runs, the current path is requested from the associated Intro object.
   The reply is sent back through a signal.

   This ensures that no queue of checking requests is built up while the user is
   still entering the path, and that always the most recently entered path is checked as
   soon as the thread becomes available.
*/
class FreespaceChecker : public QObject
{
    Q_OBJECT

public:
    FreespaceChecker(Intro *intro);

    enum Status {
        ST_OK,
        ST_ERROR
    };

public slots:
    void check();

signals:
    void reply(int status, const QString &message, quint64 available);

private:
    Intro *intro;
};

#include "intro.moc"

FreespaceChecker::FreespaceChecker(Intro *intro)
{
    this->intro = intro;
}

void FreespaceChecker::check()
{
    namespace fs = boost::filesystem;
    QString dataDirStr = intro->getPathToCheck();
    fs::path dataDir = fs::path(dataDirStr.toStdString());
    uint64_t freeBytesAvailable = 0;
    int replyStatus = ST_OK;
    QString replyMessage = tr("A new data directory will be created.");

    /* Find first parent that exists, so that fs::space does not fail */
    fs::path parentDir = dataDir;
    fs::path parentDirOld = fs::path();
    while(parentDir.has_parent_path() && !fs::exists(parentDir))
    {
        parentDir = parentDir.parent_path();

        /* Check if we make any progress, break if not to prevent an infinite loop here */
        if (parentDirOld == parentDir)
            break;

        parentDirOld = parentDir;
    }

    try {
        freeBytesAvailable = fs::space(parentDir).available;
        if(fs::exists(dataDir))
        {
            if(fs::is_directory(dataDir))
            {
                QString separator = "" + QDir::toNativeSeparators("/") + tr("name") + "";
                replyStatus = ST_OK;
                replyMessage = tr("Directory already exists. Add %1 if you intend to create a new directory here.").arg(separator);
            } else {
                replyStatus = ST_ERROR;
                replyMessage = tr("Path already exists, and is not a directory.");
            }
        }
    } catch(fs::filesystem_error &e)
    {
        /* Parent directory does not exist or is not accessible */
        replyStatus = ST_ERROR;
        replyMessage = tr("Cannot create data directory here.");
    }
    emit reply(replyStatus, replyMessage, freeBytesAvailable);
}

Intro::Intro(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Intro),
    thread(0),
    signalled(false)
{
    ui->setupUi(this);
    ui->sizeWarningLabel->setText(ui->sizeWarningLabel->text().arg(BLOCK_CHAIN_SIZE/GB_BYTES));
    startThread();
}

Intro::~Intro()
{
    delete ui;
    /* Ensure thread is finished before it is deleted */
    emit stopThread();
    thread->wait();
}

QString Intro::getDataDirectory()
{
    return ui->dataDirectory->text();
}

void Intro::setDataDirectory(const QString &dataDir)
{
    ui->dataDirectory->setText(dataDir);
    if(dataDir == getDefaultDataDirectory())
    {
        ui->dataDirDefault->setChecked(true);
        ui->dataDirectory->setEnabled(false);
        ui->ellipsisButton->setEnabled(false);
    } else {
        ui->dataDirCustom->setChecked(true);
        ui->dataDirectory->setEnabled(true);
        ui->ellipsisButton->setEnabled(true);
    }
}

QString Intro::getDefaultDataDirectory()
{
    return QString::fromStdString(GetDefaultDataDir().string());
}

void Intro::pickDataDirectory(bool fIsTestnet)
{
    namespace fs = boost::filesystem;
    QSettings settings;
    /* If data directory provided on command line, no need to look at settings
       or show a picking dialog */
    if(!GetArg("-datadir", "").empty())
        return;
    /* 1) Default data directory for operating system */
    QString dataDir = getDefaultDataDirectory();
    /* 2) Allow QSettings to override default dir */
    dataDir = settings.value("strDataDir", dataDir).toString();

    if(!fs::exists(dataDir.toStdString()) || GetBoolArg("-choosedatadir", false))
    {
        /* If current default data directory does not exist, let the user choose one */
        Intro intro;
        intro.setDataDirectory(dataDir);
        if (!fIsTestnet)
            intro.setWindowIcon(QIcon(":icons/bitcoin"));
        else
            intro.setWindowIcon(QIcon(":icons/bitcoin_testnet"));

        while(true)
        {
            if(!intro.exec())
            {
                /* Cancel clicked */
                exit(0);
            }
            dataDir = intro.getDataDirectory();
            try {
                fs::create_directory(dataDir.toStdString());
                break;
            } catch(fs::filesystem_error &e) {
                QMessageBox::critical(0, tr("Bitcoin"),
                    tr("Error: Specified data directory \"%1\" can not be created.").arg(dataDir));
                /* fall through, back to choosing screen */
            }
        }

        settings.setValue("strDataDir", dataDir);
    }
    SoftSetArg("-datadir", dataDir.toStdString());
}

void Intro::setStatus(int status, const QString &message, quint64 bytesAvailable)
{
    switch(status)
    {
    case FreespaceChecker::ST_OK:
        ui->errorMessage->setText(message);
        ui->errorMessage->setStyleSheet("");
        break;
    case FreespaceChecker::ST_ERROR:
        ui->errorMessage->setText(tr("Error") + ": " + message);
        ui->errorMessage->setStyleSheet("QLabel { color: #800000 }");
        break;
    }
    /* Indicate number of bytes available */
    if(status == FreespaceChecker::ST_ERROR)
    {
        ui->freeSpace->setText("");
    } else {
        QString freeString = QString::number(bytesAvailable/GB_BYTES) + tr("GB of free space available");
        if(bytesAvailable < BLOCK_CHAIN_SIZE)
        {
            freeString += " " + tr("(of %1GB needed)").arg(BLOCK_CHAIN_SIZE/GB_BYTES);
            ui->freeSpace->setStyleSheet("QLabel { color: #800000 }");
        } else {
            ui->freeSpace->setStyleSheet("");
        }
        ui->freeSpace->setText(freeString + ".");
    }
    /* Don't allow confirm in ERROR state */
    ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status != FreespaceChecker::ST_ERROR);
}

void Intro::on_dataDirectory_textChanged(const QString &dataDirStr)
{
    /* Disable OK button until check result comes in */
    ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
    checkPath(dataDirStr);
}

void Intro::on_ellipsisButton_clicked()
{
    QString dir = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(0, "Choose data directory", ui->dataDirectory->text()));
    if(!dir.isEmpty())
        ui->dataDirectory->setText(dir);
}

void Intro::on_dataDirDefault_clicked()
{
    setDataDirectory(getDefaultDataDirectory());
}

void Intro::on_dataDirCustom_clicked()
{
    ui->dataDirectory->setEnabled(true);
    ui->ellipsisButton->setEnabled(true);
}

void Intro::startThread()
{
    thread = new QThread(this);
    FreespaceChecker *executor = new FreespaceChecker(this);
    executor->moveToThread(thread);

    connect(executor, SIGNAL(reply(int,QString,quint64)), this, SLOT(setStatus(int,QString,quint64)));
    connect(this, SIGNAL(requestCheck()), executor, SLOT(check()));
    /*  make sure executor object is deleted in its own thread */
    connect(this, SIGNAL(stopThread()), executor, SLOT(deleteLater()));
    connect(this, SIGNAL(stopThread()), thread, SLOT(quit()));

    thread->start();
}

void Intro::checkPath(const QString &dataDir)
{
    mutex.lock();
    pathToCheck = dataDir;
    if(!signalled)
    {
        signalled = true;
        emit requestCheck();
    }
    mutex.unlock();
}

QString Intro::getPathToCheck()
{
    QString retval;
    mutex.lock();
    retval = pathToCheck;
    signalled = false; /* new request can be queued now */
    mutex.unlock();
    return retval;
}
<|endoftext|>