<commit_before>/* This file is part of Ingen.
 * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
 *
 * Ingen is free software; you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <iostream>
#include <cassert>
#include <cmath>
#include <stdint.h>
#include <raul/Maid.hpp>
#include <boost/optional.hpp>
#include "LADSPANode.hpp"
#include "AudioBuffer.hpp"
#include "InputPort.hpp"
#include "OutputPort.hpp"
#include "PluginImpl.hpp"
#include "ProcessContext.hpp"

using namespace std;

namespace Ingen {

/** Partially construct a LADSPANode.
 *
 * Object is not usable until instantiate() is called with success.
 * (It _will_ crash!)
 */
LADSPANode::LADSPANode(PluginImpl* plugin, const string& path, bool polyphonic, PatchImpl* parent, const LADSPA_Descriptor* descriptor, SampleRate srate, size_t buffer_size)
	: NodeBase(plugin, path, polyphonic, parent, srate, buffer_size)
	, _descriptor(descriptor)
	, _instances(NULL)
	, _prepared_instances(NULL)
{
	assert(_descriptor != NULL);
}

LADSPANode::~LADSPANode()
{
	for (uint32_t i=0; i < _polyphony; ++i)
		_descriptor->cleanup((*_instances)[i]);

	delete _instances;
}

bool
LADSPANode::prepare_poly(uint32_t poly)
{
	NodeBase::prepare_poly(poly);

	if ( (!_polyphonic)
			|| (_prepared_instances && poly <= _prepared_instances->size()) ) {
		return true;
	}

	_prepared_instances = new Raul::Array<LADSPA_Handle>(poly, *_instances);
	for (uint32_t i = _polyphony; i < _prepared_instances->size(); ++i) {
		_prepared_instances->at(i) = _descriptor->instantiate(_descriptor, _srate);
		if ((*_prepared_instances)[i] == NULL) {
			cerr << "Failed to instantiate plugin!" << endl;
			return false;
		}

		if (_activated)
			_descriptor->activate((*_prepared_instances)[i]);
	}

	return true;
}

bool
LADSPANode::apply_poly(Raul::Maid& maid, uint32_t poly)
{
	if (!_polyphonic)
		return true;

	if (_prepared_instances) {
		assert(poly <= _prepared_instances->size());
		maid.push(_instances);
		_instances = _prepared_instances;
		_prepared_instances = NULL;
	}

	assert(poly <= _instances->size());
	_polyphony = poly;

	NodeBase::apply_poly(maid, poly);

	return true;
}

/** Instantiate self from LADSPA plugin descriptor.
 *
 * Implemented as a seperate function (rather than in the constructor) to
 * allow graceful error-catching of broken plugins.
 *
 * Returns whether or not plugin was successfully instantiated.  If return
 * value is false, this object may not be used.
 */
bool
LADSPANode::instantiate()
{
	if (!_ports)
		_ports = new Raul::Array<PortImpl*>(_descriptor->PortCount);

	_instances = new Raul::Array<LADSPA_Handle>(_polyphony, NULL);

	size_t port_buffer_size = 0;

	for (uint32_t i=0; i < _polyphony; ++i) {
		(*_instances)[i] = _descriptor->instantiate(_descriptor, _srate);
		if ((*_instances)[i] == NULL) {
			cerr << "Failed to instantiate plugin!" << endl;
			return false;
		}
	}

	string port_name;
	string port_path;

	PortImpl* port = NULL;

	for (size_t j=0; j < _descriptor->PortCount; ++j) {
		port_name = Path::nameify(_descriptor->PortNames[j]);

		string::size_type slash_index;

		// Name mangling, to guarantee port names are unique
		for (size_t k=0; k < _descriptor->PortCount; ++k) {
			assert(_descriptor->PortNames[k] != NULL);
			if (k != j && port_name == _descriptor->PortNames[k]) { // clash
				if (LADSPA_IS_PORT_CONTROL(_descriptor->PortDescriptors[j]))
					port_name += "(CR)";
				else
					port_name += "(AR)";
			}
			// Replace all slashes with "-" (so they don't screw up paths)
			while ((slash_index = port_name.find("/")) != string::npos)
				port_name[slash_index] = '-';
		}

		/*if (_descriptor->PortNames[j] != port_name)
			cerr << "NOTICE: Translated LADSPA port name: " <<
				_descriptor->PortNames[j] << " -> " << port_name << endl;*/

		port_path = path() + "/" + port_name;

		DataType type = DataType::AUDIO;
		port_buffer_size = _buffer_size;

		if (LADSPA_IS_PORT_CONTROL(_descriptor->PortDescriptors[j])) {
			type = DataType::CONTROL;
			port_buffer_size = 1;
		} else {
			assert(LADSPA_IS_PORT_AUDIO(_descriptor->PortDescriptors[j]));
		}
		assert (LADSPA_IS_PORT_INPUT(_descriptor->PortDescriptors[j])
			|| LADSPA_IS_PORT_OUTPUT(_descriptor->PortDescriptors[j]));

		boost::optional<Sample> default_val, min, max;
		get_port_limits(j, default_val, min, max);

		const float value = default_val ? default_val.get() : 0.0f;

		if (LADSPA_IS_PORT_INPUT(_descriptor->PortDescriptors[j])) {
			port = new InputPort(this, port_name, j, _polyphony, type, value, port_buffer_size);
			_ports->at(j) = port;
		} else if (LADSPA_IS_PORT_OUTPUT(_descriptor->PortDescriptors[j])) {
			port = new OutputPort(this, port_name, j, _polyphony, type, value, port_buffer_size);
			_ports->at(j) = port;
		}

		assert(port);
		assert(_ports->at(j) == port);

		// Work around broke-ass crackhead plugins
		if (default_val && default_val.get() < min.get()) {
			cerr << "WARNING: Broken LADSPA " << _descriptor->UniqueID
					<< ": Port default < minimum.  Minimum adjusted." << endl;
			min = default_val;
		}

		if (default_val && default_val.get() > max.get()) {
			cerr << "WARNING: Broken LADSPA " << _descriptor->UniqueID
					<< ": Maximum adjusted." << endl;
			max = default_val;
		}

		// Set initial/default value
		if (port->buffer_size() == 1) {
			for (uint32_t i=0; i < _polyphony; ++i)
				((AudioBuffer*)port->buffer(i))->set(value, 0);
		}

		if (port->is_input() && port->buffer_size() == 1) {
			if (min)
				port->set_variable("ingen:minimum", min.get());
			if (max)
				port->set_variable("ingen:maximum", max.get());
			if (default_val)
				port->set_variable("ingen:default", default_val.get());
		}
	}

	return true;
}

void
LADSPANode::activate()
{
	NodeBase::activate();

	for (uint32_t i=0; i < _polyphony; ++i) {
		for (unsigned long j=0; j < _descriptor->PortCount; ++j) {
			set_port_buffer(i, j, _ports->at(j)->buffer(i));
			/*	if (port->type() == DataType::FLOAT && port->buffer_size() == 1)
					port->set_value(0.0f, 0); // FIXME
				else if (port->type() == DataType::FLOAT && port->buffer_size() > 1)
					port->set_value(0.0f, 0);*/
		}
		if (_descriptor->activate != NULL)
			_descriptor->activate((*_instances)[i]);
	}
}

void
LADSPANode::deactivate()
{
	NodeBase::deactivate();

	for (uint32_t i=0; i < _polyphony; ++i)
		if (_descriptor->deactivate != NULL)
			_descriptor->deactivate((*_instances)[i]);
}

void
LADSPANode::process(ProcessContext& context)
{
	NodeBase::pre_process(context);

	for (uint32_t i=0; i < _polyphony; ++i)
		_descriptor->run((*_instances)[i], context.nframes());

	NodeBase::post_process(context);
}

void
LADSPANode::set_port_buffer(uint32_t voice, uint32_t port_num, Buffer* buf)
{
	assert(voice < _polyphony);

	AudioBuffer* audio_buffer = dynamic_cast<AudioBuffer*>(buf);
	assert(audio_buffer);

	if (port_num < _descriptor->PortCount)
		_descriptor->connect_port((*_instances)[voice], port_num,
			audio_buffer->data());
}

