X-Git-Url: http://git.ieval.ro/?a=blobdiff_plain;f=lib%2FGruntmaster%2FData.pm;h=4393a62de6f687d724f8cab874b7e190cfd31283;hb=5bfe4fee59691741265842a2d06cf53a1d86aa6e;hp=3da25290bcb30cb9788dab5c16c2125066992865;hpb=52172a1a697be94c99123f265ebcd64871d6379e;p=gruntmaster-data.git diff --git a/lib/Gruntmaster/Data.pm b/lib/Gruntmaster/Data.pm index 3da2529..4393a62 100644 --- a/lib/Gruntmaster/Data.pm +++ b/lib/Gruntmaster/Data.pm @@ -1,56 +1,23 @@ -use utf8; package Gruntmaster::Data; - -# Created by DBIx::Class::Schema::Loader -# DO NOT MODIFY THE FIRST PART OF THIS FILE - -use strict; +use 5.014; use warnings; -use base 'DBIx::Class::Schema'; - -__PACKAGE__->load_namespaces; - - -# Created by DBIx::Class::Schema::Loader v0.07039 @ 2014-03-05 13:11:39 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:dAEmtAexvUaNXLgYz2rNEg - use parent qw/Exporter/; our $VERSION = '5999.000_013'; -our @EXPORT = qw/purge/; ## no critic (ProhibitAutomaticExportation) +our @EXPORT = qw/purge db user_list user_entry problem_list problem_entry contest_list contest_entry contest_full contest_has_problem job_list job_entry job_full create_job standings update_status rerun_job/; ## no critic (ProhibitAutomaticExportation) -use Lingua::EN::Inflect qw/PL_N/; use JSON::MaybeXS qw/decode_json/; use HTTP::Tiny; use PerlX::Maybe qw/maybe/; -use Sub::Name qw/subname/; -use Class::Method::Modifiers qw/around/; use DBI; use DBIx::Simple; use List::Util qw/sum/; use SQL::Abstract; -use constant CONTEST_PUBLIC_COLUMNS => [qw/id name description start stop owner/]; use constant PROBLEM_PUBLIC_COLUMNS => [qw/id author writer level name owner private timeout olimit value/]; -use constant USER_PUBLIC_COLUMNS => [qw/id admin name town university country level/]; use constant JOBS_PER_PAGE => 50; -sub dynsub{ - our ($name, $sub) = @_; - no strict 'refs'; ## no critic (Strict) - *$name = subname $name => $sub -} - -BEGIN { - for my $rs (qw/contest contest_problem job open limit problem user problem_status contest_status/) { - my $rsname = ucfirst $rs; - $rsname =~ s/_([a-z])/\u$1/gs; - dynsub PL_N($rs) => sub { $_[0]->resultset($rsname) }; - dynsub $rs => sub { $_[0]->resultset($rsname)->find($_[1]) }; - } -} - my %statements = ( user_list_sth => 'SELECT * FROM user_list LIMIT 200', user_entry_sth => 'SELECT * FROM user_data WHERE id = ?', @@ -71,120 +38,68 @@ my %statements = ( job_entry_sth => 'SELECT * FROM job_entry WHERE id = ?', job_full_sth => 'SELECT * FROM jobs WHERE id = ?', + + rerun_job_sth => 'UPDATE jobs SET daemon=NULL,result=-2,result_text=NULL,results=NULL,errors=NULL WHERE id = ?', ); -around connect => sub { - my $orig = shift; - my $self = $orig->(@_); - $self->{dbh} = DBI->connect($_[1]); - $self->{dbis} = DBIx::Simple->new($self->{dbh}); - $self->{dbis}->keep_statements = 100; - $self +our $db; +sub db () { $db } + +sub init { + $db = DBIx::Simple->new(@_); + $db->keep_statements = 100; }; sub purge; sub query { - my ($self, $stat, @extra) = @_; - $self->{dbis}->query($statements{$stat} // $stat, @extra) + my ($stat, @extra) = @_; + $db->query($statements{$stat}, @extra) } my (%name_cache, %name_cache_time); use constant NAME_CACHE_MAX_AGE => 5; sub object_name { - my ($self, $table, $id) = @_; + my ($table, $id) = @_; $name_cache_time{$table} //= 0; if (time - $name_cache_time{$table} > NAME_CACHE_MAX_AGE) { $name_cache_time{$table} = time; $name_cache{$table} = {}; - $name_cache{$table} = $self->{dbis}->select($table, 'id,name')->map; + $name_cache{$table} = $db->select($table, 'id,name')->map; } $name_cache{$table}{$id} } -sub add_names { - my ($self, $el) = @_; +sub add_names ($) { ## no critic (ProhibitSubroutinePrototypes) + my ($el) = @_; if (ref $el eq 'ARRAY') { - $self->add_names($_) for @$el + &add_names ($_) for @$el ## no critic (ProhibitAmpersandSigils) } else { for my $object (qw/contest owner problem/) { my $table = $object eq 'owner' ? 'users' : "${object}s"; - $el->{"${object}_name"} = $self->object_name($table, $el->{$object}) if defined $el->{$object} + $el->{"${object}_name"} = object_name $table, $el->{$object} if defined $el->{$object} } } $el } -sub user_list_orig { - my ($self) = @_; - my $rs = $self->users->search(undef, {columns => USER_PUBLIC_COLUMNS} ); - my (%solved, %attempted, %contests); - - for my $row ($self->problem_statuses->all) { - $solved {$row->rawowner}++ if $row->solved; - $attempted{$row->rawowner}++ unless $row->solved; - } - $contests{$_->rawowner}++ for $self->contest_statuses->all; - - my @users = sort { $b->{solved} <=> $a->{solved} or $b->{attempted} <=> $a->{attempted} } ## no critic (ProhibitReverseSort) - map { - my $id = $_->id; - +{ $_->get_columns, - solved => ($solved{$id} // 0), - attempted => ($attempted{$id} // 0), - contests => ($contests{$id} // 0) } - } $rs->all; - @users = @users[0 .. 199] if @users > 200; - \@users -} - -sub user_entry_orig { - my ($self, $id) = @_; - my $user = $self->users->find($id, {columns => USER_PUBLIC_COLUMNS, prefetch => [qw/problem_statuses contest_statuses/]}); - my @problems = map { {problem => $_->get_column('problem'), solved => $_->solved} } $user->problem_statuses->search(undef, {order_by => 'problem'}); - my @contests = map { {contest => $_->contest->id, contest_name => $_->contest->name, rank => $_->rank, score => $_->score} } $user->contest_statuses->search(undef, {prefetch => 'contest', order_by => 'contest.start DESC'}); - +{ $user->get_columns, problems => \@problems, contests => \@contests } -} - -sub user_list { - my ($self) = @_; - scalar $self->query('user_list_sth')->hashes -} +sub user_list { +{us => scalar query('user_list_sth')->hashes} } sub user_entry { - my ($self, $id) = @_; - my $ret = $self->query('user_entry_sth', $id)->hash; - $ret->{problems} = $self->query('problem_status_sth', $id)->hashes; - $ret->{contests} = $self->query('contest_status_sth', $id)->hashes; + my ($id) = @_; + my $ret = query('user_entry_sth', $id)->hash; + $ret->{problems} = add_names query('problem_status_sth', $id)->hashes; + $ret->{contests} = add_names query('contest_status_sth', $id)->hashes; - $self->add_names($ret->{problems}); - $self->add_names($ret->{contests}); $ret; } -sub problem_list_orig { - my ($self, %args) = @_; - my @columns = @{PROBLEM_PUBLIC_COLUMNS()}; - push @columns, 'solution' if $args{solution} && $args{contest} && !$self->contest($args{contest})->is_running; - my $rs = $self->problems->search(undef, {order_by => 'me.name', columns => \@columns, prefetch => 'owner'}); - $rs = $rs->search({'private' => 0}) unless $args{contest} || $args{private}; - $rs = $rs->search({'contest_problems.contest' => $args{contest}}, {join => 'contest_problems'}) if $args{contest}; - $rs = $rs->search({'me.owner' => $args{owner}}) if $args{owner}; - my %params; - $params{contest} = $args{contest} if $args{contest} && $self->contest($args{contest})->is_running; - for ($rs->all) { - $params{$_->level} //= []; - push @{$params{$_->level}}, {$_->get_columns, owner_name => $_->owner->name} ; - } - \%params -} - sub problem_list { - my ($self, %args) = @_; + my (%args) = @_; my @columns = @{PROBLEM_PUBLIC_COLUMNS()}; push @columns, 'solution' if $args{solution}; my %where; @@ -193,8 +108,7 @@ sub problem_list { $where{owner} = $args{owner} if $args{owner}; my $table = $args{contest} ? 'problems JOIN contest_problems cp ON cp.problem = id' : 'problems'; - my $ret = $self->{dbis}->select(\$table, \@columns, \%where, 'name')->hashes; - $self->add_names($ret); + my $ret = add_names $db->select(\$table, \@columns, \%where, 'name')->hashes; my %params; for (@$ret) { @@ -204,77 +118,24 @@ sub problem_list { \%params } -sub problem_entry_orig { - my ($self, $id, $contest, $user) = @_; - my $running = $contest && $self->contest($contest)->is_running; - my @columns = @{PROBLEM_PUBLIC_COLUMNS()}; - push @columns, 'statement'; - push @columns, 'solution' unless $running; - my $pb = $self->problems->find($id, {columns => \@columns, prefetch => 'owner'}); - my @limits = map { +{ - format => $_->format, - timeout => $_->timeout, - } } $self->limits->search({problem => $id}); - my $open; - $open = $self->opens->find_or_create({ - contest => $contest, - problem => $id, - owner => $user, - time => time, - }) if $running; - $contest &&= $self->contest($contest); - +{ - $pb->get_columns, - @limits ? (limits => \@limits) : (), - owner_name => $pb->owner->name, - cansubmit => !$contest || !$contest->is_finished, - $running ? ( - contest_start => $contest->start, - contest_stop => $contest->stop, - open_time => $open->time - ) : (), - } -} - sub problem_entry { - my ($self, $id, $contest, $user) = @_; - $contest &&= $self->contest_entry($contest); - my $ret = $self->query(problem_entry_sth => $id)->hash; - $self->add_names($ret); - my $limits = $self->query(limits_sth => $id)->hashes; + my ($id, $contest) = @_; + $contest = contest_entry ($contest) if $contest; + my $ret = add_names query(problem_entry_sth => $id)->hash; + my $limits = query(limits_sth => $id)->hashes; $ret->{limits} = $limits if @$limits; if ($contest) { $ret->{contest_start} = $contest->{start}; $ret->{contest_stop} = $contest->{stop}; + delete $ret->{solution} } $ret } -sub contest_list_orig { - my ($self, %args) = @_; - my $rs = $self->contests->search(undef, {columns => CONTEST_PUBLIC_COLUMNS, order_by => {-desc => 'start'}, prefetch => 'owner'}); - $rs = $rs->search({owner => $args{owner}}) if $args{owner}; - my %params; - for ($rs->all) { - my $state = $_->is_pending ? 'pending' : $_->is_running ? 'running' : 'finished'; - $params{$state} //= []; - push @{$params{$state}}, { $_->get_columns, owner_name => $_->owner->name }; - } - \%params -} - -sub contest_entry_orig { - my ($self, $id) = @_; - my $ct = $self->contests->find($id,{columns => CONTEST_PUBLIC_COLUMNS}); - +{ $ct->get_columns, started => !$ct->is_pending, finished => $ct->is_finished, owner_name => $ct->owner->name } -} - sub contest_list { - my ($self) = @_; - my $ret = $self->query('contest_list_sth')->hashes; - $self->add_names($ret); + my $ret = add_names query('contest_list_sth')->hashes; my %ret; for (@$ret) { @@ -287,63 +148,22 @@ sub contest_list { } sub contest_entry { - my ($self, $id) = @_; - my $ret = $self->query(contest_entry_sth => $id)->hash; - $self->add_names($ret); + my ($id) = @_; + add_names query(contest_entry_sth => $id)->hash; } sub contest_full { - my ($self, $id) = @_; - scalar $self->query(contest_full_sth => $id)->hash; + my ($id) = @_; + scalar query(contest_full_sth => $id)->hash; } sub contest_has_problem { - my ($self, $contest, $problem) = @_; - $self->query('contest_has_problem_sth')->flat -} - -sub job_list_orig { - my ($self, %args) = @_; - $args{page} //= 1; - my $rs = $self->jobs->search(undef, {order_by => {-desc => 'me.id'}, prefetch => ['problem', 'owner', 'contest'], rows => JOBS_PER_PAGE, page => $args{page}}); - $rs = $rs->search({contest => $args{contest} || undef}) if exists $args{contest}; - $rs = $rs->search({'me.private'=> 0}) unless $args{private}; - $rs = $rs->search({'me.owner' => $args{owner}}) if $args{owner}; - $rs = $rs->search({problem => $args{problem}}) if $args{problem}; - $rs = $rs->search({result => $args{result}}) if defined $args{result}; - return { - log => [map { - my %params = $_->get_columns; - $params{owner_name} = $_->owner->name; - $params{problem_name} = $_->problem->name; - $params{contest_name} = $_->contest->name if $params{contest}; - $params{results} &&= decode_json $params{results}; - $params{size} = length $params{source}; - delete $params{source}; - \%params - } $rs->all], - current_page => $rs->pager->current_page, - maybe previous_page => $rs->pager->previous_page, - maybe next_page => $rs->pager->next_page, - maybe last_page => $rs->pager->last_page, - } -} - -sub job_entry_orig { - my ($self, $id) = @_; - my $job = $self->jobs->find($id, {prefetch => ['problem', 'owner', 'contest']}); - my %params = $job->get_columns; - $params{owner_name} = $job->owner->name; - $params{problem_name} = $job->problem->name; - $params{contest_name} = $job->contest->name if $params{contest}; - $params{results} &&= decode_json $params{results}; - $params{size} = length $params{source}; - delete $params{source}; - \%params + my ($contest, $problem) = @_; + query('contest_has_problem_sth', $contest, $problem)->flat } sub job_list { - my ($self, %args) = @_; + my (%args) = @_; $args{page} //= 1; my %where = ( maybe contest => $args{contest}, @@ -353,16 +173,15 @@ sub job_list { ); $where{private} = 0 unless $args{private}; - my $rows = $self->{dbis}->select('job_entry', 'COUNT(*)', \%where)->list; + my $rows = $db->select('job_entry', 'COUNT(*)', \%where)->list; my $pages = int (($rows + JOBS_PER_PAGE - 1) / JOBS_PER_PAGE); - my ($stmt, @bind) = $self->{dbis}->abstract->select('job_entry', '*', \%where, {-desc => 'id'}); - my $jobs = $self->{dbis}->query("$stmt LIMIT " . JOBS_PER_PAGE . ' OFFSET ' . ($args{page} - 1) * JOBS_PER_PAGE, @bind)->hashes; + my ($stmt, @bind) = $db->abstract->select('job_entry', '*', \%where, {-desc => 'id'}); + my $jobs = $db->query("$stmt LIMIT " . JOBS_PER_PAGE . ' OFFSET ' . ($args{page} - 1) * JOBS_PER_PAGE, @bind)->hashes; my %ret = ( - log => $jobs, + log => add_names $jobs, current_page => $args{page}, last_page => $pages, ); - $self->add_names($ret{log}); $ret{previous_page} = $args{page} - 1 if $args{page} - 1; $ret{next_page} = $args{page} + 1 if $args{page} < $pages; @@ -370,29 +189,28 @@ sub job_list { } sub job_entry { - my ($self, $id) = @_; - my $ret = $self->query(job_entry_sth => $id)->hash; - $ret->{results} &&= decode_json $ret->{results}; - $self->add_names($ret); + my ($id) = @_; + my $ret = add_names query(job_entry_sth => $id)->hash; + $ret->{results} = decode_json $ret->{results} if $ret->{results}; + $ret } sub job_full { - my ($self, $id) = @_; - scalar $self->query(job_full_sth => $id)->hash + my ($id) = @_; + scalar query(job_full_sth => $id)->hash } sub create_job { - my ($self, %args) = @_; - $self->{dbis}->update('users', {lastjob => time}); + my (%args) = @_; + $db->update('users', {lastjob => time}); purge '/log/'; - scalar $self->{dbis}->insert('jobs', \%args, {returning => 'id'})->list + scalar $db->insert('jobs', \%args, {returning => 'id'})->list } sub calc_score { my ($mxscore, $time, $tries, $totaltime) = @_; my $score = $mxscore; - $time = 0 if $time < 0; - $time = 300 if $time > $totaltime; + $time = 300 if $time > $totaltime; # uncoverable branch true does not happen anymore (only possible if opens are broken) $score = ($totaltime - $time) / $totaltime * $score; $score -= $tries / 10 * $mxscore; $score = $mxscore * 3 / 10 if $score < $mxscore * 3 / 10; @@ -400,26 +218,25 @@ sub calc_score { } sub standings { - my ($self, $ct) = @_; - $ct = $self->contest_entry($ct); + my ($ct) = @_; + $ct = contest_entry $ct; - my @problems = $self->query(contest_problems_sth => $ct->{id})->flat; - my $pblist = $self->problem_list; - my %values = $self->query('problem_values_sth')->map; -# $values{$_} = $values{$_}->{value} for keys %values; + my @problems = query(contest_problems_sth => $ct->{id})->flat; + my $pblist = problem_list; + my %values = query('problem_values_sth')->map; my (%scores, %tries, %opens); - my $opens = $self->query(opens_sth => $ct->{id}); + my $opens = query(opens_sth => $ct->{id}); while ($opens->into(my ($problem, $owner, $time))) { $opens{$problem, $owner} = $time; } - my $jobs = $self->{dbis}->select('job_entry', '*', {contest => $ct->{id}}, 'id'); + my $jobs = $db->select('job_entry', '*', {contest => $ct->{id}}, 'id'); while (my $job = $jobs->hash) { my $open = $opens{$job->{problem}, $job->{owner}} // $ct->{start}; my $time = $job->{date} - $open; - next if $time < 0; + next if $time < 0; # uncoverable branch true job sent before contest is deprecated my $value = $values{$job->{problem}}; my $factor = $job->{result} ? 0 : 1; $factor = $1 / 100 if $job->{result_text} =~ /^(\d+ )/s; @@ -430,7 +247,7 @@ sub standings { my $user = $_; +{ user => $user, - user_name => $self->object_name(users => $user), + user_name => object_name(users => $user), score => sum (values %{$scores{$user}}), scores => [map { $scores{$user}{$_} // '-'} @problems], } @@ -440,43 +257,12 @@ sub standings { $st[$_]->{rank} = $st[$_ - 1]->{rank} + ($st[$_]->{score} < $st[$_ - 1]->{score}) for 1 .. $#st; +{ st => \@st, - problems => [map { [ $_, $self->object_name(problems => $_)] } @problems], - } -} - -sub update_status_orig { - my ($self) = @_; - my @jobs = $self->jobs->search({'me.private' => 0}, {cache => 1, prefetch => 'problem', order_by => 'me.id'})->all; - - my %private; - my %hash; - for (@jobs) { - my $pb = $_->get_column('problem'); - $private{$pb} //= $_->problem->private; - next if $private{$pb}; - $hash{$pb, $_->get_column('owner')} = [$_->id, $_->result ? 0 : 1]; + problems => [map { [ $_, object_name(problems => $_)] } @problems], } - - my @problem_statuses = map { [split ($;), @{$hash{$_}} ] } keys %hash; - - my @contest_statuses = map { - my $contest = $_->id; - map { [$contest, $_->{user}, $_->{score}, $_->{rank}] } $_->standings - } $self->contests->all; - - my $txn = sub { - $self->problem_statuses->delete; - $self->problem_statuses->populate([[qw/problem owner job solved/], @problem_statuses]); - $self->contest_statuses->delete; - $self->contest_statuses->populate([[qw/contest owner score rank/], @contest_statuses]); - }; - - $self->txn_do($txn); } sub update_status { - my ($self) = @_; - my $jobs = $self->{dbis}->select('jobs', 'id,owner,problem,result', {}, 'id'); + my $jobs = $db->select('jobs', 'id,owner,problem,result', {-not_bool => 'private'}, 'id'); my %hash; while ($jobs->into(my ($id, $owner, $problem, $result))) { @@ -487,15 +273,22 @@ sub update_status { my @contest_statuses = map { my $ct = $_; - map { [$ct, $_->{user}, $_->{score}, $_->{rank}] } @{$self->standings($ct)->{st}} - } $self->{dbis}->select('contests', 'id')->flat; - - $self->{dbis}->begin; - $self->{dbis}->delete('problem_status'); - $self->{dbis}->query('INSERT INTO problem_status (problem,owner,job,solved) VALUES (??)', @$_) for @problem_statuses; - $self->{dbis}->delete('contest_status'); - $self->{dbis}->query('INSERT INTO contest_status (contest,owner,score,rank) VALUES (??)', @$_) for @contest_statuses; - $self->{dbis}->commit + map { [$ct, $_->{user}, $_->{score}, $_->{rank}] } @{standings($ct)->{st}} + } $db->select('contests', 'id')->flat; + + $db->begin; + $db->delete('problem_status'); + $db->query('INSERT INTO problem_status (problem,owner,job,solved) VALUES (??)', @$_) for @problem_statuses; + $db->delete('contest_status'); + $db->query('INSERT INTO contest_status (contest,owner,score,rank) VALUES (??)', @$_) for @contest_statuses; + $db->commit +} + +sub rerun_job { + my ($id) = @_; + $db->query(rerun_job_sth => $id); + purge '/log/'; + purge "/log/$id"; } my @PURGE_HOSTS = exists $ENV{PURGE_HOSTS} ? split ' ', $ENV{PURGE_HOSTS} : ();