change the placing of adding PC9x headers
[spider.git] / perl / Route.pm
1 #!/usr/bin/perl
2 #
3 # This module impliments the abstracted routing for all protocols and
4 # is probably what I SHOULD have done the first time. 
5 #
6 # Heyho.
7 #
8 # This is just a container class which I expect to subclass 
9 #
10 # Copyright (c) 2001 Dirk Koopman G1TLH
11 #
12 # $Id$
13
14
15 package Route;
16
17 use DXDebug;
18 use DXChannel;
19 use Prefix;
20
21 use strict;
22
23
24 use vars qw($VERSION $BRANCH);
25 $VERSION = sprintf( "%d.%03d", q$Revision$ =~ /(\d+)\.(\d+)/ );
26 $BRANCH = sprintf( "%d.%03d", q$Revision$ =~ /\d+\.\d+\.(\d+)\.(\d+)/  || (0,0));
27 $main::build += $VERSION;
28 $main::branch += $BRANCH;
29
30 use vars qw(%list %valid $filterdef $default_metric);
31
32 %valid = (
33                   call => "0,Callsign",
34                   flags => "0,Flags,phex",
35                   dxcc => '0,Country Code',
36                   itu => '0,ITU Zone',
37                   cq => '0,CQ Zone',
38                   state => '0,State',
39                   city => '0,City',
40                   dxchan => '0,DXChans,parray',
41                   links => '0,Node Links,parray',
42                  );
43
44
45 $filterdef = bless ([
46                           # tag, sort, field, priv, special parser 
47                           ['channel', 'c', 0],
48                           ['channel_dxcc', 'nc', 1],
49                           ['channel_itu', 'ni', 2],
50                           ['channel_zone', 'nz', 3],
51                           ['call', 'c', 4],
52                           ['by', 'c', 4],
53                           ['call_dxcc', 'nc', 5],
54                           ['by_dxcc', 'nc', 5],
55                           ['call_itu', 'ni', 6],
56                           ['by_itu', 'ni', 6],
57                           ['call_zone', 'nz', 7],
58                           ['by_zone', 'nz', 7],
59                           ['channel_state', 'ns', 8],
60                           ['call_state', 'ns', 9],
61                           ['by_state', 'ns', 9],
62                          ], 'Filter::Cmd');
63
64 $default_metric = 10;
65
66 sub new
67 {
68         my ($pkg, $call) = @_;
69         $pkg = ref $pkg if ref $pkg;
70
71         my $self = bless {call => $call}, $pkg;
72         dbg("create $pkg with $call") if isdbg('routelow');
73
74         # add in all the dxcc, itu, zone info
75         my @dxcc = Prefix::extract($call);
76         if (@dxcc > 0) {
77                 $self->{dxcc} = $dxcc[1]->dxcc;
78                 $self->{itu} = $dxcc[1]->itu;
79                 $self->{cq} = $dxcc[1]->cq;
80                 $self->{state} = $dxcc[1]->state;
81                 $self->{city} = $dxcc[1]->city;
82         }
83         $self->{flags} = here(1);
84         
85         return $self; 
86 }
87
88 #
89 # get a callsign from a passed reference or a string
90 #
91
92 sub _getcall
93 {
94         my $self = shift;
95         my $thingy = shift;
96         $thingy = $self unless $thingy;
97         $thingy = $thingy->call if ref $thingy;
98         $thingy = uc $thingy if $thingy;
99         return $thingy;
100 }
101
102
103 # add and delete a callsign to/from a list
104 #
105
106 sub _addlist
107 {
108         my $self = shift;
109         my $field = shift;
110         my @out;
111         foreach my $c (@_) {
112                 confess "Need a ref here" unless ref($c);
113                 
114                 my $call = $c->{call};
115                 unless (grep $_ eq $call, @{$self->{$field}}) {
116                         push @{$self->{$field}}, $call;
117                         dbg(ref($self) . " adding $call to " . $self->{call} . "->\{$field\}") if isdbg('routelow');
118                         push @out, $c;
119                 }
120         }
121         return @out;
122 }
123
124 sub _dellist
125 {
126         my $self = shift;
127         my $field = shift;
128         my @out;
129         foreach my $c (@_) {
130                 confess "Need a ref here" unless ref($c);
131                 my $call = $c->{call};
132                 if (grep $_ eq $call, @{$self->{$field}}) {
133                         $self->{$field} = [ grep {$_ ne $call} @{$self->{$field}} ];
134                         dbg(ref($self) . " deleting $call from " . $self->{call} . "->\{$field\}") if isdbg('routelow');
135                         push @out, $c;
136                 }
137         }
138         return @out;
139 }
140
141 sub is_empty
142 {
143         my $self = shift;
144         return @{$self->{$_[0]}} == 0;
145 }
146
147 #
148 # flag field constructors/enquirers
149 #
150 # These can be called in various ways:-
151 #
152 # Route::here or $ref->here returns 1 or 0 depending on value of the here flag
153 # Route::here(1) returns 2 (the bit value of the here flag)
154 # $ref->here(1) or $ref->here(0) sets the here flag
155 #
156
157 sub here
158 {
159         my $self = shift;
160         my $r = shift;
161         return $self ? 2 : 0 unless ref $self;
162         return ($self->{flags} & 2) ? 1 : 0 unless defined $r;
163         $self->{flags} = (($self->{flags} & ~2) | ($r ? 2 : 0));
164         return $r ? 1 : 0;
165 }
166
167 sub conf
168 {
169         my $self = shift;
170         my $r = shift;
171         return $self ? 1 : 0 unless ref $self;
172         return ($self->{flags} & 1) ? 1 : 0 unless defined $r;
173         $self->{flags} = (($self->{flags} & ~1) | ($r ? 1 : 0));
174         return $r ? 1 : 0;
175 }
176
177
178 # display routines
179 #
180
181 sub user_call
182 {
183         my $self = shift;
184         my $call = sprintf "%s", $self->{call};
185         return $self->here ? "$call" : "($call)";
186 }
187
188 sub config
189 {
190         my $self = shift;
191         my $nodes_only = shift;
192         my $level = shift || 0;
193         my $seen = shift;
194         my @out;
195         my $line;
196         my $ucall = $self->user_call;
197         my $call = $self->{call};
198         my $printit = 1;
199         
200         return if $level > 1 && DXChannel->get($call);
201
202 #       my @seen = @$seen || ();
203 #       return if $level > 1 && grep $call eq $_, @seen;
204 #       push @seen, @{$self->{links}};
205         
206
207         # allow ranges
208         if (@_) {
209                 $printit = grep $call =~ m|$_|, @_;
210         }
211
212         if ($printit) {
213                 $line = ' ' x ($level*3) . "$ucall";
214                 push @out, $line;
215 #               push @$seen, $call;
216                 
217                 foreach my $ncall (sort @{$self->{links}}) {
218                         my $nref = Route::Node::get($ncall);
219                         
220                         if ($nref) {
221                                 my $c = $nref->user_call;
222                                 dbg("recursing from $call -> $c (" . (join ',', @$seen) . ")") if isdbg('routec');
223                                 
224                                 unless (grep $ncall eq $_, @$seen) {
225 #                                       push @seen, $call;
226                                         push @$seen, $ncall;
227                                         push @out, $nref->config($nodes_only, $level+1, $seen, @_);
228 #                                       pop @$seen;
229 #                                       pop @seen;
230                                 } else {
231 #                                       $line .= "->$ncall" if $line !~ $ncall;
232 #                                       push @out, $line;
233                                 }
234                         } else {
235                                 push @out, ' ' x (($level+1)*2)  . "$ncall?" if @_ == 0 || (@_ && grep $ncall =~ m|$_|, @_); 
236                         }
237                 }
238 #               pop @$seen;
239         }
240
241         return @out;
242 }
243
244 sub cluster
245 {
246         my $nodes = Route::Node::count();
247         my $tot = Route::User::count();
248         my $users = scalar DXCommandmode::get_all();
249         my $maxusers = Route::User::max();
250         my $uptime = main::uptime();
251         
252         return " $nodes nodes, $users local / $tot total users  Max users $maxusers  Uptime $uptime";
253 }
254
255 #
256 # routing things
257 #
258
259 sub get
260 {
261         my $call = shift;
262         return Route::Node::get($call) || Route::User::get($call);
263 }
264
265 sub _distance
266 {
267         my $self = shift;
268         my $ah = shift;
269         my $call = $self->{call};
270         
271         if (DXChannel->get($call)) {
272                 my $n = scalar @_ || 0;
273                 my $o = $ah->{$call} || 9999;
274                 $ah->{$call} = $n if $n < $o;
275                 dbg("_distance hit: $call = $n") if isdbg('routech');
276                 return;
277         } 
278
279         dbg("_distance miss $call: " . join(',', @_)) if isdbg('routech');
280         
281         foreach my $c (@{$self->{links}}) {
282                 next if $c eq $call || $c eq $main::mycall;
283                 next if grep $c eq $_, @_;
284                 
285                 my $n = get($c);
286                 _distance($n, $ah, @_, $c);
287         }
288         return;
289 }
290
291 sub _ordered_routes
292 {
293         my $self = shift;
294         my @routes;
295
296         if (exists $self->{dxchan}) {
297                 dbg("stored routes for $self->{call}: " . join(',', @{$self->{dxchan}})) if isdbg('routech');
298                 return @{$self->{dxchan}} if exists $self->{dxchan};
299         }
300
301         my %ah;
302         _distance($self, \%ah);
303         
304         @routes = sort {$ah{$a} <=> $ah{$b}} keys %ah;
305         $self->{dxchan} = \@routes;
306         dbg("new routes for $self->{call}: " . join(',', @routes)) if isdbg('routech');
307         return @routes;
308 }
309
310 # find all the possible dxchannels which this object might be on
311 sub alldxchan
312 {
313         my $self = shift;
314         my @dxchan;
315
316         my $dxchan = DXChannel->get($self->{call});
317         push @dxchan, $dxchan if $dxchan;
318
319         @dxchan = map {DXChannel->get($_)} _ordered_routes($self) unless @dxchan;
320         return @dxchan;
321 }
322
323 sub dxchan
324 {
325         my $self = shift;
326         
327         # ALWAYS return the locally connected channel if present;
328         my $dxchan = DXChannel->get($self->call);
329         return $dxchan if $dxchan;
330         
331         my @dxchan = $self->alldxchan;
332         return undef unless @dxchan;
333         
334         # determine the minimum ping channel
335         my $minping = 99999999;
336         foreach my $dxc (@dxchan) {
337                 my $p = $dxc->pingave;
338                 if (defined $p  && $p < $minping) {
339                         $minping = $p;
340                         $dxchan = $dxc;
341                 }
342         }
343         $dxchan = shift @dxchan unless $dxchan;
344         return $dxchan;
345 }
346
347 sub _addlink
348 {
349         my $self = shift;
350         delete $self->{dxchan};
351     return $self->_addlist('links', @_);
352 }
353
354 sub _dellink
355 {
356         my $self = shift;
357         delete $self->{dxchan};
358     return $self->_dellist('links', @_);
359 }
360
361 sub haslink
362 {
363         my $self = shift;
364         my $other = shift->{call};
365         return grep $other eq $_, @{$self->{links}};
366 }
367
368 #
369 # track destruction
370 #
371
372 sub DESTROY
373 {
374         my $self = shift;
375         my $pkg = ref $self;
376         
377         dbg("$pkg $self->{call} destroyed") if isdbg('routelow');
378 }
379
380 no strict;
381 #
382 # return a list of valid elements 
383
384
385 sub fields
386 {
387         my $pkg = shift;
388         $pkg = ref $pkg if ref $pkg;
389     my $val = "${pkg}::valid";
390         my @out = keys %$val;
391         push @out, keys %valid;
392         return @out;
393 }
394
395 #
396 # return a prompt for a field
397 #
398
399 sub field_prompt
400
401         my ($self, $ele) = @_;
402         my $pkg = ref $self;
403     my $val = "${pkg}::valid";
404         return $val->{$ele} || $valid{$ele};
405 }
406
407 #
408 # generic AUTOLOAD for accessors
409 #
410 sub AUTOLOAD
411 {
412         no strict;
413         my $name = $AUTOLOAD;
414         return if $name =~ /::DESTROY$/;
415         $name =~ s/^.*:://o;
416   
417         confess "Non-existant field '$AUTOLOAD'" if !$valid{$name};
418
419         # this clever line of code creates a subroutine which takes over from autoload
420         # from OO Perl - Conway
421         *{$AUTOLOAD} = sub {@_ > 1 ? $_[0]->{$name} = $_[1] : $_[0]->{$name}};
422        goto &$AUTOLOAD;
423
424 }
425
426 1;