void
LADSPANode::get_port_limits(unsigned long            port_index,
                            boost::optional<Sample>& normal,
                            boost::optional<Sample>& lower,
                            boost::optional<Sample>& upper)
{
	LADSPA_PortRangeHintDescriptor hint_descriptor = _descriptor->PortRangeHints[port_index].HintDescriptor;

	/* set upper and lower, possibly adjusted to the sample rate */
	if (LADSPA_IS_HINT_SAMPLE_RATE(hint_descriptor)) {
		upper = _descriptor->PortRangeHints[port_index].UpperBound * _srate;
		lower = _descriptor->PortRangeHints[port_index].LowerBound * _srate;
	} else {
		upper = _descriptor->PortRangeHints[port_index].UpperBound;
		lower = _descriptor->PortRangeHints[port_index].LowerBound;
	}

	if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) {
		/* FLT_EPSILON is defined as the different between 1.0 and the minimum
		 * float greater than 1.0.  So, if lower is < FLT_EPSILON, it will be 1.0
		 * and the logarithmic control will have a base of 1 and thus not change
		 */
		if (lower < FLT_EPSILON) lower = FLT_EPSILON;
	}

	if (LADSPA_IS_HINT_HAS_DEFAULT(hint_descriptor)) {

		if (LADSPA_IS_HINT_DEFAULT_MINIMUM(hint_descriptor)) {
			normal = lower;
		} else if (LADSPA_IS_HINT_DEFAULT_LOW(hint_descriptor)) {
			assert(lower);
			assert(upper);
			if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) {
				normal = exp(log(lower.get()) * 0.75 + log(upper.get()) * 0.25);
			} else {
				normal = lower.get() * 0.75 + upper.get() * 0.25;
			}
		} else if (LADSPA_IS_HINT_DEFAULT_MIDDLE(hint_descriptor)) {
			assert(lower);
			assert(upper);
			if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) {
				normal = exp(log(lower.get()) * 0.5 + log(upper.get()) * 0.5);
			} else {
				normal = lower.get() * 0.5 + upper.get() * 0.5;
			}
		} else if (LADSPA_IS_HINT_DEFAULT_HIGH(hint_descriptor)) {
			assert(lower);
			assert(upper);
			if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) {
				normal = exp(log(lower.get()) * 0.25 + log(upper.get()) * 0.75);
			} else {
				normal = lower.get() * 0.25 + upper.get() * 0.75;
			}
		} else if (LADSPA_IS_HINT_DEFAULT_MAXIMUM(hint_descriptor)) {
			assert(upper);
			normal = upper.get();
		} else if (LADSPA_IS_HINT_DEFAULT_0(hint_descriptor)) {
			normal = 0.0;
		} else if (LADSPA_IS_HINT_DEFAULT_1(hint_descriptor)) {
			normal = 1.0;
		} else if (LADSPA_IS_HINT_DEFAULT_100(hint_descriptor)) {
			normal = 100.0;
		} else if (LADSPA_IS_HINT_DEFAULT_440(hint_descriptor)) {
			normal = 440.0;
		}
	} else {  // No default hint
		if (LADSPA_IS_HINT_BOUNDED_BELOW(hint_descriptor)) {
			assert(lower);
			normal = lower.get();
		} else if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint_descriptor)) {
			assert(upper);
			normal = upper.get();
		}
	}
}

} // namespace Ingen

<commit_msg>Fix more boost::optional stuff.<commit_after>/* This file is part of Ingen.
 * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
 *
 * Ingen is free software; you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <iostream>
#include <cassert>
#include <cmath>
#include <stdint.h>
#include <raul/Maid.hpp>
#include <boost/optional.hpp>
#include "LADSPANode.hpp"
#include "AudioBuffer.hpp"
#include "InputPort.hpp"
#include "OutputPort.hpp"
#include "PluginImpl.hpp"
#include "ProcessContext.hpp"

using namespace std;

namespace Ingen {

/** Partially construct a LADSPANode.
 *
 * Object is not usable until instantiate() is called with success.
 * (It _will_ crash!)
 */
LADSPANode::LADSPANode(PluginImpl* plugin, const string& path, bool polyphonic, PatchImpl* parent, const LADSPA_Descriptor* descriptor, SampleRate srate, size_t buffer_size)
	: NodeBase(plugin, path, polyphonic, parent, srate, buffer_size)
	, _descriptor(descriptor)
	, _instances(NULL)
	, _prepared_instances(NULL)
{
	assert(_descriptor != NULL);
}

LADSPANode::~LADSPANode()
{
	for (uint32_t i=0; i < _polyphony; ++i)
		_descriptor->cleanup((*_instances)[i]);

	delete _instances;
}

bool
LADSPANode::prepare_poly(uint32_t poly)
{
	NodeBase::prepare_poly(poly);

	if ( (!_polyphonic)
			|| (_prepared_instances && poly <= _prepared_instances->size()) ) {
		return true;
	}

	_prepared_instances = new Raul::Array<LADSPA_Handle>(poly, *_instances);
	for (uint32_t i = _polyphony; i < _prepared_instances->size(); ++i) {
		_prepared_instances->at(i) = _descriptor->instantiate(_descriptor, _srate);
		if ((*_prepared_instances)[i] == NULL) {
			cerr << "Failed to instantiate plugin!" << endl;
			return false;
		}

		if (_activated)
			_descriptor->activate((*_prepared_instances)[i]);
	}

	return true;
}

bool
LADSPANode::apply_poly(Raul::Maid& maid, uint32_t poly)
{
	if (!_polyphonic)
		return true;

	if (_prepared_instances) {
		assert(poly <= _prepared_instances->size());
		maid.push(_instances);
		_instances = _prepared_instances;
		_prepared_instances = NULL;
	}

	assert(poly <= _instances->size());
	_polyphony = poly;

	NodeBase::apply_poly(maid, poly);

	return true;
}

/** Instantiate self from LADSPA plugin descriptor.
 *
 * Implemented as a seperate function (rather than in the constructor) to
 * allow graceful error-catching of broken plugins.
 *
 * Returns whether or not plugin was successfully instantiated.  If return
 * value is false, this object may not be used.
 */
bool
LADSPANode::instantiate()
{
	if (!_ports)
		_ports = new Raul::Array<PortImpl*>(_descriptor->PortCount);

	_instances = new Raul::Array<LADSPA_Handle>(_polyphony, NULL);

	size_t port_buffer_size = 0;

	for (uint32_t i=0; i < _polyphony; ++i) {
		(*_instances)[i] = _descriptor->instantiate(_descriptor, _srate);
		if ((*_instances)[i] == NULL) {
			cerr << "Failed to instantiate plugin!" << endl;
			return false;
		}
	}

	string port_name;
	string port_path;

	PortImpl* port = NULL;

	for (size_t j=0; j < _descriptor->PortCount; ++j) {
		port_name = Path::nameify(_descriptor->PortNames[j]);

		string::size_type slash_index;

		// Name mangling, to guarantee port names are unique
		for (size_t k=0; k < _descriptor->PortCount; ++k) {
			assert(_descriptor->PortNames[k] != NULL);
			if (k != j && port_name == _descriptor->PortNames[k]) { // clash
				if (LADSPA_IS_PORT_CONTROL(_descriptor->PortDescriptors[j]))
					port_name += "(CR)";
				else
					port_name += "(AR)";
			}
			// Replace all slashes with "-" (so they don't screw up paths)
			while ((slash_index = port_name.find("/")) != string::npos)
				port_name[slash_index] = '-';
		}

		/*if (_descriptor->PortNames[j] != port_name)
			cerr << "NOTICE: Translated LADSPA port name: " <<
				_descriptor->PortNames[j] << " -> " << port_name << endl;*/

		port_path = path() + "/" + port_name;

		DataType type = DataType::AUDIO;
		port_buffer_size = _buffer_size;

		if (LADSPA_IS_PORT_CONTROL(_descriptor->PortDescriptors[j])) {
			type = DataType::CONTROL;
			port_buffer_size = 1;
		} else {
			assert(LADSPA_IS_PORT_AUDIO(_descriptor->PortDescriptors[j]));
		}
		assert (LADSPA_IS_PORT_INPUT(_descriptor->PortDescriptors[j])
			|| LADSPA_IS_PORT_OUTPUT(_descriptor->PortDescriptors[j]));

		boost::optional<Sample> default_val, min, max;
		get_port_limits(j, default_val, min, max);

		const float value = default_val ? default_val.get() : 0.0f;

		if (LADSPA_IS_PORT_INPUT(_descriptor->PortDescriptors[j])) {
			port = new InputPort(this, port_name, j, _polyphony, type, value, port_buffer_size);
			_ports->at(j) = port;
		} else if (LADSPA_IS_PORT_OUTPUT(_descriptor->PortDescriptors[j])) {
			port = new OutputPort(this, port_name, j, _polyphony, type, value, port_buffer_size);
			_ports->at(j) = port;
		}

		assert(port);
		assert(_ports->at(j) == port);

		// Work around broke-ass crackhead plugins
		if (default_val && default_val.get() < min.get()) {
			cerr << "WARNING: Broken LADSPA " << _descriptor->UniqueID
					<< ": Port default < minimum.  Minimum adjusted." << endl;
			min = default_val;
		}

		if (default_val && default_val.get() > max.get()) {
			cerr << "WARNING: Broken LADSPA " << _descriptor->UniqueID
					<< ": Maximum adjusted." << endl;
			max = default_val;
		}

		// Set initial/default value
		if (port->buffer_size() == 1) {
			for (uint32_t i=0; i < _polyphony; ++i)
				((AudioBuffer*)port->buffer(i))->set(value, 0);
		}

		if (port->is_input() && port->buffer_size() == 1) {
			if (min)
				port->set_variable("ingen:minimum", min.get());
			if (max)
				port->set_variable("ingen:maximum", max.get());
			if (default_val)
				port->set_variable("ingen:default", default_val.get());
		}
	}

	return true;
}

void
LADSPANode::activate()
{
	NodeBase::activate();

	for (uint32_t i=0; i < _polyphony; ++i) {
		for (unsigned long j=0; j < _descriptor->PortCount; ++j) {
			set_port_buffer(i, j, _ports->at(j)->buffer(i));
			/*	if (port->type() == DataType::FLOAT && port->buffer_size() == 1)
					port->set_value(0.0f, 0); // FIXME
				else if (port->type() == DataType::FLOAT && port->buffer_size() > 1)
					port->set_value(0.0f, 0);*/
		}
		if (_descriptor->activate != NULL)
			_descriptor->activate((*_instances)[i]);
	}
}

void
LADSPANode::deactivate()
{
	NodeBase::deactivate();

	for (uint32_t i=0; i < _polyphony; ++i)
		if (_descriptor->deactivate != NULL)
			_descriptor->deactivate((*_instances)[i]);
}

