2350f86f062dacbd0218e8f4a2c53f67a00df042
[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, dxchan, 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 $self = get($call);
70         if ($self) {
71                 $self->_addparent($parent);
72                 $parent->_addnode($self);
73                 return undef;
74         }
75         $self = $parent->new($call, @_);
76         $parent->_addnode($self);
77         return $self;
78 }
79
80 #
81 # this routine is the opposite of 'add' above.
82 #
83 # It will return an object if (and only if) this 'del' will remove
84 # this object completely
85 #
86
87 sub del
88 {
89         my $self = shift;
90         my $pref = shift;
91
92         # delete parent from this call's parent list
93         $pref->_delnode($self);
94     $self->_delparent($pref);
95         my @nodes;
96         my $ncall = $self->{call};
97         
98         # is this the last connection, I have no parents anymore?
99         unless (@{$self->{parent}}) {
100                 foreach my $rcall (@{$self->{nodes}}) {
101                         next if grep $rcall eq $_, @_;
102                         my $r = Route::Node::get($rcall);
103                         push @nodes, $r->del($self, $ncall, @_) if $r;
104                 }
105                 $self->_del_users;
106                 delete $list{$self->{call}};
107                 push @nodes, $self;
108         }
109         return @nodes;
110 }
111
112 sub del_nodes
113 {
114         my $parent = shift;
115         my @out;
116         foreach my $rcall (@{$parent->{nodes}}) {
117                 my $r = get($rcall);
118                 push @out, $r->del($parent, $parent->{call}, @_) if $r;
119         }
120         return @out;
121 }
122
123 sub _del_users
124 {
125         my $self = shift;
126         for (@{$self->{users}}) {
127                 my $ref = Route::User::get($_);
128                 $ref->del($self) if $ref;
129         }
130         $self->{users} = [];
131 }
132
133 # add a user to this node
134 sub add_user
135 {
136         my $self = shift;
137         my $ucall = shift;
138
139         confess "Trying to add NULL User call to routing tables" unless $ucall;
140
141         my $uref = Route::User::get($ucall);
142         my @out;
143         if ($uref) {
144                 @out = $uref->addparent($self);
145         } else {
146                 $uref = Route::User->new($ucall, $self->{call}, @_);
147                 @out = $uref;
148         }
149         $self->_adduser($uref);
150         $self->{usercount} = scalar @{$self->{users}};
151
152         return @out;
153 }
154
155 # delete a user from this node
156 sub del_user
157 {
158         my $self = shift;
159         my $ref = shift;
160         my @out;
161         
162         if ($ref) {
163                 @out = $self->_deluser($ref);
164                 $ref->del($self);
165         } else {
166                 confess "tried to delete non-existant $ref->{call} from $self->{call}";
167         }
168         $self->{usercount} = scalar @{$self->{users}};
169         return @out;
170 }
171
172 sub usercount
173 {
174         my $self = shift;
175         if (@_ && @{$self->{users}} == 0) {
176                 $self->{usercount} = shift;
177         }
178         return $self->{usercount};
179 }
180
181 sub users
182 {
183         my $self = shift;
184         return @{$self->{users}};
185 }
186
187 sub nodes
188 {
189         my $self = shift;
190         return @{$self->{nodes}};
191 }
192
193 sub parents
194 {
195         my $self = shift;
196         return @{$self->{parent}};
197 }
198
199 sub rnodes
200 {
201         my $self = shift;
202         my @out;
203         foreach my $call (@{$self->{nodes}}) {
204                 next if grep $call eq $_, @_;
205                 push @out, $call;
206                 my $r = get($call);
207                 push @out, $r->rnodes($call, @_) if $r;
208         }
209         return @out;
210 }
211
212 # return the differences in nodes between what we currently have and
213 # the list proffered. Returns two refs one to a list of nodes to remove and  
214 # the other a list of nodes to add
215
216 # input is a list of callsigns (not refs)
217 sub diff_nodes
218 {
219         my $self = shift;
220         my $in = ref $_[0] ? shift : \@_;
221         my %del = map {($_, 1)} nodes($self);
222         my %in = map {($_, 1)} @$in;
223         
224         # remove all the calls that are in both lists
225         for (@$in) {
226                 delete $in{$_} if delete $del{$_};
227         }
228         return ([keys %del], [keys %in]);
229 }
230
231 # same as above but for users
232 sub diff_users
233 {
234         my $self = shift;
235         my $in = ref $_[0] ? shift : \@_;
236         my %del = map {($_, 1)} users($self);
237         my %in = map {($_, 1)} @$in;
238         
239         # remove all the calls that are in both lists
240         for (@$in) {
241                 delete $in{$_} if delete $del{$_};
242         }
243         return ([keys %del], [keys %in]);
244 }
245
246 sub new
247 {
248         my $pkg = shift;
249         my $call = uc shift;
250         
251         confess "already have $call in $pkg" if $list{$call};
252         
253         my $self = $pkg->SUPER::new($call);
254         $self->{parent} = ref $pkg ? [ $pkg->{call} ] : [ ];
255         $self->{version} = 0 || shift;
256         $self->{flags} = 0 || shift;
257         $self->{users} = [];
258         $self->{nodes} = [];
259         $self->{lid} = 0;
260         
261         $list{$call} = $self;
262         
263         return $self;
264 }
265
266 sub get
267 {
268         my $call = shift;
269         $call = shift if ref $call;
270         my $ref = $list{uc $call};
271         dbg("Failed to get Node $call" ) if !$ref && isdbg('routerr');
272         return $ref;
273 }
274
275 sub get_all
276 {
277         return values %list;
278 }
279
280 sub newid
281 {
282         my $self = shift;
283         my $id = shift;
284         
285         return 0 if $id == $self->{lid};
286         if ($id > $self->{lid}) {
287                 $self->{lid} = $id;
288                 return 1;
289         } elsif ($self->{lid} - $id > 500) {
290                 $self->{id} = $id;
291                 return 1;
292         }
293         return 0;
294 }
295
296 sub _addparent
297 {
298         my $self = shift;
299     return $self->_addlist('parent', @_);
300 }
301
302 sub _delparent
303 {
304         my $self = shift;
305     return $self->_dellist('parent', @_);
306 }
307
308
309 sub _addnode
310 {
311         my $self = shift;
312     return $self->_addlist('nodes', @_);
313 }
314
315 sub _delnode
316 {
317         my $self = shift;
318     return $self->_dellist('nodes', @_);
319 }
320
321
322 sub _adduser
323 {
324         my $self = shift;
325     return $self->_addlist('users', @_);
326 }
327
328 sub _deluser
329 {
330         my $self = shift;
331     return $self->_dellist('users', @_);
332 }
333
334 sub DESTROY
335 {
336         my $self = shift;
337         my $pkg = ref $self;
338         my $call = $self->{call} || "Unknown";
339         
340         dbg("destroying $pkg with $call") if isdbg('routelow');
341 }
342
343 #
344 # generic AUTOLOAD for accessors
345 #
346
347 sub AUTOLOAD
348 {
349         no strict;
350         my $name = $AUTOLOAD;
351         return if $name =~ /::DESTROY$/;
352         $name =~ s/^.*:://o;
353   
354         confess "Non-existant field '$AUTOLOAD'" unless $valid{$name} || $Route::valid{$name};
355
356         # this clever line of code creates a subroutine which takes over from autoload
357         # from OO Perl - Conway
358         *$AUTOLOAD = sub {$_[0]->{$name} = $_[1] if @_ > 1; return $_[0]->{$name}};
359         goto &$AUTOLOAD;
360 }
361
362 1;
363