chiark / gitweb /
de68fbad98250c24cb43199cb1316e7dc4f79a0f
[ypp-sc-tools.db-test.git] / yarrg / commod-email-processor
1 #!/usr/bin/perl -w
2 #
3 # This script is invoked to process an email sent by the
4 # commod-update-receiver Perl script.
5
6 # This is part of ypp-sc-tools, a set of third-party tools for assisting
7 # players of Yohoho Puzzle Pirates.
8 #
9 # Copyright (C) 2009 Ian Jackson <ijackson@chiark.greenend.org.uk>
10 #
11 # This program is free software: you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation, either version 3 of the License, or
14 # (at your option) any later version.
15 #
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 # GNU General Public License for more details.
20 #
21 # You should have received a copy of the GNU General Public License
22 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
23 #
24 # Yohoho and Puzzle Pirates are probably trademarks of Three Rings and
25 # are used without permission.  This program is not endorsed or
26 # sponsored by Three Rings.
27
28 # Emails are:
29 #  multipart/mixed, containing
30 #   text/plain; name="metadata"; charset="utf-8"
31 #   Content-Disposition: inline; filename="metadata"
32 #     ocean\t<ocean>            canonical mixed case
33 #     island\t<island>          canonical mixed case
34 #     timestamp\t<digits>       time_t (non-leap secs since start of 1970 UTC)
35 #     clientname\t<cname>       may contain spaces
36 #     clientversion\t<cversion> may contain spaces
37 #     clientfixes\t<cfixes>     space-delimited list
38 #     clientspec\t<cspec>       <cname>\t<cversion>\t<cfixes>
39 #     servername\t<sname>       may contain spaces
40 #     serverversion\t<sversion> may contain spaces
41 #     serverfixes\t<sfixes>     space-delimited list
42 #     serverspec\t<sspec>       <sname>\t<serverversion>\t<serverfixes>
43 #   application/octet-stream; name="deduped.tsv.gz"
44 #   Content-Disposition: attachment; filename="deduped.tsv.gz"
45 #     <base64>
46
47 use strict (qw(vars));
48
49 use POSIX;
50 use MIME::Parser;
51
52 BEGIN {
53     my $selfdir= $0;
54     $selfdir =~ s,/+[^/]*$,,;
55     chdir("$selfdir") or die "$selfdir $!";
56 }
57
58 use Commods;
59 use CommodsDatabase;
60
61 setlocale(LC_CTYPE, "en_GB.UTF-8");
62 my $parser= new MIME::Parser;
63 our $entity;
64
65 $|=1;
66
67 sub find_part ($$$) {
68     my ($filename, $type, $accepter) = @_;
69     foreach my $part ($entity->parts()) {
70         my $h= $part->head();
71         next unless $h->recommended_filename() eq $filename;
72         next unless $h->mime_type()            eq $type;
73         next unless $part->effective_type()    eq $type;
74         next if defined $accepter and !&$accepter($h);
75         return $part;
76     }
77     die "no appropriate part with name $filename and type $type";
78 }
79
80 sub bad_data_fail ($) { die $_[0]; }
81
82 sub main () {
83     $parser->extract_nested_messages(0);
84     $parser->ignore_errors(0);
85
86     $entity= $parser->parse(\*STDIN);
87     my $eff_type= $entity->effective_type();
88     die "effective type $eff_type\n" unless $eff_type eq 'multipart/mixed';
89
90     my $mdpart= find_part('metadata', 'text/plain', sub {
91         my $charset= $_[0]->mime_attr('content-type.charset');
92         return 1 if grep { $_ eq $charset } qw(utf-8 us-ascii);
93     });
94
95     my $mdh= $mdpart->open('r') or die "failed to open metadata $!\n";
96     my %md;
97     while (defined($_= $mdh->getline())) {
98         m/^([a-z]+)\t(.*)$/ or next;
99         $md{$1}= $2;
100     }
101
102     foreach my $needed (qw(ocean island timestamp clientspec serverspec)) {
103         defined $md{$needed} or die "missing metadata $needed\n";
104     }
105
106     my $mid= $entity->head()->get('message-id');
107     defined $mid or die "missing Message-ID\n";
108     chomp($mid);
109     $mid !~ m/[^ -~]/ or die "Message-ID has strange character(s)\n";
110
111     my $tsvpart= find_part('deduped.tsv.gz', 'application/octet-stream',
112                            undef);
113     my $tsv= pipethrough_prep();
114     $tsvpart->bodyhandle()->print($tsv);
115     my $pt= pipethrough_run_along($tsv,undef, 'gunzip','gunzip');
116
117     db_setocean($md{'ocean'});
118     my $dbfn= db_filename();
119     (stat $dbfn) or die "stat database $dbfn failed $!\n";
120     db_writer();
121     db_connect();
122     db_onconflict(sub { print STDERR "temporary failure: @_\n"; exit 75; });
123
124     my ($islandid) = $dbh->selectrow_array(
125               "SELECT islands.islandid
126                       FROM islands
127                       WHERE islandname == ?;
128               ", {}, $md{'island'});
129
130     die "unknown island\n" unless defined $islandid;
131
132     db_doall("DELETE FROM uploads WHERE islandid == $islandid;
133               DELETE FROM buy     WHERE islandid == $islandid;
134               DELETE FROM sell    WHERE islandid == $islandid;
135              ");
136     
137     $dbh->do("INSERT INTO uploads
138                      (islandid, message,
139                       timestamp, clientspec, serverspec)
140                      VALUES (?,?,?,?,?);
141              ", {},
142              $islandid, $mid,
143              map { $md{$_} } (qw(timestamp clientspec serverspec)));
144
145     my (%sth, %sub_cs, %cache_cs, %sth_insert);
146
147     $sth_insert{'stall'}= $dbh->prepare(
148                 "INSERT OR IGNORE
149                         INTO stalls
150                         (islandid, stallname) VALUES ($islandid, ?)
151                 ");
152     $sth_insert{'commods'}= $dbh->prepare(
153                 "INSERT OR IGNORE
154                         INTO commods
155                         (commodname) VALUES (?)
156                 ");
157
158     foreach my $cs (qw(stall commod)) {
159         my $sth_lookup= $dbh->prepare(
160                 "SELECT ${cs}id FROM ${cs}s WHERE ${cs}name == ?;
161                 ");
162         $sub_cs{$cs}= sub {
163             my ($name)= @_;
164             my $r= $cache_cs{$cs}{$name};
165             return $r if defined $r;
166             $sth_lookup->execute($name) or die;
167             ($r)= $sth_lookup->fetchrow_array();
168             if (!defined $r) {
169                 $sth_insert{$cs}->execute($name);
170                 $sth_lookup->execute($name) or die;
171                 ($r)= $sth_lookup->fetchrow_array();
172                 die unless defined $r;
173             }
174             $cache_cs{$cs}{$name}= $r;
175             return $r;
176         };
177     }
178     my @v;
179
180     my %sub_bs;
181     foreach my $bs (qw(buy sell)) {
182         my $sth= $dbh->prepare(
183                "INSERT INTO $bs
184                        (commodid, stallid, islandid, price, qty)
185                        VALUES (?,?,?,?,?);
186                ");
187         $sub_bs{$bs}= sub {
188             my ($priceix) = @_;
189             my $price= $v[$priceix];  return if !length $price;
190             my $qty= $v[$priceix+1];
191             $qty++ if $qty =~ s/^\>//;
192             $sth->execute($sub_cs{'commod'}($v[0]),
193                           $sub_cs{'stall'}($v[1]),
194                           $islandid,$price,$qty);
195         };          
196     }
197
198     while (<$pt>) {
199         @v= check_tsv_line($_, \&bad_data_fail);
200 #       chomp;
201 #       @v= split /\t/, $_, -1;
202
203         &{$sub_bs{'buy'}}(2);
204         &{$sub_bs{'sell'}}(4);
205
206 #       print ".";
207     }
208
209     pipethrough_run_finish($pt, 'gunzip <$deduped_tsv.gz');
210
211 #    print "\n";
212     $dbh->commit();
213
214     # select * from ((buy natural join commods) natural join stalls) natural join islands;
215     # select * from ((sell natural join commods) natural join stalls) natural join islands;
216
217 }
218
219 my $ok= eval {
220     main();
221     1;
222 };
223 my $err= $@;
224
225 $parser->filer->purge();
226
227 if (!$ok) {
228     print STDERR "PROCESSING FAILED\n $@\n";
229     exit 1;
230 }