sort working...
[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;
193         my $seen = shift;
194         my @out;
195         my $line;
196         my $call = $self->user_call;
197         my $printit = 1;
198
199         # allow ranges
200         if (@_) {
201                 $printit = grep $call =~ m|$_|, @_;
202         }
203
204         if ($printit) {
205                 $line = ' ' x ($level*2) . "$call";
206                 $call = ' ' x length $call; 
207                 
208                 # recursion detector
209                 if ((DXChannel->get($self->{call}) && $level > 1) || grep $self->{call} eq $_, @$seen) {
210                         $line .= ' ...';
211                         push @out, $line;
212                         return @out;
213                 }
214                 push @$seen, $self->{call};
215
216                 # print users
217                 unless ($nodes_only) {
218                         if (@{$self->{users}}) {
219                                 $line .= '->';
220                                 foreach my $ucall (sort @{$self->{users}}) {
221                                         my $uref = Route::User::get($ucall);
222                                         my $c;
223                                         if ($uref) {
224                                                 $c = $uref->user_call;
225                                         } else {
226                                                 $c = "$ucall?";
227                                         }
228                                         if ((length $line) + (length $c) + 1 < 79) {
229                                                 $line .= $c . ' ';
230                                         } else {
231                                                 $line =~ s/\s+$//;
232                                                 push @out, $line;
233                                                 $line = ' ' x ($level*2) . "$call->$c ";
234                                         }
235                                 }
236                         }
237                 }
238                 $line =~ s/->$//g;
239                 $line =~ s/\s+$//;
240                 push @out, $line if length $line;
241         }
242         
243         # deal with more nodes
244         foreach my $ncall (sort @{$self->{links}}) {
245                 my $nref = Route::Node::get($ncall);
246
247                 if ($nref) {
248                         my $c = $nref->user_call;
249 #                       dbg("recursing from $call -> $c") if isdbg('routec');
250                         push @out, $nref->config($nodes_only, $level+1, $seen, @_);
251                 } else {
252                         push @out, ' ' x (($level+1)*2)  . "$ncall?" if @_ == 0 || (@_ && grep $ncall =~ m|$_|, @_); 
253                 }
254         }
255
256         return @out;
257 }
258
259 sub cluster
260 {
261         my $nodes = Route::Node::count();
262         my $tot = Route::User::count();
263         my $users = scalar DXCommandmode::get_all();
264         my $maxusers = Route::User::max();
265         my $uptime = main::uptime();
266         
267         return " $nodes nodes, $users local / $tot total users  Max users $maxusers  Uptime $uptime";
268 }
269
270 #
271 # routing things
272 #
273
274 sub get
275 {
276         my $call = shift;
277         return Route::Node::get($call) || Route::User::get($call);
278 }
279
280 sub _distance
281 {
282         my $self = shift;
283         my $ah = shift;
284         my $call = $self->{call};
285         
286         if (DXChannel->get($call)) {
287                 my $n = scalar @_ || 0;
288                 my $o = $ah->{$call} || 9999;
289                 $ah->{$call} = $n if $n < $o;
290                 dbg("_distance hit: $call = $n") if isdbg('routech');
291                 return;
292         } 
293
294         dbg("_distance miss $call: " . join(',', @_)) if isdbg('routech');
295         
296         foreach my $c (@{$self->{links}}) {
297                 next if $c eq $call || $c eq $main::mycall;
298                 next if grep $c eq $_, @_;
299                 
300                 my $n = get($c);
301                 _distance($n, $ah, @_, $c);
302         }
303         return;
304 }
305
306 sub _ordered_routes
307 {
308         my $self = shift;
309         my @routes;
310
311         if (exists $self->{dxchan}) {
312                 dbg("stored routes for $self->{call}: " . join(',', @{$self->{dxchan}})) if isdbg('routech');
313                 return @{$self->{dxchan}} if exists $self->{dxchan};
314         }
315
316         my %ah;
317         _distance($self, \%ah);
318         
319         @routes = sort {$ah{$a} <=> $ah{$b}} keys %ah;
320         $self->{dxchan} = \@routes;
321         dbg("new routes for $self->{call}: " . join(',', @routes)) if isdbg('routech');
322         return @routes;
323 }
324
325 # find all the possible dxchannels which this object might be on
326 sub alldxchan
327 {
328         my $self = shift;
329         my @dxchan;
330
331         my $dxchan = DXChannel->get($self->{call});
332         push @dxchan, $dxchan if $dxchan;
333
334         @dxchan = map {DXChannel->get($_)} _ordered_routes($self) unless @dxchan;
335         return @dxchan;
336 }
337
338 sub dxchan
339 {
340         my $self = shift;
341         
342         # ALWAYS return the locally connected channel if present;
343         my $dxchan = DXChannel->get($self->call);
344         return $dxchan if $dxchan;
345         
346         my @dxchan = $self->alldxchan;
347         return undef unless @dxchan;
348         
349         # determine the minimum ping channel
350         my $minping = 99999999;
351         foreach my $dxc (@dxchan) {
352                 my $p = $dxc->pingave;
353                 if (defined $p  && $p < $minping) {
354                         $minping = $p;
355                         $dxchan = $dxc;
356                 }
357         }
358         $dxchan = shift @dxchan unless $dxchan;
359         return $dxchan;
360 }
361
362 sub _addlink
363 {
364         my $self = shift;
365         delete $self->{dxchan};
366     return $self->_addlist('links', @_);
367 }
368
369 sub _dellink
370 {
371         my $self = shift;
372         delete $self->{dxchan};
373     return $self->_dellist('links', @_);
374 }
375
376 #
377 # track destruction
378 #
379
380 sub DESTROY
381 {
382         my $self = shift;
383         my $pkg = ref $self;
384         
385         dbg("$pkg $self->{call} destroyed") if isdbg('routelow');
386 }
387
388 no strict;
389 #
390 # return a list of valid elements 
391
392
393 sub fields
394 {
395         my $pkg = shift;
396         $pkg = ref $pkg if ref $pkg;
397     my $val = "${pkg}::valid";
398         my @out = keys %$val;
399         push @out, keys %valid;
400         return @out;
401 }
402
403 #
404 # return a prompt for a field
405 #
406
407 sub field_prompt
408
409         my ($self, $ele) = @_;
410         my $pkg = ref $self;
411     my $val = "${pkg}::valid";
412         return $val->{$ele} || $valid{$ele};
413 }
414
415 #
416 # generic AUTOLOAD for accessors
417 #
418 sub AUTOLOAD
419 {
420         no strict;
421         my $name = $AUTOLOAD;
422         return if $name =~ /::DESTROY$/;
423         $name =~ s/^.*:://o;
424   
425         confess "Non-existant field '$AUTOLOAD'" if !$valid{$name};
426
427         # this clever line of code creates a subroutine which takes over from autoload
428         # from OO Perl - Conway
429         *{$AUTOLOAD} = sub {@_ > 1 ? $_[0]->{$name} = $_[1] : $_[0]->{$name}};
430        goto &$AUTOLOAD;
431
432 }
433
434 1;