X-Git-Url: http://mj.ucw.cz/gitweb/?a=blobdiff_plain;f=submit%2Fcontest;h=3f9e6c39af4936fe5035dbded64bfe43894d0950;hb=fc23667b6f78c96c83e15a516602dc74f2773e24;hp=e9ebd68c69440a449d9f2b1bde6075e97b5b8d74;hpb=65ff89923436ec785ce2b35d26827aa95ce8c3ce;p=eval.git diff --git a/submit/contest b/submit/contest index e9ebd68..3f9e6c3 100755 --- a/submit/contest +++ b/submit/contest @@ -8,11 +8,14 @@ use warnings; BEGIN { defined $ENV{"MO_ROOT"} or die "Please set MO_ROOT to the contest root directory first.\n"; } -use lib $ENV{"MO_ROOT"} . "/submit"; +use lib $ENV{"MO_ROOT"} . "/lib/perl5"; use lib $ENV{"MO_ROOT"} . "/submit/lib/perl5"; use MO::Submit; use Sherlock::Object; +use POSIX; +use IO::File; +use File::stat; use Gtk2 -init; my $conn = new MO::Submit; @@ -20,8 +23,12 @@ my $conn = new MO::Submit; ### GUI INITIALIZATION ### sub init_refresh(); -sub timed_refresh(); -sub refresh($); +sub start_refresh_timer($); +sub stop_refresh_timer(); +my $force_refresh = 0; +sub refresh(); + +sub submit($); my $busy_cursor = Gtk2::Gdk::Cursor->new('watch'); @@ -30,7 +37,7 @@ my $window = Gtk2::Window->new('toplevel'); $window->signal_connect("delete-event" => sub { Gtk2->main_quit }); $window->set_title($conn->{"Contest"} . " Submitter"); $window->set_wmclass("submitter", "Submitter"); -$window->set_default_size(320, 400); +$window->set_default_size(640, 480); # The title label my $title_lab = Gtk2::Label->new; @@ -38,15 +45,15 @@ $title_lab->set_markup("" . $conn->{"Contest"} . "") # The row of buttons my $b_submit = Gtk2::Button->new('Submit'); -$b_submit->signal_connect(clicked => sub { Gtk2->main_quit }); +$b_submit->signal_connect(clicked => sub { submit(0) }); $b_submit->set_sensitive(0); my $b_check = Gtk2::Button->new('Check'); -$b_check->signal_connect(clicked => sub { Gtk2->main_quit }); +$b_check->signal_connect(clicked => sub { submit(1) }); $b_check->set_sensitive(0); my $b_refresh = Gtk2::Button->new('Refresh'); -$b_refresh->signal_connect(clicked => sub { refresh(0) }); +$b_refresh->signal_connect(clicked => sub { start_refresh_timer(1) }); my $button_box = Gtk2::HBox->new; $button_box->pack_start_defaults($b_submit); @@ -55,16 +62,14 @@ $button_box->pack_start_defaults($b_refresh); $button_box->set_border_width(5); # The list of tasks -my $task_store = Gtk2::ListStore->new('Glib::String', 'Glib::String', 'Glib::String'); +my $task_store = Gtk2::ListStore->new('Glib::String', 'Glib::String'); my $task_view = Gtk2::TreeView->new($task_store); my $task_renderer = Gtk2::CellRendererText->new; my $task_col1 = Gtk2::TreeViewColumn->new_with_attributes("Task", $task_renderer, "text", 0); $task_view->append_column($task_col1); -my $task_col2 = Gtk2::TreeViewColumn->new_with_attributes("Part", $task_renderer, "text", 1); +my $task_col2 = Gtk2::TreeViewColumn->new_with_attributes("Status", $task_renderer, "text", 1); $task_view->append_column($task_col2); -my $task_col3 = Gtk2::TreeViewColumn->new_with_attributes("Status", $task_renderer, "text", 2); -$task_view->append_column($task_col3); $task_view->set_headers_visible(0); my $task_scroll = Gtk2::ScrolledWindow->new; @@ -82,7 +87,6 @@ $task_sel->signal_connect(changed => sub { my $iter = $_[0]->get_selected; if ($iter) { $selected_task = $task_store->get($iter, 0); - print "Selected $selected_task\n"; $b_submit->set_sensitive(1); $b_check->set_sensitive(1); } else { @@ -106,16 +110,16 @@ $window->add($vbox); $window->signal_connect("expose-event" => sub { init_refresh(); return 0; }); $window->show_all; -Glib::Timeout->add(5000, \&timed_refresh); -Gtk2->main; -exit 0; - -### ACTIONS ### +### REFRESHING ### my $last_status_id; -sub status($) { +sub msg($) { print "GUI: ", $_[0], "\n" if $conn->{"Trace"}; +} + +sub status($) { + msg($_[0]); defined $last_status_id and $status_bar->remove($bar_ctx, $last_status_id); $last_status_id = $status_bar->push($bar_ctx, shift @_); } @@ -136,43 +140,96 @@ my $window_inited = 0; sub init_refresh() { if (!$window_inited) { - refresh(1); + $force_refresh = 1; + start_refresh_timer(1); $window_inited = 1; } return 1; } +my $refresh_timer_id; + sub timed_refresh() { - refresh(0); # FIXME: If-modified-since version? + refresh(); # FIXME: If-modified-since version? return 1; # We wish to re-run the timer } +sub start_refresh_timer($) { + my ($go) = @_; + stop_refresh_timer(); + refresh() if $go; + $refresh_timer_id = Glib::Timeout->add($conn->{"RefreshTimer"}, \&timed_refresh); +} + +sub stop_refresh_timer() { + if (defined $refresh_timer_id) { + Glib::Source->remove($refresh_timer_id); + $refresh_timer_id = undef; + } +} + my $task_status_object; -my $task_parts = {}; +my @task_parts = (); +my @task_stat = (); sub recalc_task_list() { - $task_parts = {}; - $task_store->clear; + my @new_tp = (); + my @new_stat = (); foreach my $t ($task_status_object->getarray("(T")) { my $task = $t->get("T"); foreach my $p ($t->getarray("(P")) { my $part = $p->get("P"); - $task_parts->{$task}->{$part} = $p; + my $taskpart = ($task eq $part) ? $task : "$task/$part"; + push @new_tp, $taskpart; my $status = "---"; + my $current_ver = $p->get("V"); + foreach my $v ($p->getarray("(V")) { + if ($v->get("V") == $current_ver) { + my $time = strftime("%H:%M:%S", localtime $v->get("T")); + $status = "OK (" . + "$part." . $v->get("X") . ", " . + $v->get("L") . " bytes, " . + $v->get("S") . " $time)"; + last; + } + } + push @new_stat, $status; + } + } + + if (join("\n", @new_tp) ne join("\n", @task_parts)) { + # The tasks have changed, repopulate the whole structure + $task_store->clear; + my @s = @new_stat; + foreach my $taskpart (@new_tp) { my $iter = $task_store->append; $task_store->set($iter, - 0, $task, - 1, ($task eq $part ? "" : $part), - 2, $status); + 0, $taskpart, + 1, shift @s); } + } else { + # Update the task status + my @s = @task_stat; + my @ns = @new_stat; + $task_store->foreach(sub { + my ($obj, $path, $iter) = @_; + if ($s[0] ne $ns[0]) { + $task_store->set($iter, 1, $ns[0]); + } + shift @s; + shift @ns; + return 0; + }); } + + @task_parts = @new_tp; + @task_stat = @new_stat; } -sub refresh($) +sub refresh() { - my $force = shift @_; - if (!$conn->is_connected || $force) { + if (!$conn->is_connected || $force_refresh) { busy("Connecting to server..."); if ($conn->connect) { ready("Connected successfully"); @@ -191,12 +248,268 @@ sub refresh($) } else { $task_status_object = $r; recalc_task_list; + $force_refresh = 0; ready("Ready"); } } - if (!$conn->is_connected && !$force) { + if (!$conn->is_connected && !$force_refresh) { # Retry $conn->log("Retrying"); - refresh(1); + refresh(); } } + +### SUBMITTING ### + +my $subwin; +my $chooser; +my $subwin_vbox; +my $subwin_label; +my $bbutton_box; +my $submitting_label; +my $status_label; +my $check_only; +my $submit_filename; +my $submit_extension; +my %submit_fn_cache = (); + +sub end_submit($) { + my ($close) = @_; + $subwin->destroy if $close; + start_refresh_timer(0); + ### FIXME: ... and refresh status +} + +sub finish_submit() { + my $button = Gtk2::Button->new('Return'); + $button->signal_connect(clicked => sub { end_submit(1) }); + + $bbutton_box = Gtk2::HButtonBox->new; + $bbutton_box->pack_start_defaults($button); + $subwin_vbox->pack_start($bbutton_box, 0, 0, 10); + + ready("Ready"); + $subwin->show_all; + $subwin->window->set_cursor(undef); +} + +sub submit_ok() { + $status_label->set_markup("Submitted OK"); + $submitting_label->set_markup("The task has been successfully submitted."); + refresh(); + finish_submit(); +} + +sub submit_failed($) { + my ($msg) = @_; + $status_label->set_markup("Submit failed"); + $submitting_label->set_markup("$msg"); + finish_submit(); +} + +sub run_submit() { + my ($task, $part) = split /\//, $selected_task; + defined $part or $part = $task; + + if (defined $conn->{"History"}) { + busy("Submitting locally to " . $conn->{"History"}); + my $err = $conn->local_submit($task, $part, $submit_extension, $submit_filename); + if (defined $err) { + submit_failed("Recording to local history failed\n($err)"); + return; + } + } + + if ($conn->is_connected) { + busy("Checking server status..."); + my $r = new Sherlock::Object("!" => "STATUS"); ### FIXME: use a NOP command + $r = $conn->request($r); + } + if (!$conn->is_connected) { + busy("Reconnecting to server..."); + if (!$conn->connect) { + ready($conn->{"error"}); + submit_failed("Unable to connect to the server"); ### FIXME: Mention local submit + return; + } + } + busy("Submitting..."); + + my $fh = new IO::File($submit_filename); + if (!$fh) { + submit_failed("Unable to open $submit_filename\n($!)"); + return; + } + my $stat = File::stat::populate($fh->stat); + if (!$stat) { + submit_failed("Unable to stat $submit_filename\n($!)"); + return; + } + my $size = $stat->size; + + my $r = new Sherlock::Object("!" => "SUBMIT", "T" => $task, "P" => $part, "X" => $submit_extension, "S" => $size); + $r = $conn->request($r); + if (!defined($r)) { + submit_failed("Connection to the server lost"); + return; + } elsif ($r->get("-")) { + submit_failed($r->get("-")); + return; + } + + $r = $conn->send_file($fh, $size); + if (!defined($r)) { + submit_failed("Connection to the server lost"); + return; + } elsif ($r->get("-")) { + submit_failed($r->get("-")); + return; + } + + close $fh; + submit_ok(); +} + +sub checks_failed($) { + my ($msg) = @_; + + $status_label->set_markup("Check failed"); + + my $text_buffer = Gtk2::TextBuffer->new; + $text_buffer->set_text($msg); + + my $text_view = Gtk2::TextView->new_with_buffer($text_buffer); + $text_view->set_editable(0); + $text_view->set_cursor_visible(0); + + my $text_scroll = Gtk2::ScrolledWindow->new; + $text_scroll->set_policy("automatic", "automatic"); + $text_scroll->add($text_view); + + my $text_frame = Gtk2::Frame->new("Checker log"); + $text_frame->add($text_scroll); + + $submitting_label->destroy; + $subwin_vbox->pack_start_defaults($text_frame); + + finish_submit(); +} + +sub checks_ok() { + if ($check_only) { + $status_label->set_markup("Checked successfully"); + $submitting_label->set_markup("The task has passed the checks."); + finish_submit(); + return; + } + + ### FIXME: Record to local history here + + $status_label->set_markup("Submitting"); + $subwin->show_all; + + # Continue when everything is displayed + Glib::Idle->add(sub { + $window->Gtk2::Gdk::flush; + run_submit(); + return 0; + }); +} + +sub run_checks() { + ($submit_extension) = ($submit_filename =~ /\.([^.]+)$/); + if (!$submit_extension) { + checks_failed("The filename does not have a valid extension"); + return; + } + if (!$conn->{"Checks"}) { + checks_ok(); + return; + } + my $root = $conn->{"root"}; + my ($task, $part) = split /\//, $selected_task; + defined $part or $part = ""; + my $verdict = `$root/bin/check -s "$submit_filename" $task $part 2>&1`; + if ($?) { + checks_failed($verdict); + } else { + checks_ok(); + } +} + +sub do_submit() { + $submit_filename = $chooser->get_filename; + $submit_fn_cache{$selected_task} = $submit_filename; + msg "Selected $submit_filename"; + defined $submit_filename or return; + -f $submit_filename or return; + + $chooser->destroy; + $bbutton_box->destroy; + + $status_label->set_markup("Checking"); + + $submitting_label = Gtk2::Label->new("Please wait..."); + $subwin_vbox->pack_start_defaults($submitting_label); + $subwin->show_all; + $subwin->window->set_cursor($busy_cursor); + + # Continue when everything is displayed + Glib::Idle->add(sub { + $window->Gtk2::Gdk::flush; + run_checks(); + return 0; + }); +} + +sub submit($) { + $check_only = shift @_; + + stop_refresh_timer(); + + $subwin = Gtk2::Window->new('toplevel'); + $subwin->set_default_size(640, 480); + $subwin->set_modal(1); + $subwin->set_transient_for($window); + $subwin->set_destroy_with_parent(1); + $subwin->set_title("Submit task $selected_task"); + $subwin->set_wmclass("submitter", "Submitter"); + $subwin->signal_connect("delete-event" => sub { end_submit(0); return 0; }); + + my $bb_submit = Gtk2::Button->new($check_only ? 'Check' : 'Submit'); + $bb_submit->signal_connect(clicked => \&do_submit); + #$bb_submit->set_sensitive(0); + + my $bb_cancel = Gtk2::Button->new('Cancel'); + $bb_cancel->signal_connect(clicked => sub { end_submit(1) }); + + $bbutton_box = Gtk2::HButtonBox->new; + $bbutton_box->pack_start_defaults($bb_submit); + $bbutton_box->pack_start_defaults($bb_cancel); + $bbutton_box->set_border_width(5); + + my $subwin_label = Gtk2::Label->new; + $subwin_label->set_markup("" . ($check_only ? "Checking" : "Submitting") . " $selected_task"); + + $status_label = Gtk2::Label->new; + $status_label->set_markup("Please select file to " . ($check_only ? "check" : "submit") . ""); + + $chooser = Gtk2::FileChooserWidget->new("open"); + $chooser->set_local_only(1); + $chooser->signal_connect("file-activated" => \&do_submit); + $chooser->set_filename($submit_fn_cache{$selected_task}) if defined $submit_fn_cache{$selected_task}; + + $subwin_vbox = Gtk2::VBox->new; + $subwin_vbox->pack_start($subwin_label, 0, 0, 10); + $subwin_vbox->pack_start($status_label, 0, 0, 10); + $subwin_vbox->pack_start_defaults($chooser); + $subwin_vbox->pack_start($bbutton_box, 0, 0, 0); + + $subwin->add($subwin_vbox); + $subwin->show_all; +} + +### MAIN ### + +Gtk2->main; +exit 0;