void
LADSPANode::process(ProcessContext& context)
{
	NodeBase::pre_process(context);

	for (uint32_t i=0; i < _polyphony; ++i)
		_descriptor->run((*_instances)[i], context.nframes());

	NodeBase::post_process(context);
}

void
LADSPANode::set_port_buffer(uint32_t voice, uint32_t port_num, Buffer* buf)
{
	assert(voice < _polyphony);

	AudioBuffer* audio_buffer = dynamic_cast<AudioBuffer*>(buf);
	assert(audio_buffer);

	if (port_num < _descriptor->PortCount)
		_descriptor->connect_port((*_instances)[voice], port_num,
			audio_buffer->data());
}

void
LADSPANode::get_port_limits(unsigned long            port_index,
                            boost::optional<Sample>& normal,
                            boost::optional<Sample>& lower,
                            boost::optional<Sample>& upper)
{
	LADSPA_PortRangeHintDescriptor hint_descriptor = _descriptor->PortRangeHints[port_index].HintDescriptor;

	/* set upper and lower, possibly adjusted to the sample rate */
	if (LADSPA_IS_HINT_SAMPLE_RATE(hint_descriptor)) {
		upper = _descriptor->PortRangeHints[port_index].UpperBound * _srate;
		lower = _descriptor->PortRangeHints[port_index].LowerBound * _srate;
	} else {
		upper = _descriptor->PortRangeHints[port_index].UpperBound;
		lower = _descriptor->PortRangeHints[port_index].LowerBound;
	}

	if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) {
		/* FLT_EPSILON is defined as the different between 1.0 and the minimum
		 * float greater than 1.0.  So, if lower is < FLT_EPSILON, it will be 1.0
		 * and the logarithmic control will have a base of 1 and thus not change
		 */
		if (lower.get() < FLT_EPSILON) lower = FLT_EPSILON;
	}

	if (LADSPA_IS_HINT_HAS_DEFAULT(hint_descriptor)) {

		if (LADSPA_IS_HINT_DEFAULT_MINIMUM(hint_descriptor)) {
			normal = lower;
		} else if (LADSPA_IS_HINT_DEFAULT_LOW(hint_descriptor)) {
			assert(lower);
			assert(upper);
			if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) {
				normal = exp(log(lower.get()) * 0.75 + log(upper.get()) * 0.25);
			} else {
				normal = lower.get() * 0.75 + upper.get() * 0.25;
			}
		} else if (LADSPA_IS_HINT_DEFAULT_MIDDLE(hint_descriptor)) {
			assert(lower);
			assert(upper);
			if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) {
				normal = exp(log(lower.get()) * 0.5 + log(upper.get()) * 0.5);
			} else {
				normal = lower.get() * 0.5 + upper.get() * 0.5;
			}
		} else if (LADSPA_IS_HINT_DEFAULT_HIGH(hint_descriptor)) {
			assert(lower);
			assert(upper);
			if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) {
				normal = exp(log(lower.get()) * 0.25 + log(upper.get()) * 0.75);
			} else {
				normal = lower.get() * 0.25 + upper.get() * 0.75;
			}
		} else if (LADSPA_IS_HINT_DEFAULT_MAXIMUM(hint_descriptor)) {
			assert(upper);
			normal = upper.get();
		} else if (LADSPA_IS_HINT_DEFAULT_0(hint_descriptor)) {
			normal = 0.0;
		} else if (LADSPA_IS_HINT_DEFAULT_1(hint_descriptor)) {
			normal = 1.0;
		} else if (LADSPA_IS_HINT_DEFAULT_100(hint_descriptor)) {
			normal = 100.0;
		} else if (LADSPA_IS_HINT_DEFAULT_440(hint_descriptor)) {
			normal = 440.0;
		}
	} else {  // No default hint
		if (LADSPA_IS_HINT_BOUNDED_BELOW(hint_descriptor)) {
			assert(lower);
			normal = lower.get();
		} else if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint_descriptor)) {
			assert(upper);
			normal = upper.get();
		}
	}
}

} // namespace Ingen

<|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 qt-info@nokia.com.
**
**************************************************************************/

#include "environment.h"

#include <QtCore/QProcess>
#include <QtCore/QDir>
#include <QtCore/QString>

using namespace Utils;

QList<EnvironmentItem> EnvironmentItem::fromStringList(QStringList list)
{
    QList<EnvironmentItem> result;
    foreach (const QString &string, list) {
        int pos = string.indexOf(QLatin1Char('='));
        if (pos == -1) {
            EnvironmentItem item(string, QString());
            item.unset = true;
            result.append(item);
        } else {
            EnvironmentItem item(string.left(pos), string.mid(pos+1));
            result.append(item);
        }
    }
    return result;
}

QStringList EnvironmentItem::toStringList(QList<EnvironmentItem> list)
{
    QStringList result;
    foreach (const EnvironmentItem &item, list) {
        if (item.unset)
            result << QString(item.name);
        else
            result << QString(item.name + '=' + item.value);
    }
    return result;
}

Environment::Environment()
{
}

Environment::Environment(QStringList env)
{
    foreach (const QString &s, env) {
        int i = s.indexOf("=");
        if (i >= 0) {
#ifdef Q_OS_WIN
            m_values.insert(s.left(i).toUpper(), s.mid(i+1));
#else
            m_values.insert(s.left(i), s.mid(i+1));
#endif
        }
    }
}

QStringList Environment::toStringList() const
{
    QStringList result;
    const QMap<QString, QString>::const_iterator end = m_values.constEnd();
    for (QMap<QString, QString>::const_iterator it = m_values.constBegin(); it != end; ++it) {
        QString entry = it.key();
        entry += QLatin1Char('=');
        entry += it.value();
        result.push_back(entry);
    }
    return result;
}

void Environment::set(const QString &key, const QString &value)
{
#ifdef Q_OS_WIN
    QString _key = key.toUpper();
#else
    const QString &_key = key;
#endif
    m_values.insert(_key, value);
}

void Environment::unset(const QString &key)
{
#ifdef Q_OS_WIN
    QString _key = key.toUpper();
#else
    const QString &_key = key;
#endif
    m_values.remove(_key);
}

void Environment::appendOrSet(const QString &key, const QString &value, const QString &sep)
{
#ifdef Q_OS_WIN
    QString _key = key.toUpper();
#else
    const QString &_key = key;
#endif
    QMap<QString, QString>::iterator it = m_values.find(key);
    if (it == m_values.end()) {
        m_values.insert(_key, value);
    } else {
        // Append unless it is already there
        const QString toAppend = sep + value;
        if (!it.value().endsWith(toAppend))
            it.value().append(toAppend);
    }
}

void Environment::prependOrSet(const QString&key, const QString &value, const QString &sep)
{
#ifdef Q_OS_WIN
    QString _key = key.toUpper();
#else
    const QString &_key = key;
#endif
    QMap<QString, QString>::iterator it = m_values.find(key);
    if (it == m_values.end()) {
        m_values.insert(_key, value);
    } else {
        // Prepend unless it is already there
        const QString toPrepend = value + sep;
        if (!it.value().startsWith(toPrepend))
            it.value().prepend(toPrepend);
    }
}

void Environment::appendOrSetPath(const QString &value)
{
#ifdef Q_OS_WIN
    const QChar sep = QLatin1Char(';');
#else
    const QChar sep = QLatin1Char(':');
#endif
    appendOrSet(QLatin1String("PATH"), QDir::toNativeSeparators(value), QString(sep));
}

void Environment::prependOrSetPath(const QString &value)
{
#ifdef Q_OS_WIN
    const QChar sep = QLatin1Char(';');
#else
    const QChar sep = QLatin1Char(':');
#endif
    prependOrSet(QLatin1String("PATH"), QDir::toNativeSeparators(value), QString(sep));
}

Environment Environment::systemEnvironment()
{
    return Environment(QProcess::systemEnvironment());
}

void Environment::clear()
{
    m_values.clear();
}

QString Environment::searchInPath(const QString &executable,
                                  const QStringList &additionalDirs) const
{
    QStringList execs;
    execs << executable;
#ifdef Q_OS_WIN
    // Check all the executable extensions on windows:
    QStringList extensions = value(QLatin1String("PATHEXT")).split(QLatin1Char(';'));

    // .exe.bat is legal (and run when starting new.exe), so always go through the complete list once:
    foreach (const QString &ext, extensions)
        execs << executable + ext.toLower();
#endif
    return searchInPath(execs, additionalDirs);
}

QString Environment::searchInPath(const QStringList &executables,
                                  const QStringList &additionalDirs) const
{
    foreach (const QString &executable, executables) {
        QString exec = expandVariables(executable);

        if (exec.isEmpty())
            continue;

        QFileInfo baseFi(exec);
        if (baseFi.isAbsolute() && baseFi.exists())
            return QDir::toNativeSeparators(exec);

        // Check in directories:
        foreach (const QString &dir, additionalDirs) {
            if (dir.isEmpty())
                continue;
            QFileInfo fi(dir + QLatin1Char('/') + exec);
            if (fi.isFile() && fi.isExecutable())
                return fi.absoluteFilePath();
        }

        // Check in path:
        const QChar slash = QLatin1Char('/');
        if (exec.indexOf(slash) != -1)
            continue;
        foreach (const QString &p, path()) {
            QString fp = p;
            fp += slash;
            fp += exec;
            const QFileInfo fi(fp);
            if (fi.exists() && fi.isExecutable() && !fi.isDir())
                return fi.absoluteFilePath();
        }
    }
    return QString();
}

