]> mj.ucw.cz Git - eval.git/blobdiff - submit/contest
Logfile of submit and check renamed to `check-log' to prevent collisions.
[eval.git] / submit / contest
index e9ebd68c69440a449d9f2b1bde6075e97b5b8d74..3f9e6c39af4936fe5035dbded64bfe43894d0950 100755 (executable)
@@ -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("<span size='x-large'>" . $conn->{"Contest"} . "</span>")
 
 # 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("<span size='large'>Submitted OK</span>");
+       $submitting_label->set_markup("<span size='large'>The task has been successfully submitted.</span>");
+       refresh();
+       finish_submit();
+}
+
+sub submit_failed($) {
+       my ($msg) = @_;
+       $status_label->set_markup("<span size='large'>Submit failed</span>");
+       $submitting_label->set_markup("<span size='large'>$msg</span>");
+       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("<span size='large'>Check failed</span>");
+
+       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("<span size='large'>Checked successfully</span>");
+               $submitting_label->set_markup("<span size='large'>The task has passed the checks.</span>");
+               finish_submit();
+               return;
+       }
+
+       ### FIXME: Record to local history here
+
+       $status_label->set_markup("<span size='large'>Submitting</span>");
+       $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("<span size='large'>Checking</span>");
+
+       $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("<span size='x-large'>" . ($check_only ? "Checking" : "Submitting") . " $selected_task</span>");
+
+       $status_label = Gtk2::Label->new;
+       $status_label->set_markup("<span size='large'>Please select file to " . ($check_only ? "check" : "submit") . "</span>");
+
+       $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;