2 # Contest UI for the MO Submitter
3 # (c) 2007 Martin Mares <mj@ucw.cz>
9 defined $ENV{"MO_ROOT"} or die "Please set MO_ROOT to the contest root directory first.\n";
11 use lib $ENV{"MO_ROOT"} . "/lib/perl5";
12 use lib $ENV{"MO_ROOT"} . "/submit/lib/perl5";
21 my $conn = new MO::Submit;
23 ### GUI INITIALIZATION ###
26 sub start_refresh_timer($);
27 sub stop_refresh_timer();
28 my $force_refresh = 0;
33 my $busy_cursor = Gtk2::Gdk::Cursor->new('watch');
36 my $window = Gtk2::Window->new('toplevel');
37 $window->signal_connect("delete-event" => sub { Gtk2->main_quit });
38 $window->set_title($conn->{"Contest"} . " Submitter");
39 $window->set_wmclass("submitter", "Submitter");
40 $window->set_default_size(640, 480);
43 my $title_lab = Gtk2::Label->new;
44 $title_lab->set_markup("<span size='x-large'>" . $conn->{"Contest"} . "</span>");
47 my $b_submit = Gtk2::Button->new('Submit');
48 $b_submit->set_image(Gtk2::Image->new_from_stock('gtk-apply', 'button'));
49 $b_submit->signal_connect(clicked => sub { submit(0) });
50 $b_submit->set_sensitive(0);
52 my $b_check = Gtk2::Button->new('Check');
53 $b_check->set_image(Gtk2::Image->new_from_stock('gtk-find', 'button'));
54 $b_check->signal_connect(clicked => sub { submit(1) });
55 $b_check->set_sensitive(0);
57 my $b_refresh = Gtk2::Button->new('Refresh');
58 $b_refresh->set_image(Gtk2::Image->new_from_stock('gtk-refresh', 'button'));
59 $b_refresh->signal_connect(clicked => sub { start_refresh_timer(1) });
61 my $button_box = Gtk2::HBox->new;
62 $button_box->pack_start_defaults($b_submit);
63 $button_box->pack_start_defaults($b_check);
64 $button_box->pack_start_defaults($b_refresh);
65 $button_box->set_border_width(5);
68 my $task_store = Gtk2::ListStore->new('Glib::String', 'Glib::String');
70 my $task_view = Gtk2::TreeView->new($task_store);
71 my $task_renderer = Gtk2::CellRendererText->new;
72 my $task_col1 = Gtk2::TreeViewColumn->new_with_attributes("Task", $task_renderer, "text", 0);
73 $task_view->append_column($task_col1);
74 my $task_col2 = Gtk2::TreeViewColumn->new_with_attributes("Status", $task_renderer, "text", 1);
75 $task_view->append_column($task_col2);
76 $task_view->set_headers_visible(0);
78 my $task_scroll = Gtk2::ScrolledWindow->new;
79 $task_scroll->set_policy("automatic", "automatic");
80 $task_scroll->add($task_view);
81 $task_scroll->set_border_width(5);
83 my $task_frame = Gtk2::Frame->new("Tasks");
84 $task_frame->add($task_scroll);
87 my $task_sel = $task_view->get_selection;
88 $task_sel->set_mode('single');
89 $task_sel->signal_connect(changed => sub {
90 my $iter = $_[0]->get_selected;
92 $selected_task = $task_store->get($iter, 0);
93 $b_submit->set_sensitive(1);
94 $b_check->set_sensitive(1);
96 $selected_task = undef;
97 print "Deselected task\n";
98 $b_submit->set_sensitive(0);
99 $b_check->set_sensitive(0);
103 my $status_bar = Gtk2::Statusbar->new;
104 my $bar_ctx = $status_bar->get_context_id('xyzzy');
106 my $vbox = Gtk2::VBox->new;
107 $vbox->pack_start($title_lab, 0, 0, 10);
108 $vbox->pack_start($task_frame, 1, 1, 0);
109 $vbox->pack_start($button_box, 0, 0, 0);
110 $vbox->pack_start($status_bar, 0, 0, 0);
113 $window->signal_connect("expose-event" => sub { init_refresh(); return 0; });
121 print "GUI: ", $_[0], "\n" if $conn->{"Trace"};
126 defined $last_status_id and $status_bar->remove($bar_ctx, $last_status_id);
127 $last_status_id = $status_bar->push($bar_ctx, shift @_);
132 $window->window->set_cursor($busy_cursor);
133 $window->Gtk2::Gdk::flush;
138 $window->window->set_cursor(undef);
139 $window->Gtk2::Gdk::flush;
142 my $window_inited = 0;
145 if (!$window_inited) {
147 start_refresh_timer(1);
153 my $refresh_timer_id;
158 return 1; # We wish to re-run the timer
161 sub start_refresh_timer($) {
163 stop_refresh_timer();
165 $refresh_timer_id = Glib::Timeout->add($conn->{"RefreshTimer"}, \&timed_refresh);
168 sub stop_refresh_timer() {
169 if (defined $refresh_timer_id) {
170 Glib::Source->remove($refresh_timer_id);
171 $refresh_timer_id = undef;
175 my $task_status_object;
179 sub recalc_task_list() {
182 foreach my $t ($task_status_object->getarray("(T")) {
183 my $task = $t->get("T");
184 foreach my $p ($t->getarray("(P")) {
185 my $part = $p->get("P");
186 my $taskpart = ($task eq $part) ? $task : "$task/$part";
187 push @new_tp, $taskpart;
189 my $current_ver = $p->get("V");
190 foreach my $v ($p->getarray("(V")) {
191 if ($v->get("V") == $current_ver) {
192 my $time = strftime("%H:%M:%S", localtime $v->get("T"));
194 "$part." . $v->get("X") . ", " .
195 $v->get("L") . " bytes, " .
196 $v->get("S") . " $time)";
200 push @new_stat, $status;
204 if (join("\n", @new_tp) ne join("\n", @task_parts)) {
205 # The tasks have changed, repopulate the whole structure
208 foreach my $taskpart (@new_tp) {
209 my $iter = $task_store->append;
210 $task_store->set($iter,
215 # Update the task status
218 $task_store->foreach(sub {
219 my ($obj, $path, $iter) = @_;
220 if ($s[0] ne $ns[0]) {
221 $task_store->set($iter, 1, $ns[0]);
229 @task_parts = @new_tp;
230 @task_stat = @new_stat;
235 if (!$conn->is_connected || $force_refresh) {
236 busy("Connecting to server...");
237 if ($conn->connect) {
238 ready("Connected successfully");
240 ready($conn->{"error"});
243 if ($conn->is_connected) {
244 busy("Updating status...");
245 my $r = new Sherlock::Object("!" => "STATUS");
246 $r = $conn->request($r);
248 ready($conn->{"error"});
249 } elsif ($r->get("-")) {
252 $task_status_object = $r;
258 if (!$conn->is_connected && !$force_refresh) {
260 $conn->log("Retrying");
273 my $submitting_label;
278 my $submit_extension;
279 my %submit_fn_cache = ();
283 $subwin->destroy if $close;
284 start_refresh_timer(1);
287 sub finish_submit() {
288 my $button = Gtk2::Button->new_from_stock('gtk-close');
289 $button->signal_connect(clicked => sub { end_submit(1) });
291 $bbutton_box = Gtk2::HButtonBox->new;
292 $bbutton_box->pack_start_defaults($button);
293 $subwin_vbox->pack_start($bbutton_box, 0, 0, 10);
297 $subwin->window->set_cursor(undef);
301 $status_label->set_markup("<span size='large'>Submitted OK</span>");
302 $submitting_label->set_markup("<span size='large'>The task has been successfully submitted.</span>");
307 sub submit_failed($) {
309 $status_label->set_markup("<span size='large'>Submit failed</span>");
310 $submitting_label->set_markup("<span size='large'>$msg</span>");
315 my ($task, $part) = split /\//, $selected_task;
316 defined $part or $part = $task;
318 if (defined $conn->{"History"}) {
319 busy("Submitting locally to " . $conn->{"History"});
320 my $err = $conn->write_history($task, $part, $submit_extension, $submit_filename);
322 submit_failed("Recording to local history failed\n($err)");
327 if ($conn->is_connected) {
328 busy("Checking server status...");
329 my $r = new Sherlock::Object("!" => "NOP");
330 $r = $conn->request($r);
332 if (!$conn->is_connected) {
333 busy("Reconnecting to server...");
334 if (!$conn->connect) {
335 ready($conn->{"error"});
336 submit_failed("Unable to connect to the server");
340 busy("Submitting...");
342 my $fh = new IO::File($submit_filename);
344 submit_failed("Unable to open $submit_filename\n($!)");
347 my $stat = File::stat::populate($fh->stat);
349 submit_failed("Unable to stat $submit_filename\n($!)");
352 my $size = $stat->size;
354 my $r = new Sherlock::Object("!" => "SUBMIT", "T" => $task, "P" => $part, "X" => $submit_extension, "S" => $size);
355 $r = $conn->request($r);
357 submit_failed("Connection to the server lost");
359 } elsif ($r->get("-")) {
360 submit_failed($r->get("-"));
364 $r = $conn->send_file($fh, $size);
366 submit_failed("Connection to the server lost");
368 } elsif ($r->get("-")) {
369 submit_failed($r->get("-"));
379 $status_label->set_markup("<span size='large'>Checked successfully</span>");
380 $submitting_label->set_markup("<span size='large'>The task has passed the checks.</span>");
385 $status_label->set_markup("<span size='large'>Submitting</span>");
388 # Continue when everything is displayed
389 Glib::Idle->add(sub {
390 $window->Gtk2::Gdk::flush;
396 sub checks_override() {
397 $submitting_label = Gtk2::Label->new("Please wait...");
398 $subwin_vbox->pack_start_defaults($submitting_label);
400 $subwin->window->set_cursor($busy_cursor);
401 $bbutton_box->destroy;
402 $text_frame->destroy;
406 sub checks_failed($) {
409 $status_label->set_markup("<span size='large'>Check failed</span>");
410 $submitting_label->destroy;
412 my $text_buffer = Gtk2::TextBuffer->new;
413 $text_buffer->set_text($msg);
415 my $text_view = Gtk2::TextView->new_with_buffer($text_buffer);
416 $text_view->set_editable(0);
417 $text_view->set_cursor_visible(0);
419 my $text_scroll = Gtk2::ScrolledWindow->new;
420 $text_scroll->set_policy("automatic", "automatic");
421 $text_scroll->add($text_view);
423 $text_frame = Gtk2::Frame->new("Checker log");
424 $text_frame->add($text_scroll);
426 $subwin_vbox->pack_start_defaults($text_frame);
428 if ($check_only || !$conn->{"AllowOverride"}) {
433 my $close_button = Gtk2::Button->new_from_stock('gtk-close');
434 $close_button->signal_connect(clicked => sub { end_submit(1) });
436 my $anyway_button = Gtk2::Button->new('Submit anyway');
437 $anyway_button->signal_connect(clicked => \&checks_override);
439 $bbutton_box = Gtk2::HButtonBox->new;
440 $bbutton_box->pack_start_defaults($anyway_button);
441 $bbutton_box->pack_start_defaults($close_button);
442 $bbutton_box->set_border_width(5);
443 $subwin_vbox->pack_start($bbutton_box, 0, 0, 10);
447 $subwin->window->set_cursor(undef);
451 ($submit_extension) = ($submit_filename =~ /\.([^.]+)$/);
452 if (!$submit_extension) {
453 checks_failed("The filename does not have a valid extension");
456 if (!$conn->{"Checks"}) {
460 my $root = $conn->{"root"};
461 my ($task, $part) = split /\//, $selected_task;
462 defined $part or $part = "";
463 my $verdict = `$root/bin/check -s "$submit_filename" $task $part 2>&1`;
465 checks_failed($verdict);
472 $submit_filename = $chooser->get_filename;
473 $submit_fn_cache{$selected_task} = $submit_filename;
474 msg "Selected $submit_filename";
475 defined $submit_filename or return;
476 -f $submit_filename or return;
479 $bbutton_box->destroy;
481 $status_label->set_markup("<span size='large'>Checking</span>");
483 $submitting_label = Gtk2::Label->new("Please wait...");
484 $subwin_vbox->pack_start_defaults($submitting_label);
486 $subwin->window->set_cursor($busy_cursor);
488 # Continue when everything is displayed
489 Glib::Idle->add(sub {
490 $window->Gtk2::Gdk::flush;
497 $check_only = shift @_;
499 stop_refresh_timer();
501 $subwin = Gtk2::Window->new('toplevel');
502 $subwin->set_default_size(640, 480);
503 $subwin->set_modal(1);
504 $subwin->set_transient_for($window);
505 $subwin->set_destroy_with_parent(1);
506 $subwin->set_title("Submit task $selected_task");
507 $subwin->set_wmclass("submitter", "Submitter");
508 $subwin->signal_connect("delete-event" => sub { end_submit(0); return 0; });
510 my $bb_submit = Gtk2::Button->new($check_only ? 'Check' : 'Submit');
511 $bb_submit->set_image(Gtk2::Image->new_from_stock('gtk-apply', 'button'));
512 $bb_submit->signal_connect(clicked => \&do_submit);
514 my $bb_cancel = Gtk2::Button->new_from_stock('gtk-cancel');
515 $bb_cancel->signal_connect(clicked => sub { end_submit(1) });
517 $bbutton_box = Gtk2::HButtonBox->new;
518 $bbutton_box->pack_start_defaults($bb_submit);
519 $bbutton_box->pack_start_defaults($bb_cancel);
520 $bbutton_box->set_border_width(5);
522 my $subwin_label = Gtk2::Label->new;
523 $subwin_label->set_markup("<span size='x-large'>" . ($check_only ? "Checking" : "Submitting") . " $selected_task</span>");
525 $status_label = Gtk2::Label->new;
526 $status_label->set_markup("<span size='large'>Please select file to " . ($check_only ? "check" : "submit") . "</span>");
528 $chooser = Gtk2::FileChooserWidget->new("open");
529 $chooser->set_local_only(1);
530 $chooser->signal_connect("file-activated" => \&do_submit);
531 $chooser->set_filename($submit_fn_cache{$selected_task}) if defined $submit_fn_cache{$selected_task};
533 $subwin_vbox = Gtk2::VBox->new;
534 $subwin_vbox->pack_start($subwin_label, 0, 0, 10);
535 $subwin_vbox->pack_start($status_label, 0, 0, 10);
536 $subwin_vbox->pack_start_defaults($chooser);
537 $subwin_vbox->pack_start($bbutton_box, 0, 0, 0);
539 $subwin->add($subwin_vbox);