<commit_before>/**
 *  Copyright 2014-2015 MongoDB, Inc.
 *
 *  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 "hphp/runtime/ext/extension.h"
#include "hphp/runtime/vm/native-data.h"

#include "../../../bson.h"
#include "../../../mongodb.h"
#include "../../../utils.h"

#include "Command.h"
#include "Server.h"
#include "WriteConcern.h"

namespace HPHP {

const StaticString s_MongoDriverServer_className("MongoDB\\Driver\\Server");
Class* MongoDBDriverServerData::s_class = nullptr;
const StaticString MongoDBDriverServerData::s_className("MongoDBDriverServer");
const StaticString s_MongoDriverServer_host("host");
const StaticString s_MongoDriverServer_port("port");
const StaticString s_MongoDriverServer_type("type");
const StaticString s_MongoDriverServer_is_primary("is_primary");
const StaticString s_MongoDriverServer_is_secondary("is_secondary");
const StaticString s_MongoDriverServer_is_arbiter("is_arbiter");
const StaticString s_MongoDriverServer_hidden("hidden");
const StaticString s_MongoDriverServer_passive("passive");
const StaticString s_MongoDriverServer_is_hidden("is_hidden");
const StaticString s_MongoDriverServer_is_passive("is_passive");
const StaticString s_MongoDriverServer_tags("tags");
const StaticString s_MongoDriverServer_last_is_master("last_is_master");
const StaticString s_MongoDriverServer_round_trip_time("round_trip_time");
IMPLEMENT_GET_CLASS(MongoDBDriverServerData);

Array HHVM_METHOD(MongoDBDriverServer, __debugInfo)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);
	mongoc_server_description_t *sd;

	Array retval = Array::Create();

	if ((sd = mongoc_topology_description_server_by_id(&data->m_client->topology->description, data->m_server_id))) {
		Variant v_last_is_master;
		Array   a_last_is_master;

		retval.set(s_MongoDriverServer_host, sd->host.host);
		retval.set(s_MongoDriverServer_port, sd->host.port);
		retval.set(s_MongoDriverServer_type, sd->type);
		retval.set(s_MongoDriverServer_is_primary, !!(sd->type == MONGOC_SERVER_RS_PRIMARY));
		retval.set(s_MongoDriverServer_is_secondary, !!(sd->type == MONGOC_SERVER_RS_SECONDARY));
		retval.set(s_MongoDriverServer_is_arbiter, !!(sd->type == MONGOC_SERVER_RS_ARBITER));

		hippo_bson_conversion_options_t options = HIPPO_TYPEMAP_DEBUG_INITIALIZER;
		BsonToVariantConverter convertor(bson_get_data(&sd->last_is_master), sd->last_is_master.len, options);
		convertor.convert(&v_last_is_master);
		a_last_is_master = v_last_is_master.toArray();

		retval.set(s_MongoDriverServer_is_hidden, a_last_is_master.exists(s_MongoDriverServer_hidden) && !!a_last_is_master[s_MongoDriverServer_hidden].toBoolean());
		retval.set(s_MongoDriverServer_is_passive, a_last_is_master.exists(s_MongoDriverServer_passive) && !!a_last_is_master[s_MongoDriverServer_passive].toBoolean());
		retval.set(s_MongoDriverServer_last_is_master, a_last_is_master);

		retval.set(s_MongoDriverServer_round_trip_time, sd->round_trip_time);

		return retval;
	}

	throw MongoDriver::Utils::CreateAndConstruct(MongoDriver::s_MongoDriverExceptionRuntimeException_className, HPHP::Variant("Failed to get server description, server likely gone"), HPHP::Variant((uint64_t) 0));
}

String HHVM_METHOD(MongoDBDriverServer, getHost)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);
	mongoc_server_description_t *sd;

	if ((sd = mongoc_topology_description_server_by_id(&data->m_client->topology->description, data->m_server_id))) {
		return String(sd->host.host);
	}

	throw MongoDriver::Utils::CreateAndConstruct(MongoDriver::s_MongoDriverExceptionRuntimeException_className, HPHP::Variant("Failed to get server description, server likely gone"), HPHP::Variant((uint64_t) 0));
}

Array HHVM_METHOD(MongoDBDriverServer, getInfo)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);
	mongoc_server_description_t *sd;

	if ((sd = mongoc_topology_description_server_by_id(&data->m_client->topology->description, data->m_server_id))) {
		Variant v;
		hippo_bson_conversion_options_t options = HIPPO_TYPEMAP_DEBUG_INITIALIZER;

		BsonToVariantConverter convertor(bson_get_data(&sd->last_is_master), sd->last_is_master.len, options);
		convertor.convert(&v);

		return v.toArray();
	}

	throw MongoDriver::Utils::CreateAndConstruct(MongoDriver::s_MongoDriverExceptionRuntimeException_className, HPHP::Variant("Failed to get server description, server likely gone"), HPHP::Variant((uint64_t) 0));
}

int64_t HHVM_METHOD(MongoDBDriverServer, getLatency)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);
	mongoc_server_description_t *sd;

	if ((sd = mongoc_topology_description_server_by_id(&data->m_client->topology->description, data->m_server_id))) {
		return (int64_t) (sd->round_trip_time);
	}

	throw MongoDriver::Utils::CreateAndConstruct(MongoDriver::s_MongoDriverExceptionRuntimeException_className, HPHP::Variant("Failed to get server description, server likely gone"), HPHP::Variant((uint64_t) 0));
}

int64_t HHVM_METHOD(MongoDBDriverServer, getPort)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);
	mongoc_server_description_t *sd;

	if ((sd = mongoc_topology_description_server_by_id(&data->m_client->topology->description, data->m_server_id))) {
		return (int64_t) (sd->host.port);
	}

	throw MongoDriver::Utils::CreateAndConstruct(MongoDriver::s_MongoDriverExceptionRuntimeException_className, HPHP::Variant("Failed to get server description, server likely gone"), HPHP::Variant((uint64_t) 0));
}

Array HHVM_METHOD(MongoDBDriverServer, getTags)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);
	mongoc_server_description_t *sd;

	if ((sd = mongoc_topology_description_server_by_id(&data->m_client->topology->description, data->m_server_id))) {
		Variant v;
		hippo_bson_conversion_options_t options = HIPPO_TYPEMAP_DEBUG_INITIALIZER;

		BsonToVariantConverter convertor(bson_get_data(&sd->tags), sd->tags.len, options);
		convertor.convert(&v);

		return v.toArray();
	}

	throw MongoDriver::Utils::CreateAndConstruct(MongoDriver::s_MongoDriverExceptionRuntimeException_className, HPHP::Variant("Failed to get server description, server likely gone"), HPHP::Variant((uint64_t) 0));
}

int64_t HHVM_METHOD(MongoDBDriverServer, getType)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);
	mongoc_server_description_t *sd;

	if ((sd = mongoc_topology_description_server_by_id(&data->m_client->topology->description, data->m_server_id))) {
		return (int64_t) (sd->type);
	}

	throw MongoDriver::Utils::CreateAndConstruct(MongoDriver::s_MongoDriverExceptionRuntimeException_className, HPHP::Variant("Failed to get server description, server likely gone"), HPHP::Variant((uint64_t) 0));
}

bool HHVM_METHOD(MongoDBDriverServer, isPrimary)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);
	mongoc_server_description_t *sd;

	if ((sd = mongoc_topology_description_server_by_id(&data->m_client->topology->description, data->m_server_id))) {
		return (bool) (sd->type == MONGOC_SERVER_RS_PRIMARY);
	}

	throw MongoDriver::Utils::CreateAndConstruct(MongoDriver::s_MongoDriverExceptionRuntimeException_className, HPHP::Variant("Failed to get server description, server likely gone"), HPHP::Variant((uint64_t) 0));
}

bool HHVM_METHOD(MongoDBDriverServer, isSecondary)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);
	mongoc_server_description_t *sd;

	if ((sd = mongoc_topology_description_server_by_id(&data->m_client->topology->description, data->m_server_id))) {
		return (bool) (sd->type == MONGOC_SERVER_RS_SECONDARY);
	}

	throw MongoDriver::Utils::CreateAndConstruct(MongoDriver::s_MongoDriverExceptionRuntimeException_className, HPHP::Variant("Failed to get server description, server likely gone"), HPHP::Variant((uint64_t) 0));
}

bool HHVM_METHOD(MongoDBDriverServer, isArbiter)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);
	mongoc_server_description_t *sd;

	if ((sd = mongoc_topology_description_server_by_id(&data->m_client->topology->description, data->m_server_id))) {
		return (bool) (sd->type == MONGOC_SERVER_RS_ARBITER);
	}

	throw MongoDriver::Utils::CreateAndConstruct(MongoDriver::s_MongoDriverExceptionRuntimeException_className, HPHP::Variant("Failed to get server description, server likely gone"), HPHP::Variant((uint64_t) 0));
}

bool HHVM_METHOD(MongoDBDriverServer, isHidden)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);
	mongoc_server_description_t *sd;

	if ((sd = mongoc_topology_description_server_by_id(&data->m_client->topology->description, data->m_server_id))) {
		bson_iter_t iter;

		return !!(bson_iter_init_find_case(&iter, &sd->last_is_master, "hidden") && bson_iter_as_bool(&iter));
	}

	throw MongoDriver::Utils::CreateAndConstruct(MongoDriver::s_MongoDriverExceptionRuntimeException_className, HPHP::Variant("Failed to get server description, server likely gone"), HPHP::Variant((uint64_t) 0));
}

bool HHVM_METHOD(MongoDBDriverServer, isPassive)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);
	mongoc_server_description_t *sd;

	if ((sd = mongoc_topology_description_server_by_id(&data->m_client->topology->description, data->m_server_id))) {
		bson_iter_t iter;

		return !!(bson_iter_init_find_case(&iter, &sd->last_is_master, "passive") && bson_iter_as_bool(&iter));
	}

	throw MongoDriver::Utils::CreateAndConstruct(MongoDriver::s_MongoDriverExceptionRuntimeException_className, HPHP::Variant("Failed to get server description, server likely gone"), HPHP::Variant((uint64_t) 0));
}

Object HHVM_METHOD(MongoDBDriverServer, executeCommand, const String &db, const Object &command, const Variant &readPreference)
{
	bson_t *bson;
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);

	auto zquery = command->o_get(String("command"), false, s_MongoDriverCommand_className);

	VariantToBsonConverter converter(zquery, HIPPO_BSON_NO_FLAGS);
	bson = bson_new();
	converter.convert(bson);

	return MongoDriver::Utils::doExecuteCommand(
		db.c_str(),
		data->m_client,
		data->m_server_id,
		bson,
		readPreference
	);
}

Object HHVM_METHOD(MongoDBDriverServer, executeQuery, const String &ns, const Object &query, const Variant &readPreference)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);

	return MongoDriver::Utils::doExecuteQuery(
		ns,
		data->m_client,
		data->m_server_id,
		query,
		readPreference
	);
}

Object HHVM_METHOD(MongoDBDriverServer, executeBulkWrite, const String &ns, const Object &bulk, const Variant &writeConcern)
{
	const mongoc_write_concern_t *write_concern = NULL;
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);

	/* Deal with write concerns */
	if (!writeConcern.isNull()) {
		MongoDBDriverWriteConcernData* wc_data = Native::data<MongoDBDriverWriteConcernData>(writeConcern.toObject().get());
		write_concern = wc_data->m_write_concern;
	}
	if (!write_concern) {
		write_concern = mongoc_client_get_write_concern(data->m_client);
	}

	return MongoDriver::Utils::doExecuteBulkWrite(
		ns,
		data->m_client,
		data->m_server_id,
		bulk,
		write_concern
	);
}

}
<commit_msg>Extract adding of server information into its own function so we can reuse it<commit_after>/**
 *  Copyright 2014-2015 MongoDB, Inc.
 *
 *  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 "hphp/runtime/ext/extension.h"
#include "hphp/runtime/vm/native-data.h"

#include "../../../bson.h"
#include "../../../mongodb.h"
#include "../../../utils.h"

#include "Command.h"
#include "Server.h"
#include "WriteConcern.h"

namespace HPHP {

const StaticString s_MongoDriverServer_className("MongoDB\\Driver\\Server");
Class* MongoDBDriverServerData::s_class = nullptr;
const StaticString MongoDBDriverServerData::s_className("MongoDBDriverServer");
IMPLEMENT_GET_CLASS(MongoDBDriverServerData);

const StaticString
	s_MongoDriverServer_host("host"),
	s_MongoDriverServer_port("port"),
	s_MongoDriverServer_type("type"),
	s_MongoDriverServer_is_primary("is_primary"),
	s_MongoDriverServer_is_secondary("is_secondary"),
	s_MongoDriverServer_is_arbiter("is_arbiter"),
	s_MongoDriverServer_hidden("hidden"),
	s_MongoDriverServer_passive("passive"),
	s_MongoDriverServer_is_hidden("is_hidden"),
	s_MongoDriverServer_is_passive("is_passive"),
	s_MongoDriverServer_tags("tags"),
	s_MongoDriverServer_last_is_master("last_is_master"),
	s_MongoDriverServer_round_trip_time("round_trip_time");

static bool mongodb_driver_add_server_debug(mongoc_server_description_t *sd, Array *retval)
{
	Variant v_last_is_master;
	Array   a_last_is_master;

	retval->set(s_MongoDriverServer_host, sd->host.host);
	retval->set(s_MongoDriverServer_port, sd->host.port);
	retval->set(s_MongoDriverServer_type, sd->type);
	retval->set(s_MongoDriverServer_is_primary, !!(sd->type == MONGOC_SERVER_RS_PRIMARY));
	retval->set(s_MongoDriverServer_is_secondary, !!(sd->type == MONGOC_SERVER_RS_SECONDARY));
	retval->set(s_MongoDriverServer_is_arbiter, !!(sd->type == MONGOC_SERVER_RS_ARBITER));

	hippo_bson_conversion_options_t options = HIPPO_TYPEMAP_DEBUG_INITIALIZER;
	BsonToVariantConverter convertor(bson_get_data(&sd->last_is_master), sd->last_is_master.len, options);
	convertor.convert(&v_last_is_master);
	a_last_is_master = v_last_is_master.toArray();

	retval->set(s_MongoDriverServer_is_hidden, a_last_is_master.exists(s_MongoDriverServer_hidden) && !!a_last_is_master[s_MongoDriverServer_hidden].toBoolean());
	retval->set(s_MongoDriverServer_is_passive, a_last_is_master.exists(s_MongoDriverServer_passive) && !!a_last_is_master[s_MongoDriverServer_passive].toBoolean());

	if (sd->tags.len) {
		Variant v_tags;

		hippo_bson_conversion_options_t options = HIPPO_TYPEMAP_DEBUG_INITIALIZER;
		BsonToVariantConverter convertor(bson_get_data(&sd->tags), sd->tags.len, options);
		convertor.convert(&v_tags);
		retval->set(s_MongoDriverServer_tags, v_tags);
	}

	retval->set(s_MongoDriverServer_last_is_master, a_last_is_master);

	retval->set(s_MongoDriverServer_round_trip_time, sd->round_trip_time);

	return true;
}

Array HHVM_METHOD(MongoDBDriverServer, __debugInfo)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);
	mongoc_server_description_t *sd;

	Array retval = Array::Create();

	if ((sd = mongoc_topology_description_server_by_id(&data->m_client->topology->description, data->m_server_id))) {
		mongodb_driver_add_server_debug(sd, &retval);
		return retval;
	}

	throw MongoDriver::Utils::CreateAndConstruct(MongoDriver::s_MongoDriverExceptionRuntimeException_className, HPHP::Variant("Failed to get server description, server likely gone"), HPHP::Variant((uint64_t) 0));
}

String HHVM_METHOD(MongoDBDriverServer, getHost)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);
	mongoc_server_description_t *sd;

	if ((sd = mongoc_topology_description_server_by_id(&data->m_client->topology->description, data->m_server_id))) {
		return String(sd->host.host);
	}

	throw MongoDriver::Utils::CreateAndConstruct(MongoDriver::s_MongoDriverExceptionRuntimeException_className, HPHP::Variant("Failed to get server description, server likely gone"), HPHP::Variant((uint64_t) 0));
}

Array HHVM_METHOD(MongoDBDriverServer, getInfo)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);
	mongoc_server_description_t *sd;

	if ((sd = mongoc_topology_description_server_by_id(&data->m_client->topology->description, data->m_server_id))) {
		Variant v;
		hippo_bson_conversion_options_t options = HIPPO_TYPEMAP_DEBUG_INITIALIZER;

		BsonToVariantConverter convertor(bson_get_data(&sd->last_is_master), sd->last_is_master.len, options);
		convertor.convert(&v);

		return v.toArray();
	}

	throw MongoDriver::Utils::CreateAndConstruct(MongoDriver::s_MongoDriverExceptionRuntimeException_className, HPHP::Variant("Failed to get server description, server likely gone"), HPHP::Variant((uint64_t) 0));
}

int64_t HHVM_METHOD(MongoDBDriverServer, getLatency)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);
	mongoc_server_description_t *sd;

	if ((sd = mongoc_topology_description_server_by_id(&data->m_client->topology->description, data->m_server_id))) {
		return (int64_t) (sd->round_trip_time);
	}

	throw MongoDriver::Utils::CreateAndConstruct(MongoDriver::s_MongoDriverExceptionRuntimeException_className, HPHP::Variant("Failed to get server description, server likely gone"), HPHP::Variant((uint64_t) 0));
}

int64_t HHVM_METHOD(MongoDBDriverServer, getPort)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);
	mongoc_server_description_t *sd;

	if ((sd = mongoc_topology_description_server_by_id(&data->m_client->topology->description, data->m_server_id))) {
		return (int64_t) (sd->host.port);
	}

	throw MongoDriver::Utils::CreateAndConstruct(MongoDriver::s_MongoDriverExceptionRuntimeException_className, HPHP::Variant("Failed to get server description, server likely gone"), HPHP::Variant((uint64_t) 0));
}

Array HHVM_METHOD(MongoDBDriverServer, getTags)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);
	mongoc_server_description_t *sd;

	if ((sd = mongoc_topology_description_server_by_id(&data->m_client->topology->description, data->m_server_id))) {
		Variant v;
		hippo_bson_conversion_options_t options = HIPPO_TYPEMAP_DEBUG_INITIALIZER;

		BsonToVariantConverter convertor(bson_get_data(&sd->tags), sd->tags.len, options);
		convertor.convert(&v);

		return v.toArray();
	}

	throw MongoDriver::Utils::CreateAndConstruct(MongoDriver::s_MongoDriverExceptionRuntimeException_className, HPHP::Variant("Failed to get server description, server likely gone"), HPHP::Variant((uint64_t) 0));
}

int64_t HHVM_METHOD(MongoDBDriverServer, getType)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);
	mongoc_server_description_t *sd;

	if ((sd = mongoc_topology_description_server_by_id(&data->m_client->topology->description, data->m_server_id))) {
		return (int64_t) (sd->type);
	}

	throw MongoDriver::Utils::CreateAndConstruct(MongoDriver::s_MongoDriverExceptionRuntimeException_className, HPHP::Variant("Failed to get server description, server likely gone"), HPHP::Variant((uint64_t) 0));
}

bool HHVM_METHOD(MongoDBDriverServer, isPrimary)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);
	mongoc_server_description_t *sd;

	if ((sd = mongoc_topology_description_server_by_id(&data->m_client->topology->description, data->m_server_id))) {
		return (bool) (sd->type == MONGOC_SERVER_RS_PRIMARY);
	}

	throw MongoDriver::Utils::CreateAndConstruct(MongoDriver::s_MongoDriverExceptionRuntimeException_className, HPHP::Variant("Failed to get server description, server likely gone"), HPHP::Variant((uint64_t) 0));
}

bool HHVM_METHOD(MongoDBDriverServer, isSecondary)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);
	mongoc_server_description_t *sd;

	if ((sd = mongoc_topology_description_server_by_id(&data->m_client->topology->description, data->m_server_id))) {
		return (bool) (sd->type == MONGOC_SERVER_RS_SECONDARY);
	}

	throw MongoDriver::Utils::CreateAndConstruct(MongoDriver::s_MongoDriverExceptionRuntimeException_className, HPHP::Variant("Failed to get server description, server likely gone"), HPHP::Variant((uint64_t) 0));
}

bool HHVM_METHOD(MongoDBDriverServer, isArbiter)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);
	mongoc_server_description_t *sd;

	if ((sd = mongoc_topology_description_server_by_id(&data->m_client->topology->description, data->m_server_id))) {
		return (bool) (sd->type == MONGOC_SERVER_RS_ARBITER);
	}

	throw MongoDriver::Utils::CreateAndConstruct(MongoDriver::s_MongoDriverExceptionRuntimeException_className, HPHP::Variant("Failed to get server description, server likely gone"), HPHP::Variant((uint64_t) 0));
}

bool HHVM_METHOD(MongoDBDriverServer, isHidden)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);
	mongoc_server_description_t *sd;

	if ((sd = mongoc_topology_description_server_by_id(&data->m_client->topology->description, data->m_server_id))) {
		bson_iter_t iter;

		return !!(bson_iter_init_find_case(&iter, &sd->last_is_master, "hidden") && bson_iter_as_bool(&iter));
	}

	throw MongoDriver::Utils::CreateAndConstruct(MongoDriver::s_MongoDriverExceptionRuntimeException_className, HPHP::Variant("Failed to get server description, server likely gone"), HPHP::Variant((uint64_t) 0));
}

bool HHVM_METHOD(MongoDBDriverServer, isPassive)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);
	mongoc_server_description_t *sd;

	if ((sd = mongoc_topology_description_server_by_id(&data->m_client->topology->description, data->m_server_id))) {
		bson_iter_t iter;

		return !!(bson_iter_init_find_case(&iter, &sd->last_is_master, "passive") && bson_iter_as_bool(&iter));
	}

	throw MongoDriver::Utils::CreateAndConstruct(MongoDriver::s_MongoDriverExceptionRuntimeException_className, HPHP::Variant("Failed to get server description, server likely gone"), HPHP::Variant((uint64_t) 0));
}

Object HHVM_METHOD(MongoDBDriverServer, executeCommand, const String &db, const Object &command, const Variant &readPreference)
{
	bson_t *bson;
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);

	auto zquery = command->o_get(String("command"), false, s_MongoDriverCommand_className);

	VariantToBsonConverter converter(zquery, HIPPO_BSON_NO_FLAGS);
	bson = bson_new();
	converter.convert(bson);

	return MongoDriver::Utils::doExecuteCommand(
		db.c_str(),
		data->m_client,
		data->m_server_id,
		bson,
		readPreference
	);
}

Object HHVM_METHOD(MongoDBDriverServer, executeQuery, const String &ns, const Object &query, const Variant &readPreference)
{
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);

	return MongoDriver::Utils::doExecuteQuery(
		ns,
		data->m_client,
		data->m_server_id,
		query,
		readPreference
	);
}

Object HHVM_METHOD(MongoDBDriverServer, executeBulkWrite, const String &ns, const Object &bulk, const Variant &writeConcern)
{
	const mongoc_write_concern_t *write_concern = NULL;
	MongoDBDriverServerData* data = Native::data<MongoDBDriverServerData>(this_);

	/* Deal with write concerns */
	if (!writeConcern.isNull()) {
		MongoDBDriverWriteConcernData* wc_data = Native::data<MongoDBDriverWriteConcernData>(writeConcern.toObject().get());
		write_concern = wc_data->m_write_concern;
	}
	if (!write_concern) {
		write_concern = mongoc_client_get_write_concern(data->m_client);
	}

	return MongoDriver::Utils::doExecuteBulkWrite(
		ns,
		data->m_client,
		data->m_server_id,
		bulk,
		write_concern
	);
}

}
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2018 Inria
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors: Daniel Carvalho
 */

