]> mj.ucw.cz Git - moe.git/blob - submit/contest
Adapted to recent libucw (log -> msg).
[moe.git] / submit / contest
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->signal_connect(clicked => sub { submit(0) });
49 $b_submit->set_sensitive(0);
50
51 my $b_check = Gtk2::Button->new('Check');
52 $b_check->signal_connect(clicked => sub { submit(1) });
53 $b_check->set_sensitive(0);
54
55 my $b_refresh = Gtk2::Button->new('Refresh');
56 $b_refresh->signal_connect(clicked => sub { start_refresh_timer(1) });
57
58 my $button_box = Gtk2::HBox->new;
59 $button_box->pack_start_defaults($b_submit);
60 $button_box->pack_start_defaults($b_check);
61 $button_box->pack_start_defaults($b_refresh);
62 $button_box->set_border_width(5);
63
64 # The list of tasks
65 my $task_store = Gtk2::ListStore->new('Glib::String', 'Glib::String');
66
67 my $task_view = Gtk2::TreeView->new($task_store);
68 my $task_renderer = Gtk2::CellRendererText->new;
69 my $task_col1 = Gtk2::TreeViewColumn->new_with_attributes("Task", $task_renderer, "text", 0);
70 $task_view->append_column($task_col1);
71 my $task_col2 = Gtk2::TreeViewColumn->new_with_attributes("Status", $task_renderer, "text", 1);
72 $task_view->append_column($task_col2);
73 $task_view->set_headers_visible(0);
74
75 my $task_scroll = Gtk2::ScrolledWindow->new;
76 $task_scroll->set_policy("automatic", "automatic");
77 $task_scroll->add($task_view);
78 $task_scroll->set_border_width(5);
79
80 my $task_frame = Gtk2::Frame->new("Tasks");
81 $task_frame->add($task_scroll);
82
83 my $selected_task;
84 my $task_sel = $task_view->get_selection;
85 $task_sel->set_mode('single');
86 $task_sel->signal_connect(changed => sub {
87         my $iter = $_[0]->get_selected;
88         if ($iter) {
89                 $selected_task = $task_store->get($iter, 0);
90                 $b_submit->set_sensitive(1);
91                 $b_check->set_sensitive(1);
92         } else {
93                 $selected_task = undef;
94                 print "Deselected task\n";
95                 $b_submit->set_sensitive(0);
96                 $b_check->set_sensitive(0);
97         }
98 });
99
100 my $status_bar = Gtk2::Statusbar->new;
101 my $bar_ctx = $status_bar->get_context_id('xyzzy');
102
103 my $vbox = Gtk2::VBox->new;
104 $vbox->pack_start($title_lab, 0, 0, 10);
105 $vbox->pack_start($task_frame, 1, 1, 0);
106 $vbox->pack_start($button_box, 0, 0, 0);
107 $vbox->pack_start($status_bar, 0, 0, 0);
108
109 $window->add($vbox);
110 $window->signal_connect("expose-event" => sub { init_refresh(); return 0; });
111 $window->show_all;
112
113 ### REFRESHING ###
114
115 my $last_status_id;
116
117 sub msg($) {
118         print "GUI: ", $_[0], "\n" if $conn->{"Trace"};
119 }
120
121 sub status($) {
122         msg($_[0]);
123         defined $last_status_id and $status_bar->remove($bar_ctx, $last_status_id);
124         $last_status_id = $status_bar->push($bar_ctx, shift @_);
125 }
126
127 sub busy($) {
128         status($_[0]);
129         $window->window->set_cursor($busy_cursor);
130         $window->Gtk2::Gdk::flush;
131 }
132
133 sub ready($) {
134         status($_[0]);
135         $window->window->set_cursor(undef);
136         $window->Gtk2::Gdk::flush;
137 }
138
139 my $window_inited = 0;
140 sub init_refresh()
141 {
142         if (!$window_inited) {
143                 $force_refresh = 1;
144                 start_refresh_timer(1);
145                 $window_inited = 1;
146         }
147         return 1;
148 }
149
150 my $refresh_timer_id;
151
152 sub timed_refresh()
153 {
154         refresh();      # FIXME: If-modified-since version?
155         return 1;       # We wish to re-run the timer
156 }
157
158 sub start_refresh_timer($) {
159         my ($go) = @_;
160         stop_refresh_timer();
161         refresh() if $go;
162         $refresh_timer_id = Glib::Timeout->add($conn->{"RefreshTimer"}, \&timed_refresh);
163 }
164
165 sub stop_refresh_timer() {
166         if (defined $refresh_timer_id) {
167                 Glib::Source->remove($refresh_timer_id);
168                 $refresh_timer_id = undef;
169         }
170 }
171
172 my $task_status_object;
173 my @task_parts = ();
174 my @task_stat = ();
175
176 sub recalc_task_list() {
177         my @new_tp = ();
178         my @new_stat = ();
179         foreach my $t ($task_status_object->getarray("(T")) {
180                 my $task = $t->get("T");
181                 foreach my $p ($t->getarray("(P")) {
182                         my $part = $p->get("P");
183                         my $taskpart = ($task eq $part) ? $task : "$task/$part";
184                         push @new_tp, $taskpart;
185                         my $status = "---";
186                         my $current_ver = $p->get("V");
187                         foreach my $v ($p->getarray("(V")) {
188                                 if ($v->get("V") == $current_ver) {
189                                         my $time = strftime("%H:%M:%S", localtime $v->get("T"));
190                                         $status = "OK (" .
191                                                 "$part." . $v->get("X") . ", " .
192                                                 $v->get("L") . " bytes, " .
193                                                 $v->get("S") . " $time)";
194                                         last;
195                                 }
196                         }
197                         push @new_stat, $status;
198                 }
199         }
200
201         if (join("\n", @new_tp) ne join("\n", @task_parts)) {
202                 # The tasks have changed, repopulate the whole structure
203                 $task_store->clear;
204                 my @s = @new_stat;
205                 foreach my $taskpart (@new_tp) {
206                         my $iter = $task_store->append;
207                         $task_store->set($iter,
208                                 0, $taskpart,
209                                 1, shift @s);
210                 }
211         } else {
212                 # Update the task status
213                 my @s = @task_stat;
214                 my @ns = @new_stat;
215                 $task_store->foreach(sub {
216                         my ($obj, $path, $iter) = @_;
217                         if ($s[0] ne $ns[0]) {
218                                 $task_store->set($iter, 1, $ns[0]);
219                         }
220                         shift @s;
221                         shift @ns;
222                         return 0;
223                 });
224         }
225
226         @task_parts = @new_tp;
227         @task_stat = @new_stat;
228 }
229
230 sub refresh()
231 {
232         if (!$conn->is_connected || $force_refresh) {
233                 busy("Connecting to server...");
234                 if ($conn->connect) {
235                         ready("Connected successfully");
236                 } else {
237                         ready($conn->{"error"});
238                 }
239         }
240         if ($conn->is_connected) {
241                 busy("Updating status...");
242                 my $r = new Sherlock::Object("!" => "STATUS");
243                 $r = $conn->request($r);
244                 if (!defined $r) {
245                         ready($conn->{"error"});
246                 } elsif ($r->get("-")) {
247                         ready($r->get("-"));
248                 } else {
249                         $task_status_object = $r;
250                         recalc_task_list;
251                         $force_refresh = 0;
252                         ready("Ready");
253                 }
254         }
255         if (!$conn->is_connected && !$force_refresh) {
256                 # Retry
257                 $conn->log("Retrying");
258                 refresh();
259         }
260 }
261
262 ### SUBMITTING ###
263
264 my $subwin;
265 my $chooser;
266 my $subwin_vbox;
267 my $subwin_label;
268 my $bbutton_box;
269 my $submitting_label;
270 my $status_label;
271 my $check_only;
272 my $submit_filename;
273 my $submit_extension;
274 my %submit_fn_cache = ();
275
276 sub end_submit($) {
277         my ($close) = @_;
278         $subwin->destroy if $close;
279         start_refresh_timer(0);
280         ### FIXME: ... and refresh status
281 }
282
283 sub finish_submit() {
284         my $button = Gtk2::Button->new('Return');
285         $button->signal_connect(clicked => sub { end_submit(1) });
286
287         $bbutton_box = Gtk2::HButtonBox->new;
288         $bbutton_box->pack_start_defaults($button);
289         $subwin_vbox->pack_start($bbutton_box, 0, 0, 10);
290
291         ready("Ready");
292         $subwin->show_all;
293         $subwin->window->set_cursor(undef);
294 }
295
296 sub submit_ok() {
297         $status_label->set_markup("<span size='large'>Submitted OK</span>");
298         $submitting_label->set_markup("<span size='large'>The task has been successfully submitted.</span>");
299         refresh();
300         finish_submit();
301 }
302
303 sub submit_failed($) {
304         my ($msg) = @_;
305         $status_label->set_markup("<span size='large'>Submit failed</span>");
306         $submitting_label->set_markup("<span size='large'>$msg</span>");
307         finish_submit();
308 }
309
310 sub run_submit() {
311         my ($task, $part) = split /\//, $selected_task;
312         defined $part or $part = $task;
313
314         if (defined $conn->{"History"}) {
315                 busy("Submitting locally to " . $conn->{"History"});
316                 my $err = $conn->local_submit($task, $part, $submit_extension, $submit_filename);
317                 if (defined $err) {
318                         submit_failed("Recording to local history failed\n($err)");
319                         return;
320                 }
321         }
322
323         if ($conn->is_connected) {
324                 busy("Checking server status...");
325                 my $r = new Sherlock::Object("!" => "STATUS");  ### FIXME: use a NOP command
326                 $r = $conn->request($r);
327         }
328         if (!$conn->is_connected) {
329                 busy("Reconnecting to server...");
330                 if (!$conn->connect) {
331                         ready($conn->{"error"});
332                         submit_failed("Unable to connect to the server");       ### FIXME: Mention local submit
333                         return;
334                 }
335         }
336         busy("Submitting...");
337
338         my $fh = new IO::File($submit_filename);
339         if (!$fh) {
340                 submit_failed("Unable to open $submit_filename\n($!)");
341                 return;
342         }
343         my $stat = File::stat::populate($fh->stat);
344         if (!$stat) {
345                 submit_failed("Unable to stat $submit_filename\n($!)");
346                 return;
347         }
348         my $size = $stat->size;
349
350         my $r = new Sherlock::Object("!" => "SUBMIT", "T" => $task, "P" => $part, "X" => $submit_extension, "S" => $size);
351         $r = $conn->request($r);
352         if (!defined($r)) {
353                 submit_failed("Connection to the server lost");
354                 return;
355         } elsif ($r->get("-")) {
356                 submit_failed($r->get("-"));
357                 return;
358         }
359
360         $r = $conn->send_file($fh, $size);
361         if (!defined($r)) {
362                 submit_failed("Connection to the server lost");
363                 return;
364         } elsif ($r->get("-")) {
365                 submit_failed($r->get("-"));
366                 return;
367         }
368
369         close $fh;
370         submit_ok();
371 }
372
373 sub checks_failed($) {
374         my ($msg) = @_;
375
376         $status_label->set_markup("<span size='large'>Check failed</span>");
377
378         my $text_buffer = Gtk2::TextBuffer->new;
379         $text_buffer->set_text($msg);
380
381         my $text_view = Gtk2::TextView->new_with_buffer($text_buffer);
382         $text_view->set_editable(0);
383         $text_view->set_cursor_visible(0);
384
385         my $text_scroll = Gtk2::ScrolledWindow->new;
386         $text_scroll->set_policy("automatic", "automatic");
387         $text_scroll->add($text_view);
388
389         my $text_frame = Gtk2::Frame->new("Checker log");
390         $text_frame->add($text_scroll);
391
392         $submitting_label->destroy;
393         $subwin_vbox->pack_start_defaults($text_frame);
394
395         finish_submit();
396 }
397
398 sub checks_ok() {
399         if ($check_only) {
400                 $status_label->set_markup("<span size='large'>Checked successfully</span>");
401                 $submitting_label->set_markup("<span size='large'>The task has passed the checks.</span>");
402                 finish_submit();
403                 return;
404         }
405
406         ### FIXME: Record to local history here
407
408         $status_label->set_markup("<span size='large'>Submitting</span>");
409         $subwin->show_all;
410
411         # Continue when everything is displayed
412         Glib::Idle->add(sub {
413                 $window->Gtk2::Gdk::flush;
414                 run_submit();
415                 return 0;
416         });
417 }
418
419 sub run_checks() {
420         ($submit_extension) = ($submit_filename =~ /\.([^.]+)$/);
421         if (!$submit_extension) {
422                 checks_failed("The filename does not have a valid extension");
423                 return;
424         }
425         if (!$conn->{"Checks"}) {
426                 checks_ok();
427                 return;
428         }
429         my $root = $conn->{"root"};
430         my ($task, $part) = split /\//, $selected_task;
431         defined $part or $part = "";
432         my $verdict = `$root/bin/check -s "$submit_filename" $task $part 2>&1`;
433         if ($?) {
434                 checks_failed($verdict);
435         } else {
436                 checks_ok();
437         }
438 }
439
440 sub do_submit() {
441         $submit_filename = $chooser->get_filename;
442         $submit_fn_cache{$selected_task} = $submit_filename;
443         msg "Selected $submit_filename";
444         defined $submit_filename or return;
445         -f $submit_filename or return;
446
447         $chooser->destroy;
448         $bbutton_box->destroy;
449
450         $status_label->set_markup("<span size='large'>Checking</span>");
451
452         $submitting_label = Gtk2::Label->new("Please wait...");
453         $subwin_vbox->pack_start_defaults($submitting_label);
454         $subwin->show_all;
455         $subwin->window->set_cursor($busy_cursor);
456
457         # Continue when everything is displayed
458         Glib::Idle->add(sub {
459                 $window->Gtk2::Gdk::flush;
460                 run_checks();
461                 return 0;
462         });
463 }
464
465 sub submit($) {
466         $check_only = shift @_;
467
468         stop_refresh_timer();
469
470         $subwin = Gtk2::Window->new('toplevel');
471         $subwin->set_default_size(640, 480);
472         $subwin->set_modal(1);
473         $subwin->set_transient_for($window);
474         $subwin->set_destroy_with_parent(1);
475         $subwin->set_title("Submit task $selected_task");
476         $subwin->set_wmclass("submitter", "Submitter");
477         $subwin->signal_connect("delete-event" => sub { end_submit(0); return 0; });
478
479         my $bb_submit = Gtk2::Button->new($check_only ? 'Check' : 'Submit');
480         $bb_submit->signal_connect(clicked => \&do_submit);
481         #$bb_submit->set_sensitive(0);
482
483         my $bb_cancel = Gtk2::Button->new('Cancel');
484         $bb_cancel->signal_connect(clicked => sub { end_submit(1) });
485
486         $bbutton_box = Gtk2::HButtonBox->new;
487         $bbutton_box->pack_start_defaults($bb_submit);
488         $bbutton_box->pack_start_defaults($bb_cancel);
489         $bbutton_box->set_border_width(5);
490
491         my $subwin_label = Gtk2::Label->new;
492         $subwin_label->set_markup("<span size='x-large'>" . ($check_only ? "Checking" : "Submitting") . " $selected_task</span>");
493
494         $status_label = Gtk2::Label->new;
495         $status_label->set_markup("<span size='large'>Please select file to " . ($check_only ? "check" : "submit") . "</span>");
496
497         $chooser = Gtk2::FileChooserWidget->new("open");
498         $chooser->set_local_only(1);
499         $chooser->signal_connect("file-activated" => \&do_submit);
500         $chooser->set_filename($submit_fn_cache{$selected_task}) if defined $submit_fn_cache{$selected_task};
501
502         $subwin_vbox = Gtk2::VBox->new;
503         $subwin_vbox->pack_start($subwin_label, 0, 0, 10);
504         $subwin_vbox->pack_start($status_label, 0, 0, 10);
505         $subwin_vbox->pack_start_defaults($chooser);
506         $subwin_vbox->pack_start($bbutton_box, 0, 0, 0);
507
508         $subwin->add($subwin_vbox);
509         $subwin->show_all;
510 }
511
512 ### MAIN ###
513
514 Gtk2->main;
515 exit 0;