make the ping stuff not crash immediately
[spider.git] / perl / Thingy.pm
1 #
2 # Thingy handling
3 #
4 # This is the new fundamental protocol engine handler
5
6 # This is where all the new things (and eventually all the old things
7 # as well) happen.
8 #
9 # $Id$
10 #
11 # Copyright (c) 2004 Dirk Koopman G1TLH
12 #
13
14 use strict;
15
16 package Thingy;
17
18 use vars qw($VERSION $BRANCH @queue @permin @persec);
19
20 main::mkver($VERSION = q$Revision$);
21
22 @queue = ();                                    # the input / processing queue
23
24 #
25 # these are set up using the Thingy->add_second_process($addr, $name)
26 # and Thingy->add_minute_process($addr, $name)
27 #
28 # They replace the old cycle in cluster.pl
29 #
30
31 @persec = ();                                   # this replaces the cycle in cluster.pl
32 @permin = ();                                   # this is an extra per minute cycle
33
34 my $lastsec = time;
35 my $lastmin = time;
36
37 use DXChannel;
38 use DXDebug;
39 use DXUtil;
40
41
42 # we expect all thingies to be subclassed
43 sub new
44 {
45         my $class = shift;
46         my $thing = {@_};
47
48         $thing->{origin} ||= $main::mycall;
49         
50         bless $thing, $class;
51         return $thing;
52 }
53
54 # send it out in the format asked for, if available
55 sub send
56 {
57         my $thing = shift;
58         my $dxchan = shift;
59         my $class;
60         my $sub;
61         
62         if (@_) {
63                 $class = shift;
64         } elsif ($dxchan->isa('DXChannel')) {
65                 $class = ref $dxchan;
66         }
67
68         # BEWARE!!!!!
69         no strict 'refs';
70
71         # do output filtering
72         if ($thing->can('out_filter')) {
73                 return unless $thing->out_filter($dxchan);
74         }
75
76         # before send (and line generation) things
77         # function must return true to make the send happen
78         $sub = "before_send_$class";
79         if ($thing->can($sub)) {
80                 return $thing->$sub($dxchan);
81         }
82         
83         # generate the protocol line which may (or not) be cached
84         my $ref;
85         unless ($ref = $thing->{class}) {
86                 $sub = "gen_$class";
87                 $ref = $thing->$sub($dxchan) if $thing->can($sub);
88         }
89         $dxchan->send(ref $ref ? @$ref : $ref) if $ref;
90
91         # after send
92         if ($thing->can('after_send_all')) {
93                 $thing->after_send_all($dxchan);
94         } else {
95                 $sub = "after_send_$class";
96                 $thing->$sub($dxchan) if $thing->can($sub);
97         }
98 }
99
100
101 # This is the main routing engine for the new protocol. Broadcast is a slight
102 # misnomer, because if it thinks it can route it down one or interfaces, it will.
103
104 # It handles anything it recognises as a callsign, sees if it can find it in a 
105 # routing table, and if it does, then routes the message.
106 #
107 # If it can't then it will broadcast it.
108 #
109 sub broadcast
110 {
111         my $thing = shift;
112         dbg("Thingy::broadcast: " . $thing->ascii) if isdbg('thing'); 
113
114         my @dxchan;
115         my $to ||= $thing->{touser};
116         $to ||= $thing->{group};
117         if ($to && is_callsign($to) && (my $ref = Route::get($to))) {
118                 dbg("Thingy::broadcast: routing for $to") if isdbg('thing');
119                 @dxchan = $ref->alldxchan;
120         } else {
121                 @dxchan = DXChannel::get_all();
122         }
123
124         dbg("Thingy::broadcast: offered " . join(',', map {$_->call} @dxchan)) if isdbg('thing');
125         
126         foreach my $dxchan (@dxchan) {
127                 next if $dxchan == $main::me;
128                 next if grep $dxchan == $_, @_;
129                 next if $dxchan->{call} eq $thing->{origin};
130                 next if $thing->{user} && !$dxchan->is_user && $dxchan->{call} eq $thing->{user};
131                 
132                 dbg("Thingy::broadcast: sending to $dxchan->{call}") if isdbg('thing');
133                 $thing->send($dxchan); 
134         }
135 }
136
137 # queue this thing for processing
138 sub queue
139 {
140         my $thing = shift;
141         my $dxchan = shift;
142         $thing->{dxchan} = $dxchan->call;
143         push @queue, $thing;
144 }
145
146 #
147 # this is the main commutator loop. In due course it will
148 # become the *only* commutator loop, This can be called in one
149 # of two ways: either with 2 args or with none.
150 #
151 # The two arg form is an immediate "queue and handle" and does
152 # a full cycle, immediately
153 #
154 sub process
155 {
156         my $thing;
157
158         if (@_ == 2) {
159                 $thing = shift;
160                 $thing->queue(shift);
161         }
162
163         while (@queue) {
164                 $thing = shift @queue;
165                 my $dxchan = DXChannel::get($thing->{dxchan});
166                 if ($dxchan) {
167                         if ($thing->can('in_filter')) {
168                                 next unless $thing->in_filter($dxchan);
169                         }
170
171                         # remember any useful routes
172                         RouteDB::update($thing->{origin}, $dxchan->{call}, $thing->{hopsaway});
173                         RouteDB::update($thing->{user}, $dxchan->{call}, $thing->{hopsaway}) if exists $thing->{user};
174                 
175                         $thing->handle($dxchan);
176                 }
177         }
178
179         # per second and per minute processing
180         if ($main::systime != $lastsec) {
181                 if ($main::systime >= $lastmin+60) {
182                         foreach my $r (@permin) {
183                                 &{$r->[0]}();
184                         }
185                         $lastmin = $main::systime;
186                 }
187                 foreach my $r (@persec) {
188                         &{$r->[0]}();
189                 }
190                 $lastsec = $main::systime;
191         }
192 }
193
194 sub add_minute_process
195 {
196         my $pkg = shift;
197         my $addr = shift;
198         my $name = shift;
199         dbg('Adding $name to Thingy per minute queue');
200         push @permin, [$addr, $name];
201 }
202
203 sub add_second_process
204 {
205         my $pkg = shift;
206         my $addr = shift;
207         my $name = shift;
208         dbg('Adding $name to Thingy per second queue');
209         push @persec, [$addr, $name];
210 }
211
212
213 sub ascii
214 {
215         my $thing = shift;
216         my $dd = new Data::Dumper([$thing]);
217         $dd->Indent(0);
218         $dd->Terse(1);
219         $dd->Sortkeys(1);
220     $dd->Quotekeys($] < 5.005 ? 1 : 0);
221         return $dd->Dumpxs;
222 }
223
224 sub add_auth
225 {
226         my $thing = shift;
227         my $s = $thing->{'s'} = sprintf "%X", int(rand() * 100000000);
228         my $auth = Verify->new("DXSp,$main::mycall,$s,$thing->{v},$thing->{b}");
229         $thing->{auth} = $auth->challenge($main::me->user->passphrase);
230 }
231
232 #
233 # create a generalised reply to a passed thing, if it isn't replyable 
234 # to then undef is returned
235 #  
236 sub new_reply
237 {
238         my $thing = shift;
239         my $out;
240         
241         if ($thing->{group} eq $main::mycall) {
242                 $out = $thing->new;
243                 $out->{touser} = $thing->{user} if $thing->{user};
244         } elsif (DXChannel::get($thing->{group})) {
245                 $out = $thing->new(user => $thing->{group});
246                 $out->{touser} = $thing->{user} if $thing->{user};
247         } elsif ($thing->{touser} && DXChannel::get($thing->{touser})) {
248                 $out = $thing->new(user => $thing->{touser});
249                 $out->{group} = $thing->{group};
250         }
251         return $out;
252 }
253 1;
254