#include "mem/cache/replacement_policies/random_rp.hh"

#include "base/random.hh"
#include "mem/cache/blk.hh"

RandomRP::RandomRP(const Params *p)
    : BaseReplacementPolicy(p)
{
}

void
RandomRP::invalidate(const std::shared_ptr<ReplacementData>& replacement_data)
const
{
    // Unprioritize replacement data victimization
    std::static_pointer_cast<RandomReplData>(
        replacement_data)->valid = false;
}

void
RandomRP::touch(const std::shared_ptr<ReplacementData>& replacement_data) const
{
}

void
RandomRP::reset(const std::shared_ptr<ReplacementData>& replacement_data) const
{
    // Unprioritize replacement data victimization
    std::static_pointer_cast<RandomReplData>(
        replacement_data)->valid = true;
}

ReplaceableEntry*
RandomRP::getVictim(const ReplacementCandidates& candidates) const
{
    // There must be at least one replacement candidate
    assert(candidates.size() > 0);

    // Choose one candidate at random
    ReplaceableEntry* victim = candidates[random_mt.random<unsigned>(0,
                                    candidates.size() - 1)];

    // Visit all candidates to search for an invalid entry. If one is found,
    // its eviction is prioritized
    for (const auto& candidate : candidates) {
        if (!std::static_pointer_cast<RandomReplData>(
                    candidate->replacementData)->valid) {
            victim = candidate;
            break;
        }
    }

    return victim;
}