QStringList Environment::path() const
{
#ifdef Q_OS_WIN
    const QChar sep = QLatin1Char(';');
#else
    const QChar sep = QLatin1Char(':');
#endif
    return m_values.value(QLatin1String("PATH")).split(sep, QString::SkipEmptyParts);
}

QString Environment::value(const QString &key) const
{
    return m_values.value(key);
}

QString Environment::key(Environment::const_iterator it) const
{
    return it.key();
}

QString Environment::value(Environment::const_iterator it) const
{
    return it.value();
}

Environment::const_iterator Environment::constBegin() const
{
    return m_values.constBegin();
}

Environment::const_iterator Environment::constEnd() const
{
    return m_values.constEnd();
}

Environment::const_iterator Environment::constFind(const QString &name) const
{
    QMap<QString, QString>::const_iterator it = m_values.constFind(name);
    if (it == m_values.constEnd())
        return constEnd();
    else
        return it;
}

int Environment::size() const
{
    return m_values.size();
}

void Environment::modify(const QList<EnvironmentItem> & list)
{
    Environment resultEnvironment = *this;
    foreach (const EnvironmentItem &item, list) {
        if (item.unset) {
            resultEnvironment.unset(item.name);
        } else {
            // TODO use variable expansion
            QString value = item.value;
            for (int i=0; i < value.size(); ++i) {
                if (value.at(i) == QLatin1Char('$')) {
                    if ((i + 1) < value.size()) {
                        const QChar &c = value.at(i+1);
                        int end = -1;
                        if (c == '(')
                            end = value.indexOf(')', i);
                        else if (c == '{')
                            end = value.indexOf('}', i);
                        if (end != -1) {
                            const QString &name = value.mid(i+2, end-i-2);
                            Environment::const_iterator it = constFind(name);
                            if (it != constEnd())
                                value.replace(i, end-i+1, it.value());
                        }
                    }
                }
            }
            resultEnvironment.set(item.name, value);
        }
    }
    *this = resultEnvironment;
}

QList<EnvironmentItem> Environment::diff(const Environment &other) const
{
    QMap<QString, QString>::const_iterator thisIt = constBegin();
    QMap<QString, QString>::const_iterator otherIt = other.constBegin();

    QList<EnvironmentItem> result;
    while (thisIt != constEnd() || otherIt != other.constEnd()) {
        if (thisIt == constEnd()) {
            result.append(Utils::EnvironmentItem(otherIt.key(), otherIt.value()));
            ++otherIt;
        } else if (otherIt == constEnd()) {
            Utils::EnvironmentItem item(thisIt.key(), QString());
            item.unset = true;
            result.append(item);
            ++thisIt;
        } else if (thisIt.key() < otherIt.key()) {
            Utils::EnvironmentItem item(thisIt.key(), QString());
            item.unset = true;
            result.append(item);
            ++thisIt;
        } else if (thisIt.key() > otherIt.key()) {
            result.append(Utils::EnvironmentItem(otherIt.key(), otherIt.value()));
            ++otherIt;
        } else {
            result.append(Utils::EnvironmentItem(otherIt.key(), otherIt.value()));
            ++otherIt;
            ++thisIt;
        }
    }
    return result;
}

bool Environment::hasKey(const QString &key)
{
    return m_values.contains(key);
}

bool Environment::operator!=(const Environment &other) const
{
    return !(*this == other);
}

bool Environment::operator==(const Environment &other) const
{
    return m_values == other.m_values;
}

/** Expand environment variables in a string.
 *
 * Environment variables are accepted in the following forms:
 * $SOMEVAR, ${SOMEVAR} on Unix and %SOMEVAR% on Windows.
 * No escapes and quoting are supported.
 * If a variable is not found, it is not substituted.
 */
QString Environment::expandVariables(const QString &input) const
{
    QString result = input;

#ifdef Q_OS_WIN
    for (int vStart = -1, i = 0; i < result.length(); ) {
        if (result.at(i++) == QLatin1Char('%')) {
            if (vStart > 0) {
                const_iterator it = m_values.constFind(result.mid(vStart, i - vStart - 1).toUpper());
                if (it != m_values.constEnd()) {
                    result.replace(vStart - 1, i - vStart + 1, *it);
                    i = vStart - 1 + it->length();
                    vStart = -1;
                } else {
                    vStart = i;
                }
            } else {
                vStart = i;
            }
        }
    }
#else
    enum { BASE, OPTIONALVARIABLEBRACE, VARIABLE, BRACEDVARIABLE } state = BASE;
    int vStart = -1;

    for (int i = 0; i < result.length();) {
        QChar c = result.at(i++);
        if (state == BASE) {
            if (c == QLatin1Char('$'))
                state = OPTIONALVARIABLEBRACE;
        } else if (state == OPTIONALVARIABLEBRACE) {
            if (c == QLatin1Char('{')) {
                state = BRACEDVARIABLE;
                vStart = i;
            } else if (c.isLetterOrNumber() || c == QLatin1Char('_')) {
                state = VARIABLE;
                vStart = i - 1;
            } else {
                state = BASE;
            }
        } else if (state == BRACEDVARIABLE) {
            if (c == QLatin1Char('}')) {
                const_iterator it = m_values.constFind(result.mid(vStart, i - 1 - vStart));
                if (it != constEnd()) {
                    result.replace(vStart - 2, i - vStart + 2, *it);
                    i = vStart - 2 + it->length();
                }
                state = BASE;
            }
        } else if (state == VARIABLE) {
            if (!c.isLetterOrNumber() && c != QLatin1Char('_')) {
                const_iterator it = m_values.constFind(result.mid(vStart, i - vStart - 1));
                if (it != constEnd()) {
                    result.replace(vStart - 1, i - vStart, *it);
                    i = vStart - 1 + it->length();
                }
                state = BASE;
            }
        }
    }
    if (state == VARIABLE) {
        const_iterator it = m_values.constFind(result.mid(vStart));
        if (it != constEnd())
            result.replace(vStart - 1, result.length() - vStart + 1, *it);
    }
#endif
    return result;
}

QStringList Environment::expandVariables(const QStringList &variables) const
{
    QStringList results;
    foreach (const QString & i, variables)
        results << expandVariables(i);
    return results;
}
<commit_msg>Use non-native separators internally<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 qt-info@nokia.com.
**
**************************************************************************/

#include "environment.h"

#include <QtCore/QProcess>
#include <QtCore/QDir>
#include <QtCore/QString>

using namespace Utils;

QList<EnvironmentItem> EnvironmentItem::fromStringList(QStringList list)
{
    QList<EnvironmentItem> result;
    foreach (const QString &string, list) {
        int pos = string.indexOf(QLatin1Char('='));
        if (pos == -1) {
            EnvironmentItem item(string, QString());
            item.unset = true;
            result.append(item);
        } else {
            EnvironmentItem item(string.left(pos), string.mid(pos+1));
            result.append(item);
        }
    }
    return result;
}

QStringList EnvironmentItem::toStringList(QList<EnvironmentItem> list)
{
    QStringList result;
    foreach (const EnvironmentItem &item, list) {
        if (item.unset)
            result << QString(item.name);
        else
            result << QString(item.name + '=' + item.value);
    }
    return result;
}

Environment::Environment()
{
}

Environment::Environment(QStringList env)
{
    foreach (const QString &s, env) {
        int i = s.indexOf("=");
        if (i >= 0) {
#ifdef Q_OS_WIN
            m_values.insert(s.left(i).toUpper(), s.mid(i+1));
#else
            m_values.insert(s.left(i), s.mid(i+1));
#endif
        }
    }
}

QStringList Environment::toStringList() const
{
    QStringList result;
    const QMap<QString, QString>::const_iterator end = m_values.constEnd();
    for (QMap<QString, QString>::const_iterator it = m_values.constBegin(); it != end; ++it) {
        QString entry = it.key();
        entry += QLatin1Char('=');
        entry += it.value();
        result.push_back(entry);
    }
    return result;
}

void Environment::set(const QString &key, const QString &value)
{
#ifdef Q_OS_WIN
    QString _key = key.toUpper();
#else
    const QString &_key = key;
#endif
    m_values.insert(_key, value);
}

void Environment::unset(const QString &key)
{
#ifdef Q_OS_WIN
    QString _key = key.toUpper();
#else
    const QString &_key = key;
#endif
    m_values.remove(_key);
}

void Environment::appendOrSet(const QString &key, const QString &value, const QString &sep)
{
#ifdef Q_OS_WIN
    QString _key = key.toUpper();
#else
    const QString &_key = key;
#endif
    QMap<QString, QString>::iterator it = m_values.find(key);
    if (it == m_values.end()) {
        m_values.insert(_key, value);
    } else {
        // Append unless it is already there
        const QString toAppend = sep + value;
        if (!it.value().endsWith(toAppend))
            it.value().append(toAppend);
    }
}

void Environment::prependOrSet(const QString&key, const QString &value, const QString &sep)
{
#ifdef Q_OS_WIN
    QString _key = key.toUpper();
#else
    const QString &_key = key;
#endif
    QMap<QString, QString>::iterator it = m_values.find(key);
    if (it == m_values.end()) {
        m_values.insert(_key, value);
    } else {
        // Prepend unless it is already there
        const QString toPrepend = value + sep;
        if (!it.value().startsWith(toPrepend))
            it.value().prepend(toPrepend);
    }
}

void Environment::appendOrSetPath(const QString &value)
{
#ifdef Q_OS_WIN
    const QChar sep = QLatin1Char(';');
#else
    const QChar sep = QLatin1Char(':');
#endif
    appendOrSet(QLatin1String("PATH"), QDir::toNativeSeparators(value), QString(sep));
}

