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