std::shared_ptr<ReplacementData>
RandomRP::instantiateEntry()
{
    return std::shared_ptr<ReplacementData>(new ReplacementData());
}

RandomRP*
RandomRPParams::create()
{
    return new RandomRP(this);
}
<commit_msg>mem-cache: Fix RandomReplData<commit_after>/**
 * Copyright (c) 2018 Inria
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors: Daniel Carvalho
 */

#include "mem/cache/replacement_policies/random_rp.hh"

#include "base/random.hh"
#include "mem/cache/blk.hh"

RandomRP::RandomRP(const Params *p)
    : BaseReplacementPolicy(p)
{
}

void
RandomRP::invalidate(const std::shared_ptr<ReplacementData>& replacement_data)
const
{
    // Unprioritize replacement data victimization
    std::static_pointer_cast<RandomReplData>(
        replacement_data)->valid = false;
}

void
RandomRP::touch(const std::shared_ptr<ReplacementData>& replacement_data) const
{
}

void
RandomRP::reset(const std::shared_ptr<ReplacementData>& replacement_data) const
{
    // Unprioritize replacement data victimization
    std::static_pointer_cast<RandomReplData>(
        replacement_data)->valid = true;
}

ReplaceableEntry*
RandomRP::getVictim(const ReplacementCandidates& candidates) const
{
    // There must be at least one replacement candidate
    assert(candidates.size() > 0);

    // Choose one candidate at random
    ReplaceableEntry* victim = candidates[random_mt.random<unsigned>(0,
                                    candidates.size() - 1)];

    // Visit all candidates to search for an invalid entry. If one is found,
    // its eviction is prioritized
    for (const auto& candidate : candidates) {
        if (!std::static_pointer_cast<RandomReplData>(
                    candidate->replacementData)->valid) {
            victim = candidate;
            break;
        }
    }

    return victim;
}

std::shared_ptr<ReplacementData>
RandomRP::instantiateEntry()
{
    return std::shared_ptr<ReplacementData>(new RandomReplData());
}

RandomRP*
RandomRPParams::create()
{
    return new RandomRP(this);
}
<|endoftext|>


<commit_before>/*

Copyright (c) 2006, Arvid Norberg
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in
      the documentation and/or other materials provided with the distribution.
    * Neither the name of the author nor the names of its
      contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

*/

#include "libtorrent/pch.hpp"
#include "libtorrent/socket.hpp"

#include <boost/bind.hpp>
#include <boost/mpl/max_element.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/sizeof.hpp>
#include <boost/mpl/transform_view.hpp>
#include <boost/mpl/deref.hpp>
#include <boost/lexical_cast.hpp>

#include <libtorrent/io.hpp>
#include <libtorrent/invariant_check.hpp>
#include <libtorrent/kademlia/rpc_manager.hpp>
#include <libtorrent/kademlia/logging.hpp>
#include <libtorrent/kademlia/routing_table.hpp>
#include <libtorrent/kademlia/find_data.hpp>
#include <libtorrent/kademlia/closest_nodes.hpp>
#include <libtorrent/kademlia/refresh.hpp>
#include <libtorrent/kademlia/node.hpp>
#include <libtorrent/kademlia/observer.hpp>
#include <libtorrent/hasher.hpp>

#include <fstream>

using boost::shared_ptr;
using boost::bind;

namespace libtorrent { namespace dht
{

namespace io = libtorrent::detail;
namespace mpl = boost::mpl;

#ifdef TORRENT_DHT_VERBOSE_LOGGING
TORRENT_DEFINE_LOG(rpc)
#endif

void intrusive_ptr_add_ref(observer const* o)
{
	TORRENT_ASSERT(o->m_refs >= 0);
	TORRENT_ASSERT(o != 0);
	++o->m_refs;
}

void intrusive_ptr_release(observer const* o)
{
	TORRENT_ASSERT(o->m_refs > 0);
	TORRENT_ASSERT(o != 0);
	if (--o->m_refs == 0)
	{
		boost::pool<>& p = o->pool_allocator;
		o->~observer();
		p.free(const_cast<observer*>(o));
	}
}

node_id generate_id();

typedef mpl::vector<
	closest_nodes_observer
	, find_data_observer
	, announce_observer
	, get_peers_observer
	, refresh_observer
	, ping_observer
	, null_observer
	> observer_types;

typedef mpl::max_element<
	mpl::transform_view<observer_types, mpl::sizeof_<mpl::_1> >
    >::type max_observer_type_iter;

rpc_manager::rpc_manager(fun const& f, node_id const& our_id
	, routing_table& table, send_fun const& sf)
	: m_pool_allocator(sizeof(mpl::deref<max_observer_type_iter::base>::type))
	, m_next_transaction_id(rand() % max_transactions)
	, m_oldest_transaction_id(m_next_transaction_id)
	, m_incoming(f)
	, m_send(sf)
	, m_our_id(our_id)
	, m_table(table)
	, m_timer(time_now())
	, m_random_number(generate_id())
	, m_destructing(false)
{
	std::srand(time(0));
}

rpc_manager::~rpc_manager()
{
	m_destructing = true;
#ifdef TORRENT_DHT_VERBOSE_LOGGING
	TORRENT_LOG(rpc) << "Destructing";
#endif
	std::for_each(m_aborted_transactions.begin(), m_aborted_transactions.end()
		, bind(&observer::abort, _1));

	for (transactions_t::iterator i = m_transactions.begin()
		, end(m_transactions.end()); i != end; ++i)
	{
		if (*i) (*i)->abort();
	}
}

#ifndef NDEBUG
void rpc_manager::check_invariant() const
{
	TORRENT_ASSERT(m_oldest_transaction_id >= 0);
	TORRENT_ASSERT(m_oldest_transaction_id < max_transactions);
	TORRENT_ASSERT(m_next_transaction_id >= 0);
	TORRENT_ASSERT(m_next_transaction_id < max_transactions);
	TORRENT_ASSERT(!m_transactions[m_next_transaction_id]);

	for (int i = (m_next_transaction_id + 1) % max_transactions;
		i != m_oldest_transaction_id; i = (i + 1) % max_transactions)
	{
		TORRENT_ASSERT(!m_transactions[i]);
	}
}
#endif

bool rpc_manager::incoming(msg const& m)
{
	INVARIANT_CHECK;

	if (m_destructing) return false;

	if (m.reply)
	{
		// if we don't have the transaction id in our
		// request list, ignore the packet

		if (m.transaction_id.size() < 2)
		{
#ifdef TORRENT_DHT_VERBOSE_LOGGING
			TORRENT_LOG(rpc) << "Reply with invalid transaction id size: "
				<< m.transaction_id.size() << " from " << m.addr;
#endif
			msg reply;
			reply.reply = true;
			reply.message_id = messages::error;
			reply.error_code = 203; // Protocol error
			reply.error_msg = "reply with invalid transaction id, size "
				+ boost::lexical_cast<std::string>(m.transaction_id.size());
			reply.addr = m.addr;
			reply.transaction_id = "";
			m_send(reply);
			return false;
		}

		std::string::const_iterator i = m.transaction_id.begin();
		int tid = io::read_uint16(i);

		if (tid >= (int)m_transactions.size()
			|| tid < 0)
		{
#ifdef TORRENT_DHT_VERBOSE_LOGGING
			TORRENT_LOG(rpc) << "Reply with invalid transaction id: "
				<< tid << " from " << m.addr;
#endif
			msg reply;
			reply.reply = true;
			reply.message_id = messages::error;
			reply.error_code = 203; // Protocol error
			reply.error_msg = "reply with invalid transaction id";
			reply.addr = m.addr;
			reply.transaction_id = "";
			m_send(reply);
			return false;
		}

		observer_ptr o = m_transactions[tid];

		if (!o)
		{
#ifdef TORRENT_DHT_VERBOSE_LOGGING
			TORRENT_LOG(rpc) << "Reply with unknown transaction id: "
				<< tid << " from " << m.addr << " (possibly timed out)";
#endif
			return false;
		}

		if (m.addr != o->target_addr)
		{
#ifdef TORRENT_DHT_VERBOSE_LOGGING
			TORRENT_LOG(rpc) << "Reply with incorrect address and valid transaction id: "
				<< tid << " from " << m.addr;
#endif
			return false;
		}

#ifdef TORRENT_DHT_VERBOSE_LOGGING
		std::ofstream reply_stats("libtorrent_logs/round_trip_ms.log", std::ios::app);
		reply_stats << m.addr << "\t" << total_milliseconds(time_now() - o->sent)
			<< std::endl;
#endif
		o->reply(m);
		m_transactions[tid] = 0;

		if (m.piggy_backed_ping)
		{
			// there is a ping request piggy
			// backed in this reply
			msg ph;
			ph.message_id = messages::ping;
			ph.transaction_id = m.ping_transaction_id;
			ph.addr = m.addr;
			ph.reply = true;

			reply(ph);
		}
		return m_table.node_seen(m.id, m.addr);
	}
	else
	{
		TORRENT_ASSERT(m.message_id != messages::error);
		// this is an incoming request
		m_incoming(m);
	}
	return false;
}

time_duration rpc_manager::tick()
{
	INVARIANT_CHECK;

	const int timeout_ms = 10 * 1000;

	//	look for observers that has timed out

	if (m_next_transaction_id == m_oldest_transaction_id) return milliseconds(timeout_ms);

	std::vector<observer_ptr > timeouts;

	for (;m_next_transaction_id != m_oldest_transaction_id;
		m_oldest_transaction_id = (m_oldest_transaction_id + 1) % max_transactions)
	{
		TORRENT_ASSERT(m_oldest_transaction_id >= 0);
		TORRENT_ASSERT(m_oldest_transaction_id < max_transactions);

		observer_ptr o = m_transactions[m_oldest_transaction_id];
		if (!o) continue;

		time_duration diff = o->sent + milliseconds(timeout_ms) - time_now();
		if (diff > seconds(0))
		{
			if (diff < seconds(1)) return seconds(1);
			return diff;
		}

		try
		{
			m_transactions[m_oldest_transaction_id] = 0;
			timeouts.push_back(o);
		} catch (std::exception) {}
	}

	std::for_each(timeouts.begin(), timeouts.end(), bind(&observer::timeout, _1));
	timeouts.clear();

	// clear the aborted transactions, will likely
	// generate new requests. We need to swap, since the
	// destrutors may add more observers to the m_aborted_transactions
	std::vector<observer_ptr >().swap(m_aborted_transactions);
	return milliseconds(timeout_ms);
}

unsigned int rpc_manager::new_transaction_id(observer_ptr o)
{
	INVARIANT_CHECK;

	unsigned int tid = m_next_transaction_id;
	m_next_transaction_id = (m_next_transaction_id + 1) % max_transactions;
	if (m_transactions[m_next_transaction_id])
	{
		// moving the observer into the set of aborted transactions
		// it will prevent it from spawning new requests right now,
		// since that would break the invariant
		m_aborted_transactions.push_back(m_transactions[m_next_transaction_id]);
		m_transactions[m_next_transaction_id] = 0;
		TORRENT_ASSERT(m_oldest_transaction_id == m_next_transaction_id);
	}
	TORRENT_ASSERT(!m_transactions[tid]);
	m_transactions[tid] = o;
	if (m_oldest_transaction_id == m_next_transaction_id)
	{
		m_oldest_transaction_id = (m_oldest_transaction_id + 1) % max_transactions;
#ifdef TORRENT_DHT_VERBOSE_LOGGING
		TORRENT_LOG(rpc) << "WARNING: transaction limit reached! Too many concurrent"
			" messages! limit: " << (int)max_transactions;
#endif
		update_oldest_transaction_id();
	}

	return tid;
}

void rpc_manager::update_oldest_transaction_id()
{
	INVARIANT_CHECK;

	TORRENT_ASSERT(m_oldest_transaction_id != m_next_transaction_id);
	while (!m_transactions[m_oldest_transaction_id])
	{
		m_oldest_transaction_id = (m_oldest_transaction_id + 1)
			% max_transactions;
		if (m_oldest_transaction_id == m_next_transaction_id)
			break;
	}
}

void rpc_manager::invoke(int message_id, udp::endpoint target_addr
	, observer_ptr o)
{
	INVARIANT_CHECK;

	if (m_destructing)
	{
		o->abort();
		return;
	}

	msg m;
	m.message_id = message_id;
	m.reply = false;
	m.id = m_our_id;
	m.addr = target_addr;
	TORRENT_ASSERT(!m_transactions[m_next_transaction_id]);
#ifndef NDEBUG
	int potential_new_id = m_next_transaction_id;
#endif
	try
	{
		m.transaction_id.clear();
		std::back_insert_iterator<std::string> out(m.transaction_id);
		io::write_uint16(m_next_transaction_id, out);

		o->send(m);

		o->sent = time_now();
		o->target_addr = target_addr;

	#ifdef TORRENT_DHT_VERBOSE_LOGGING
		TORRENT_LOG(rpc) << "Invoking " << messages::ids[message_id]
			<< " -> " << target_addr;
	#endif
		m_send(m);
		new_transaction_id(o);
	}
	catch (std::exception& e)
	{
		// m_send may fail with "no route to host"
		TORRENT_ASSERT(potential_new_id == m_next_transaction_id);
		o->abort();
	}
}

void rpc_manager::reply(msg& m)
{
	INVARIANT_CHECK;

	if (m_destructing) return;

	TORRENT_ASSERT(m.reply);
	m.piggy_backed_ping = false;
	m.id = m_our_id;

	m_send(m);
}

void rpc_manager::reply_with_ping(msg& m)
{
	INVARIANT_CHECK;

	if (m_destructing) return;
	TORRENT_ASSERT(m.reply);

	m.piggy_backed_ping = true;
	m.id = m_our_id;

	m.ping_transaction_id.clear();
	std::back_insert_iterator<std::string> out(m.ping_transaction_id);
	io::write_uint16(m_next_transaction_id, out);

	observer_ptr o(new (allocator().malloc()) null_observer(allocator()));
	TORRENT_ASSERT(!m_transactions[m_next_transaction_id]);
	o->sent = time_now();
	o->target_addr = m.addr;

	m_send(m);
	new_transaction_id(o);
}

} } // namespace libtorrent::dht