void Environment::prependOrSetPath(const QString &value)
{
#ifdef Q_OS_WIN
    const QChar sep = QLatin1Char(';');
#else
    const QChar sep = QLatin1Char(':');
#endif
    prependOrSet(QLatin1String("PATH"), QDir::toNativeSeparators(value), QString(sep));
}

Environment Environment::systemEnvironment()
{
    return Environment(QProcess::systemEnvironment());
}

void Environment::clear()
{
    m_values.clear();
}

QString Environment::searchInPath(const QString &executable,
                                  const QStringList &additionalDirs) const
{
    QStringList execs;
    execs << executable;
#ifdef Q_OS_WIN
    // Check all the executable extensions on windows:
    QStringList extensions = value(QLatin1String("PATHEXT")).split(QLatin1Char(';'));

    // .exe.bat is legal (and run when starting new.exe), so always go through the complete list once:
    foreach (const QString &ext, extensions)
        execs << executable + ext.toLower();
#endif
    return searchInPath(execs, additionalDirs);
}

QString Environment::searchInPath(const QStringList &executables,
                                  const QStringList &additionalDirs) const
{
    foreach (const QString &executable, executables) {
        QString exec = expandVariables(executable);

        if (exec.isEmpty())
            continue;

        QFileInfo baseFi(exec);
        if (baseFi.isAbsolute() && baseFi.exists())
            return QDir::fromNativeSeparators(exec);

        // Check in directories:
        foreach (const QString &dir, additionalDirs) {
            if (dir.isEmpty())
                continue;
            QFileInfo fi(dir + QLatin1Char('/') + exec);
            if (fi.isFile() && fi.isExecutable())
                return fi.absoluteFilePath();
        }

        // Check in path:
        const QChar slash = QLatin1Char('/');
        if (exec.indexOf(slash) != -1)
            continue;
        foreach (const QString &p, path()) {
            QString fp = p;
            fp += slash;
            fp += exec;
            const QFileInfo fi(fp);
            if (fi.exists() && fi.isExecutable() && !fi.isDir())
                return fi.absoluteFilePath();
        }
    }
    return QString();
}

QStringList Environment::path() const
{
#ifdef Q_OS_WIN
    const QChar sep = QLatin1Char(';');
#else
    const QChar sep = QLatin1Char(':');
#endif
    return m_values.value(QLatin1String("PATH")).split(sep, QString::SkipEmptyParts);
}

QString Environment::value(const QString &key) const
{
    return m_values.value(key);
}

QString Environment::key(Environment::const_iterator it) const
{
    return it.key();
}

QString Environment::value(Environment::const_iterator it) const
{
    return it.value();
}

Environment::const_iterator Environment::constBegin() const
{
    return m_values.constBegin();
}

Environment::const_iterator Environment::constEnd() const
{
    return m_values.constEnd();
}

Environment::const_iterator Environment::constFind(const QString &name) const
{
    QMap<QString, QString>::const_iterator it = m_values.constFind(name);
    if (it == m_values.constEnd())
        return constEnd();
    else
        return it;
}

int Environment::size() const
{
    return m_values.size();
}

void Environment::modify(const QList<EnvironmentItem> & list)
{
    Environment resultEnvironment = *this;
    foreach (const EnvironmentItem &item, list) {
        if (item.unset) {
            resultEnvironment.unset(item.name);
        } else {
            // TODO use variable expansion
            QString value = item.value;
            for (int i=0; i < value.size(); ++i) {
                if (value.at(i) == QLatin1Char('$')) {
                    if ((i + 1) < value.size()) {
                        const QChar &c = value.at(i+1);
                        int end = -1;
                        if (c == '(')
                            end = value.indexOf(')', i);
                        else if (c == '{')
                            end = value.indexOf('}', i);
                        if (end != -1) {
                            const QString &name = value.mid(i+2, end-i-2);
                            Environment::const_iterator it = constFind(name);
                            if (it != constEnd())
                                value.replace(i, end-i+1, it.value());
                        }
                    }
                }
            }
            resultEnvironment.set(item.name, value);
        }
    }
    *this = resultEnvironment;
}

QList<EnvironmentItem> Environment::diff(const Environment &other) const
{
    QMap<QString, QString>::const_iterator thisIt = constBegin();
    QMap<QString, QString>::const_iterator otherIt = other.constBegin();

    QList<EnvironmentItem> result;
    while (thisIt != constEnd() || otherIt != other.constEnd()) {
        if (thisIt == constEnd()) {
            result.append(Utils::EnvironmentItem(otherIt.key(), otherIt.value()));
            ++otherIt;
        } else if (otherIt == constEnd()) {
            Utils::EnvironmentItem item(thisIt.key(), QString());
            item.unset = true;
            result.append(item);
            ++thisIt;
        } else if (thisIt.key() < otherIt.key()) {
            Utils::EnvironmentItem item(thisIt.key(), QString());
            item.unset = true;
            result.append(item);
            ++thisIt;
        } else if (thisIt.key() > otherIt.key()) {
            result.append(Utils::EnvironmentItem(otherIt.key(), otherIt.value()));
            ++otherIt;
        } else {
            result.append(Utils::EnvironmentItem(otherIt.key(), otherIt.value()));
            ++otherIt;
            ++thisIt;
        }
    }
    return result;
}

bool Environment::hasKey(const QString &key)
{
    return m_values.contains(key);
}

bool Environment::operator!=(const Environment &other) const
{
    return !(*this == other);
}

bool Environment::operator==(const Environment &other) const
{
    return m_values == other.m_values;
}

/** Expand environment variables in a string.
 *
 * Environment variables are accepted in the following forms:
 * $SOMEVAR, ${SOMEVAR} on Unix and %SOMEVAR% on Windows.
 * No escapes and quoting are supported.
 * If a variable is not found, it is not substituted.
 */
QString Environment::expandVariables(const QString &input) const
{
    QString result = input;

#ifdef Q_OS_WIN
    for (int vStart = -1, i = 0; i < result.length(); ) {
        if (result.at(i++) == QLatin1Char('%')) {
            if (vStart > 0) {
                const_iterator it = m_values.constFind(result.mid(vStart, i - vStart - 1).toUpper());
                if (it != m_values.constEnd()) {
                    result.replace(vStart - 1, i - vStart + 1, *it);
                    i = vStart - 1 + it->length();
                    vStart = -1;
                } else {
                    vStart = i;
                }
            } else {
                vStart = i;
            }
        }
    }
#else
    enum { BASE, OPTIONALVARIABLEBRACE, VARIABLE, BRACEDVARIABLE } state = BASE;
    int vStart = -1;

    for (int i = 0; i < result.length();) {
        QChar c = result.at(i++);
        if (state == BASE) {
            if (c == QLatin1Char('$'))
                state = OPTIONALVARIABLEBRACE;
        } else if (state == OPTIONALVARIABLEBRACE) {
            if (c == QLatin1Char('{')) {
                state = BRACEDVARIABLE;
                vStart = i;
            } else if (c.isLetterOrNumber() || c == QLatin1Char('_')) {
                state = VARIABLE;
                vStart = i - 1;
            } else {
                state = BASE;
            }
        } else if (state == BRACEDVARIABLE) {
            if (c == QLatin1Char('}')) {
                const_iterator it = m_values.constFind(result.mid(vStart, i - 1 - vStart));
                if (it != constEnd()) {
                    result.replace(vStart - 2, i - vStart + 2, *it);
                    i = vStart - 2 + it->length();
                }
                state = BASE;
            }
        } else if (state == VARIABLE) {
            if (!c.isLetterOrNumber() && c != QLatin1Char('_')) {
                const_iterator it = m_values.constFind(result.mid(vStart, i - vStart - 1));
                if (it != constEnd()) {
                    result.replace(vStart - 1, i - vStart, *it);
                    i = vStart - 1 + it->length();
                }
                state = BASE;
            }
        }
    }
    if (state == VARIABLE) {
        const_iterator it = m_values.constFind(result.mid(vStart));
        if (it != constEnd())
            result.replace(vStart - 1, result.length() - vStart + 1, *it);
    }
#endif
    return result;
}

QStringList Environment::expandVariables(const QStringList &variables) const
{
    QStringList results;
    foreach (const QString & i, variables)
        results << expandVariables(i);
    return results;
}
<|endoftext|>


<commit_before>#include "util.hh"
#include "local-store.hh"
#include "globals.hh"

#include <cstdlib>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>

