3.4. Ordenando cosas

La necesidad de mantener cierta información ordenada es muy normal en los módulos eBox. También es común ofrecer funciones de reordenación. Un ejemplo claro son las reglas del firewall, las cuales necesitan ser aplicadas en un cierto orden, y el usuario ha de poderles cambiar su orden. La clase EBox::Order resuelve justamente este problema.

La idea es dar un directorio a cada objeto que se quiere mantener ordenado. Siguiendo con el ejemplo del firewall, podríamos tener el directorio rules/ y, por debajo, un directorio por regla. Cada regla tendría un identificador único, y eso sería el nombre de su directorio bajo rules/. Una regla con el id r3561 se almacenaría en rules/r3561, y debajo de ese directorio se almacenarían las claves con cada uno de los parámetros de la regla. Ésta es la forma más natural de organizar elementos como reglas de firewall, y es con una organización de este tipo para lo que está diseñado EBox::Order para trabajar.

El mecanismo de ordenación añade un campo a los objetos para ser ordenados. Lógicamente, se le ha denominado order. Para usar el API de ordenación ha de crear una instancia de EBox::Order. Su constructor toma como argumentos: la instancia del módulo al que pertenecen los objetos que van a ser ordenados y el directorio base donde los objetos serán almacenados.

EBox::Order implementa estas operaciones:

highest

Devuelve la clave order más alta de todos los objetos.

lowest

Devuelve la clave order más baja de todos los elementos.

nextn

Dado un número, devuelve la clave order del siguiente objeto.

prevn

Dado un número, devuelve la clave order del objeto anterior.

get

Devuelve el identificador del objeto cuya clave order es igual a un número dado.

swap

Encuentra los objetos cuyas claves order coinciden con los dos números dados e intercambia sus valores.

list

Devuelve una referencia a un array que contenga los identificadores de todos los objetos, ordenados del más bajo al más alto.

Ejemplo 3.6. Ordenando las reglas del firewall

Vamos a ver como utiliza EBox::Order el módulo firewall para mantener sus reglas de forwarding ordenadas. La función _fwdRulesOrder devuelve la instancia de EBox::Order de las reglas del firewall:

sub _fwdRulesOrder
{
	my $self = shift;
	return new EBox::Order($self, "fwdrules");
}

fwdrules es el directorio que contiene todas las reglas. Otra función auxiliar privada es _fwdRuleNumber, la cual devuelve el número de orden para el identificador de una regla dada:

sub _fwdRuleNumber # (rule)
{
	my ($self, $rule) = @_;
	return $self->get_int("fwdrules/$rule/order");
}

Las nuevas reglas son añadidas al final de la lista, por lo que vamos a buscar el número de orden más alto y le añadiremos una nueva. Este código es parte de la función addFwdRule:

my $order = $self->_lastFwdRule() + 1;

$self->set_string("fwdrules/$id/name", $id);
$self->set_string("fwdrules/$id/action", $action);
$self->set_bool("fwdrules/$id/active", 1);
$self->set_int("fwdrules/$id/order", $order);

_lastFwdRule es una función recubridora trivial que devuelve el número de orden más alto:

sub _lastFwdRule
{
	my $self = shift;
	my $order = $self->_fwdRulesOrder();
	defined($order) or return 0;
	return $order->highest;
}

Finalmente hay dos funciones que admiten la reordenación de las reglas, éstas son FwdRuleUp y FwdRuleDown (mostramos sólo la primera dado que son prácticamente idénticas):

sub FwdRuleUp # (rule)
{
	my ($self, $rule) = @_;
	my $order = $self->_fwdRulesOrder();
	defined($order) or return;
	my $num = $self->_fwdRuleNumber($rule);
	if ($num == 0) {
		return;
	}
	my $prev = $order->prevn($num);
	$order->swap($num, $prev);
}