<commit_msg>updated dht verbose logging to try to catch #176<commit_after>/*

Copyright (c) 2006, Arvid Norberg
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in
      the documentation and/or other materials provided with the distribution.
    * Neither the name of the author nor the names of its
      contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

*/

#include "libtorrent/pch.hpp"
#include "libtorrent/socket.hpp"

#include <boost/bind.hpp>
#include <boost/mpl/max_element.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/sizeof.hpp>
#include <boost/mpl/transform_view.hpp>
#include <boost/mpl/deref.hpp>
#include <boost/lexical_cast.hpp>

#include <libtorrent/io.hpp>
#include <libtorrent/invariant_check.hpp>
#include <libtorrent/kademlia/rpc_manager.hpp>
#include <libtorrent/kademlia/logging.hpp>
#include <libtorrent/kademlia/routing_table.hpp>
#include <libtorrent/kademlia/find_data.hpp>
#include <libtorrent/kademlia/closest_nodes.hpp>
#include <libtorrent/kademlia/refresh.hpp>
#include <libtorrent/kademlia/node.hpp>
#include <libtorrent/kademlia/observer.hpp>
#include <libtorrent/hasher.hpp>

#include <fstream>

using boost::shared_ptr;
using boost::bind;

namespace libtorrent { namespace dht
{

namespace io = libtorrent::detail;
namespace mpl = boost::mpl;

#ifdef TORRENT_DHT_VERBOSE_LOGGING
TORRENT_DEFINE_LOG(rpc)
#endif

void intrusive_ptr_add_ref(observer const* o)
{
	TORRENT_ASSERT(o->m_refs >= 0);
	TORRENT_ASSERT(o != 0);
	++o->m_refs;
}

void intrusive_ptr_release(observer const* o)
{
	TORRENT_ASSERT(o->m_refs > 0);
	TORRENT_ASSERT(o != 0);
	if (--o->m_refs == 0)
	{
		boost::pool<>& p = o->pool_allocator;
		o->~observer();
		p.free(const_cast<observer*>(o));
	}
}

node_id generate_id();

typedef mpl::vector<
	closest_nodes_observer
	, find_data_observer
	, announce_observer
	, get_peers_observer
	, refresh_observer
	, ping_observer
	, null_observer
	> observer_types;

typedef mpl::max_element<
	mpl::transform_view<observer_types, mpl::sizeof_<mpl::_1> >
    >::type max_observer_type_iter;

rpc_manager::rpc_manager(fun const& f, node_id const& our_id
	, routing_table& table, send_fun const& sf)
	: m_pool_allocator(sizeof(mpl::deref<max_observer_type_iter::base>::type))
	, m_next_transaction_id(rand() % max_transactions)
	, m_oldest_transaction_id(m_next_transaction_id)
	, m_incoming(f)
	, m_send(sf)
	, m_our_id(our_id)
	, m_table(table)
	, m_timer(time_now())
	, m_random_number(generate_id())
	, m_destructing(false)
{
	std::srand(time(0));
}

rpc_manager::~rpc_manager()
{
	m_destructing = true;
#ifdef TORRENT_DHT_VERBOSE_LOGGING
	TORRENT_LOG(rpc) << "Destructing";
#endif
	std::for_each(m_aborted_transactions.begin(), m_aborted_transactions.end()
		, bind(&observer::abort, _1));

	for (transactions_t::iterator i = m_transactions.begin()
		, end(m_transactions.end()); i != end; ++i)
	{
		if (*i) (*i)->abort();
	}
}

#ifndef NDEBUG
void rpc_manager::check_invariant() const
{
	TORRENT_ASSERT(m_oldest_transaction_id >= 0);
	TORRENT_ASSERT(m_oldest_transaction_id < max_transactions);
	TORRENT_ASSERT(m_next_transaction_id >= 0);
	TORRENT_ASSERT(m_next_transaction_id < max_transactions);
	TORRENT_ASSERT(!m_transactions[m_next_transaction_id]);

	for (int i = (m_next_transaction_id + 1) % max_transactions;
		i != m_oldest_transaction_id; i = (i + 1) % max_transactions)
	{
		TORRENT_ASSERT(!m_transactions[i]);
	}
}
#endif

bool rpc_manager::incoming(msg const& m)
{
	INVARIANT_CHECK;

	if (m_destructing) return false;

	if (m.reply)
	{
		// if we don't have the transaction id in our
		// request list, ignore the packet

		if (m.transaction_id.size() < 2)
		{
#ifdef TORRENT_DHT_VERBOSE_LOGGING
			TORRENT_LOG(rpc) << "Reply with invalid transaction id size: "
				<< m.transaction_id.size() << " from " << m.addr;
#endif
			msg reply;
			reply.reply = true;
			reply.message_id = messages::error;
			reply.error_code = 203; // Protocol error
			reply.error_msg = "reply with invalid transaction id, size "
				+ boost::lexical_cast<std::string>(m.transaction_id.size());
			reply.addr = m.addr;
			reply.transaction_id = "";
			m_send(reply);
			return false;
		}

		std::string::const_iterator i = m.transaction_id.begin();
		int tid = io::read_uint16(i);

		if (tid >= (int)m_transactions.size()
			|| tid < 0)
		{
#ifdef TORRENT_DHT_VERBOSE_LOGGING
			TORRENT_LOG(rpc) << "Reply with invalid transaction id: "
				<< tid << " from " << m.addr;
#endif
			msg reply;
			reply.reply = true;
			reply.message_id = messages::error;
			reply.error_code = 203; // Protocol error
			reply.error_msg = "reply with invalid transaction id";
			reply.addr = m.addr;
			reply.transaction_id = "";
			m_send(reply);
			return false;
		}

		observer_ptr o = m_transactions[tid];

		if (!o)
		{
#ifdef TORRENT_DHT_VERBOSE_LOGGING
			TORRENT_LOG(rpc) << "Reply with unknown transaction id: "
				<< tid << " from " << m.addr << " (possibly timed out)";
#endif
			return false;
		}

		if (m.addr != o->target_addr)
		{
#ifdef TORRENT_DHT_VERBOSE_LOGGING
			TORRENT_LOG(rpc) << "Reply with incorrect address and valid transaction id: "
				<< tid << " from " << m.addr;
#endif
			return false;
		}

#ifdef TORRENT_DHT_VERBOSE_LOGGING
		std::ofstream reply_stats("libtorrent_logs/round_trip_ms.log", std::ios::app);
		reply_stats << m.addr << "\t" << total_milliseconds(time_now() - o->sent)
			<< std::endl;
#endif
#ifdef TORRENT_DHT_VERBOSE_LOGGING
		TORRENT_LOG(rpc) << "Reply with transaction id: "
			<< tid << " from " << m.addr;
#endif
		o->reply(m);
		m_transactions[tid] = 0;

		if (m.piggy_backed_ping)
		{
			// there is a ping request piggy
			// backed in this reply
			msg ph;
			ph.message_id = messages::ping;
			ph.transaction_id = m.ping_transaction_id;
			ph.addr = m.addr;
			ph.reply = true;

			reply(ph);
		}
		return m_table.node_seen(m.id, m.addr);
	}
	else
	{
		TORRENT_ASSERT(m.message_id != messages::error);
		// this is an incoming request
		m_incoming(m);
	}
	return false;
}

time_duration rpc_manager::tick()
{
	INVARIANT_CHECK;

	const int timeout_ms = 10 * 1000;

	//	look for observers that has timed out

	if (m_next_transaction_id == m_oldest_transaction_id) return milliseconds(timeout_ms);

	std::vector<observer_ptr > timeouts;

	for (;m_next_transaction_id != m_oldest_transaction_id;
		m_oldest_transaction_id = (m_oldest_transaction_id + 1) % max_transactions)
	{
		TORRENT_ASSERT(m_oldest_transaction_id >= 0);
		TORRENT_ASSERT(m_oldest_transaction_id < max_transactions);

		observer_ptr o = m_transactions[m_oldest_transaction_id];
		if (!o) continue;

		time_duration diff = o->sent + milliseconds(timeout_ms) - time_now();
		if (diff > seconds(0))
		{
			if (diff < seconds(1)) return seconds(1);
			return diff;
		}

		try
		{
			m_transactions[m_oldest_transaction_id] = 0;
#ifdef TORRENT_DHT_VERBOSE_LOGGING
			TORRENT_LOG(rpc) << "Timing out transaction id: "
				<< m_oldest_transaction_id << " from " << o->target_addr;
#endif
			timeouts.push_back(o);
		} catch (std::exception) {}
	}

	std::for_each(timeouts.begin(), timeouts.end(), bind(&observer::timeout, _1));
	timeouts.clear();

	// clear the aborted transactions, will likely
	// generate new requests. We need to swap, since the
	// destrutors may add more observers to the m_aborted_transactions
	std::vector<observer_ptr >().swap(m_aborted_transactions);
	return milliseconds(timeout_ms);
}

unsigned int rpc_manager::new_transaction_id(observer_ptr o)
{
	INVARIANT_CHECK;

	unsigned int tid = m_next_transaction_id;
	m_next_transaction_id = (m_next_transaction_id + 1) % max_transactions;
	if (m_transactions[m_next_transaction_id])
	{
		// moving the observer into the set of aborted transactions
		// it will prevent it from spawning new requests right now,
		// since that would break the invariant
		observer_ptr o = m_transactions[m_next_transaction_id];
		m_aborted_transactions.push_back(o);
#ifdef TORRENT_DHT_VERBOSE_LOGGING
		TORRENT_LOG(rpc) << "[new_transaction_id] Aborting message with transaction id: "
			<< m_next_transaction_id << " sent to " << o->target_addr
			<< " at " << o->sent;
#endif
		m_transactions[m_next_transaction_id] = 0;
		TORRENT_ASSERT(m_oldest_transaction_id == m_next_transaction_id);
	}
	TORRENT_ASSERT(!m_transactions[tid]);
	m_transactions[tid] = o;
	if (m_oldest_transaction_id == m_next_transaction_id)
	{
		m_oldest_transaction_id = (m_oldest_transaction_id + 1) % max_transactions;
#ifdef TORRENT_DHT_VERBOSE_LOGGING
		TORRENT_LOG(rpc) << "WARNING: transaction limit reached! Too many concurrent"
			" messages! limit: " << (int)max_transactions;
#endif
		update_oldest_transaction_id();
	}

	return tid;
}

void rpc_manager::update_oldest_transaction_id()
{
	INVARIANT_CHECK;

	TORRENT_ASSERT(m_oldest_transaction_id != m_next_transaction_id);
	while (!m_transactions[m_oldest_transaction_id])
	{
		m_oldest_transaction_id = (m_oldest_transaction_id + 1)
			% max_transactions;
		if (m_oldest_transaction_id == m_next_transaction_id)
			break;
	}
}

void rpc_manager::invoke(int message_id, udp::endpoint target_addr
	, observer_ptr o)
{
	INVARIANT_CHECK;

	if (m_destructing)
	{
		o->abort();
		return;
	}

	msg m;
	m.message_id = message_id;
	m.reply = false;
	m.id = m_our_id;
	m.addr = target_addr;
	TORRENT_ASSERT(!m_transactions[m_next_transaction_id]);
#ifndef NDEBUG
	int potential_new_id = m_next_transaction_id;
#endif
	try
	{
		m.transaction_id.clear();
		std::back_insert_iterator<std::string> out(m.transaction_id);
		io::write_uint16(m_next_transaction_id, out);

		o->send(m);

		o->sent = time_now();
		o->target_addr = target_addr;

	#ifdef TORRENT_DHT_VERBOSE_LOGGING
		TORRENT_LOG(rpc) << "Invoking " << messages::ids[message_id]
			<< " -> " << target_addr;
	#endif
		m_send(m);
		new_transaction_id(o);
	}
	catch (std::exception& e)
	{
		// m_send may fail with "no route to host"
		TORRENT_ASSERT(potential_new_id == m_next_transaction_id);
		o->abort();
	}
}

void rpc_manager::reply(msg& m)
{
	INVARIANT_CHECK;

	if (m_destructing) return;

	TORRENT_ASSERT(m.reply);
	m.piggy_backed_ping = false;
	m.id = m_our_id;

	m_send(m);
}

void rpc_manager::reply_with_ping(msg& m)
{
	INVARIANT_CHECK;

	if (m_destructing) return;
	TORRENT_ASSERT(m.reply);

	m.piggy_backed_ping = true;
	m.id = m_our_id;

	m.ping_transaction_id.clear();
	std::back_insert_iterator<std::string> out(m.ping_transaction_id);
	io::write_uint16(m_next_transaction_id, out);

	observer_ptr o(new (allocator().malloc()) null_observer(allocator()));
	TORRENT_ASSERT(!m_transactions[m_next_transaction_id]);
	o->sent = time_now();
	o->target_addr = m.addr;

	m_send(m);
	new_transaction_id(o);
}

} } // namespace libtorrent::dht