namespace nix {

static void makeWritable(const Path & path)
{
    struct stat st;
    if (lstat(path.c_str(), &st))
        throw SysError(format("getting attributes of path ‘%1%’") % path);
    if (chmod(path.c_str(), st.st_mode | S_IWUSR) == -1)
        throw SysError(format("changing writability of ‘%1%’") % path);
}

struct MakeReadOnly
{
    Path path;
    MakeReadOnly(const Path & path) : path(path) { }
    ~MakeReadOnly()
    {
        try {
            /* This will make the path read-only. */
            if (path != "") canonicaliseTimestampAndPermissions(path);
        } catch (...) {
            ignoreException();
        }
    }
};

LocalStore::InodeHash LocalStore::loadInodeHash()
{
    debug("loading hash inodes in memory");
    InodeHash inodeHash;

    AutoCloseDir dir(opendir(linksDir.c_str()));
    if (!dir) throw SysError(format("opening directory ‘%1%’") % linksDir);

    struct dirent * dirent;
    while (errno = 0, dirent = readdir(dir.get())) { /* sic */
        checkInterrupt();
        // We don't care if we hit non-hash files, anything goes
        inodeHash.insert(dirent->d_ino);
    }
    if (errno) throw SysError(format("reading directory ‘%1%’") % linksDir);

    printMsg(lvlTalkative, format("loaded %1% hash inodes") % inodeHash.size());

    return inodeHash;
}

Strings LocalStore::readDirectoryIgnoringInodes(const Path & path, const InodeHash & inodeHash)
{
    Strings names;

    AutoCloseDir dir(opendir(path.c_str()));
    if (!dir) throw SysError(format("opening directory ‘%1%’") % path);

    struct dirent * dirent;
    while (errno = 0, dirent = readdir(dir.get())) { /* sic */
        checkInterrupt();

        if (inodeHash.count(dirent->d_ino)) {
            debug(format("‘%1%’ is already linked") % dirent->d_name);
            continue;
        }

        string name = dirent->d_name;
        if (name == "." || name == "..") continue;
        names.push_back(name);
    }
    if (errno) throw SysError(format("reading directory ‘%1%’") % path);

    return names;
}

void LocalStore::optimisePath_(OptimiseStats & stats, const Path & path, InodeHash & inodeHash)
{
    checkInterrupt();

    struct stat st;
    if (lstat(path.c_str(), &st))
        throw SysError(format("getting attributes of path ‘%1%’") % path);

    if (S_ISDIR(st.st_mode)) {
        Strings names = readDirectoryIgnoringInodes(path, inodeHash);
        for (auto & i : names)
            optimisePath_(stats, path + "/" + i, inodeHash);
        return;
    }

    /* We can hard link regular files and maybe symlinks. */
    if (!S_ISREG(st.st_mode)
#if CAN_LINK_SYMLINK
        && !S_ISLNK(st.st_mode)
#endif
        ) return;

    /* Sometimes SNAFUs can cause files in the Nix store to be
       modified, in particular when running programs as root under
       NixOS (example: $fontconfig/var/cache being modified).  Skip
       those files.  FIXME: check the modification time. */
    if (S_ISREG(st.st_mode) && (st.st_mode & S_IWUSR)) {
        printError(format("skipping suspicious writable file ‘%1%’") % path);
        return;
    }

    /* This can still happen on top-level files. */
    if (st.st_nlink > 1 && inodeHash.count(st.st_ino)) {
        debug(format("‘%1%’ is already linked, with %2% other file(s)") % path % (st.st_nlink - 2));
        return;
    }

    /* Hash the file.  Note that hashPath() returns the hash over the
       NAR serialisation, which includes the execute bit on the file.
       Thus, executable and non-executable files with the same
       contents *won't* be linked (which is good because otherwise the
       permissions would be screwed up).

       Also note that if `path' is a symlink, then we're hashing the
       contents of the symlink (i.e. the result of readlink()), not
       the contents of the target (which may not even exist). */
    Hash hash = hashPath(htSHA256, path).first;
    debug(format("‘%1%’ has hash ‘%2%’") % path % printHash(hash));

    /* Check if this is a known hash. */
    Path linkPath = linksDir + "/" + printHash32(hash);

 retry:
    if (!pathExists(linkPath)) {
        /* Nope, create a hard link in the links directory. */
        if (link(path.c_str(), linkPath.c_str()) == 0) {
            inodeHash.insert(st.st_ino);
            return;
        }

        switch (errno) {
        case EEXIST:
            /* Fall through if another process created ‘linkPath’ before
               we did. */
            break;

        case ENOSPC:
            /* On ext4, that probably means the directory index is
               full.  When that happens, it's fine to ignore it: we
               just effectively disable deduplication of this
               file.  */
            printInfo("cannot link ‘%s’ to ‘%s’: %s", linkPath, path, strerror(errno));
            return;

        default:
            throw SysError("cannot link ‘%1%’ to ‘%2%’", linkPath, path);
        }
    }

    /* Yes!  We've seen a file with the same contents.  Replace the
       current file with a hard link to that file. */
    struct stat stLink;
    if (lstat(linkPath.c_str(), &stLink))
        throw SysError(format("getting attributes of path ‘%1%’") % linkPath);

    if (st.st_ino == stLink.st_ino) {
        debug(format("‘%1%’ is already linked to ‘%2%’") % path % linkPath);
        return;
    }

    if (st.st_size != stLink.st_size) {
        printError(format("removing corrupted link ‘%1%’") % linkPath);
        unlink(linkPath.c_str());
        goto retry;
    }

    printMsg(lvlTalkative, format("linking ‘%1%’ to ‘%2%’") % path % linkPath);

    /* Make the containing directory writable, but only if it's not
       the store itself (we don't want or need to mess with its
       permissions). */
    bool mustToggle = dirOf(path) != realStoreDir;
    if (mustToggle) makeWritable(dirOf(path));

    /* When we're done, make the directory read-only again and reset
       its timestamp back to 0. */
    MakeReadOnly makeReadOnly(mustToggle ? dirOf(path) : "");

    Path tempLink = (format("%1%/.tmp-link-%2%-%3%")
        % realStoreDir % getpid() % rand()).str();

    if (link(linkPath.c_str(), tempLink.c_str()) == -1) {
        if (errno == EMLINK) {
            /* Too many links to the same file (>= 32000 on most file
               systems).  This is likely to happen with empty files.
               Just shrug and ignore. */
            if (st.st_size)
                printInfo(format("‘%1%’ has maximum number of links") % linkPath);
            return;
        }
        throw SysError("cannot link ‘%1%’ to ‘%2%’", tempLink, linkPath);
    }

    /* Atomically replace the old file with the new hard link. */
    if (rename(tempLink.c_str(), path.c_str()) == -1) {
        if (unlink(tempLink.c_str()) == -1)
            printError(format("unable to unlink ‘%1%’") % tempLink);
        if (errno == EMLINK) {
            /* Some filesystems generate too many links on the rename,
               rather than on the original link.  (Probably it
               temporarily increases the st_nlink field before
               decreasing it again.) */
            if (st.st_size)
                printInfo(format("‘%1%’ has maximum number of links") % linkPath);
            return;
        }
        throw SysError(format("cannot rename ‘%1%’ to ‘%2%’") % tempLink % path);
    }

    stats.filesLinked++;
    stats.bytesFreed += st.st_size;
    stats.blocksFreed += st.st_blocks;
}

void LocalStore::optimiseStore(OptimiseStats & stats)
{
    PathSet paths = queryAllValidPaths();
    InodeHash inodeHash = loadInodeHash();

    for (auto & i : paths) {
        addTempRoot(i);
        if (!isValidPath(i)) continue; /* path was GC'ed, probably */
        Activity act(*logger, lvlChatty, format("hashing files in ‘%1%’") % i);
        optimisePath_(stats, realStoreDir + "/" + baseNameOf(i), inodeHash);
    }
}

static string showBytes(unsigned long long bytes)
{
    return (format("%.2f MiB") % (bytes / (1024.0 * 1024.0))).str();
}

void LocalStore::optimiseStore()
{
    OptimiseStats stats;

    optimiseStore(stats);

    printError(
        format("%1% freed by hard-linking %2% files")
        % showBytes(stats.bytesFreed)
        % stats.filesLinked);
}

void LocalStore::optimisePath(const Path & path)
{
    OptimiseStats stats;
    InodeHash inodeHash;

    if (settings.autoOptimiseStore) optimisePath_(stats, path, inodeHash);
}

}
<commit_msg>Reduce severity of EMLINK warnings<commit_after>#include "util.hh"
#include "local-store.hh"
#include "globals.hh"

#include <cstdlib>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>

