]> mj.ucw.cz Git - eval.git/blob - submit/contest.pl
Need to switch directory when reading configuration.
[eval.git] / submit / contest.pl
1 #!/usr/bin/perl
2 # Contest UI for the MO Submitter
3 # (c) 2007 Martin Mares <mj@ucw.cz>
4
5 use strict;
6 use warnings;
7
8 BEGIN {
9         defined $ENV{"MO_ROOT"} or die "Please set MO_ROOT to the contest root directory first.\n";
10 }
11 use lib $ENV{"MO_ROOT"} . "/lib/perl5";
12 use lib $ENV{"MO_ROOT"} . "/submit/lib/perl5";
13
14 use MO::Submit;
15 use Sherlock::Object;
16 use POSIX;
17 use IO::File;
18 use File::stat;
19 use Gtk2 -init;
20
21 my $conn = new MO::Submit;
22
23 ### GUI INITIALIZATION ###
24
25 sub init_refresh();
26 sub start_refresh_timer($);
27 sub stop_refresh_timer();
28 my $force_refresh = 0;
29 sub refresh();
30
31 sub submit($);
32
33 my $busy_cursor = Gtk2::Gdk::Cursor->new('watch');
34
35 # The main window
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);
41
42 # The title label
43 my $title_lab = Gtk2::Label->new;
44 $title_lab->set_markup("<span size='x-large'>" . $conn->{"Contest"} . "</span>");
45
46 # The row of buttons
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);
51
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);
56
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) });
60
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);
66
67 # The list of tasks
68 my $task_store = Gtk2::ListStore->new('Glib::String', 'Glib::String');
69
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);
77
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);
82
83 my $task_frame = Gtk2::Frame->new("Tasks");
84 $task_frame->add($task_scroll);
85
86 my $selected_task;
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;
91         if ($iter) {
92                 $selected_task = $task_store->get($iter, 0);
93                 $b_submit->set_sensitive(1);
94                 $b_check->set_sensitive(1);
95         } else {
96                 $selected_task = undef;
97                 print "Deselected task\n";
98                 $b_submit->set_sensitive(0);
99                 $b_check->set_sensitive(0);
100         }
101 });
102
103 my $status_bar = Gtk2::Statusbar->new;
104 my $bar_ctx = $status_bar->get_context_id('xyzzy');
105
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);
111
112 $window->add($vbox);
113 $window->signal_connect("expose-event" => sub { init_refresh(); return 0; });
114 $window->show_all;
115
116 ### REFRESHING ###
117
118 my $last_status_id;
119
120 sub msg($) {
121         print "GUI: ", $_[0], "\n" if $conn->{"Trace"};
122 }
123
124 sub status($) {
125         msg($_[0]);
126         defined $last_status_id and $status_bar->remove($bar_ctx, $last_status_id);
127         $last_status_id = $status_bar->push($bar_ctx, shift @_);
128 }
129
130 sub busy($) {
131         status($_[0]);
132         $window->window->set_cursor($busy_cursor);
133         $window->Gtk2::Gdk::flush;
134 }
135
136 sub ready($) {
137         status($_[0]);
138         $window->window->set_cursor(undef);
139         $window->Gtk2::Gdk::flush;
140 }
141
142 my $window_inited = 0;
143 sub init_refresh()
144 {
145         if (!$window_inited) {
146                 $force_refresh = 1;
147                 start_refresh_timer(1);
148                 $window_inited = 1;
149         }
150         return 1;
151 }
152
153 my $refresh_timer_id;
154
155 sub timed_refresh()
156 {
157         refresh();
158         return 1;       # We wish to re-run the timer
159 }
160
161 sub start_refresh_timer($) {
162         my ($go) = @_;
163         stop_refresh_timer();
164         refresh() if $go;
165         $refresh_timer_id = Glib::Timeout->add($conn->{"RefreshTimer"}, \&timed_refresh);
166 }
167
168 sub stop_refresh_timer() {
169         if (defined $refresh_timer_id) {
170                 Glib::Source->remove($refresh_timer_id);
171                 $refresh_timer_id = undef;
172         }
173 }
174
175 my $task_status_object;
176 my @task_parts = ();
177 my @task_stat = ();
178
179 sub recalc_task_list() {
180         my @new_tp = ();
181         my @new_stat = ();
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;
188                         my $status = "---";
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"));
193                                         $status = "OK (" .
194                                                 "$part." . $v->get("X") . ", " .
195                                                 $v->get("L") . " bytes, " .
196                                                 $v->get("S") . " $time)";
197                                         last;
198                                 }
199                         }
200                         push @new_stat, $status;
201                 }
202         }
203
204         if (join("\n", @new_tp) ne join("\n", @task_parts)) {
205                 # The tasks have changed, repopulate the whole structure
206                 $task_store->clear;
207                 my @s = @new_stat;
208                 foreach my $taskpart (@new_tp) {
209                         my $iter = $task_store->append;
210                         $task_store->set($iter,
211                                 0, $taskpart,
212                                 1, shift @s);
213                 }
214         } else {
215                 # Update the task status
216                 my @s = @task_stat;
217                 my @ns = @new_stat;
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]);
222                         }
223                         shift @s;
224                         shift @ns;
225                         return 0;
226                 });
227         }
228
229         @task_parts = @new_tp;
230         @task_stat = @new_stat;
231 }
232
233 sub refresh()
234 {
235         if (!$conn->is_connected || $force_refresh) {
236                 busy("Connecting to server...");
237                 if ($conn->connect) {
238                         ready("Connected successfully");
239                 } else {
240                         ready($conn->{"error"});
241                 }
242         }
243         if ($conn->is_connected) {
244                 busy("Updating status...");
245                 my $r = new Sherlock::Object("!" => "STATUS");
246                 $r = $conn->request($r);
247                 if (!defined $r) {
248                         ready($conn->{"error"});
249                 } elsif ($r->get("-")) {
250                         ready($r->get("-"));
251                 } else {
252                         $task_status_object = $r;
253                         recalc_task_list;
254                         $force_refresh = 0;
255                         ready("Ready");
256                 }
257         }
258         if (!$conn->is_connected && !$force_refresh) {
259                 # Retry
260                 $conn->log("Retrying");
261                 $force_refresh = 1;
262                 refresh();
263         }
264 }
265
266 ### SUBMITTING ###
267
268 my $subwin;
269 my $chooser;
270 my $subwin_vbox;
271 my $subwin_label;
272 my $bbutton_box;
273 my $submitting_label;
274 my $text_frame;
275 my $status_label;
276 my $check_only;
277 my $submit_filename;
278 my $submit_extension;
279 my %submit_fn_cache = ();
280
281 sub end_submit($) {
282         my ($close) = @_;
283         $subwin->destroy if $close;
284         start_refresh_timer(1);
285 }
286
287 sub finish_submit() {
288         my $button = Gtk2::Button->new_from_stock('gtk-close');
289         $button->signal_connect(clicked => sub { end_submit(1) });
290
291         $bbutton_box = Gtk2::HButtonBox->new;
292         $bbutton_box->pack_start_defaults($button);
293         $subwin_vbox->pack_start($bbutton_box, 0, 0, 10);
294
295         ready("Ready");
296         $subwin->show_all;
297         $subwin->window->set_cursor(undef);
298 }
299
300 sub submit_ok() {
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>");
303         refresh();
304         finish_submit();
305 }
306
307 sub submit_failed($) {
308         my ($msg) = @_;
309         $status_label->set_markup("<span size='large'>Submit failed</span>");
310         $submitting_label->set_markup("<span size='large'>$msg</span>");
311         finish_submit();
312 }
313
314 sub run_submit() {
315         my ($task, $part) = split /\//, $selected_task;
316         defined $part or $part = $task;
317
318         if (defined $conn->{"History"}) {
319                 busy("Submitting locally to " . $conn->{"History"});
320                 my $err = $conn->write_history($task, $part, $submit_extension, $submit_filename);
321                 if (defined $err) {
322                         submit_failed("Recording to local history failed\n($err)");
323                         return;
324                 }
325         }
326
327         if ($conn->is_connected) {
328                 busy("Checking server status...");
329                 my $r = new Sherlock::Object("!" => "NOP");
330                 $r = $conn->request($r);
331         }
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");
337                         return;
338                 }
339         }
340         busy("Submitting...");
341
342         my $fh = new IO::File($submit_filename);
343         if (!$fh) {
344                 submit_failed("Unable to open $submit_filename\n($!)");
345                 return;
346         }
347         my $stat = File::stat::populate($fh->stat);
348         if (!$stat) {
349                 submit_failed("Unable to stat $submit_filename\n($!)");
350                 return;
351         }
352         my $size = $stat->size;
353
354         my $r = new Sherlock::Object("!" => "SUBMIT", "T" => $task, "P" => $part, "X" => $submit_extension, "S" => $size);
355         $r = $conn->request($r);
356         if (!defined($r)) {
357                 submit_failed("Connection to the server lost");
358                 return;
359         } elsif ($r->get("-")) {
360                 submit_failed($r->get("-"));
361                 return;
362         }
363
364         $r = $conn->send_file($fh, $size);
365         if (!defined($r)) {
366                 submit_failed("Connection to the server lost");
367                 return;
368         } elsif ($r->get("-")) {
369                 submit_failed($r->get("-"));
370                 return;
371         }
372
373         close $fh;
374         submit_ok();
375 }
376
377 sub checks_ok() {
378         if ($check_only) {
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>");
381                 finish_submit();
382                 return;
383         }
384
385         $status_label->set_markup("<span size='large'>Submitting</span>");
386         $subwin->show_all;
387
388         # Continue when everything is displayed
389         Glib::Idle->add(sub {
390                 $window->Gtk2::Gdk::flush;
391                 run_submit();
392                 return 0;
393         });
394 }
395
396 sub checks_override() {
397         $submitting_label = Gtk2::Label->new("Please wait...");
398         $subwin_vbox->pack_start_defaults($submitting_label);
399
400         $subwin->window->set_cursor($busy_cursor);
401         $bbutton_box->destroy;
402         $text_frame->destroy;
403         checks_ok();
404 }
405
406 sub checks_failed($) {
407         my ($msg) = @_;
408
409         $status_label->set_markup("<span size='large'>Check failed</span>");
410         $submitting_label->destroy;
411
412         my $text_buffer = Gtk2::TextBuffer->new;
413         $text_buffer->set_text($msg);
414
415         my $text_view = Gtk2::TextView->new_with_buffer($text_buffer);
416         $text_view->set_editable(0);
417         $text_view->set_cursor_visible(0);
418
419         my $text_scroll = Gtk2::ScrolledWindow->new;
420         $text_scroll->set_policy("automatic", "automatic");
421         $text_scroll->add($text_view);
422
423         $text_frame = Gtk2::Frame->new("Checker log");
424         $text_frame->add($text_scroll);
425
426         $subwin_vbox->pack_start_defaults($text_frame);
427
428         if ($check_only || !$conn->{"AllowOverride"}) {
429                 finish_submit();
430                 return;
431         }
432
433         my $close_button = Gtk2::Button->new_from_stock('gtk-close');
434         $close_button->signal_connect(clicked => sub { end_submit(1) });
435
436         my $anyway_button = Gtk2::Button->new('Submit anyway');
437         $anyway_button->signal_connect(clicked => \&checks_override);
438
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);
444
445         ready("Ready");
446         $subwin->show_all;
447         $subwin->window->set_cursor(undef);
448 }
449
450 sub run_checks() {
451         ($submit_extension) = ($submit_filename =~ /\.([^.]+)$/);
452         if (!$submit_extension) {
453                 checks_failed("The filename does not have a valid extension");
454                 return;
455         }
456         if (!$conn->{"Checks"}) {
457                 checks_ok();
458                 return;
459         }
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`;
464         if ($?) {
465                 checks_failed($verdict);
466         } else {
467                 checks_ok();
468         }
469 }
470
471 sub do_submit() {
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;
477
478         $chooser->destroy;
479         $bbutton_box->destroy;
480
481         $status_label->set_markup("<span size='large'>Checking</span>");
482
483         $submitting_label = Gtk2::Label->new("Please wait...");
484         $subwin_vbox->pack_start_defaults($submitting_label);
485         $subwin->show_all;
486         $subwin->window->set_cursor($busy_cursor);
487
488         # Continue when everything is displayed
489         Glib::Idle->add(sub {
490                 $window->Gtk2::Gdk::flush;
491                 run_checks();
492                 return 0;
493         });
494 }
495
496 sub submit($) {
497         $check_only = shift @_;
498
499         stop_refresh_timer();
500
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; });
509
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);
513
514         my $bb_cancel = Gtk2::Button->new_from_stock('gtk-cancel');
515         $bb_cancel->signal_connect(clicked => sub { end_submit(1) });
516
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);
521
522         my $subwin_label = Gtk2::Label->new;
523         $subwin_label->set_markup("<span size='x-large'>" . ($check_only ? "Checking" : "Submitting") . " $selected_task</span>");
524
525         $status_label = Gtk2::Label->new;
526         $status_label->set_markup("<span size='large'>Please select file to " . ($check_only ? "check" : "submit") . "</span>");
527
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};
532
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);
538
539         $subwin->add($subwin_vbox);
540         $subwin->show_all;
541 }
542
543 ### MAIN ###
544
545 Gtk2->main;
546 exit 0;