<|endoftext|>


<commit_before>#include "utils/zmq.h"
#include <array>

void z_send(zmq::socket_t& socket, const std::string& str, int flags) {
    zmq::message_t msg(str.size());
    std::memcpy(msg.data(), str.c_str(), str.size());
    socket.send(msg, flags);
}

void z_send(zmq::socket_t& socket, zmq::message_t& msg, int flags) {
    socket.send(msg, flags);
}

std::string z_recv(zmq::socket_t& socket) {
    zmq::message_t msg;
    socket.recv(&msg);
    return std::string(static_cast<char*>(msg.data()), msg.size());
}

LoadBalancer::LoadBalancer(zmq::context_t& context) : clients(context, ZMQ_ROUTER), workers(context, ZMQ_ROUTER) {}
void LoadBalancer::bind(const std::string& clients_socket_path, const std::string& workers_socket_path) {
    clients.bind(clients_socket_path.c_str());
    workers.bind(workers_socket_path.c_str());
}

void LoadBalancer::run() {
    while (true) {
        zmq_pollitem_t items[] = {{static_cast<void*>(workers), 0, ZMQ_POLLIN, 0},
                                  {static_cast<void*>(clients), 0, ZMQ_POLLIN, 0}};
        if (avalailable_worker.empty()) {
            // we don't look for request from client if there is no worker for handling them
            zmq::poll(items, 1, -1);
        } else {
            zmq::poll(items, 2, -1);
        }
        // handle worker
        if (items[0].revents & ZMQ_POLLIN) {
            // the first frame is the identifier of the worker: we add it to the available worker
            avalailable_worker.push(z_recv(workers));
            {
                //  Second frame is empty
                std::string empty = z_recv(workers);
                assert(empty.size() == 0);
            }

            //  Third frame is READY or else a client reply address
            std::string client_addr = z_recv(workers);

            //  If client reply, send resp back to the appropriate client
            if (client_addr != "READY") {
                {
                    // another empty frame
                    std::string empty = z_recv(workers);
                    assert(empty.size() == 0);
                }
                // the actual reply
                zmq::message_t reply;
                workers.recv(&reply);
                z_send(clients, client_addr, ZMQ_SNDMORE);
                z_send(clients, "", ZMQ_SNDMORE);
                z_send(clients, reply);
            }
        }
        // handle clients request
        if (items[1].revents & ZMQ_POLLIN){
            // The client request is a multi-part ZMQ message, we have to check every frame and be sure the multi-part message frame
            // is composed as we wish, otherwise the multi-part message may be shifted unexpectedly.

            // The multi-part ZMQ message should have 3 parts
            // The first one is the ID of message
            // The second one is an empty frame
            // The third one is the real request
            size_t more = 0;
            size_t more_size = sizeof (more);

            size_t nb_frames = 0;
            // there is no copy/move constructor in message_t in v2.2, which is the verison used by Jenkins...
            std::array<zmq::message_t, 3> frames{};

            do {
                zmq::message_t frame{};
                clients.recv(&frame);

                if (nb_frames < 3) {
                    frames[nb_frames].move(&frame);
                }
                // Are there more frames coming?
                clients.getsockopt(ZMQ_RCVMORE, &more, &more_size);
                nb_frames++;
            } while (more);

            if (nb_frames > 3 || frames[1].size() != 0 ) {
                z_send(clients, "");
                continue;
            }

            std::string worker_addr = avalailable_worker.top();
            avalailable_worker.pop();

            z_send(workers, worker_addr, ZMQ_SNDMORE);
            z_send(workers, "", ZMQ_SNDMORE);
            // frames[0] is the id of message
            z_send(workers, frames[0], ZMQ_SNDMORE);
            z_send(workers, "", ZMQ_SNDMORE);
            // frames[2] is the request
            z_send(workers, frames[2]);
        }
    }
}

<commit_msg>use at<commit_after>#include "utils/zmq.h"
#include <array>

void z_send(zmq::socket_t& socket, const std::string& str, int flags) {
    zmq::message_t msg(str.size());
    std::memcpy(msg.data(), str.c_str(), str.size());
    socket.send(msg, flags);
}

void z_send(zmq::socket_t& socket, zmq::message_t& msg, int flags) {
    socket.send(msg, flags);
}

std::string z_recv(zmq::socket_t& socket) {
    zmq::message_t msg;
    socket.recv(&msg);
    return std::string(static_cast<char*>(msg.data()), msg.size());
}

LoadBalancer::LoadBalancer(zmq::context_t& context) : clients(context, ZMQ_ROUTER), workers(context, ZMQ_ROUTER) {}
void LoadBalancer::bind(const std::string& clients_socket_path, const std::string& workers_socket_path) {
    clients.bind(clients_socket_path.c_str());
    workers.bind(workers_socket_path.c_str());
}

void LoadBalancer::run() {
    while (true) {
        zmq_pollitem_t items[] = {{static_cast<void*>(workers), 0, ZMQ_POLLIN, 0},
                                  {static_cast<void*>(clients), 0, ZMQ_POLLIN, 0}};
        if (avalailable_worker.empty()) {
            // we don't look for request from client if there is no worker for handling them
            zmq::poll(items, 1, -1);
        } else {
            zmq::poll(items, 2, -1);
        }
        // handle worker
        if (items[0].revents & ZMQ_POLLIN) {
            // the first frame is the identifier of the worker: we add it to the available worker
            avalailable_worker.push(z_recv(workers));
            {
                //  Second frame is empty
                std::string empty = z_recv(workers);
                assert(empty.size() == 0);
            }

            //  Third frame is READY or else a client reply address
            std::string client_addr = z_recv(workers);

            //  If client reply, send resp back to the appropriate client
            if (client_addr != "READY") {
                {
                    // another empty frame
                    std::string empty = z_recv(workers);
                    assert(empty.size() == 0);
                }
                // the actual reply
                zmq::message_t reply;
                workers.recv(&reply);
                z_send(clients, client_addr, ZMQ_SNDMORE);
                z_send(clients, "", ZMQ_SNDMORE);
                z_send(clients, reply);
            }
        }
        // handle clients request
        if (items[1].revents & ZMQ_POLLIN){
            // The client request is a multi-part ZMQ message, we have to check every frame and be sure the multi-part message frame
            // is composed as we wish, otherwise the multi-part message may be shifted unexpectedly.

            // The multi-part ZMQ message should have 3 parts
            // The first one is the ID of message
            // The second one is an empty frame
            // The third one is the real request
            size_t more = 0;
            size_t more_size = sizeof (more);

            size_t nb_frames = 0;
            // there is no copy/move constructor in message_t in v2.2, which is the verison used by Jenkins...
            std::array<zmq::message_t, 3> frames{};

            do {
                zmq::message_t frame{};
                clients.recv(&frame);

                if (nb_frames < 3) {
                    frames[nb_frames].move(&frame);
                }
                // Are there more frames coming?
                clients.getsockopt(ZMQ_RCVMORE, &more, &more_size);
                nb_frames++;
            } while (more);

            if (nb_frames > 3 || frames.at(1).size() != 0 ) {
                z_send(clients, "");
                continue;
            }

            std::string worker_addr = avalailable_worker.top();
            avalailable_worker.pop();

            z_send(workers, worker_addr, ZMQ_SNDMORE);
            z_send(workers, "", ZMQ_SNDMORE);
            // frames[0] is the id of message
            z_send(workers, frames[0], ZMQ_SNDMORE);
            z_send(workers, "", ZMQ_SNDMORE);
            // frames[2] is the request
            z_send(workers, frames[2]);
        }
    }
}
<|endoftext|>


