e7b18772eadeb0bc91fb99066de18ea40161ceb4
[spider.git] / perl / Route / Node.pm
1 #
2 # Node routing routines
3 #
4 # Copyright (c) 2001 Dirk Koopman G1TLH
5 #
6 # $Id$
7
8
9 package Route::Node;
10
11 use DXDebug;
12 use Route;
13 use Route::User;
14
15 use strict;
16
17 use vars qw($VERSION $BRANCH);
18
19 main::mkver($VERSION = q$Revision$);
20
21 use vars qw(%list %valid @ISA $max $filterdef);
22 @ISA = qw(Route);
23
24 %valid = (
25                   parent => '0,Parent Calls,parray',
26                   nodes => '0,Nodes,parray',
27                   users => '0,Users,parray',
28                   usercount => '0,User Count',
29                   version => '0,Version',
30                   build => '0,Build',
31                   sw => '0,Software',
32                   np => '0,Using New Prot,yesno',
33 );
34
35 $filterdef = $Route::filterdef;
36 %list = ();
37 $max = 0;
38
39 sub count
40 {
41         my $n = scalar (keys %list);
42         $max = $n if $n > $max;
43         return $n;
44 }
45
46 sub max
47 {
48         count();
49         return $max;
50 }
51
52 #
53 # this routine handles the possible adding of an entry in the routing
54 # table. It will only add an entry if it is new. It may have all sorts of
55 # other side effects which may include fixing up other links.
56 #
57 # It will return a node object if (and only if) it is a completely new
58 # object with that callsign. The upper layers are expected to do something
59 # sensible with this!
60 #
61 # called as $parent->add(call, version, flags) 
62 #
63
64 sub add
65 {
66         my $parent = shift;
67         my $call = uc shift;
68         confess "Route::add trying to add $call to myself" if $call eq $parent->{call};
69         my $version = shift;
70         my $here = shift;
71         
72         my $self = get($call);
73         if ($self) {
74                 $self->_addparent($parent);
75                 $parent->_addnode($self);
76                 if ($self->{version} != $version || $self->{flags} != $here) {
77                         $self->{version} = $version;
78                         $self->{flags} = $here;
79                         return $self;
80                 }
81                 return undef;
82         }
83         $self = $parent->new($call, $version, $here);
84         $parent->_addnode($self);
85         return $self;
86 }
87
88 #
89 # this routine is the opposite of 'add' above.
90 #
91 # It will return an object if (and only if) this 'del' will remove
92 # this object completely
93 #
94
95 sub del
96 {
97         my $self = shift;
98         my $pref = shift;
99
100         # delete parent from this call's parent list
101         $pref->_delnode($self);
102     $self->_delparent($pref);
103         my @nodes;
104         my $ncall = $self->{call};
105         
106         # is this the last connection, I have no parents anymore?
107         unless (@{$self->{parent}}) {
108                 foreach my $rcall (@{$self->{nodes}}) {
109                         next if grep $rcall eq $_, @_;
110                         my $r = Route::Node::get($rcall);
111                         push @nodes, $r->del($self, $ncall, @_) if $r;
112                 }
113                 $self->_del_users;
114                 delete $list{$self->{call}};
115                 push @nodes, $self;
116         }
117         return @nodes;
118 }
119
120 sub del_nodes
121 {
122         my $parent = shift;
123         my @out;
124         foreach my $rcall (@{$parent->{nodes}}) {
125                 my $r = get($rcall);
126                 push @out, $r->del($parent, $parent->{call}, @_) if $r;
127         }
128         return @out;
129 }
130
131 sub _del_users
132 {
133         my $self = shift;
134         for (@{$self->{users}}) {
135                 my $ref = Route::User::get($_);
136                 $ref->del($self) if $ref;
137         }
138         $self->{users} = [];
139 }
140
141 # add a user to this node
142 sub add_user
143 {
144         my $self = shift;
145         my $ucall = shift;
146
147         confess "Trying to add NULL User call to routing tables" unless $ucall;
148
149         my $uref = Route::User::get($ucall);
150         my @out;
151         if ($uref) {
152                 push @out, $uref->addparent($self);
153         } else {
154                 $uref = Route::User->new($ucall, $self->{call}, @_);
155                 push @out, $uref;
156         }
157         $self->_adduser($uref);
158         $self->{usercount} = scalar @{$self->{users}};
159
160         return @out;
161 }
162
163 # delete a user from this node
164 sub del_user
165 {
166         my $self = shift;
167         my $ref = shift;
168         my @out;
169         
170         if ($ref) {
171                 @out = $self->_deluser($ref);
172                 $ref->del($self);
173         } else {
174                 confess "tried to delete non-existant $ref->{call} from $self->{call}";
175         }
176         $self->{usercount} = scalar @{$self->{users}};
177         return @out;
178 }
179
180 sub usercount
181 {
182         my $self = shift;
183         if (@_ && @{$self->{users}} == 0) {
184                 $self->{usercount} = shift;
185         }
186         return $self->{usercount};
187 }
188
189 sub users
190 {
191         my $self = shift;
192         return @{$self->{users}};
193 }
194
195 sub nodes
196 {
197         my $self = shift;
198         return @{$self->{nodes}};
199 }
200
201 sub parents
202 {
203         my $self = shift;
204         return @{$self->{parent}};
205 }
206
207 sub has_user
208 {
209         my $self = shift;
210         return $self->_haslist('users', shift);
211 }
212
213 sub has_node
214 {
215         my $self = shift;
216         return $self->_haslist('nodes', shift);
217 }
218
219 sub has_parent
220 {
221         my $self = shift;
222         return $self->_haslist('parent', shift);
223 }
224
225
226 sub rnodes
227 {
228         my $self = shift;
229         my @out;
230         foreach my $call (@{$self->{nodes}}) {
231                 next if grep $call eq $_, @_;
232                 push @out, $call;
233                 my $r = get($call);
234                 push @out, $r->rnodes($call, @_) if $r;
235         }
236         return @out;
237 }
238
239 # return the differences in nodes between what we currently have and
240 # the list proffered. Returns two refs one to a list of nodes to remove and  
241 # the other a list of nodes to add
242
243 # input is a list of callsigns (not refs)
244 sub diff_nodes
245 {
246         my $self = shift;
247         my $in = ref $_[0] ? shift : \@_;
248         my %del = map {($_, 1)} nodes($self);
249         my %in = map {($_, 1)} @$in;
250         
251         # remove all the calls that are in both lists
252         for (@$in) {
253                 delete $in{$_} if delete $del{$_};
254         }
255         return ([keys %del], [keys %in]);
256 }
257
258 # same as above but for users
259 sub diff_users
260 {
261         my $self = shift;
262         my $in = ref $_[0] ? shift : \@_;
263         my %del = map {($_, 1)} users($self);
264         my %in = map {($_, 1)} @$in;
265         
266         # remove all the calls that are in both lists
267         for (@$in) {
268                 delete $in{$_} if delete $del{$_};
269         }
270         return ([keys %del], [keys %in]);
271 }
272
273 sub new
274 {
275         my $pkg = shift;
276         my $call = uc shift;
277         
278         confess "already have $call in $pkg" if $list{$call};
279         
280         my $self = $pkg->SUPER::new($call);
281         $self->{parent} = ref $pkg ? [ $pkg->{call} ] : [ ];
282         $self->{version} = 0 || shift;
283         $self->{flags} = 0 || shift;
284         $self->{users} = [];
285         $self->{nodes} = [];
286         $self->{lid} = 0;
287         
288         $list{$call} = $self;
289         
290         return $self;
291 }
292
293 sub get
294 {
295         my $call = shift;
296         $call = shift if ref $call;
297         my $ref = $list{uc $call};
298         dbg("Failed to get Node $call" ) if !$ref && isdbg('routerr');
299         return $ref;
300 }
301
302 sub get_all
303 {
304         return values %list;
305 }
306
307 sub newid
308 {
309         my $self = shift;
310         my $id = shift;
311         
312         return 0 if $id == $self->{lid};
313         if ($id > $self->{lid}) {
314                 $self->{lid} = $id;
315                 return 1;
316         } elsif ($self->{lid} - $id > 500) {
317                 $self->{id} = $id;
318                 return 1;
319         }
320         return 0;
321 }
322
323 sub _addparent
324 {
325         my $self = shift;
326     return $self->_addlist('parent', @_);
327 }
328
329 sub _delparent
330 {
331         my $self = shift;
332     return $self->_dellist('parent', @_);
333 }
334
335
336 sub _addnode
337 {
338         my $self = shift;
339     return $self->_addlist('nodes', @_);
340 }
341
342 sub _delnode
343 {
344         my $self = shift;
345     return $self->_dellist('nodes', @_);
346 }
347
348
349 sub _adduser
350 {
351         my $self = shift;
352     return $self->_addlist('users', @_);
353 }
354
355 sub _deluser
356 {
357         my $self = shift;
358     return $self->_dellist('users', @_);
359 }
360
361 #
362 # generic AUTOLOAD for accessors
363 #
364
365 sub AUTOLOAD
366 {
367         no strict;
368         my $name = $AUTOLOAD;
369         return if $name =~ /::DESTROY$/;
370         $name =~ s/^.*:://o;
371   
372         confess "Non-existant field '$AUTOLOAD'" unless $valid{$name} || $Route::valid{$name};
373
374         # this clever line of code creates a subroutine which takes over from autoload
375         # from OO Perl - Conway
376         *$AUTOLOAD = sub {$_[0]->{$name} = $_[1] if @_ > 1; return $_[0]->{$name}};
377         goto &$AUTOLOAD;
378 }
379
380 1;
381