X-Git-Url: http://dxcluster.net/gitweb/gitweb.cgi?a=blobdiff_plain;f=perl%2FFilter.pm;h=b71ee95a5b81833acb4a1fc566592b832071529f;hb=ab811a0c902225075a9bd69749f65594079433a9;hp=aefa922419ee94afac488101b11c184aba45a8eb;hpb=82de56e409a19a05761794c9588713160b51144e;p=spider.git diff --git a/perl/Filter.pm b/perl/Filter.pm index aefa9224..b71ee95a 100644 --- a/perl/Filter.pm +++ b/perl/Filter.pm @@ -10,15 +10,579 @@ # # Copyright (c) 1999 Dirk Koopman G1TLH # -# $Id$ # -# The INSTRUCTIONS +# +# The NEW INSTRUCTIONS +# +# use the commands accept/spot|ann|wwv|wcy and reject/spot|ann|wwv|wcy +# also show/filter spot|ann|wwv|wcy # # The filters live in a directory tree of their own in $main::root/filter # # Each type of filter (e.g. spot, wwv) live in a tree of their own so you # can have different filters for different things for the same callsign. # + + +package Filter; + +use DXVars; +use DXUtil; +use DXDebug; +use Data::Dumper; +use Prefix; + +use strict; + +use vars qw ($filterbasefn $in); + +$filterbasefn = "$main::root/filter"; +$in = undef; + +# initial filter system +sub init +{ + +} + +sub new +{ + my ($class, $sort, $call, $flag) = @_; + $flag = ($flag) ? "in_" : ""; + return bless {sort => $sort, name => "$flag$call.pl" }, $class; +} + +# standard filename generator +sub getfn +{ + my ($sort, $call, $flag) = @_; + + # first uppercase + $flag = ($flag) ? "in_" : ""; + $call = uc $call; + my $fn = "$filterbasefn/$sort/$flag$call.pl"; + + # otherwise lowercase + unless (-e $fn) { + $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 ($fn = getfn($sort, $call, $flag)) { + $in = undef; + my $s = readfilestr($fn); + my $newin = eval $s; + 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 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 +# +# 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. +# +# 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 +# easily change them by program. +# +# 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 +# +# + +sub it +{ + my $self = shift; + + my $filter; + my @keys = sort $self->getfilkeys; + my $key; + my $type = 'Dunno'; + my $asc = '?'; + + 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); +} + +# this writes out the filter in a form suitable to be read in by 'read_in' +# It expects a list of references to filter lines +sub write +{ + my $self = shift; + my $sort = $self->{sort}; + my $name = $self->{name}; + my $dir = "$filterbasefn/$sort"; + my $fn = "$dir/$name"; + + mkdir $dir, 0775 unless -e $dir; + rename $fn, "$fn.o" if -e $fn; + my $fh = new IO::File ">$fn"; + if ($fh) { + my $dd = new Data::Dumper([ $self ]); + $dd->Indent(1); + $dd->Terse(1); + $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; +} + +sub print +{ + my $self = shift; + 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); + +# the OLD instructions! +# # Each filter file has the same structure:- # # @@ -43,33 +607,15 @@ # 0 is the first field of the list. # -package Filter; - -use DXVars; -use DXUtil; -use DXDebug; -use Carp; - -use strict; - -use vars qw ($filterbasefn $in); - -$filterbasefn = "$main::root/filter"; -$in = undef; - -# initial filter system -sub init -{ - -} - # # takes the reference to the filter (the first argument) and applies # it to the subsequent arguments and returns the action specified. # sub it { - my $filter = shift; + my $self = shift; + my $filter = $self->{filter}; # this is now a bless ref of course but so what + my ($action, $field, $fieldsort, $comp, $actiondata); my $ref; @@ -98,77 +644,13 @@ sub it } } -# 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 +sub print { - my ($sort, $call, $flag) = @_; - - # first uppercase - $flag = ($flag) ? "in_" : ""; - $call = uc $call; - my $fn = "$filterbasefn/$sort/$flag$call.pl"; - - # otherwise lowercase - unless (-e $fn) { - $call = lc $call; - $fn = "$filterbasefn/$sort/$flag$call.pl"; - } - - # load it - if (-e $fn) { - do "$fn"; - dbg('conn', "$@") if $@; - return $in; - } - return undef; -} - -# this writes out the filter in a form suitable to be read in by 'read_in' -# It expects a list of references to filter lines -sub write_out -{ - my $sort = shift; + my $self = shift; my $call = shift; - my $fn = "$filterbasefn/$sort"; - - - # make the output directory - mkdir $fn, 0777 unless -e $fn; - - # write out the file - $fn = "$fn/$call.pl"; - unless (open FILTER, ">$fn") { - warn "can't open $fn $!" ; - return; - } - - my $today = localtime; - print FILTER "#!/usr/bin/perl -# -# Filter for $call stored $today -# -\$in = [ -"; - - my $ref; - for $ref (@_) { - my ($action, $field, $fieldsort, $comp, $actiondata) = @{$ref}; - print FILTER "\t[ $action, $field, $fieldsort,"; - if ($fieldsort eq 'n' || $fieldsort eq 'r') { - print FILTER "[ ", join (',', $comp), " ],"; - } elsif ($fieldsort eq 'a') { - my $f = $comp; - print FILTER "'$f'"; - } - print FILTER " ],\n"; - } - print FILTER "];\n"; - close FILTER; + my $sort = shift; + my $flag = shift || ""; + return "$call: Old Style Filter $flag $sort"; } 1;