+# per second
+sub process
+{
+ foreach my $dxchan (DXChannel::get_all()) {
+ next unless $dxchan->is_rbn;
+
+ # At this point we run the queue to see if anything can be sent onwards to the punter
+ my $now = $main::systime;
+
+ # now run the waiting queue which just contains KEYS ($call|$qrg)
+ foreach my $sp (keys %{$dxchan->{queue}}) {
+ my $cand = $spots->{$sp};
+ unless ($cand && $cand->[CTime]) {
+ dbg "RBN Cand $sp " . ($cand ? 'def' : 'undef') . " [CTime] " . ($cand->[CTime] ? 'def' : 'undef') . " dwell $dwelltime";
+ next;
+ }
+ if ($now >= $cand->[CTime] + $dwelltime ) {
+ # we have a candidate, create qualitee value(s);
+ unless (@$cand > CData) {
+ dbg "RBN: QUEUE key '$sp' MISSING RECORDS, IGNORED" . dd($cand) if isdbg 'rbn';
+ next;
+ }
+ dbg "RBN: QUEUE PROCESSING key: '$sp' $now >= $cand->[CTime]" if isdbg 'rbnqueue';
+ my $r = $cand->[CData];
+ my $quality = @$cand - CData;
+ $quality = 9 if $quality > 9;
+ $cand->[CQual] = $quality if $quality > $cand->[CQual];
+ my $squality = "Q:$cand->[CQual]";
+
+ if ($cand->[CQual] >= $minqual) {
+ if (isdbg('progress')) {
+ my $s = "RBN: SPOT key: '$sp' = $r->[RCall] on $r->[RQrg] by $r->[ROrigin] \@ $r->[RTime] $squality route: $dxchan->{call}";
+ dbg($s);
+ }
+ send_dx_spot($dxchan, $squality, $cand);
+ } elsif (isdbg('rbn')) {
+ my $s = "RBN: SPOT IGNORED(Q $cand->[CQual] < $minqual) key: '$sp' = $r->[RCall] on $r->[RQrg] by $r->[ROrigin] \@ $r->[RTime] $squality route: $dxchan->{call}";
+ dbg($s);
+ }
+
+ # clear out the data and make this now just "spotted", but no further action required until respot time
+ dbg "RBN: QUEUE key '$sp' cleared" if isdbg 'rbn';
+
+ $spots->{$sp} = [$now, $cand->[CQual]];
+ delete $dxchan->{queue}->{$sp};
+ } else {
+ dbg sprintf("RBN: QUEUE key: '$sp' SEND time not yet reached %.1f secs left", $cand->[CTime] + $dwelltime - $now) if isdbg 'rbnqueue';
+ }
+ }
+ }
+
+}
+
+sub per_minute
+{
+ foreach my $dxchan (DXChannel::get_all()) {
+ next unless $dxchan->is_rbn;
+ dbg "RBN:STATS minute $dxchan->{call} raw: $dxchan->{noraw} sent: $dxchan->{norbn} delivered: $dxchan->{nospot} users: " . scalar keys %{$dxchan->{nousers}} if isdbg('rbnstats');
+ if ($dxchan->{noraw} == 0 && $dxchan->{lasttime} > 60) {
+ LogDbg('RBN', "RBN: no input from $dxchan->{call}, disconnecting");
+ $dxchan->disconnect;
+ }
+ $dxchan->{noraw} = $dxchan->{norbn} = $dxchan->{nospot} = 0; $dxchan->{nousers} = {};
+ $runtime{$dxchan->{call}} += 60;
+ }
+
+ # save the spot cache
+ write_cache() unless $main::systime + $startup_delay < $main::systime;;
+}
+
+sub per_10_minute
+{
+ my $count = 0;
+ my $removed = 0;
+ while (my ($k,$cand) = each %{$spots}) {
+ if ($main::systime - $cand->[CTime] > $minspottime*2) {
+ delete $spots->{$k};
+ ++$removed;
+ }
+ else {
+ ++$count;
+ }
+ }
+ dbg "RBN:STATS spot cache remain: $count removed: $removed"; # if isdbg('rbn');
+ foreach my $dxchan (DXChannel::get_all()) {
+ next unless $dxchan->is_rbn;
+ my $nq = keys %{$dxchan->{queue}};
+ dbg "RBN:STATS 10-minute $dxchan->{call} queue: $nq raw: $dxchan->{noraw10} sent: $dxchan->{norbn10} delivered: $dxchan->{nospot10} users: " . scalar keys %{$dxchan->{nousers10}};
+ $dxchan->{noraw10} = $dxchan->{norbn10} = $dxchan->{nospot10} = 0; $dxchan->{nousers10} = {};
+ }
+}
+
+sub per_hour
+{
+ foreach my $dxchan (DXChannel::get_all()) {
+ next unless $dxchan->is_rbn;
+ my $nq = keys %{$dxchan->{queue}};
+ dbg "RBN:STATS hour $dxchan->{call} queue: $nq raw: $dxchan->{norawhour} sent: $dxchan->{norbnhour} delivered: $dxchan->{nospothour} users: " . scalar keys %{$dxchan->{nousershour}};
+ $dxchan->{norawhour} = $dxchan->{norbnhour} = $dxchan->{nospothour} = 0; $dxchan->{nousershour} = {};
+ }
+}
+
+sub finish
+{
+ write_cache();
+}
+
+sub write_cache
+{
+ my $fh = IO::File->new(">$cachefn") or confess("writing $cachefn $!");
+ my $s = $json->encode($spots);
+ $fh->print($s);
+ $fh->close;
+}
+
+sub check_cache
+{
+ if (-e $cachefn) {
+ my $mt = (stat($cachefn))[9];
+ my $t = $main::systime - $mt || 1;
+ my $p = difft($mt);
+ if ($t < $cache_valid) {
+ dbg("RBN:check_cache '$cachefn' spot cache exists, created $p ago and not too old");
+ my $fh = IO::File->new($cachefn);
+ my $s;
+ if ($fh) {
+ local $/ = undef;
+ $s = <$fh>;
+ dbg("RBN:check_cache cache read size " . length $s);
+ $fh->close;
+ } else {
+ dbg("RBN:check_cache file read error $!");
+ return undef;
+ }
+ if ($s) {
+ eval {$spots = $json->decode($s)};
+ if ($spots && ref $spots) {
+ if (exists $spots->{VERSION} && $spots->{VERSION} == $DATA_VERSION) {
+ # now clean out anything that is current
+ while (my ($k, $cand) = each %$spots) {
+ next unless ref $cand;
+ if (@$cand > CData) {
+ $spots->{$k} = [$cand->[CTime], $cand->[CQual]];
+ }
+ }
+ dbg("RBN:check_cache spot cache restored");
+ return 1;
+ }
+ }
+ }
+ dbg("RBN::checkcache error decoding $@");
+ } else {
+ my $d = difft($main::systime-$cache_valid);
+ dbg("RBN::checkcache '$cachefn' created $p ago is too old (> $d), ignored");
+ }
+ } else {
+ dbg("RBN:check_cache '$cachefn' spot cache not present");
+ }
+
+ return undef;
+}
+