namespace nix {

static void makeWritable(const Path & path)
{
    struct stat st;
    if (lstat(path.c_str(), &st))
        throw SysError(format("getting attributes of path ‘%1%’") % path);
    if (chmod(path.c_str(), st.st_mode | S_IWUSR) == -1)
        throw SysError(format("changing writability of ‘%1%’") % path);
}

struct MakeReadOnly
{
    Path path;
    MakeReadOnly(const Path & path) : path(path) { }
    ~MakeReadOnly()
    {
        try {
            /* This will make the path read-only. */
            if (path != "") canonicaliseTimestampAndPermissions(path);
        } catch (...) {
            ignoreException();
        }
    }
};

LocalStore::InodeHash LocalStore::loadInodeHash()
{
    debug("loading hash inodes in memory");
    InodeHash inodeHash;

    AutoCloseDir dir(opendir(linksDir.c_str()));
    if (!dir) throw SysError(format("opening directory ‘%1%’") % linksDir);

    struct dirent * dirent;
    while (errno = 0, dirent = readdir(dir.get())) { /* sic */
        checkInterrupt();
        // We don't care if we hit non-hash files, anything goes
        inodeHash.insert(dirent->d_ino);
    }
    if (errno) throw SysError(format("reading directory ‘%1%’") % linksDir);

    printMsg(lvlTalkative, format("loaded %1% hash inodes") % inodeHash.size());

    return inodeHash;
}

Strings LocalStore::readDirectoryIgnoringInodes(const Path & path, const InodeHash & inodeHash)
{
    Strings names;

    AutoCloseDir dir(opendir(path.c_str()));
    if (!dir) throw SysError(format("opening directory ‘%1%’") % path);

    struct dirent * dirent;
    while (errno = 0, dirent = readdir(dir.get())) { /* sic */
        checkInterrupt();

        if (inodeHash.count(dirent->d_ino)) {
            debug(format("‘%1%’ is already linked") % dirent->d_name);
            continue;
        }

        string name = dirent->d_name;
        if (name == "." || name == "..") continue;
        names.push_back(name);
    }
    if (errno) throw SysError(format("reading directory ‘%1%’") % path);

    return names;
}

void LocalStore::optimisePath_(OptimiseStats & stats, const Path & path, InodeHash & inodeHash)
{
    checkInterrupt();

    struct stat st;
    if (lstat(path.c_str(), &st))
        throw SysError(format("getting attributes of path ‘%1%’") % path);

    if (S_ISDIR(st.st_mode)) {
        Strings names = readDirectoryIgnoringInodes(path, inodeHash);
        for (auto & i : names)
            optimisePath_(stats, path + "/" + i, inodeHash);
        return;
    }

    /* We can hard link regular files and maybe symlinks. */
    if (!S_ISREG(st.st_mode)
#if CAN_LINK_SYMLINK
        && !S_ISLNK(st.st_mode)
#endif
        ) return;

    /* Sometimes SNAFUs can cause files in the Nix store to be
       modified, in particular when running programs as root under
       NixOS (example: $fontconfig/var/cache being modified).  Skip
       those files.  FIXME: check the modification time. */
    if (S_ISREG(st.st_mode) && (st.st_mode & S_IWUSR)) {
        printError(format("skipping suspicious writable file ‘%1%’") % path);
        return;
    }

    /* This can still happen on top-level files. */
    if (st.st_nlink > 1 && inodeHash.count(st.st_ino)) {
        debug(format("‘%1%’ is already linked, with %2% other file(s)") % path % (st.st_nlink - 2));
        return;
    }

    /* Hash the file.  Note that hashPath() returns the hash over the
       NAR serialisation, which includes the execute bit on the file.
       Thus, executable and non-executable files with the same
       contents *won't* be linked (which is good because otherwise the
       permissions would be screwed up).

       Also note that if `path' is a symlink, then we're hashing the
       contents of the symlink (i.e. the result of readlink()), not
       the contents of the target (which may not even exist). */
    Hash hash = hashPath(htSHA256, path).first;
    debug(format("‘%1%’ has hash ‘%2%’") % path % printHash(hash));

    /* Check if this is a known hash. */
    Path linkPath = linksDir + "/" + printHash32(hash);

 retry:
    if (!pathExists(linkPath)) {
        /* Nope, create a hard link in the links directory. */
        if (link(path.c_str(), linkPath.c_str()) == 0) {
            inodeHash.insert(st.st_ino);
            return;
        }

        switch (errno) {
        case EEXIST:
            /* Fall through if another process created ‘linkPath’ before
               we did. */
            break;

        case ENOSPC:
            /* On ext4, that probably means the directory index is
               full.  When that happens, it's fine to ignore it: we
               just effectively disable deduplication of this
               file.  */
            printInfo("cannot link ‘%s’ to ‘%s’: %s", linkPath, path, strerror(errno));
            return;

        default:
            throw SysError("cannot link ‘%1%’ to ‘%2%’", linkPath, path);
        }
    }

    /* Yes!  We've seen a file with the same contents.  Replace the
       current file with a hard link to that file. */
    struct stat stLink;
    if (lstat(linkPath.c_str(), &stLink))
        throw SysError(format("getting attributes of path ‘%1%’") % linkPath);

    if (st.st_ino == stLink.st_ino) {
        debug(format("‘%1%’ is already linked to ‘%2%’") % path % linkPath);
        return;
    }

    if (st.st_size != stLink.st_size) {
        printError(format("removing corrupted link ‘%1%’") % linkPath);
        unlink(linkPath.c_str());
        goto retry;
    }

    printMsg(lvlTalkative, format("linking ‘%1%’ to ‘%2%’") % path % linkPath);

    /* Make the containing directory writable, but only if it's not
       the store itself (we don't want or need to mess with its
       permissions). */
    bool mustToggle = dirOf(path) != realStoreDir;
    if (mustToggle) makeWritable(dirOf(path));

    /* When we're done, make the directory read-only again and reset
       its timestamp back to 0. */
    MakeReadOnly makeReadOnly(mustToggle ? dirOf(path) : "");

    Path tempLink = (format("%1%/.tmp-link-%2%-%3%")
        % realStoreDir % getpid() % rand()).str();

    if (link(linkPath.c_str(), tempLink.c_str()) == -1) {
        if (errno == EMLINK) {
            /* Too many links to the same file (>= 32000 on most file
               systems).  This is likely to happen with empty files.
               Just shrug and ignore. */
            if (st.st_size)
                printInfo(format("‘%1%’ has maximum number of links") % linkPath);
            return;
        }
        throw SysError("cannot link ‘%1%’ to ‘%2%’", tempLink, linkPath);
    }

    /* Atomically replace the old file with the new hard link. */
    if (rename(tempLink.c_str(), path.c_str()) == -1) {
        if (unlink(tempLink.c_str()) == -1)
            printError(format("unable to unlink ‘%1%’") % tempLink);
        if (errno == EMLINK) {
            /* Some filesystems generate too many links on the rename,
               rather than on the original link.  (Probably it
               temporarily increases the st_nlink field before
               decreasing it again.) */
            debug("‘%s’ has reached maximum number of links", linkPath);
            return;
        }
        throw SysError(format("cannot rename ‘%1%’ to ‘%2%’") % tempLink % path);
    }

    stats.filesLinked++;
    stats.bytesFreed += st.st_size;
    stats.blocksFreed += st.st_blocks;
}

void LocalStore::optimiseStore(OptimiseStats & stats)
{
    PathSet paths = queryAllValidPaths();
    InodeHash inodeHash = loadInodeHash();

    for (auto & i : paths) {
        addTempRoot(i);
        if (!isValidPath(i)) continue; /* path was GC'ed, probably */
        Activity act(*logger, lvlChatty, format("hashing files in ‘%1%’") % i);
        optimisePath_(stats, realStoreDir + "/" + baseNameOf(i), inodeHash);
    }
}

static string showBytes(unsigned long long bytes)
{
    return (format("%.2f MiB") % (bytes / (1024.0 * 1024.0))).str();
}

void LocalStore::optimiseStore()
{
    OptimiseStats stats;

    optimiseStore(stats);

    printError(
        format("%1% freed by hard-linking %2% files")
        % showBytes(stats.bytesFreed)
        % stats.filesLinked);
}

void LocalStore::optimisePath(const Path & path)
{
    OptimiseStats stats;
    InodeHash inodeHash;

    if (settings.autoOptimiseStore) optimisePath_(stats, path, inodeHash);
}

}
<|endoftext|>


<commit_before>/****************************************************************************
 *   Copyright (C) 2016 by Savoir-faire Linux                               *
 *   Author : Alexandre Lision <alexandre.lision@savoirfairelinux.com>      *
 *                                                                          *
 *   This library is free software; you can redistribute it and/or          *
 *   modify it under the terms of the GNU Lesser General Public             *
 *   License as published by the Free Software Foundation; either           *
 *   version 2.1 of the License, or (at your option) any later version.     *
 *                                                                          *
 *   This library is distributed in the hope that it will be useful,        *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of         *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU      *
 *   Lesser General Public License for more details.                        *
 *                                                                          *
 *   You should have received a copy of the GNU General Public License      *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.  *
 ***************************************************************************/
#include "localprofilecollection.h"

//Qt
#include <QtCore/QFile>
#include <QtCore/QDir>
#include <QtCore/QHash>
#include <QtCore/QStandardPaths>
#include <QtCore/QUrl>
#include <QtCore/QVector>
#include <QtCore/QDateTime>

//Ring
#include "profile.h"
#include "private/vcardutils.h"
#include "account.h"
#include "accountmodel.h"
#include "person.h"

class LocalProfileEditor final : public CollectionEditor<Profile>
{
public:
   LocalProfileEditor(CollectionMediator<Profile>* m, LocalProfileCollection* parent);
   virtual bool save       ( const Profile* item ) override;
   virtual bool remove     ( const Profile* item ) override;
   virtual bool edit       ( Profile*       item ) override;
   virtual bool addNew     ( Profile*       item ) override;
   virtual bool addExisting( const Profile* item ) override;

private:
    virtual QVector<Profile*> items() const override;

    //Helpers
    QString path(const Profile* p) const;

    //Attributes
    QVector<Profile*> m_lItems;
    LocalProfileCollection* m_pCollection;
};

LocalProfileEditor::LocalProfileEditor(CollectionMediator<Profile>* m, LocalProfileCollection* parent) :
CollectionEditor<Profile>(m),m_pCollection(parent)
{

}

LocalProfileCollection::LocalProfileCollection(CollectionMediator<Profile>* mediator) :
CollectionInterface(new LocalProfileEditor(mediator,this))
{

}

LocalProfileCollection::~LocalProfileCollection()
{

}

bool LocalProfileEditor::save(const Profile* pro)
{
    const auto& filename = path(pro);
    const auto& result = pro->person()->toVCard(pro->accounts().toList());

    qDebug() << "Saving profile in:" << filename;
    QFile file {filename};
    file.open(QIODevice::WriteOnly);
    file.write(result);
    file.close();
    return true;
}

bool LocalProfileEditor::remove(const Profile* item)
{
    if (QFile::remove(path(item))) {
        mediator()->removeItem(item);
        return true;
    }

    return false;
}

bool LocalProfileEditor::edit( Profile* item)
{
   Q_UNUSED(item)
   return false;
}

