Keep track of number of viewers.
[remoteglot] / www / analysis.pl
1 #! /usr/bin/perl
2 use CGI;
3 use Linux::Inotify2;
4 use AnyEvent;
5 use IPC::ShareLite;
6 use Storable;
7 use strict;
8 use warnings;
9
10 our $json_filename = "/srv/analysis.sesse.net/www/analysis.json";
11
12 my $cv = AnyEvent->condvar;
13 my $updated = 0;
14 my $cgi = CGI->new;
15 my $inotify = Linux::Inotify2->new;
16 $inotify->watch($json_filename, IN_MODIFY, sub {
17         $updated = 1;
18         $cv->send;
19 });
20         
21 my $inotify_w = AnyEvent->io (
22         fh => $inotify->fileno, poll => 'r', cb => sub { $inotify->poll }
23 );
24 my $wait = AnyEvent->timer (
25         after => 30,
26         cb    => sub { $cv->send; },
27 );
28
29 my $unique = $cgi->param('unique');
30 our $num_viewers = count_viewers($unique);
31
32 # Yes, this is reinventing If-Modified-Since, but browsers are so incredibly
33 # unpredictable on this, so blargh.
34 my $ims = 0;
35 if (defined($cgi->param('ims')) && $cgi->param('ims') ne '') {
36         $ims = $cgi->param('ims');
37 }
38 my $time = (stat($json_filename))[9];
39
40 # If we have something that's modified since IMS, send it out at once
41 if ($time > $ims) {
42         output();
43         exit;
44 }
45
46 # If not, wait, then send.
47 $cv->recv;
48 output();
49
50 sub count_viewers {
51         my $unique = shift;
52         my $time = time;
53         my $share = IPC::ShareLite->new(
54                 -key => 'RGLT',
55                 -create  => 'yes',
56                 -destroy => 'no',
57                 -size => 1048576,
58         ) or die "IPC::ShareLite: $!";
59         $share->lock(IPC::ShareLite::LOCK_EX);
60         my $viewers = {};
61         eval {
62                 $viewers = Storable::thaw($share->fetch());
63         };
64         $viewers->{$unique} = time;
65
66         # Go through and remove old viewers, and count them at the same time.
67         my $num_viewers = 0;
68         while (my ($key, $value) = each %$viewers) {
69                 if ($time - $value > 60) {
70                         delete $viewers->{$key};
71                 } else {
72                         ++$num_viewers;
73                 }
74         }
75
76         $share->store(Storable::freeze($viewers));
77         $share->unlock();
78
79         return $num_viewers;
80 }
81
82 sub output {
83         open my $fh, "<", $json_filename
84                 or die "$json_filename: $!";
85         my $data;
86         {
87                 local $/ = undef;
88                 $data = <$fh>;
89         }
90         my $time = (stat($fh))[9];
91         close $fh;
92
93         print CGI->header(-type=>'text/json',
94                           -x_remoteglot_last_modified=>$time,
95                           -x_remoteglot_num_viewers=>$num_viewers,
96                           -access_control_allow_origin=>'http://analysis.sesse.net',
97                           -access_control_expose_headers=>'X-Remoteglot-Last-Modified, X-Remoteglot-Num-Viewers',
98                           -expires=>'now');
99         print $data;
100 }