#!/usr/bin/perl -w # # This script is invoked to process an email sent by the # commod-update-receiver Perl script. # This is part of ypp-sc-tools, a set of third-party tools for assisting # players of Yohoho Puzzle Pirates. # # Copyright (C) 2009 Ian Jackson # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Yohoho and Puzzle Pirates are probably trademarks of Three Rings and # are used without permission. This program is not endorsed or # sponsored by Three Rings. use strict (qw(vars)); use POSIX; use MIME::Parser; BEGIN { my $selfdir= $0; $selfdir =~ s,/+[^/]*$,,; chdir("$selfdir") or die "$selfdir $!"; } use Commods; use CommodsDatabase; set_ctype_utf8(); my $parser= new MIME::Parser; our $entity; $|=1; sub find_part ($$$) { my ($filename, $type, $accepter) = @_; foreach my $part ($entity->parts()) { my $h= $part->head(); next unless $h->recommended_filename() eq $filename; next unless $h->mime_type() eq $type; next unless $part->effective_type() eq $type; next if defined $accepter and !&$accepter($h); return $part; } die "no appropriate part with name $filename and type $type"; } sub bad_data_fail ($) { die $_[0]; } sub main () { $parser->extract_nested_messages(0); $parser->ignore_errors(0); $entity= $parser->parse(\*STDIN); my $eff_type= $entity->effective_type(); die "effective type $eff_type\n" unless $eff_type eq 'multipart/mixed'; my $mdpart= find_part('metadata', 'text/plain', sub { my $charset= $_[0]->mime_attr('content-type.charset'); return 1 if grep { $_ eq $charset } qw(utf-8 us-ascii); }); my $mdh= $mdpart->open('r') or die "failed to open metadata $!\n"; my %md; while (defined($_= $mdh->getline())) { m/^([a-z]+)\t(.*)$/ or next; $md{$1}= $2; } foreach my $needed (qw(ocean island timestamp clientspec serverspec)) { defined $md{$needed} or die "missing metadata $needed\n"; } my $mid= $entity->head()->get('message-id'); defined $mid or die "missing Message-ID\n"; chomp($mid); $mid !~ m/[^ -~]/ or die "Message-ID has strange character(s)\n"; my $tsvpart= find_part('deduped.tsv.gz', 'application/octet-stream', undef); my $tsv= pipethrough_prep(); $tsvpart->bodyhandle()->print($tsv); my $pt= pipethrough_run_along($tsv,undef, 'gunzip','gunzip'); db_setocean($md{'ocean'}); my $dbfn= db_filename(); (stat $dbfn) or die "stat database $dbfn failed $!\n"; db_writer(); db_connect(); db_onconflict(sub { print STDERR "temporary failure: @_\n"; exit 75; }); my ($islandid) = $dbh->selectrow_array( "SELECT islands.islandid FROM islands WHERE islandname == ?; ", {}, $md{'island'}); die "unknown island\n" unless defined $islandid; db_doall("DELETE FROM uploads WHERE islandid == $islandid; DELETE FROM buy WHERE islandid == $islandid; DELETE FROM sell WHERE islandid == $islandid; "); $dbh->do("INSERT INTO uploads (islandid, message, timestamp, clientspec, serverspec) VALUES (?,?,?,?,?); ", {}, $islandid, $mid, map { $md{$_} } (qw(timestamp clientspec serverspec))); my (%sth, %sub_cs, %cache_cs, %sth_insert, %sth_lookup); $sth_insert{'stall'}= $dbh->prepare( "INSERT OR IGNORE INTO stalls (islandid, stallname) VALUES ($islandid, ?) "); $sth_lookup{'stall'}= $dbh->prepare( "SELECT stallid FROM stalls WHERE islandid == $islandid AND stallname == ? "); $sth_insert{'commod'}= $dbh->prepare( "INSERT OR IGNORE INTO commods (commodname) VALUES (?) "); $sth_lookup{'commod'}= $dbh->prepare( "SELECT commodid FROM commods WHERE commodname == ? "); foreach my $cs (qw(stall commod)) { $sub_cs{$cs}= sub { my ($name)= @_; my $r= $cache_cs{$cs}{$name}; return $r if defined $r; $sth_lookup{$cs}->execute($name) or die; ($r)= $sth_lookup{$cs}->fetchrow_array(); if (!defined $r) { $sth_insert{$cs}->execute($name); $sth_lookup{$cs}->execute($name) or die; ($r)= $sth_lookup{$cs}->fetchrow_array(); die unless defined $r; } $cache_cs{$cs}{$name}= $r; return $r; }; } my @v; my %sub_bs; foreach my $bs (qw(buy sell)) { my $sth= $dbh->prepare( "INSERT INTO $bs (commodid, stallid, islandid, price, qty) VALUES (?,?,?,?,?); "); $sub_bs{$bs}= sub { my ($priceix) = @_; my $price= $v[$priceix]; return if !length $price; my $qty= $v[$priceix+1]; $qty++ if $qty =~ s/^\>//; $sth->execute($sub_cs{'commod'}($v[0]), $sub_cs{'stall'}($v[1]), $islandid,$price,$qty); }; } while (<$pt>) { @v= check_tsv_line($_, \&bad_data_fail); # chomp; # @v= split /\t/, $_, -1; &{$sub_bs{'buy'}}(2); &{$sub_bs{'sell'}}(4); # print "."; } pipethrough_run_finish($pt, 'gunzip <$deduped_tsv.gz'); # print "\n"; db_chkcommit(0); # select * from ((buy natural join commods) natural join stalls) natural join islands; # select * from ((sell natural join commods) natural join stalls) natural join islands; } my $ok= eval { main(); 1; }; my $err= $@; $parser->filer->purge(); if (!$ok) { print STDERR "PROCESSING FAILED\n $@\n"; exit 1; }