<commit_before>#pragma once

#include <imgui.h>

#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui_internal.h>

#include <cmath>
#include <vector>
#include <Video/ParticleSystemRenderer.hpp>

struct MyCurve {
    std::string curve_name;
    float maxTime = 1.0f;
    ImVec2 value[10];
    ImGuiID id;
    int item = 0;
    float value_you_care_about = 0.0f;

    // Variables for controlling particle.
    bool editVelocityX = false;
    bool editVelocityY = false;
    bool editVelocityZ = false;
};

class CurveEditor {
public:
    CurveEditor();
    ~CurveEditor();

    /// Show the editor.
    void Show();

    /// If editor is visible.
    bool IsVisible() const;

    /// Set visibility.
    /**
     * @param visible setting visibility on editor.
     */
    void SetVisible(bool visible);

    /// Adds a curve.
    /**
     * @param curve_name name of curve.
     * @param uniqueId unique ID.
     * @param item unique ID just set it to 0.
     */
    void AddMyCurve(std::string& curve_name, ImGuiID uniqueId, int item);

    /// Updates all curves.
    /**
     * @param deltaTime deltatime.
     * @param totalTime total time for the curves.
     */
    void UpdateCurves(float deltaTime, float totalTime);

    /// Render the curve editor.
    void RenderCurveEditor();

    /// Get all curves.
    /**
     * @return All curves.
     */
    const std::vector<MyCurve>& GetAllCurves() const;

private:
    bool visible = false;
    ImGuiID addedCurve = 0;
    std::string curvename = "Default";
    char curveBuf[10];
    std::string editor_name;
    std::vector<MyCurve> curves;
    float time = 0.0f;
    Video::ParticleSystemRenderer::EmitterSettings emitterSettings;
    bool goBack = false;
    bool play = false;

};<commit_msg>Add newline at end of file.<commit_after>#pragma once

#include <imgui.h>

#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui_internal.h>

#include <cmath>
#include <vector>
#include <Video/ParticleSystemRenderer.hpp>

struct MyCurve {
    std::string curve_name;
    float maxTime = 1.0f;
    ImVec2 value[10];
    ImGuiID id;
    int item = 0;
    float value_you_care_about = 0.0f;

    // Variables for controlling particle.
    bool editVelocityX = false;
    bool editVelocityY = false;
    bool editVelocityZ = false;
};

class CurveEditor {
public:
    CurveEditor();
    ~CurveEditor();

    /// Show the editor.
    void Show();

    /// If editor is visible.
    bool IsVisible() const;

    /// Set visibility.
    /**
     * @param visible setting visibility on editor.
     */
    void SetVisible(bool visible);

    /// Adds a curve.
    /**
     * @param curve_name name of curve.
     * @param uniqueId unique ID.
     * @param item unique ID just set it to 0.
     */
    void AddMyCurve(std::string& curve_name, ImGuiID uniqueId, int item);

    /// Updates all curves.
    /**
     * @param deltaTime deltatime.
     * @param totalTime total time for the curves.
     */
    void UpdateCurves(float deltaTime, float totalTime);

    /// Render the curve editor.
    void RenderCurveEditor();

    /// Get all curves.
    /**
     * @return All curves.
     */
    const std::vector<MyCurve>& GetAllCurves() const;

private:
    bool visible = false;
    ImGuiID addedCurve = 0;
    std::string curvename = "Default";
    char curveBuf[10];
    std::string editor_name;
    std::vector<MyCurve> curves;
    float time = 0.0f;
    Video::ParticleSystemRenderer::EmitterSettings emitterSettings;
    bool goBack = false;
    bool play = false;

};
<|endoftext|>


<commit_before>/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (info@qt.nokia.com)
**
**
** GNU Lesser General Public License Usage
**
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
** If you have questions regarding the use of this file, please contact
** Nokia at info@qt.nokia.com.
**
**************************************************************************/

#include "qmljsscriptconsole.h"
#include "interactiveinterpreter.h"
#include "qmladapter.h"
#include "debuggerstringutils.h"

#include <texteditor/fontsettings.h>
#include <texteditor/texteditorsettings.h>

#include <extensionsystem/pluginmanager.h>
#include <coreplugin/coreconstants.h>
#include <utils/statuslabel.h>

#include <QtGui/QMenu>
#include <QtGui/QTextBlock>
#include <QtGui/QHBoxLayout>
#include <QtGui/QVBoxLayout>
#include <QtGui/QToolButton>

namespace Debugger {
namespace Internal {

class QmlJSScriptConsolePrivate
{
public:
    QmlJSScriptConsolePrivate()
        : prompt(QLatin1String("> ")),
          startOfEditableArea(-1),
          lastKnownPosition(0),
          inferiorStopped(false)
    {
        resetCache();
    }

    void resetCache();
    void appendToHistory(const QString &script);
    bool canEvaluateScript(const QString &script);

    QWeakPointer<QmlAdapter> adapter;

    QString prompt;
    int startOfEditableArea;
    int lastKnownPosition;

    QStringList scriptHistory;
    int scriptHistoryIndex;

    InteractiveInterpreter interpreter;

    bool inferiorStopped;
    QList<QTextEdit::ExtraSelection> selections;
};

void QmlJSScriptConsolePrivate::resetCache()
{
    scriptHistory.clear();
    scriptHistory.append(QLatin1String(""));
    scriptHistoryIndex = scriptHistory.count();

    selections.clear();
}

void QmlJSScriptConsolePrivate::appendToHistory(const QString &script)
{
    scriptHistoryIndex = scriptHistory.count();
    scriptHistory.replace(scriptHistoryIndex - 1,script);
    scriptHistory.append(QLatin1String(""));
    scriptHistoryIndex = scriptHistory.count();
}

bool QmlJSScriptConsolePrivate::canEvaluateScript(const QString &script)
{
    interpreter.clearText();
    interpreter.appendText(script);
    return interpreter.canEvaluate();
}

///////////////////////////////////////////////////////////////////////
//
// QmlJSScriptConsoleWidget
//
///////////////////////////////////////////////////////////////////////

QmlJSScriptConsoleWidget::QmlJSScriptConsoleWidget(QWidget *parent)
    : QWidget(parent)
{
    QVBoxLayout *vbox = new QVBoxLayout(this);
    vbox->setMargin(0);
    vbox->setSpacing(0);

    QWidget *statusbarContainer = new QWidget;

    QHBoxLayout *hbox = new QHBoxLayout(statusbarContainer);
    hbox->setMargin(0);
    hbox->setSpacing(0);

    //Clear Button
    QToolButton *clearButton = new QToolButton;
    QAction *clearAction = new QAction(tr("Clear Console"), this);
    clearAction->setIcon(QIcon(_(Core::Constants::ICON_CLEAN_PANE)));

    clearButton->setDefaultAction(clearAction);

    //Status Label
    m_statusLabel = new Utils::StatusLabel;

    hbox->addWidget(m_statusLabel, 20, Qt::AlignLeft);
    hbox->addWidget(clearButton, 0, Qt::AlignRight);

    m_console = new QmlJSScriptConsole;
    connect(m_console, SIGNAL(evaluateExpression(QString)), this,
            SIGNAL(evaluateExpression(QString)));
    connect(m_console, SIGNAL(updateStatusMessage(const QString &, int)), m_statusLabel,
            SLOT(showStatusMessage(const QString &, int)));
    connect(clearAction, SIGNAL(triggered()), m_console, SLOT(clear()));
    vbox->addWidget(statusbarContainer);
    vbox->addWidget(m_console);

}

void QmlJSScriptConsoleWidget::setQmlAdapter(QmlAdapter *adapter)
{
    m_console->setQmlAdapter(adapter);
}

void QmlJSScriptConsoleWidget::setInferiorStopped(bool inferiorStopped)
{
    m_console->setInferiorStopped(inferiorStopped);
}

void QmlJSScriptConsoleWidget::appendResult(const QString &result)
{
    m_console->appendResult(result);
}

///////////////////////////////////////////////////////////////////////
//
// QmlJSScriptConsole
//
///////////////////////////////////////////////////////////////////////

QmlJSScriptConsole::QmlJSScriptConsole(QWidget *parent)
    : QPlainTextEdit(parent),
      d(new QmlJSScriptConsolePrivate())
{
    connect(this, SIGNAL(cursorPositionChanged()), SLOT(onCursorPositionChanged()));

    setFrameStyle(QFrame::NoFrame);
    setUndoRedoEnabled(false);
    setBackgroundVisible(false);
    const TextEditor::FontSettings &fs = TextEditor::TextEditorSettings::instance()->fontSettings();
    setFont(fs.font());

    displayPrompt();
}

QmlJSScriptConsole::~QmlJSScriptConsole()
{
    delete d;
}

void QmlJSScriptConsole::setPrompt(const QString &prompt)
{
    d->prompt = prompt;
}

QString QmlJSScriptConsole::prompt() const
{
    return d->prompt;
}

void QmlJSScriptConsole::setInferiorStopped(bool inferiorStopped)
{
    d->inferiorStopped = inferiorStopped;
    onSelectionChanged();
}

void QmlJSScriptConsole::setQmlAdapter(QmlAdapter *adapter)
{
    d->adapter = adapter;
    clear();
}

void QmlJSScriptConsole::appendResult(const QString &result)
{
    QString currentScript = getCurrentScript();
    d->appendToHistory(currentScript);

    QTextCursor cur = textCursor();
    cur.movePosition(QTextCursor::EndOfLine);
    cur.insertText(QLatin1String("\n"));
    cur.insertText(result);
    cur.movePosition(QTextCursor::EndOfLine);
    cur.insertText(QLatin1String("\n"));
    setTextCursor(cur);
    displayPrompt();

    QTextEdit::ExtraSelection sel;

    QTextCharFormat resultFormat;
    resultFormat.setForeground(QBrush(QColor(Qt::darkGray)));

    QTextCursor c(document()->findBlockByNumber(cur.blockNumber()-1));
    c.movePosition(QTextCursor::StartOfBlock);
    c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor);

    sel.format = resultFormat;
    sel.cursor = c;

    d->selections.append(sel);

