]> mj.ucw.cz Git - eval.git/blob - submit/contest
640ca15a6d9df40ee820bc9f9b82902d7a66f5f5
[eval.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"} . "/submit";
12 use lib $ENV{"MO_ROOT"} . "/submit/lib/perl5";
13
14 use MO::Submit;
15 use Sherlock::Object;
16 use POSIX;
17 use Gtk2 -init;
18
19 my $conn = new MO::Submit;
20
21 ### GUI INITIALIZATION ###
22
23 sub init_refresh();
24 sub timed_refresh();
25 sub refresh($);
26 sub submit();
27
28 my $busy_cursor = Gtk2::Gdk::Cursor->new('watch');
29
30 # The main window
31 my $window = Gtk2::Window->new('toplevel');
32 $window->signal_connect("delete-event" => sub { Gtk2->main_quit });
33 $window->set_title($conn->{"Contest"} . " Submitter");
34 $window->set_wmclass("submitter", "Submitter");
35 $window->set_default_size(640, 480);
36
37 # The title label
38 my $title_lab = Gtk2::Label->new;
39 $title_lab->set_markup("<span size='x-large'>" . $conn->{"Contest"} . "</span>");
40
41 # The row of buttons
42 my $b_submit = Gtk2::Button->new('Submit');
43 $b_submit->signal_connect(clicked => \&submit);
44 $b_submit->set_sensitive(0);
45
46 my $b_check = Gtk2::Button->new('Check');
47 $b_check->signal_connect(clicked => sub { Gtk2->main_quit });
48 $b_check->set_sensitive(0);
49
50 my $b_refresh = Gtk2::Button->new('Refresh');
51 $b_refresh->signal_connect(clicked => sub { refresh(0) });
52
53 my $button_box = Gtk2::HBox->new;
54 $button_box->pack_start_defaults($b_submit);
55 $button_box->pack_start_defaults($b_check);
56 $button_box->pack_start_defaults($b_refresh);
57 $button_box->set_border_width(5);
58
59 # The list of tasks
60 my $task_store = Gtk2::ListStore->new('Glib::String', 'Glib::String');
61
62 my $task_view = Gtk2::TreeView->new($task_store);
63 my $task_renderer = Gtk2::CellRendererText->new;
64 my $task_col1 = Gtk2::TreeViewColumn->new_with_attributes("Task", $task_renderer, "text", 0);
65 $task_view->append_column($task_col1);
66 my $task_col2 = Gtk2::TreeViewColumn->new_with_attributes("Status", $task_renderer, "text", 1);
67 $task_view->append_column($task_col2);
68 $task_view->set_headers_visible(0);
69
70 my $task_scroll = Gtk2::ScrolledWindow->new;
71 $task_scroll->set_policy("automatic", "automatic");
72 $task_scroll->add($task_view);
73 $task_scroll->set_border_width(5);
74
75 my $task_frame = Gtk2::Frame->new("Tasks");
76 $task_frame->add($task_scroll);
77
78 my $selected_task;
79 my $task_sel = $task_view->get_selection;
80 $task_sel->set_mode('single');
81 $task_sel->signal_connect(changed => sub {
82         my $iter = $_[0]->get_selected;
83         if ($iter) {
84                 $selected_task = $task_store->get($iter, 0);
85                 $b_submit->set_sensitive(1);
86                 $b_check->set_sensitive(1);
87         } else {
88                 $selected_task = undef;
89                 print "Deselected task\n";
90                 $b_submit->set_sensitive(0);
91                 $b_check->set_sensitive(0);
92         }
93 });
94
95 my $status_bar = Gtk2::Statusbar->new;
96 my $bar_ctx = $status_bar->get_context_id('xyzzy');
97
98 my $vbox = Gtk2::VBox->new;
99 $vbox->pack_start($title_lab, 0, 0, 10);
100 $vbox->pack_start($task_frame, 1, 1, 0);
101 $vbox->pack_start($button_box, 0, 0, 0);
102 $vbox->pack_start($status_bar, 0, 0, 0);
103
104 $window->add($vbox);
105 $window->signal_connect("expose-event" => sub { init_refresh(); return 0; });
106 $window->show_all;
107
108 ### REFRESHING ###
109
110 my $last_status_id;
111
112 sub status($) {
113         print "GUI: ", $_[0], "\n" if $conn->{"Trace"};
114         defined $last_status_id and $status_bar->remove($bar_ctx, $last_status_id);
115         $last_status_id = $status_bar->push($bar_ctx, shift @_);
116 }
117
118 sub busy($) {
119         status($_[0]);
120         $window->window->set_cursor($busy_cursor);
121         $window->Gtk2::Gdk::flush;
122 }
123
124 sub ready($) {
125         status($_[0]);
126         $window->window->set_cursor(undef);
127         $window->Gtk2::Gdk::flush;
128 }
129
130 my $window_inited = 0;
131 sub init_refresh()
132 {
133         if (!$window_inited) {
134                 refresh(1);
135                 $window_inited = 1;
136         }
137         return 1;
138 }
139
140 sub timed_refresh()
141 {
142         refresh(0);     # FIXME: If-modified-since version?
143         return 1;       # We wish to re-run the timer
144 }
145
146 my $task_status_object;
147 my @task_parts = ();
148 my @task_stat = ();
149
150 sub recalc_task_list() {
151         my @new_tp = ();
152         my @new_stat = ();
153         foreach my $t ($task_status_object->getarray("(T")) {
154                 my $task = $t->get("T");
155                 foreach my $p ($t->getarray("(P")) {
156                         my $part = $p->get("P");
157                         my $taskpart = ($task eq $part) ? $task : "$task/$part";
158                         push @new_tp, $taskpart;
159                         my $status = "---";
160                         my $current_ver = $p->get("V");
161                         foreach my $v ($p->getarray("(V")) {
162                                 if ($v->get("V") == $current_ver) {
163                                         my $time = strftime("%H:%M:%S", localtime $v->get("T"));
164                                         $status = "OK (" .
165                                                 "$part." . $v->get("X") . ", " .
166                                                 $v->get("L") . " bytes, " .
167                                                 $v->get("S") . " $time)";
168                                         last;
169                                 }
170                         }
171                         push @new_stat, $status;
172                 }
173         }
174
175         if (join("\n", @new_tp) ne join("\n", @task_parts)) {
176                 # The tasks have changed, repopulate the whole structure
177                 $task_store->clear;
178                 my @s = @new_stat;
179                 foreach my $taskpart (@new_tp) {
180                         my $iter = $task_store->append;
181                         $task_store->set($iter,
182                                 0, $taskpart,
183                                 1, shift @s);
184                 }
185         } else {
186                 # Update the task status
187                 my @s = @task_stat;
188                 my @ns = @new_stat;
189                 $task_store->foreach(sub {
190                         my ($obj, $path, $iter) = @_;
191                         if ($s[0] ne $ns[0]) {
192                                 $task_store->set($iter, 1, $ns[0]);
193                         }
194                         shift @s;
195                         shift @ns;
196                         return 0;
197                 });
198         }
199
200         @task_parts = @new_tp;
201         @task_stat = @new_stat;
202 }
203
204 sub refresh($)
205 {
206         my $force = shift @_;
207         if (!$conn->is_connected || $force) {
208                 busy("Connecting to server...");
209                 if ($conn->connect) {
210                         ready("Connected successfully");
211                 } else {
212                         ready($conn->{"error"});
213                 }
214         }
215         if ($conn->is_connected) {
216                 busy("Updating status...");
217                 my $r = new Sherlock::Object("!" => "STATUS");
218                 $r = $conn->request($r);
219                 if (!defined $r) {
220                         ready($conn->{"error"});
221                 } elsif ($r->get("-")) {
222                         ready($r->get("-"));
223                 } else {
224                         $task_status_object = $r;
225                         recalc_task_list;
226                         ready("Ready");
227                 }
228         }
229         if (!$conn->is_connected && !$force) {
230                 # Retry
231                 $conn->log("Retrying");
232                 refresh(1);
233         }
234 }
235
236 ### SUBMITTING ###
237
238 my $subwin;
239 my $chooser;
240 my $subwin_vbox;
241 my $subwin_label;
242 my $bbutton_box;
243 my $submitting_label;
244 my $status_label;
245 my $submit_filename;
246
247 sub end_submit($) {
248         my ($close) = @_;
249         $subwin->destroy if $close;
250         ### FIXME: ... and refresh status
251 }
252
253 sub finish_submit() {
254         my $button = Gtk2::Button->new('Return');
255         $button->signal_connect(clicked => sub { end_submit(1) });
256
257         $bbutton_box = Gtk2::HButtonBox->new;
258         $bbutton_box->pack_start_defaults($button);
259         $subwin_vbox->pack_start($bbutton_box, 0, 0, 10);
260
261         $subwin->show_all;
262 }
263
264 sub submit_ok() {
265         $status_label->set_markup("<span size='large'>Submitted OK</span>");
266         $submitting_label->set_markup("<span size='large'>The task has been successfully submitted.</span>");
267         finish_submit();
268 }
269
270 sub submit_failed($) {
271         my ($msg) = @_;
272         $status_label->set_markup("<span size='large'>Submit failed</span>");
273         $submitting_label->set_markup("<span size='large'>$msg</span>");
274         finish_submit();
275 }
276
277 sub run_submit() {
278         sleep 1;
279         submit_failed("Xyzzy");
280         #submit_ok();
281 }
282
283 sub checks_failed($) {
284         my ($msg) = @_;
285
286         $status_label->set_markup("<span size='large'>Check failed</span>");
287
288         my $text_buffer = Gtk2::TextBuffer->new;
289         $text_buffer->set_text($msg);
290
291         my $text_view = Gtk2::TextView->new_with_buffer($text_buffer);
292         $text_view->set_editable(0);
293         $text_view->set_cursor_visible(0);
294
295         $submitting_label->destroy;
296         $subwin_vbox->pack_start_defaults($text_view);
297
298         finish_submit();
299 }
300
301 sub checks_ok() {
302         ### FIXME: Record to local history here
303
304         $status_label->set_markup("<span size='large'>Submitting</span>");
305         $subwin->show_all;
306
307         # Continue when everything is displayed
308         Glib::Idle->add(sub {
309                 $window->Gtk2::Gdk::flush;
310                 run_submit();
311                 return 0;
312         });
313 }
314
315 sub run_checks() {
316         sleep 1;
317         #checks_failed("One\nTwo\nThree...\n");
318         checks_ok();
319 }
320
321 sub do_submit() {
322         my $submit_filename = $chooser->get_filename;
323         defined $submit_filename or return;
324         -f $submit_filename or return;
325         $conn->log("Selected $submit_filename for submit");
326
327         $chooser->destroy;
328         $bbutton_box->destroy;
329
330         $status_label->set_markup("<span size='large'>Checking</span>");
331
332         $submitting_label = Gtk2::Label->new("Please wait...");
333         $subwin_vbox->pack_start_defaults($submitting_label);
334         $subwin->show_all;
335
336         # Continue when everything is displayed
337         Glib::Idle->add(sub {
338                 $window->Gtk2::Gdk::flush;
339                 run_checks();
340                 return 0;
341         });
342 }
343
344 sub submit() {
345         ## FIXME: Stop the status timer?
346         $subwin = Gtk2::Window->new('toplevel');
347         $subwin->set_default_size(640, 480);
348         $subwin->set_modal(1);
349         $subwin->set_transient_for($window);
350         $subwin->set_destroy_with_parent(1);
351         $subwin->set_title("Submit task $selected_task");
352         $subwin->set_wmclass("submitter", "Submitter");
353         $subwin->signal_connect("delete-event" => sub { end_submit(0); return 0; });
354
355         my $bb_submit = Gtk2::Button->new('Submit');
356         $bb_submit->signal_connect(clicked => \&do_submit);
357         #$bb_submit->set_sensitive(0);
358
359         my $bb_cancel = Gtk2::Button->new('Cancel');
360         $bb_cancel->signal_connect(clicked => sub { end_submit(1) });
361
362         $bbutton_box = Gtk2::HButtonBox->new;
363         $bbutton_box->pack_start_defaults($bb_submit);
364         $bbutton_box->pack_start_defaults($bb_cancel);
365         $bbutton_box->set_border_width(5);
366
367         my $subwin_label = Gtk2::Label->new;
368         $subwin_label->set_markup("<span size='x-large'>Submitting $selected_task</span>");
369
370         $status_label = Gtk2::Label->new;
371         $status_label->set_markup("<span size='large'>Please select file to submit</span>");
372
373         $chooser = Gtk2::FileChooserWidget->new("open");
374         $chooser->set_local_only(1);
375         $chooser->signal_connect("file-activated" => \&do_submit);
376
377         $subwin_vbox = Gtk2::VBox->new;
378         $subwin_vbox->pack_start($subwin_label, 0, 0, 10);
379         $subwin_vbox->pack_start($status_label, 0, 0, 10);
380         $subwin_vbox->pack_start_defaults($chooser);
381         $subwin_vbox->pack_start($bbutton_box, 0, 0, 0);
382
383         $subwin->add($subwin_vbox);
384         $subwin->show_all;
385 }
386
387 ### MAIN ###
388
389 Glib::Timeout->add(5000, \&timed_refresh);
390 Gtk2->main;
391 exit 0;