chiark / gitweb /
cope better when POST limit exceeded
[ypp-sc-tools.main.git] / pctb / dictionary-update-receiver
1 #!/usr/bin/perl -w
2 #
3 # This script is invoked when the YPP SC PCTB client talks to the
4 # dictionary server.  See README.privacy.
5
6
7 # upload testing runes:
8 #
9 # YPPSC_PCTB_DICT_UPDATE=./ YPPSC_PCTB_DICT_SUBMIT=./ ./ypp-commodities --ocean midnight --pirate aristarchus --find-island --same --raw-tsv >raw.tsv  
10 # ./dictionary-manager --debug --approve-updates '' . .
11
12 use strict (qw(vars));
13 use POSIX;
14
15 $CGI::POST_MAX= 1024*1024;
16 $CGI::DISABLE_UPLOADS= 1;
17
18 use CGI qw/:standard -private_tempfiles/;
19 use IO::Pipe;
20 use IO::Handle;
21
22 #---------- pixmaps ----------
23
24 sub parseentryin__pixmap ($) {
25     my ($entry_in) = @_;
26     $entry_in =~
27         m/^(\S+ \- .*)\nP3\n([1-9]\d{1,3}) ([1-9]\d{1,3})\n255\n/s or die;
28     my ($def,$w,$h)= ($1, $2+0, $3+0);
29     my @d= grep { m/./ } split /\s+/, $';
30     @d == $w*$h*3 or die "$d[0]|$d[1]|...|$d[$#d-1]|$d[$#d] ?";
31     map {
32         m/\D/ and die "$& ?";
33         $_ += 0;
34         $_ >= 0 or die "$_ ?";
35         $_ <= 255 or die "$_ ?";
36     } @d;
37     my $ppm= "P3\n$w $h\n255\n";
38     my $di=0;
39     for (my $y=0; $y<$h; $y++) {
40         for (my $x=0; $x<$w; $x++, $di+=3) {
41 #print STDERR ">$x,$y,$di,",scalar(@d),"<\n";
42             $ppm .= sprintf "  %3d %3d %3d", @d[$di..$di+2];
43         }
44         $ppm .= "\n";
45     }
46
47     my $icon= pipeval($ppm,
48                          'ppmtopgm',
49                          'pnmscale -xysize 156 80',
50                          'pnmnorm -bpercent 40 -wpercent 20',
51                          'pgmtopbm -threshold',
52                          'pnminvert',
53                          'pbmtoascii -2x4');
54
55     my $whole= pipeval($ppm,
56                        'ppmtopgm',
57                        'pnmnorm -bpercent 40 -wpercent 20',
58                        'pgmtopbm -threshold',
59                        'pnminvert',
60                        'pbmtoascii');
61
62     my $entry= "$def\n$ppm";
63
64     return ('',$def,$ppm,$ppm,$def, $w,$icon,$whole,$entry);
65 }
66
67 #---------- characters ----------
68
69 sub parseentryin__char ($$) {
70     my ($ei,$h) = @_;
71     $ei =~ m/^(Digit|Upper|Lower)\n([^\n]+)\n/s or die;
72     my ($ctx,$str)= ($1,$2);
73 #print STDERR ">$'<\n";
74     my @d= grep { m/./ } split /\n/, $';
75 #print STDERR ">@d<\n";
76     die if $h>31;
77     die if @d>400;
78     my $maxval= (1<<$h)-1;
79     map {
80         m/^[0-9a-f]{1,8}$/ or die;
81         $_= hex $_;
82         die "$_ ?" if $_ > $maxval;
83     } @d;
84     my $w= @d;
85     my $ppm= "P2\n$w $h\n1\n";
86     my $whole='';
87     for (my $y=0; $y<$h; $y++) {
88         for (my $x=0; $x<$w; $x++) {
89             my $pix= !($d[$x] & (1<<$y));
90             $ppm .= sprintf " %d", $pix;
91             $whole .= $pix ? '  ' : '<>';
92         }
93         $ppm .= "\n";
94         $whole .= "\n";
95     }
96     map { $_= sprintf "%x", $_; } @d;
97     my $key= join ' ', $ctx, @d;
98
99 #    my $whole= pipeval($ppm,
100 #                     "pnmscale 2",
101 #                     'pgmtopbm -threshold',
102 #                     'pbmtoascii');
103
104     my $entry= "$ctx\n$str\n". join("\n", @d). "\n";
105     
106     return ($ctx,$str,$ppm,$key,$str, $w*2,'',$whole,$entry);
107 }
108
109 #---------- useful stuff ----------
110
111 sub pipeval ($@) {
112     my ($val, @cmds) = @_;
113     my (@pids);
114
115     my $lastpipe;
116     
117     foreach my $cmd ('',@cmds) {
118         my $pipe= new IO::Pipe or die $!;
119         my $pid= fork();  defined $pid or die $!;
120
121         if (!$pid) {
122             $pipe->writer();
123             if (!$lastpipe) {
124                  print $pipe $val or die $!;
125                  exit 0;
126              } else {
127                  open STDIN, '<&', $lastpipe or die $!;
128                  open STDOUT, '>&', $pipe or die $!;
129                  close $lastpipe or die $!;
130                  close $pipe or die $!;
131                  exec $cmd; die $!;
132              }
133         }
134         $pipe->reader();
135         if ($lastpipe) { close $lastpipe or die $!; }
136         $lastpipe= $pipe;
137         push @pids, $pid;
138     }
139
140     $!=0; { local ($/)=undef; $val= <$lastpipe>; }
141     defined $val or die $!;
142     $lastpipe->error and die $!;  close $lastpipe or die $!;
143
144     foreach my $cmd ('(paste)', @cmds) {
145         my $pid= shift @pids;
146         waitpid($pid,0) == $pid or die "$pid $? $!";
147         $?==0 or $?==13 or die "$cmd $?";
148     }
149     return $val;
150 }
151
152 #========== main program ==========
153
154 #---------- determine properties of the submission ----------
155
156 my $dict= param('dict');
157 my $entry_in= param('entry');
158 defined $entry_in or die Dump()." ?";
159
160 my $ocean= param('ocean');
161 my $pirate= param('pirate');
162 if (defined $ocean && defined $pirate) {
163     $pirate= "$ocean - $pirate";
164 } else {
165     $pirate= '';
166 }
167
168 my $caller= $ENV{'REMOTE_ADDR'};
169 $caller= 'LOCAL' unless defined $caller;
170
171 my $fwdf= $ENV{'HTTP_X_FORWARDED_FOR'};
172 if (defined $fwdf) {
173     $fwdf =~ s/\s//g;
174     $fwdf =~ s/[^0-9.,]/?/g;
175     $caller= "$fwdf,$pirate";
176 }
177
178 my $kind;
179 my @xa;
180
181 if ($dict =~ m/^pixmap$/) {
182     $kind= $&;
183 } elsif ($dict =~ m/^(char)([1-9]\d?)$/) {
184     ($kind,@xa)= ($1,$2);
185 } else {
186     die "$dict ?";
187 }
188 $dict= $&;
189
190 my ($ctx,$def,$image,$key,$val, $width,$icon,$whole,$entry)=
191     &{"parseentryin__$kind"}($entry_in, @xa);
192
193 #---------- compute the email to send ----------
194
195 my $whoami= `whoami`; $? and die $?;
196 chomp $whoami;
197
198 my $email= <<END
199 To: $whoami
200 Subject: pctb $dict $ctx $def [ypp-sc-tools]
201
202 Pirate:     $pirate
203 Caller:     $caller
204 Dictionary: $dict
205 Context:    $ctx
206 Definition: $def
207
208 END
209     ;
210
211 if (length $icon) {
212     $email .= "$icon\n\n";
213 }
214
215 $whole =~ s/(.*)\n/ sprintf "%-${width}s\n", $1 /mge;
216 $whole =~ s/^/|/mg;
217 $whole =~ s/\n/|\n/mg;
218 $whole =~ s/^(.*)/ ",".('_' x $width).".\n".$1 /e;
219 $whole =~ s/(.*)$/ $1."\n\`".('~' x $width)."'\n" /e;
220
221 my $lw= 79;
222
223 while ($whole =~ m/../) {
224     my $lhs= $whole;
225     $lhs =~ s/^(.{0,$lw}).*$/$1/mg;
226     $whole =~ s/^.{1,$lw}//mg;
227 #print STDERR "[[[[[$lhs########$whole]]]]]\n";
228     $email .= $lhs;
229 }
230
231 END
232     ;
233
234 my $cutline= "-8<-\n";
235 $email .= $cutline.$entry.$cutline;
236
237 #---------- prepare the database entry ----------
238
239 my $du=$ENV{'YPPSC_DICTUPDATES'};
240 chdir $du or die "$du $!"
241     if defined $du;
242
243
244 my $fn_t= "_update.$$-xxxxxxxxxxxxxxxx.tmp";
245 open F, "> $fn_t" or die "$fn_t $!";
246 (stat F) or die $!;
247 my $fn_i= sprintf "_update.$$-%016x.rdy", (stat _)[1];
248
249 print F "ypp-sc-tools dictionary update v1\n";
250
251 foreach my $v ($pirate,$caller,$dict,$ctx,$def,$image,$key,$val) {
252     printf F "%d\n", length($v) or die $!;
253     print F $v,"\n" or die $!;
254 }
255
256 close F or die $!;
257
258 my @tm= localtime;
259 my $tm= strftime "%Y-%m-%d %H:%M:%S %Z", @tm;
260
261 open L, ">> _dict.log" or die $!;
262 my $ll= sprintf "%s %-6s %-31s %-31s %s", $tm, $dict, $pirate, $caller, $fn_i;
263
264 #---------- commit everything ----------
265
266 print L "$ll submit\n" or die $!;
267 L->flush or die $!;
268
269 if (eval {
270
271     open S, "|/usr/lib/sendmail -odb -oee -oi -t" or die $!;
272     print S $email or die $!;
273     $!=0; $?=0; close S or die $!; $? and die $?;
274
275     rename $fn_t, $fn_i or die "$fn_t $fn_i $!";
276
277     1;
278 }) {
279     print L "$ll stored\n" or die $!;
280 } else {
281     print L "$ll ERROR! $@\n" or die $!;
282     exit 1;
283 }
284 close L or die $!;
285
286 print header('text/plain'), "OK $fn_i\n" or die $!;