X-Git-Url: http://dxcluster.net/gitweb/gitweb.cgi?a=blobdiff_plain;f=perl%2FFilter.pm;h=b71ee95a5b81833acb4a1fc566592b832071529f;hb=ab811a0c902225075a9bd69749f65594079433a9;hp=5947b856f15cff628b458091ae440e81dcb99062;hpb=704d88d7e4a8ff06024b35fc27eb94732b0a8d9f;p=spider.git diff --git a/perl/Filter.pm b/perl/Filter.pm index 5947b856..b71ee95a 100644 --- a/perl/Filter.pm +++ b/perl/Filter.pm @@ -10,7 +10,7 @@ # # Copyright (c) 1999 Dirk Koopman G1TLH # -# $Id$ +# # # The NEW INSTRUCTIONS # @@ -30,6 +30,7 @@ use DXVars; use DXUtil; use DXDebug; use Data::Dumper; +use Prefix; use strict; @@ -51,13 +52,8 @@ sub new return bless {sort => $sort, name => "$flag$call.pl" }, $class; } -# this reads in a filter statement and returns it as a list -# -# The filter is stored in straight perl so that it can be parsed and read -# in with a 'do' statement. The 'do' statement reads the filter into -# @in which is a list of references -# -sub read_in +# standard filename generator +sub getfn { my ($sort, $call, $flag) = @_; @@ -71,144 +67,177 @@ sub read_in $call = lc $call; $fn = "$filterbasefn/$sort/$flag$call.pl"; } + $fn = undef unless -e $fn; + return $fn; +} + +# this reads in a filter statement and returns it as a list +# +# The filter is stored in straight perl so that it can be parsed and read +# in with a 'do' statement. The 'do' statement reads the filter into +# @in which is a list of references +# +sub compile +{ + my $self = shift; + my $fname = shift; + my $ar = shift; + my $ref = $self->{$fname}; + my $rr; + + if ($ref->{$ar} && exists $ref->{$ar}->{asc}) { + my $s = $ref->{$ar}->{asc}; # an optimisation? + $s =~ s/\$r/\$_[0]/g; + $ref->{$ar}->{code} = eval "sub { $s }" ; + if ($@) { + my $sort = $ref->{sort}; + my $name = $ref->{name}; + dbg("Error compiling $ar $sort $name: $@"); + Log('err', "Error compiling $ar $sort $name: $@"); + } + $rr = $@; + } + return $rr; +} + +sub read_in +{ + my ($sort, $call, $flag) = @_; + my $fn; # load it - if (-e $fn) { + if ($fn = getfn($sort, $call, $flag)) { $in = undef; my $s = readfilestr($fn); my $newin = eval $s; - dbg('conn', "$@") if $@; + if ($@) { + dbg($@); + unlink($fn); + return undef; + } if ($in) { $newin = new('Filter::Old', $sort, $call, $flag); $newin->{filter} = $in; + } elsif (ref $newin && $newin->can('getfilkeys')) { + my $filter; + my $key; + foreach $key ($newin->getfilkeys) { + $newin->compile($key, 'reject'); + $newin->compile($key, 'accept'); + } + } else { + # error on reading file, delete and exit + dbg("empty or unreadable filter: $fn, deleted"); + unlink($fn); + return undef; } return $newin; } return undef; } +sub getfilters +{ + my $self = shift; + my @out; + my $key; + foreach $key (grep {/^filter/ } keys %$self) { + push @out, $self->{$key}; + } + return @out; +} + +sub getfilkeys +{ + my $self = shift; + return grep {/^filter/ } keys %$self; +} + # -# this routine accepts a composite filter with a reject component and then an accept -# the filter returns 0 if an entry is matched by any reject rule and also if any +# This routine accepts a composite filter with a reject rule and then an accept rule. +# +# The filter returns 0 if an entry is matched by any reject rule and also if any # accept rule fails otherwise it returns 1 # -# the either set of rules may be missing meaning an implicit 'ok' +# Either set of rules may be missing meaning an implicit 'opposite' ie if it +# a reject then ok else if an accept then not ok. # -# reject rules are implicitly 'or' logic (any reject rules which fires kicks it out) -# accept rules are implicitly 'and' logic (all accept rules must pass to indicate a match) +# you can set a default with either an accept/xxxx all or reject/xxxx all # -# unlike the old system, this is kept as a hash of hashes so that you can +# Unlike the old system, this is kept as a hash of hashes so that you can # easily change them by program. # -# you can have a [any] number of 'filters', they are tried in random order until one matches -# -# an example in machine readable form:- -# bless ({ -# name => 'G7BRN.pl', -# sort => 'spots', -# filter1 => { -# user_rej => { -# by_dxcc => 'W,VE', -# }, -# reject => { -# by_dxcc => [6, 'n', 226,197], -# }, -# user_acc => { -# freq => '0/30000', -# }, -# accept => { -# freq => [0, 'r', 0, 30000], -# }, -# }, -# filter2 => { -# user_acc => { -# freq => 'vhf', -# by_zone => '14,15,16', -# }, -# accept => { -# freq => [0, 'r', 50000,52000,70000,70500,144000,148000], -# by_zone => [11, 'n', 14,15,16], -# } -# }, -# }, 'Filter'); -# -# in user commands:- -# -# clear/spots 1 2 -# accept/spots 1 freq 0/30000 -# reject/spots 1 by_dxcc W,VE -# accept/spots 2 freq vhf -# accept/spots 2 by_zone 14,15,16 +# You can have 10 filter lines (0->9), they are tried in order until +# one matches +# +# There is a parser that takes a Filter::Cmd object which describes all the possible +# things you can filter on and then converts that to a bit of perl which is compiled +# and stored as a function. +# +# The result of this is that in theory you can put together an arbritrarily complex +# expression involving the things you can filter on including 'and' 'or' 'not' and +# 'brackets'. +# +# eg:- +# +# accept/spots hf and by_zone 14,15,16 and not by pa,on +# +# accept/spots freq 0/30000 and by_zone 4,5 +# +# accept/spots 2 vhf and (by_zone 14,15,16 or call_dxcc 61) # # no filter no implies filter 1 # # The field nos are the same as for the 'Old' filters # -# The user_* fields are there so that the structure can be listed easily -# in human readable form when required. They are not used in the filtering -# process itself. -# -# This defines an HF filter and a VHF filter (as it happens) # sub it { my $self = shift; - my $hops = undef; my $filter; - my $r; - - my ($key, $ref, $field, $fieldsort, $comp); - L1: foreach $key (grep {/^filter/ } keys %$self) { - my $filter = $self->{$key}; - $r = 0; - if ($filter->{reject}) { - foreach $ref (values %{$filter->{reject}}) { - ($field, $fieldsort) = @$ref[0,1]; - my $val = $_[$field]; - if ($fieldsort eq 'n') { - next L1 if grep $_ == $val, @{$ref}[2..$#$ref]; - } elsif ($fieldsort eq 'r') { - my $i; - for ($i = 2; $i < @$ref; $i += 2) { - next L1 if $val >= $ref->[$i] && $val <= $ref->[$i+1]; - } - } elsif ($fieldsort eq 'a') { - next L1 if grep $val =~ m{$_}, @$ref[2..$#$ref]; - } - } - } - if ($filter->{accept}) { - foreach $ref (values %{$filter->{accept}}) { - ($field, $fieldsort) = @$ref[0,1]; - my $val = $_[$field]; - if ($fieldsort eq 'n') { - next L1 unless grep $_ == $val, @{$ref}[2..$#$ref]; - } elsif ($fieldsort eq 'r') { - my $i; - for ($i = 2; $i < @$ref; $i += 2) { - next L1 unless $val >= $ref->[$i] && $val <= $ref->[$i+1]; - } - } elsif ($fieldsort eq 'a') { - next L1 unless grep $val =~ m{$_}, @{$ref}[2..$#$ref]; - } - } - } - $r = 1; - last; - } + my @keys = sort $self->getfilkeys; + my $key; + my $type = 'Dunno'; + my $asc = '?'; - # hops are done differently - if ($self->{hops}) { - my $h; - while (($comp, $ref) = each %{$self->{hops}}) { - ($field, $h) = @$ref; - if ($_[$field] =~ m{$comp}) { - $hops = $h; + my $r = @keys > 0 ? 0 : 1; + foreach $key (@keys) { + $filter = $self->{$key}; + if ($filter->{reject} && exists $filter->{reject}->{code}) { + $type = 'reject'; + $asc = $filter->{reject}->{user}; + if (&{$filter->{reject}->{code}}(\@_)) { + $r = 0; last; - } - } + } else { + $r = 1; + } + } + if ($filter->{accept} && exists $filter->{accept}->{code}) { + $type = 'accept'; + $asc = $filter->{accept}->{user}; + if (&{$filter->{accept}->{code}}(\@_)) { + $r = 1; + last; + } else { + $r = 0; + } + } + } + + # hops are done differently (simply) + my $hops = $self->{hops} if exists $self->{hops}; + + if (isdbg('filter')) { + my $args = join '\',\'', map {defined $_ ? $_ : 'undef'} @_; + my $true = $r ? "OK " : "REJ"; + my $sort = $self->{sort}; + my $dir = $self->{name} =~ /^in_/i ? "IN " : "OUT"; + + my $h = $hops || ''; + dbg("$true $dir: $type/$sort with $asc on '$args' $h") if isdbg('filter'); } return ($r, $hops); } @@ -219,10 +248,13 @@ sub write { my $self = shift; my $sort = $self->{sort}; - my $fn = $self->{name}; + my $name = $self->{name}; my $dir = "$filterbasefn/$sort"; + my $fn = "$dir/$name"; + mkdir $dir, 0775 unless -e $dir; - my $fh = new IO::File ">$dir/$fn" or return "$dir/$fn $!"; + rename $fn, "$fn.o" if -e $fn; + my $fh = new IO::File ">$fn"; if ($fh) { my $dd = new Data::Dumper([ $self ]); $dd->Indent(1); @@ -230,6 +262,9 @@ sub write $dd->Quotekeys($] < 5.005 ? 1 : 0); $fh->print($dd->Dumpxs); $fh->close; + } else { + rename "$fn.o", $fn if -e "$fn.o"; + return "$fn $!"; } return undef; } @@ -237,12 +272,312 @@ sub write sub print { my $self = shift; - return $self->{name}; + my $name = shift || $self->{name}; + my $sort = shift || $self->{sort}; + my $flag = shift || ""; + my @out; + $name =~ s/.pl$//; + + push @out, join(' ', $name , ':', $sort, $flag); + my $filter; + my $key; + foreach $key (sort $self->getfilkeys) { + my $filter = $self->{$key}; + if (exists $filter->{reject} && exists $filter->{reject}->{user}) { + push @out, ' ' . join(' ', $key, 'reject', $filter->{reject}->{user}); + } + if (exists $filter->{accept} && exists $filter->{accept}->{user}) { + push @out, ' ' . join(' ', $key, 'accept', $filter->{accept}->{user}); + } + } + return @out; +} + +sub install +{ + my $self = shift; + my $remove = shift; + my $name = uc $self->{name}; + my $sort = $self->{sort}; + my $in = ""; + $in = "in" if $name =~ s/^IN_//; + $name =~ s/.PL$//; + + my $dxchan; + my @dxchan; + if ($name eq 'NODE_DEFAULT') { + @dxchan = DXChannel::get_all_nodes(); + } elsif ($name eq 'USER_DEFAULT') { + @dxchan = DXChannel::get_all_users(); + } else { + $dxchan = DXChannel::get($name); + push @dxchan, $dxchan if $dxchan; + } + foreach $dxchan (@dxchan) { + my $n = "$in$sort" . "filter"; + my $i = $in ? 'IN_' : ''; + my $ref = $dxchan->$n(); + if (!$ref || ($ref && uc $ref->{name} eq "$i$name.PL")) { + $dxchan->$n($remove ? undef : $self); + } + } +} + +sub delete +{ + my ($sort, $call, $flag, $fno) = @_; + + # look for the file + my $fn = getfn($sort, $call, $flag); + my $filter = read_in($sort, $call, $flag); + if ($filter) { + if ($fno eq 'all') { + my $key; + foreach $key ($filter->getfilkeys) { + delete $filter->{$key}; + } + } elsif (exists $filter->{"filter$fno"}) { + delete $filter->{"filter$fno"}; + } + + # get rid + if ($filter->{hops} || $filter->getfilkeys) { + $filter->install; + $filter->write; + } else { + $filter->install(1); + unlink $fn; + } + } +} + +package Filter::Cmd; + +use strict; +use DXVars; +use DXUtil; +use DXDebug; +use vars qw(@ISA); +@ISA = qw(Filter); + +# the general purpose command processor +# this is called as a subroutine not as a method +sub parse +{ + my ($self, $dxchan, $sort, $line) = @_; + my $ntoken = 0; + my $fno = 1; + my $filter; + my ($flag, $call); + my $s; + my $user; + + # check the line for non legal characters + return ('ill', $dxchan->msg('e19')) if $line =~ /[^\s\w,_\-\*\/\(\)!]/; + + # add some spaces for ease of parsing + $line =~ s/([\(\)])/ $1 /g; + $line = lc $line; + + my @f = split /\s+/, $line; + my $conj = ' && '; + my $not = ""; + while (@f) { + if ($ntoken == 0) { + + if (@f && $dxchan->priv >= 8 && ((is_callsign(uc $f[0]) && DXUser::get(uc $f[0])) || $f[0] =~ /(?:node|user)_default/)) { + $call = shift @f; + if ($f[0] eq 'input') { + shift @f; + $flag++; + } + } else { + $call = $dxchan->call; + } + + if (@f && $f[0] =~ /^\d$/) { + $fno = shift @f; + } + + $filter = Filter::read_in($sort, $call, $flag); + $filter = Filter->new($sort, $call, $flag) if !$filter || $filter->isa('Filter::Old'); + + $ntoken++; + next; + } + + # do the rest of the filter tokens + if (@f) { + my $tok = shift @f; + if ($tok eq '(') { + if ($s) { + $s .= $conj; + $user .= $conj; + $conj = ""; + } + if ($not) { + $s .= $not; + $user .= $not; + $not = ""; + } + $s .= $tok; + $user .= $tok; + next; + } elsif ($tok eq ')') { + $conj = ' && '; + $not =""; + $s .= $tok; + $user .= $tok; + next; + } elsif ($tok eq 'all') { + $s .= '1'; + $user .= $tok; + last; + } elsif ($tok eq 'or') { + $conj = ' || ' if $conj ne ' || '; + next; + } elsif ($tok eq 'and') { + $conj = ' && ' if $conj ne ' && '; + next; + } elsif ($tok eq 'not' || $tok eq '!') { + $not = '!'; + next; + } + if (@f) { + my $val = shift @f; + my @val = split /,/, $val; + + if ($s) { + $s .= $conj ; + $user .= $conj; + $conj = ' && '; + } + + if ($not) { + $s .= $not; + $user .= $not; + $not = ''; + } + + $user .= "$tok $val"; + + my $fref; + my $found; + foreach $fref (@$self) { + + if ($fref->[0] eq $tok) { + if ($fref->[4]) { + my @nval; + for (@val) { + push @nval, split(',', &{$fref->[4]}($dxchan, $_)); + } + @val = @nval; + } + if ($fref->[1] eq 'a') { + my @t; + for (@val) { + s/\*//g; + push @t, "\$r->[$fref->[2]]=~/$_/i"; + } + $s .= "(" . join(' || ', @t) . ")"; + } elsif ($fref->[1] eq 'c') { + my @t; + for (@val) { + s/\*//g; + push @t, "\$r->[$fref->[2]]=~/^\U$_/"; + } + $s .= "(" . join(' || ', @t) . ")"; + } elsif ($fref->[1] eq 'n') { + my @t; + for (@val) { + return ('num', $dxchan->msg('e21', $_)) unless /^\d+$/; + push @t, "\$r->[$fref->[2]]==$_"; + } + $s .= "(" . join(' || ', @t) . ")"; + } elsif ($fref->[1] =~ /^n[ciz]$/ ) { # for DXCC, ITU, CQ Zone + my $cmd = $fref->[1]; + my @pre = Prefix::to_ciz($cmd, @val); + return ('numpre', $dxchan->msg('e27', $_)) unless @pre; + $s .= "(" . join(' || ', map {"\$r->[$fref->[2]]==$_"} @pre) . ")"; + } elsif ($fref->[1] =~ /^ns$/ ) { # for DXCC, ITU, CQ Zone + my $cmd = $fref->[1]; + my @pre = Prefix::to_ciz($cmd, @val); + return ('numpre', $dxchan->msg('e27', $_)) unless @pre; + $s .= "(" . "!\$USDB::present || grep \$r->[$fref->[2]] eq \$_, qw(" . join(' ' ,map {uc} @pre) . "))"; + } elsif ($fref->[1] eq 'r') { + my @t; + for (@val) { + return ('range', $dxchan->msg('e23', $_)) unless /^(\d+)\/(\d+)$/; + push @t, "(\$r->[$fref->[2]]>=$1 && \$r->[$fref->[2]]<=$2)"; + } + $s .= "(" . join(' || ', @t) . ")"; + } elsif ($fref->[1] eq 't') { + my @t; + for (@val) { + s/\*//g; + push @t, "\$r->[$fref->[2]]=~/$_/i"; + } + $s .= "(" . join(' || ', @t) . ")"; + } else { + confess("invalid letter $fref->[1]"); + } + ++$found; + last; + } + } + return ('unknown', $dxchan->msg('e20', $tok)) unless $found; + } else { + return ('no', $dxchan->msg('filter2', $tok)); + } + } + + } + + # tidy up the user string + $user =~ s/\&\&/ and /g; + $user =~ s/\|\|/ or /g; + $user =~ s/\!/ not /g; + $user =~ s/\s+/ /g; + + return (0, $filter, $fno, $user, "$s"); +} + +# a filter accept/reject command +sub cmd +{ + my ($self, $dxchan, $sort, $type, $line) = @_; + + return $dxchan->msg('filter5') unless $line; + + my ($r, $filter, $fno, $user, $s) = $self->parse($dxchan, $sort, $line); + my $u = DXUser::get_current($user); + return (1, $dxchan->msg('isow', $user)) if $u && $u->isolate; + return (1, $filter) if $r; + + my $fn = "filter$fno"; + + $filter->{$fn} = {} unless exists $filter->{$fn}; + $filter->{$fn}->{$type} = {} unless exists $filter->{$fn}->{$type}; + + $filter->{$fn}->{$type}->{user} = $user; + $filter->{$fn}->{$type}->{asc} = $s; + $r = $filter->compile($fn, $type); + return (1,$r) if $r; + + $r = $filter->write; + return (1,$r) if $r; + + $filter->install; + + return (0, $filter, $fno); } package Filter::Old; use strict; +use DXVars; +use DXUtil; +use DXDebug; use vars qw(@ISA); @ISA = qw(Filter); @@ -309,6 +644,14 @@ sub it } } +sub print +{ + my $self = shift; + my $call = shift; + my $sort = shift; + my $flag = shift || ""; + return "$call: Old Style Filter $flag $sort"; +} 1; __END__