    setExtraSelections(d->selections);
}

void QmlJSScriptConsole::clear()
{
    d->resetCache();

    QPlainTextEdit::clear();
    displayPrompt();
}

void QmlJSScriptConsole::onStateChanged(QmlJsDebugClient::QDeclarativeDebugQuery::State state)
{
    QDeclarativeDebugExpressionQuery *query = qobject_cast<QDeclarativeDebugExpressionQuery *>(sender());
    bool gotResult = false;
    if (query && state != QDeclarativeDebugQuery::Error) {
        QString result(query->result().toString());
        if (result != QLatin1String("<undefined>")) {
            appendResult(result);
            gotResult = true;
        }
    }

    if (!gotResult) {
        QString currentScript = getCurrentScript();
        if (d->canEvaluateScript(currentScript)) {
            emit evaluateExpression(currentScript);
        } else {
            QPlainTextEdit::appendPlainText(QLatin1String(""));
            moveCursor(QTextCursor::EndOfLine);
        }
    }
    delete query;
}

void QmlJSScriptConsole::onSelectionChanged()
{
    if (!d->adapter.isNull()) {
        QString status;
        if (!d->inferiorStopped) {
            status.append(tr("Current Selected Object: "));
            status.append(d->adapter.data()->currentSelectedDisplayName());
        }
        emit updateStatusMessage(status, 0);
    }
}

void QmlJSScriptConsole::keyPressEvent(QKeyEvent *e)
{
    bool keyConsumed = false;
    switch (e->key()) {

    case Qt::Key_Return:
    case Qt::Key_Enter:
        if (isEditableArea()) {
            handleReturnKey();
            keyConsumed = true;
        }
        break;

    case Qt::Key_Backspace: {
        QTextCursor cursor = textCursor();
        bool hasSelection = cursor.hasSelection();
        int selectionStart = cursor.selectionStart();
        if ((hasSelection && selectionStart < d->startOfEditableArea)
                || (!hasSelection && selectionStart == d->startOfEditableArea)) {
            keyConsumed = true;
        }
        break;
    }

    case Qt::Key_Delete:
        if (textCursor().selectionStart() < d->startOfEditableArea) {
            keyConsumed = true;
        }
        break;

    case Qt::Key_Tab:
    case Qt::Key_Backtab:
        keyConsumed = true;
        break;

    case Qt::Key_Left:
        if (textCursor().position() == d->startOfEditableArea) {
            keyConsumed = true;
        } else if (e->modifiers() & Qt::ControlModifier && isEditableArea()) {
            handleHomeKey();
            keyConsumed = true;
        }
        break;

    case Qt::Key_Up:
        if (isEditableArea()) {
            handleUpKey();
            keyConsumed = true;
        }
        break;

    case Qt::Key_Down:
        if (isEditableArea()) {
            handleDownKey();
            keyConsumed = true;
        }
        break;

    case Qt::Key_Home:
        if (isEditableArea()) {
            handleHomeKey();
            keyConsumed = true;
        }
        break;

    case Qt::Key_C:
    case Qt::Key_Insert: {
        //Fair to assume that for any selection beyond startOfEditableArea
        //only copy function is allowed.
        QTextCursor cursor = textCursor();
        bool hasSelection = cursor.hasSelection();
        int selectionStart = cursor.selectionStart();
        if (hasSelection && selectionStart < d->startOfEditableArea) {
            if (!(e->modifiers() & Qt::ControlModifier))
                keyConsumed = true;
        }
        break;
    }

    default: {
        QTextCursor cursor = textCursor();
        bool hasSelection = cursor.hasSelection();
        int selectionStart = cursor.selectionStart();
        if (hasSelection && selectionStart < d->startOfEditableArea) {
            keyConsumed = true;
        }
        break;
    }
    }

    if (!keyConsumed)
        QPlainTextEdit::keyPressEvent(e);
}

void QmlJSScriptConsole::contextMenuEvent(QContextMenuEvent *event)
{
    QTextCursor cursor = textCursor();
    Qt::TextInteractionFlags flags = textInteractionFlags();
    bool hasSelection = cursor.hasSelection();
    int selectionStart = cursor.selectionStart();
    bool canBeEdited = true;
    if (hasSelection && selectionStart < d->startOfEditableArea) {
        canBeEdited = false;
    }

    QMenu *menu = new QMenu();
    QAction *a;

    if ((flags & Qt::TextEditable) && canBeEdited) {
        a = menu->addAction(tr("Cut"), this, SLOT(cut()));
        a->setEnabled(cursor.hasSelection());
    }

    a = menu->addAction(tr("Copy"), this, SLOT(copy()));
    a->setEnabled(cursor.hasSelection());

    if ((flags & Qt::TextEditable) && canBeEdited) {
        a = menu->addAction(tr("Paste"), this, SLOT(paste()));
        a->setEnabled(canPaste());
    }

    menu->addSeparator();
    a = menu->addAction(tr("Select All"), this, SLOT(selectAll()));
    a->setEnabled(!document()->isEmpty());

    menu->addSeparator();
    menu->addAction(tr("Clear"), this, SLOT(clear()));

    menu->exec(event->globalPos());

    delete menu;
}

void QmlJSScriptConsole::mouseReleaseEvent(QMouseEvent *e)
{
    QPlainTextEdit::mouseReleaseEvent(e);
    QTextCursor cursor = textCursor();
    if (e->button() == Qt::LeftButton && !cursor.hasSelection() && !isEditableArea()) {
        cursor.setPosition(d->lastKnownPosition);
        setTextCursor(cursor);
    }
}

void QmlJSScriptConsole::onCursorPositionChanged()
{
    if (!isEditableArea()) {
        setTextInteractionFlags(Qt::TextSelectableByMouse);
    } else {
        d->lastKnownPosition = textCursor().position();
        setTextInteractionFlags(Qt::TextEditorInteraction);
    }
}

void QmlJSScriptConsole::displayPrompt()
{
    d->startOfEditableArea = textCursor().position() + d->prompt.length();
    QTextCursor cur = textCursor();
    cur.insertText(d->prompt);
    cur.movePosition(QTextCursor::EndOfWord);
    setTextCursor(cur);
}

void QmlJSScriptConsole::handleReturnKey()
{
    QString currentScript = getCurrentScript();
    bool evaluateScript = false;

    //Check if string is only white spaces
    if (currentScript.trimmed().isEmpty()) {
        QTextCursor cur = textCursor();
        cur.movePosition(QTextCursor::EndOfLine);
        cur.insertText(QLatin1String("\n"));
        setTextCursor(cur);
        displayPrompt();
        evaluateScript = true;
    }

    if (!evaluateScript && !d->inferiorStopped) {
        if (!d->adapter.isNull()) {
            QDeclarativeEngineDebug *engineDebug = d->adapter.data()->engineDebugClient();
            int id = d->adapter.data()->currentSelectedDebugId();
            if (engineDebug && id != -1) {
                QDeclarativeDebugExpressionQuery *query =
                        engineDebug->queryExpressionResult(id, currentScript, this);
                connect(query, SIGNAL(stateChanged(QmlJsDebugClient::QDeclarativeDebugQuery::State)),
                        this, SLOT(onStateChanged(QmlJsDebugClient::QDeclarativeDebugQuery::State)));
                evaluateScript = true;
            }
        }
    }

    if (!evaluateScript) {
        if (d->canEvaluateScript(currentScript)) {
            emit evaluateExpression(currentScript);
        } else {
            QPlainTextEdit::appendPlainText(QLatin1String(""));
            moveCursor(QTextCursor::EndOfLine);
        }
    }
}

void QmlJSScriptConsole::handleUpKey()
{
    //get the current script and update in script history
    QString currentScript = getCurrentScript();
    d->scriptHistory.replace(d->scriptHistoryIndex - 1,currentScript);

    if (d->scriptHistoryIndex > 1)
        d->scriptHistoryIndex--;

    replaceCurrentScript(d->scriptHistory.at(d->scriptHistoryIndex - 1));
}

void QmlJSScriptConsole::handleDownKey()
{
    //get the current script and update in script history
    QString currentScript = getCurrentScript();
    d->scriptHistory.replace(d->scriptHistoryIndex - 1,currentScript);

    if (d->scriptHistoryIndex < d->scriptHistory.count())
        d->scriptHistoryIndex++;

    replaceCurrentScript(d->scriptHistory.at(d->scriptHistoryIndex - 1));
}

void QmlJSScriptConsole::handleHomeKey()
{
    QTextCursor cursor = textCursor();
    cursor.setPosition(d->startOfEditableArea);
    setTextCursor(cursor);
}

QString QmlJSScriptConsole::getCurrentScript() const
{
    QTextCursor cursor = textCursor();
    cursor.setPosition(d->startOfEditableArea);
    while (cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor)) ;
    QString script = cursor.selectedText();
    cursor.clearSelection();
    //remove trailing white space
    int end = script.size() - 1;
    while (end > 0 && script[end].isSpace())
        end--;
    return script.left(end + 1);
}

void QmlJSScriptConsole::replaceCurrentScript(const QString &script)
{
    QTextCursor cursor = textCursor();
    cursor.setPosition(d->startOfEditableArea);
    while (cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor)) ;
    cursor.deleteChar();
    cursor.insertText(script);
    setTextCursor(cursor);
}

bool QmlJSScriptConsole::isEditableArea() const
{
    return textCursor().position() >= d->startOfEditableArea;
}

} //Internal
} //Debugger
<commit_msg>QmlScriptConsole: Bug Fixes<commit_after>/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (info@qt.nokia.com)
**
**
** GNU Lesser General Public License Usage
**
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
** If you have questions regarding the use of this file, please contact
** Nokia at info@qt.nokia.com.
**
**************************************************************************/

#include "qmljsscriptconsole.h"
#include "interactiveinterpreter.h"
#include "qmladapter.h"
#include "debuggerstringutils.h"

#include <texteditor/fontsettings.h>
#include <texteditor/texteditorsettings.h>

#include <extensionsystem/pluginmanager.h>
#include <coreplugin/coreconstants.h>
#include <utils/statuslabel.h>

#include <QtGui/QMenu>
#include <QtGui/QTextBlock>
#include <QtGui/QHBoxLayout>
#include <QtGui/QVBoxLayout>
#include <QtGui/QToolButton>

namespace Debugger {
namespace Internal {

class QmlJSScriptConsolePrivate
{
public:
    QmlJSScriptConsolePrivate()
        : prompt(_("> ")),
          startOfEditableArea(-1),
          lastKnownPosition(0),
          inferiorStopped(false)
    {
        resetCache();
    }

    void resetCache();
    void appendToHistory(const QString &script);
    bool canEvaluateScript(const QString &script);

    QWeakPointer<QmlAdapter> adapter;

    QString prompt;
    int startOfEditableArea;
    int lastKnownPosition;

    QStringList scriptHistory;
    int scriptHistoryIndex;

    InteractiveInterpreter interpreter;