bool LocalProfileEditor::addNew(Profile* pro)
{
    pro->person()->ensureUid();
    qDebug() << "Creating new profile" << pro->person()->uid();
    m_lItems << pro;
    pro->setCollection(m_pCollection);
    mediator()->addItem(pro);
    save(pro);
    return true;
}

bool LocalProfileEditor::addExisting(const Profile* item)
{
   m_lItems << const_cast<Profile*>(item);
   mediator()->addItem(item);
   return true;
}

QVector<Profile*> LocalProfileEditor::items() const
{
   return m_lItems;
}

QString LocalProfileEditor::path(const Profile* p) const
{
   const QDir profilesDir = (QStandardPaths::writableLocation(QStandardPaths::DataLocation)) + "/profiles/";
   profilesDir.mkpath(profilesDir.path());
   return QString("%1/%2.vcf")
      .arg(profilesDir.absolutePath())
      .arg(QString(p->person()->uid()));
}

QString LocalProfileCollection::name () const
{
   return QObject::tr("Local profiles");
}

QString LocalProfileCollection::category () const
{
   return QObject::tr("Profile Collection");
}

QVariant LocalProfileCollection::icon() const
{
   return QVariant();
}

bool LocalProfileCollection::isEnabled() const
{
   return true;
}

bool LocalProfileCollection::load()
{
    const QDir profilesDir = (QStandardPaths::writableLocation(QStandardPaths::DataLocation)) + "/profiles/";
    qDebug() << "Loading vcf from:" << profilesDir;

    const QStringList entries = profilesDir.entryList({QStringLiteral("*.vcf")}, QDir::Files);
    bool hasProfile = false;

    foreach (const QString& item , entries) {
        hasProfile = true;
        auto personProfile = new Person(nullptr);
        QList<Account*> accs;
        VCardUtils::mapToPerson(personProfile,QUrl(profilesDir.path()+'/'+item),&accs);
        auto profile = new Profile(this, personProfile);
        profile->setAccounts(accs.toVector());
        editor<Profile>()->addExisting(profile);
    }

    if (!hasProfile) {
        //Ring needs at least one global profile
        setupDefaultProfile();
    }
    return true;
}

bool LocalProfileCollection::reload()
{
    return true;
}

FlagPack<CollectionInterface::SupportedFeatures> LocalProfileCollection::supportedFeatures() const
{
   return
      CollectionInterface::SupportedFeatures::NONE       |
      CollectionInterface::SupportedFeatures::LOAD       |
      CollectionInterface::SupportedFeatures::CLEAR      |
      CollectionInterface::SupportedFeatures::REMOVE     |
      CollectionInterface::SupportedFeatures::MANAGEABLE |
      CollectionInterface::SupportedFeatures::ADD        ;
}

bool LocalProfileCollection::clear()
{
   QFile::remove((QStandardPaths::writableLocation(QStandardPaths::DataLocation)) + "/profiles/");
   return true;
}

QByteArray LocalProfileCollection::id() const
{
   return "lpc";
}

void LocalProfileCollection::setupDefaultProfile()
{
   auto profile = new Profile(this, new Person());
   profile->person()->setFormattedName(QObject::tr("Default"));

   for (int i = 0 ; i < AccountModel::instance().size() ; i++) {
       profile->addAccount(AccountModel::instance()[i]);
   }

   editor<Profile>()->addNew(profile);
}
<commit_msg>localprofile: Check the result of `open()`<commit_after>/****************************************************************************
 *   Copyright (C) 2016 by Savoir-faire Linux                               *
 *   Author : Alexandre Lision <alexandre.lision@savoirfairelinux.com>      *
 *                                                                          *
 *   This library is free software; you can redistribute it and/or          *
 *   modify it under the terms of the GNU Lesser General Public             *
 *   License as published by the Free Software Foundation; either           *
 *   version 2.1 of the License, or (at your option) any later version.     *
 *                                                                          *
 *   This library is distributed in the hope that it will be useful,        *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of         *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU      *
 *   Lesser General Public License for more details.                        *
 *                                                                          *
 *   You should have received a copy of the GNU General Public License      *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.  *
 ***************************************************************************/
#include "localprofilecollection.h"

//Qt
#include <QtCore/QFile>
#include <QtCore/QDir>
#include <QtCore/QHash>
#include <QtCore/QStandardPaths>
#include <QtCore/QUrl>
#include <QtCore/QVector>
#include <QtCore/QDateTime>

//Ring
#include "profile.h"
#include "private/vcardutils.h"
#include "account.h"
#include "accountmodel.h"
#include "person.h"

class LocalProfileEditor final : public CollectionEditor<Profile>
{
public:
   LocalProfileEditor(CollectionMediator<Profile>* m, LocalProfileCollection* parent);
   virtual bool save       ( const Profile* item ) override;
   virtual bool remove     ( const Profile* item ) override;
   virtual bool edit       ( Profile*       item ) override;
   virtual bool addNew     ( Profile*       item ) override;
   virtual bool addExisting( const Profile* item ) override;

private:
    virtual QVector<Profile*> items() const override;

    //Helpers
    QString path(const Profile* p) const;

    //Attributes
    QVector<Profile*> m_lItems;
    LocalProfileCollection* m_pCollection;
};

LocalProfileEditor::LocalProfileEditor(CollectionMediator<Profile>* m, LocalProfileCollection* parent) :
CollectionEditor<Profile>(m),m_pCollection(parent)
{

}

LocalProfileCollection::LocalProfileCollection(CollectionMediator<Profile>* mediator) :
CollectionInterface(new LocalProfileEditor(mediator,this))
{

}

LocalProfileCollection::~LocalProfileCollection()
{

}

bool LocalProfileEditor::save(const Profile* pro)
{
    const auto& filename = path(pro);
    const auto& result = pro->person()->toVCard(pro->accounts().toList());

    qDebug() << "Saving profile in:" << filename;
    QFile file {filename};

    if (Q_UNLIKELY(!file.open(QIODevice::WriteOnly))) {
        qWarning() << "Can't write to" << filename;
        return false;
    }

    file.write(result);
    file.close();
    return true;
}

bool LocalProfileEditor::remove(const Profile* item)
{
    if (QFile::remove(path(item))) {
        mediator()->removeItem(item);
        return true;
    }

    return false;
}

bool LocalProfileEditor::edit( Profile* item)
{
   Q_UNUSED(item)
   return false;
}

bool LocalProfileEditor::addNew(Profile* pro)
{
    pro->person()->ensureUid();
    qDebug() << "Creating new profile" << pro->person()->uid();
    m_lItems << pro;
    pro->setCollection(m_pCollection);
    mediator()->addItem(pro);
    save(pro);
    return true;
}

bool LocalProfileEditor::addExisting(const Profile* item)
{
   m_lItems << const_cast<Profile*>(item);
   mediator()->addItem(item);
   return true;
}

QVector<Profile*> LocalProfileEditor::items() const
{
   return m_lItems;
}

QString LocalProfileEditor::path(const Profile* p) const
{
   const QDir profilesDir = (QStandardPaths::writableLocation(QStandardPaths::DataLocation)) + "/profiles/";
   profilesDir.mkpath(profilesDir.path());
   return QString("%1/%2.vcf")
      .arg(profilesDir.absolutePath())
      .arg(QString(p->person()->uid()));
}

QString LocalProfileCollection::name () const
{
   return QObject::tr("Local profiles");
}

QString LocalProfileCollection::category () const
{
   return QObject::tr("Profile Collection");
}

QVariant LocalProfileCollection::icon() const
{
   return QVariant();
}

bool LocalProfileCollection::isEnabled() const
{
   return true;
}

bool LocalProfileCollection::load()
{
    const QDir profilesDir = (QStandardPaths::writableLocation(QStandardPaths::DataLocation)) + "/profiles/";
    qDebug() << "Loading vcf from:" << profilesDir;

    const QStringList entries = profilesDir.entryList({QStringLiteral("*.vcf")}, QDir::Files);
    bool hasProfile = false;

    foreach (const QString& item , entries) {
        hasProfile = true;
        auto personProfile = new Person(nullptr);
        QList<Account*> accs;
        VCardUtils::mapToPerson(personProfile,QUrl(profilesDir.path()+'/'+item),&accs);
        auto profile = new Profile(this, personProfile);
        profile->setAccounts(accs.toVector());
        editor<Profile>()->addExisting(profile);
    }

    if (!hasProfile) {
        //Ring needs at least one global profile
        setupDefaultProfile();
    }
    return true;
}

bool LocalProfileCollection::reload()
{
    return true;
}

FlagPack<CollectionInterface::SupportedFeatures> LocalProfileCollection::supportedFeatures() const
{
   return
      CollectionInterface::SupportedFeatures::NONE       |
      CollectionInterface::SupportedFeatures::LOAD       |
      CollectionInterface::SupportedFeatures::CLEAR      |
      CollectionInterface::SupportedFeatures::REMOVE     |
      CollectionInterface::SupportedFeatures::MANAGEABLE |
      CollectionInterface::SupportedFeatures::ADD        ;
}

bool LocalProfileCollection::clear()
{
   QFile::remove((QStandardPaths::writableLocation(QStandardPaths::DataLocation)) + "/profiles/");
   return true;
}

QByteArray LocalProfileCollection::id() const
{
   return "lpc";
}

void LocalProfileCollection::setupDefaultProfile()
{
   auto profile = new Profile(this, new Person());
   profile->person()->setFormattedName(QObject::tr("Default"));

   for (int i = 0 ; i < AccountModel::instance().size() ; i++) {
       profile->addAccount(AccountModel::instance()[i]);
   }

   editor<Profile>()->addNew(profile);
}
<|endoftext|>