3.4. Ordering stuff

The need to keep certain information ordered is fairly common across eBox modules. It is also common to provide reordering functions. An example of this are firewall rules, which need to be applied in a given order, and the user has to be able to change their order. The EBox::Order class solves just this problem.

The idea is to give a directory to each item you want to keep ordered. Following the firewall example, we could have a rules/ directory and, below it, one directory per rule. Each rule would get a unique identifier, and that would be its directory name below rules/. A rule with id r3561 would be stored in rules/r3561, and below that directory we would store keys with each one of the rule's parameters. This is the most natural way of organizing items such as firewall rules, and it is with this organization that EBox::Order is designed to work.

The ordering mechanism adds one field to the items being ordered. Not surprisingly it is called order. To use the ordering API you have to create an EBox::Order instance. Its constructor takes to arguments: the instance of the module that owns the items being ordered, and the base directory where the items are stored.

EBox::Order implements these operations:

highest

Returns the highest order key of all the items.

lowest

Returns the lowest order key of all the items.

nextn

Given a number, returns the order key for the next item.

prevn

Given a number, returns the order key for the previous item.

get

Returns the identifier for the item whose order key equals a given number.

swap

It finds the items whose order keys match two given numbers and swaps their values.

list

Returns a reference to an array that holds the identifiers of all items, ordered from lowest to highest order.

Example 3.6. Ordering firewall rules

Let's see how the firewall module uses EBox::Order to keep its forwarding rules ordered. The _fwdRulesOrder returns the EBox::Order instance for the firewall rules:

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

fwdrules is the directory that holds all the rules. Another private helper function is _fwdRuleNumer, it returns the order number for a given rule identifier:

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

New rules are appended at the end of the list, so we find the highest order number and add one to it, this code is part of the addFwdRule method:

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 is a trivial wrapper that returns the highest ordered number:

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

Finally there are two methods to allow rule reordering, they are FwdRuleUp and FwdRuleDown (only the first one is shown here since they are almost identical):

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);
}