    bool inferiorStopped;
    QList<QTextEdit::ExtraSelection> selections;
};

void QmlJSScriptConsolePrivate::resetCache()
{
    scriptHistory.clear();
    scriptHistory.append(QString());
    scriptHistoryIndex = scriptHistory.count();

    selections.clear();
}

void QmlJSScriptConsolePrivate::appendToHistory(const QString &script)
{
    scriptHistoryIndex = scriptHistory.count();
    scriptHistory.replace(scriptHistoryIndex - 1,script);
    scriptHistory.append(QString());
    scriptHistoryIndex = scriptHistory.count();
}

bool QmlJSScriptConsolePrivate::canEvaluateScript(const QString &script)
{
    interpreter.clearText();
    interpreter.appendText(script);
    return interpreter.canEvaluate();
}

///////////////////////////////////////////////////////////////////////
//
// QmlJSScriptConsoleWidget
//
///////////////////////////////////////////////////////////////////////

QmlJSScriptConsoleWidget::QmlJSScriptConsoleWidget(QWidget *parent)
    : QWidget(parent)
{
    QVBoxLayout *vbox = new QVBoxLayout(this);
    vbox->setMargin(0);
    vbox->setSpacing(0);

    QWidget *statusbarContainer = new QWidget;

    QHBoxLayout *hbox = new QHBoxLayout(statusbarContainer);
    hbox->setMargin(0);
    hbox->setSpacing(0);

    //Clear Button
    QToolButton *clearButton = new QToolButton;
    QAction *clearAction = new QAction(tr("Clear Console"), this);
    clearAction->setIcon(QIcon(_(Core::Constants::ICON_CLEAN_PANE)));

    clearButton->setDefaultAction(clearAction);

    //Status Label
    m_statusLabel = new Utils::StatusLabel;

    hbox->addWidget(m_statusLabel, 20, Qt::AlignLeft);
    hbox->addWidget(clearButton, 0, Qt::AlignRight);

    m_console = new QmlJSScriptConsole;
    connect(m_console, SIGNAL(evaluateExpression(QString)), this,
            SIGNAL(evaluateExpression(QString)));
    connect(m_console, SIGNAL(updateStatusMessage(const QString &, int)), m_statusLabel,
            SLOT(showStatusMessage(const QString &, int)));
    connect(clearAction, SIGNAL(triggered()), m_console, SLOT(clear()));
    vbox->addWidget(statusbarContainer);
    vbox->addWidget(m_console);

}

void QmlJSScriptConsoleWidget::setQmlAdapter(QmlAdapter *adapter)
{
    m_console->setQmlAdapter(adapter);
}

void QmlJSScriptConsoleWidget::setInferiorStopped(bool inferiorStopped)
{
    m_console->setInferiorStopped(inferiorStopped);
}

void QmlJSScriptConsoleWidget::appendResult(const QString &result)
{
    m_console->appendResult(result);
}

///////////////////////////////////////////////////////////////////////
//
// QmlJSScriptConsole
//
///////////////////////////////////////////////////////////////////////

QmlJSScriptConsole::QmlJSScriptConsole(QWidget *parent)
    : QPlainTextEdit(parent),
      d(new QmlJSScriptConsolePrivate())
{
    connect(this, SIGNAL(cursorPositionChanged()), SLOT(onCursorPositionChanged()));

    setFrameStyle(QFrame::NoFrame);
    setUndoRedoEnabled(false);
    setBackgroundVisible(false);
    const TextEditor::FontSettings &fs = TextEditor::TextEditorSettings::instance()->fontSettings();
    setFont(fs.font());

    displayPrompt();
}

QmlJSScriptConsole::~QmlJSScriptConsole()
{
    delete d;
}

void QmlJSScriptConsole::setPrompt(const QString &prompt)
{
    d->prompt = prompt;
}

QString QmlJSScriptConsole::prompt() const
{
    return d->prompt;
}

void QmlJSScriptConsole::setInferiorStopped(bool inferiorStopped)
{
    d->inferiorStopped = inferiorStopped;
    onSelectionChanged();
}

void QmlJSScriptConsole::setQmlAdapter(QmlAdapter *adapter)
{
    d->adapter = adapter;
    clear();
}

void QmlJSScriptConsole::appendResult(const QString &result)
{
    QString currentScript = getCurrentScript();
    d->appendToHistory(currentScript);

    QTextCursor cur = textCursor();
    cur.movePosition(QTextCursor::EndOfLine);
    cur.insertText(_("\n"));
    cur.insertText(result);
    cur.movePosition(QTextCursor::EndOfLine);
    cur.insertText(_("\n"));
    setTextCursor(cur);
    displayPrompt();

    QTextEdit::ExtraSelection sel;

    QTextCharFormat resultFormat;
    resultFormat.setForeground(QBrush(QColor(Qt::darkGray)));

    QTextCursor c(document()->findBlockByNumber(cur.blockNumber()-1));
    c.movePosition(QTextCursor::StartOfBlock);
    c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor);

    sel.format = resultFormat;
    sel.cursor = c;

    d->selections.append(sel);

    setExtraSelections(d->selections);
}

void QmlJSScriptConsole::clear()
{
    d->resetCache();

    QPlainTextEdit::clear();
    displayPrompt();
}

void QmlJSScriptConsole::onStateChanged(QmlJsDebugClient::QDeclarativeDebugQuery::State state)
{
    QDeclarativeDebugExpressionQuery *query = qobject_cast<QDeclarativeDebugExpressionQuery *>(sender());

    if (query && state != QDeclarativeDebugQuery::Error) {
        QString result(query->result().toString());
        if (result == _("<undefined>") && d->inferiorStopped) {
            //don't give up. check if we can still evaluate using javascript engine
            emit evaluateExpression(getCurrentScript());
        } else {
            appendResult(result);
        }
    } else {
        QPlainTextEdit::appendPlainText(QString());
        moveCursor(QTextCursor::EndOfLine);
    }
    delete query;
}

void QmlJSScriptConsole::onSelectionChanged()
{
    if (!d->adapter.isNull()) {
        QString status;
        if (!d->inferiorStopped) {
            status.append(tr("Current Selected Object: "));
            status.append(d->adapter.data()->currentSelectedDisplayName());
        }
        emit updateStatusMessage(status, 0);
    }
}

void QmlJSScriptConsole::keyPressEvent(QKeyEvent *e)
{
    bool keyConsumed = false;
    switch (e->key()) {

    case Qt::Key_Return:
    case Qt::Key_Enter:
        if (isEditableArea()) {
            handleReturnKey();
            keyConsumed = true;
        }
        break;

    case Qt::Key_Backspace: {
        QTextCursor cursor = textCursor();
        bool hasSelection = cursor.hasSelection();
        int selectionStart = cursor.selectionStart();
        if ((hasSelection && selectionStart < d->startOfEditableArea)
                || (!hasSelection && selectionStart == d->startOfEditableArea)) {
            keyConsumed = true;
        }
        break;
    }

    case Qt::Key_Delete:
        if (textCursor().selectionStart() < d->startOfEditableArea) {
            keyConsumed = true;
        }
        break;

    case Qt::Key_Tab:
    case Qt::Key_Backtab:
        keyConsumed = true;
        break;

    case Qt::Key_Left:
        if (textCursor().position() == d->startOfEditableArea) {
            keyConsumed = true;
        } else if (e->modifiers() & Qt::ControlModifier && isEditableArea()) {
            handleHomeKey();
            keyConsumed = true;
        }
        break;

    case Qt::Key_Up:
        if (isEditableArea()) {
            handleUpKey();
            keyConsumed = true;
        }
        break;

    case Qt::Key_Down:
        if (isEditableArea()) {
            handleDownKey();
            keyConsumed = true;
        }
        break;

    case Qt::Key_Home:
        if (isEditableArea()) {
            handleHomeKey();
            keyConsumed = true;
        }
        break;

    case Qt::Key_C:
    case Qt::Key_Insert: {
        //Fair to assume that for any selection beyond startOfEditableArea
        //only copy function is allowed.
        QTextCursor cursor = textCursor();
        bool hasSelection = cursor.hasSelection();
        int selectionStart = cursor.selectionStart();
        if (hasSelection && selectionStart < d->startOfEditableArea) {
            if (!(e->modifiers() & Qt::ControlModifier))
                keyConsumed = true;
        }
        break;
    }

    default: {
        QTextCursor cursor = textCursor();
        bool hasSelection = cursor.hasSelection();
        int selectionStart = cursor.selectionStart();
        if (hasSelection && selectionStart < d->startOfEditableArea) {
            keyConsumed = true;
        }
        break;
    }
    }

    if (!keyConsumed)
        QPlainTextEdit::keyPressEvent(e);
}

void QmlJSScriptConsole::contextMenuEvent(QContextMenuEvent *event)
{
    QTextCursor cursor = textCursor();
    Qt::TextInteractionFlags flags = textInteractionFlags();
    bool hasSelection = cursor.hasSelection();
    int selectionStart = cursor.selectionStart();
    bool canBeEdited = true;
    if (hasSelection && selectionStart < d->startOfEditableArea) {
        canBeEdited = false;
    }

    QMenu *menu = new QMenu();
    QAction *a;

    if ((flags & Qt::TextEditable) && canBeEdited) {
        a = menu->addAction(tr("Cut"), this, SLOT(cut()));
        a->setEnabled(cursor.hasSelection());
    }

    a = menu->addAction(tr("Copy"), this, SLOT(copy()));
    a->setEnabled(cursor.hasSelection());

    if ((flags & Qt::TextEditable) && canBeEdited) {
        a = menu->addAction(tr("Paste"), this, SLOT(paste()));
        a->setEnabled(canPaste());
    }

    menu->addSeparator();
    a = menu->addAction(tr("Select All"), this, SLOT(selectAll()));
    a->setEnabled(!document()->isEmpty());

    menu->addSeparator();
    menu->addAction(tr("Clear"), this, SLOT(clear()));

    menu->exec(event->globalPos());

    delete menu;
}

void QmlJSScriptConsole::mouseReleaseEvent(QMouseEvent *e)
{
    QPlainTextEdit::mouseReleaseEvent(e);
    QTextCursor cursor = textCursor();
    if (e->button() == Qt::LeftButton && !cursor.hasSelection() && !isEditableArea()) {
        cursor.setPosition(d->lastKnownPosition);
        setTextCursor(cursor);
    }
}

void QmlJSScriptConsole::onCursorPositionChanged()
{
    if (!isEditableArea()) {
        setTextInteractionFlags(Qt::TextSelectableByMouse);
    } else {
        d->lastKnownPosition = textCursor().position();
        setTextInteractionFlags(Qt::TextEditorInteraction);
    }
}

void QmlJSScriptConsole::displayPrompt()
{
    d->startOfEditableArea = textCursor().position() + d->prompt.length();
    QTextCursor cur = textCursor();
    cur.insertText(d->prompt);
    cur.movePosition(QTextCursor::EndOfWord);
    setTextCursor(cur);
}

void QmlJSScriptConsole::handleReturnKey()
{
    QString currentScript = getCurrentScript();
    bool evaluateScript = false;

    //Check if string is only white spaces
    if (currentScript.trimmed().isEmpty()) {
        QTextCursor cur = textCursor();
        cur.movePosition(QTextCursor::EndOfLine);
        cur.insertText(_("\n"));
        setTextCursor(cur);
        displayPrompt();
        evaluateScript = true;
    }

    if (!evaluateScript && !d->inferiorStopped) {
        if (!d->adapter.isNull()) {
            QDeclarativeEngineDebug *engineDebug = d->adapter.data()->engineDebugClient();
            int id = d->adapter.data()->currentSelectedDebugId();
            if (engineDebug && id != -1) {
                QDeclarativeDebugExpressionQuery *query =
                        engineDebug->queryExpressionResult(id, currentScript, this);
                connect(query, SIGNAL(stateChanged(QmlJsDebugClient::QDeclarativeDebugQuery::State)),
                        this, SLOT(onStateChanged(QmlJsDebugClient::QDeclarativeDebugQuery::State)));
                evaluateScript = true;
            }
        }
    }

    if (!evaluateScript) {
        if (d->canEvaluateScript(currentScript)) {
            emit evaluateExpression(currentScript);
        } else {
            QPlainTextEdit::appendPlainText(QString());
            moveCursor(QTextCursor::EndOfLine);
        }
    }
}

void QmlJSScriptConsole::handleUpKey()
{
    //get the current script and update in script history
    QString currentScript = getCurrentScript();
    d->scriptHistory.replace(d->scriptHistoryIndex - 1,currentScript);

    if (d->scriptHistoryIndex > 1)
        d->scriptHistoryIndex--;

    replaceCurrentScript(d->scriptHistory.at(d->scriptHistoryIndex - 1));
}

void QmlJSScriptConsole::handleDownKey()
{
    //get the current script and update in script history
    QString currentScript = getCurrentScript();
    d->scriptHistory.replace(d->scriptHistoryIndex - 1,currentScript);

    if (d->scriptHistoryIndex < d->scriptHistory.count())
        d->scriptHistoryIndex++;

    replaceCurrentScript(d->scriptHistory.at(d->scriptHistoryIndex - 1));
}

void QmlJSScriptConsole::handleHomeKey()
{
    QTextCursor cursor = textCursor();
    cursor.setPosition(d->startOfEditableArea);
    setTextCursor(cursor);
}

QString QmlJSScriptConsole::getCurrentScript() const
{
    QTextCursor cursor = textCursor();
    cursor.setPosition(d->startOfEditableArea);
    while (cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor)) ;
    QString script = cursor.selectedText();
    cursor.clearSelection();
    //remove trailing white space
    int end = script.size() - 1;
    while (end > 0 && script[end].isSpace())
        end--;
    return script.left(end + 1);
}

void QmlJSScriptConsole::replaceCurrentScript(const QString &script)
{
    QTextCursor cursor = textCursor();
    cursor.setPosition(d->startOfEditableArea);
    while (cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor)) ;
    cursor.deleteChar();
    cursor.insertText(script);
    setTextCursor(cursor);
}

bool QmlJSScriptConsole::isEditableArea() const
{
    return textCursor().position() >= d->startOfEditableArea;
}

} //Internal
} //Debugger
<|endoftext|>