]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/gnome2-console/console.c
First cut restore GUI
[bacula/bacula] / bacula / src / gnome2-console / console.c
1 /*
2  *
3  *   Bacula GNOME Console interface to the Director
4  *
5  *     Kern Sibbald, March MMII
6  *     
7  *     Version $Id$
8  */
9
10 /*
11    Copyright (C) 2002 Kern Sibbald and John Walker
12
13    This program is free software; you can redistribute it and/or
14    modify it under the terms of the GNU General Public License
15    as published by the Free Software Foundation; either version 2
16    of the License, or (at your option) any later version.
17
18    This program is distributed in the hope that it will be useful,
19    but WITHOUT ANY WARRANTY; without even the implied warranty of
20    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21    GNU General Public License for more details.
22
23    You should have received a copy of the GNU General Public License
24    along with this program; if not, write to the Free Software
25    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
26  */
27
28 #include "bacula.h"
29 #include "console.h"
30
31 #include "interface.h"
32 #include "support.h"
33
34 /* Imported functions */
35 int authenticate_director(JCR *jcr, DIRRES *director, CONRES *cons);
36 void select_restore_setup();
37        
38 /* Exported variables */
39 GtkWidget *console;   /* application window */
40 GtkWidget *text1;            /* text window */
41 GtkWidget *entry1;           /* entry box */
42 GtkWidget *status1;          /* status bar */
43 GtkWidget *combo1;           /* director combo */
44 GtkWidget *scroll1;          /* main scroll bar */
45 GtkWidget *run_dialog;       /* run dialog */
46 GtkWidget *dir_dialog;       /* director selection dialog */
47 GtkWidget *restore_dialog;   /* restore dialog */
48 GtkWidget *restore_file_selection;
49 GtkWidget *dir_select;
50 GtkWidget *about1;           /* about box */
51 GtkWidget *label_dialog;
52 GdkFont   *text_font = NULL;
53 PangoFontDescription *font_desc;
54 pthread_mutex_t cmd_mutex = PTHREAD_MUTEX_INITIALIZER;
55 pthread_cond_t  cmd_wait;
56 char cmd[1000];
57 int reply;
58 BSOCK *UA_sock = NULL;
59 GList *job_list, *client_list, *fileset_list;
60 GList *messages_list, *pool_list, *storage_list;
61 GList *type_list, *level_list;
62
63 /* Forward referenced functions */
64 static void terminate_console(int sig);
65 static gint message_handler(gpointer data);
66 static int initial_connect_to_director(gpointer data);
67 static void set_scroll_bar_to_end(void);
68
69 /* Static variables */
70 static char *configfile = NULL;
71 static DIRRES *dir; 
72 static int ndir;
73 static bool director_reader_running = false;
74 static bool at_prompt = false;
75 static bool ready = false;
76 static bool quit = false;
77 static guint initial;
78
79 #define CONFIG_FILE "./gnome-console.conf"   /* default configuration file */
80
81 static void usage()
82 {
83    fprintf(stderr, _(
84 "\nVersion: " VERSION " (" BDATE ") %s %s %s\n\n"
85 "Usage: gnome-console [-s] [-c config_file] [-d debug_level] [config_file]\n"
86 "       -c <file>   set configuration file to file\n"
87 "       -dnn        set debug level to nn\n"
88 "       -s          no signals\n"
89 "       -t          test - read configuration and exit\n"
90 "       -?          print this message.\n"
91 "\n"), HOST_OS, DISTNAME, DISTVER);
92
93    exit(1);
94 }
95
96
97 /*********************************************************************
98  *
99  *         Main Bacula GNOME Console -- User Interface Program
100  *
101  */
102 int main(int argc, char *argv[])
103 {
104    int ch, stat;
105    int no_signals = TRUE;
106    int test_config = FALSE;
107    int gargc = 1;
108    char *gargv[2] = {"gnome-console", NULL};
109    CONFONTRES *con_font;
110
111    init_stack_dump();
112    my_name_is(argc, argv, "gnome-console");
113    init_msg(NULL, NULL);
114    working_directory  = "/tmp";
115
116    struct sigaction sigignore;
117    sigignore.sa_flags = 0;
118    sigignore.sa_handler = SIG_IGN;       
119    sigfillset(&sigignore.sa_mask);
120    sigaction(SIGPIPE, &sigignore, NULL);
121
122    if ((stat=pthread_cond_init(&cmd_wait, NULL)) != 0) {
123       Emsg1(M_ABORT, 0, _("Pthread cond init error = %s\n"),
124          strerror(stat));
125    }
126
127    gnome_init("bacula", VERSION, gargc, (char **)&gargv);
128
129    while ((ch = getopt(argc, argv, "bc:d:r:st?")) != -1) {
130       switch (ch) {
131       case 'c':                    /* configuration file */
132          if (configfile != NULL)
133             free(configfile);
134          configfile = bstrdup(optarg);
135          break;
136
137       case 'd':
138          debug_level = atoi(optarg);
139          if (debug_level <= 0)
140             debug_level = 1;
141          break;
142
143       case 's':                    /* turn off signals */
144          no_signals = TRUE;
145          break;
146
147       case 't':
148          test_config = TRUE;
149          break;
150
151       case '?':
152       default:
153          usage();
154       }  
155    }
156    argc -= optind;
157    argv += optind;
158
159
160    if (!no_signals) {
161       init_signals(terminate_console);
162    }
163
164    if (argc) {
165       usage();
166    }
167
168    if (configfile == NULL) {
169       configfile = bstrdup(CONFIG_FILE);
170    }
171
172    parse_config(configfile);
173
174    LockRes();
175    ndir = 0;
176    foreach_res(dir, R_DIRECTOR) {
177       ndir++;
178    }
179    UnlockRes();
180    if (ndir == 0) {
181       Emsg1(M_ERROR_TERM, 0, _("No director resource defined in %s\n\
182 Without that I don't how to speak to the Director :-(\n"), configfile);
183    }
184
185
186
187    console = create_console();
188    gtk_window_set_default_size(GTK_WINDOW(console), 800, 700);
189    run_dialog = create_RunDialog();
190    label_dialog = create_label_dialog();
191    restore_dialog = create_RestoreDialog();
192    about1 = create_about1();
193
194    text1 = lookup_widget(console, "text1");
195    entry1 = lookup_widget(console, "entry1");
196    status1 = lookup_widget(console, "status1");
197    scroll1 = lookup_widget(console, "scroll1");
198
199    select_restore_setup();
200
201    gtk_widget_show(console);
202
203 /*
204  * Thanks to Phil Stracchino for providing the font configuration code.
205  * original default:
206    text_font = gdk_font_load("-misc-fixed-medium-r-normal-*-*-130-*-*-c-*-koi8-r");
207  * this works for me:
208    text_font = gdk_font_load("-Bigelow & Holmes-lucida console-medium-r-semi condensed-*-12-0-100-100-m-0-iso8859-1");
209  * and, new automagic:font specification!
210  */
211
212    LockRes();
213    foreach_res(con_font, R_CONSOLE_FONT) {
214        if (!con_font->fontface) {
215           Dmsg1(400, "No fontface for %s\n", con_font->hdr.name);
216           continue;
217        }
218        text_font = gdk_font_load(con_font->fontface);
219        if (text_font == NULL) {
220            Dmsg2(400, "Load of requested ConsoleFont \"%s\" (%s) failed!\n",
221                   con_font->hdr.name, con_font->fontface);
222        } else {
223            Dmsg2(400, "ConsoleFont \"%s\" (%s) loaded.\n",
224                   con_font->hdr.name, con_font->fontface);
225            break;
226        }           
227    }
228    UnlockRes();
229
230    if (text_font == NULL) {
231        Dmsg1(400, "Attempting to load fallback font %s\n",
232               "-misc-fixed-medium-r-normal-*-*-130-*-*-c-*-iso8859-1");
233        text_font = gdk_font_load("-misc-fixed-medium-r-normal-*-*-130-*-*-c-*-iso8859-1");
234    }
235    font_desc = pango_font_description_from_string("LucidaTypewriter 9");
236    gtk_widget_modify_font(console, font_desc);
237    gtk_widget_modify_font(text1, font_desc);
238    gtk_widget_modify_font(entry1, font_desc);
239    gtk_widget_modify_font(status1, font_desc);
240    pango_font_description_free(font_desc);
241
242    if (test_config) {
243       terminate_console(0);
244       exit(0);
245    }
246
247    initial = gtk_timeout_add(100, initial_connect_to_director, (gpointer)NULL);
248
249    gtk_main();
250    quit = true;
251    disconnect_from_director((gpointer)NULL);
252    return 0;
253 }
254
255 /*
256  * Every 5 seconds, ask the Director for our
257  *  messages.
258  */
259 static gint message_handler(gpointer data)
260 {
261    if (ready && UA_sock) {
262       bnet_fsend(UA_sock, ".messages");
263    }
264    return TRUE;
265 }
266
267 int disconnect_from_director(gpointer data)
268 {
269    if (!quit) {
270       set_status(_(" Not Connected"));
271    }
272    if (UA_sock) {
273       bnet_sig(UA_sock, BNET_TERMINATE); /* send EOF */
274       bnet_close(UA_sock);
275       UA_sock = NULL;
276    }
277    return 1;
278 }
279
280 /*
281  * Called just after the main loop is started to allow
282  *  us to connect to the Director.
283  */
284 static int initial_connect_to_director(gpointer data)
285 {
286    gtk_timeout_remove(initial);
287    if (connect_to_director(data)) {
288       start_director_reader(data);
289    }
290    gtk_timeout_add(5000, message_handler, (gpointer)NULL);
291    return TRUE;
292 }
293
294 static GList *get_list(char *cmd)
295 {
296    GList *options;
297    char *msg;
298
299    options = NULL;
300    write_director(cmd);
301    while (bnet_recv(UA_sock) > 0) {
302       strip_trailing_junk(UA_sock->msg);
303       msg = (char *)malloc(strlen(UA_sock->msg) + 1);
304       strcpy(msg, UA_sock->msg);
305       options = g_list_append(options, msg);
306    }
307    return options;
308    
309 }
310
311 static GList *get_and_fill_combo(GtkWidget *dialog, char *combo_name, char *cmd)
312 {
313    GtkWidget *combo;
314    GList *options;
315
316    combo = lookup_widget(dialog, combo_name);
317    options = get_list(cmd);
318    if (combo && options) {
319       gtk_combo_set_popdown_strings(GTK_COMBO(combo), options);
320    }
321    return options;
322 }
323
324 static void fill_combo(GtkWidget *dialog, char *combo_name, GList *options)
325 {
326    GtkWidget *combo;
327
328    combo = lookup_widget(dialog, combo_name);
329    if (combo) {
330       gtk_combo_set_popdown_strings(GTK_COMBO(combo), options);
331    }
332    return;
333 }
334
335
336 /*
337  * Connect to Director. If there are more than one, put up
338  * a modal dialog so that the user chooses one.
339  */
340 int connect_to_director(gpointer data)
341 {
342    GList *dirs = NULL;
343    GtkWidget *combo;
344    JCR jcr;
345
346
347    if (UA_sock) {
348       return 0;
349    }
350
351    if (ndir > 1) {
352       LockRes();
353       foreach_res(dir, R_DIRECTOR) {
354          dirs = g_list_append(dirs, dir->hdr.name);
355       }
356       UnlockRes();
357       dir_dialog = create_SelectDirectorDialog();
358       combo = lookup_widget(dir_dialog, "combo1");
359       dir_select = lookup_widget(dir_dialog, "dirselect");
360       gtk_combo_set_popdown_strings(GTK_COMBO(combo), dirs);   
361       gtk_widget_show(dir_dialog);
362       gtk_main();
363
364       if (reply == OK) {
365          gchar *ecmd = gtk_editable_get_chars((GtkEditable *)dir_select, 0, -1);
366          dir = (DIRRES *)GetResWithName(R_DIRECTOR, ecmd);
367          if (ecmd) {
368             g_free(ecmd);             /* release director name string */
369          }
370       }
371       if (dirs) {
372          g_free(dirs);
373       }
374       gtk_widget_destroy(dir_dialog);
375       dir_dialog = NULL;
376    } else {
377       /* Just take the first Director */
378       LockRes();
379       dir = (DIRRES *)GetNextRes(R_DIRECTOR, NULL);
380       UnlockRes();
381    }
382
383    if (!dir) {
384       return 0;
385    }
386
387    memset(&jcr, 0, sizeof(jcr));
388    
389    set_statusf(_(" Connecting to Director %s:%d"), dir->address,dir->DIRport);
390    set_textf(_("Connecting to Director %s:%d\n\n"), dir->address,dir->DIRport);
391
392    while (gtk_events_pending()) {     /* fully paint screen */
393       gtk_main_iteration();
394    }
395    UA_sock = bnet_connect(NULL, 5, 15, "Director daemon", dir->address, 
396                           NULL, dir->DIRport, 0);
397    if (UA_sock == NULL) {
398       return 0;
399    }
400    
401    jcr.dir_bsock = UA_sock;
402    LockRes();
403    /* If cons==NULL, default console will be used */
404    CONRES *cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)NULL);
405    UnlockRes();
406    if (!authenticate_director(&jcr, dir, cons)) {
407       set_text(UA_sock->msg, UA_sock->msglen);
408       return 0;
409    }
410
411    set_status(" Initializing ...");
412
413    bnet_fsend(UA_sock, "autodisplay on");
414
415    /* Read and display all initial messages */
416    while (bnet_recv(UA_sock) > 0) {
417       set_text(UA_sock->msg, UA_sock->msglen);
418    }
419
420    /* Paint changes */
421    while (gtk_events_pending()) {
422       gtk_main_iteration();
423    }
424
425    /* Fill the run_dialog combo boxes */
426    job_list      = get_and_fill_combo(run_dialog, "combo_job", ".jobs");
427    client_list   = get_and_fill_combo(run_dialog, "combo_client", ".clients");
428    fileset_list  = get_and_fill_combo(run_dialog, "combo_fileset", ".filesets");
429    messages_list = get_and_fill_combo(run_dialog, "combo_messages", ".msgs");
430    pool_list     = get_and_fill_combo(run_dialog, "combo_pool", ".pools");
431    storage_list  = get_and_fill_combo(run_dialog, "combo_storage", ".storage");
432    type_list     = get_and_fill_combo(run_dialog, "combo_type", ".types");
433    level_list    = get_and_fill_combo(run_dialog, "combo_level", ".levels");
434
435    /* Fill the label dialog combo boxes */
436    fill_combo(label_dialog, "label_combo_storage", storage_list);
437    fill_combo(label_dialog, "label_combo_pool", pool_list);
438
439
440    /* Fill the restore_dialog combo boxes */
441    fill_combo(restore_dialog, "combo_restore_job", job_list);
442    fill_combo(restore_dialog, "combo_restore_client", client_list);
443    fill_combo(restore_dialog, "combo_restore_fileset", fileset_list);
444    fill_combo(restore_dialog, "combo_restore_pool", pool_list);
445    fill_combo(restore_dialog, "combo_restore_storage", storage_list);
446
447    set_status(" Connected");
448    return 1;
449 }
450
451 void write_director(gchar *msg)
452 {
453    if (UA_sock) {
454       at_prompt = false;
455       set_status(_(" Processing command ..."));
456       UA_sock->msglen = strlen(msg);
457       pm_strcpy(&UA_sock->msg, msg);
458       bnet_send(UA_sock);
459    }
460    if (strcmp(msg, ".quit") == 0 || strcmp(msg, ".exit") == 0) {
461       disconnect_from_director((gpointer)NULL);
462       gtk_main_quit();
463    }
464 }
465
466 void read_director(gpointer data, gint fd, GdkInputCondition condition)
467 {
468    int stat;
469
470    if (!UA_sock || UA_sock->fd != fd) {
471       return;
472    }
473    stat = bnet_recv(UA_sock);
474    if (stat >= 0) {
475       if (at_prompt) {
476          set_text("\n", 1);
477          at_prompt = false;
478       }
479       set_text(UA_sock->msg, UA_sock->msglen);
480       return;
481    }
482    if (is_bnet_stop(UA_sock)) {         /* error or term request */
483       gtk_main_quit();
484       return;
485    }
486    /* Must be a signal -- either do something or ignore it */
487    if (UA_sock->msglen == BNET_PROMPT) {
488       at_prompt = true;
489       set_status(_(" At prompt waiting for input ..."));
490    }
491    if (UA_sock->msglen == BNET_EOD) {
492       set_status_ready();
493    }
494    return;
495 }
496
497 static gint tag;
498
499 void start_director_reader(gpointer data)
500 {
501
502    if (director_reader_running || !UA_sock) {
503       return;
504    }
505    tag = gdk_input_add(UA_sock->fd, GDK_INPUT_READ, read_director, NULL);
506    director_reader_running = true;
507 }
508
509 void stop_director_reader(gpointer data)
510 {
511    if (!director_reader_running) {
512       return;
513    }
514    gdk_input_remove(tag);
515    gdk_input_remove(tag);
516    gdk_input_remove(tag);
517    gdk_input_remove(tag);
518    gdk_input_remove(tag);
519    gdk_input_remove(tag);
520    gdk_input_remove(tag);
521    gdk_input_remove(tag);
522    gdk_input_remove(tag);
523    gdk_input_remove(tag);
524    gdk_input_remove(tag);
525    director_reader_running = false;
526 }
527
528
529
530 /* Cleanup and then exit */
531 static void terminate_console(int sig)
532 {
533    static int already_here = FALSE;
534
535    if (already_here)                  /* avoid recursive temination problems */
536       exit(1);
537    already_here = TRUE;
538    disconnect_from_director((gpointer)NULL);
539    gtk_main_quit();
540    exit(0);
541 }
542
543
544 /* Buffer approx 2000 lines -- assume 60 chars/line */
545 #define MAX_TEXT_CHARS   (2000 * 60)
546 static int text_chars = 0;
547
548 static void truncate_text_chars()
549 {
550    GtkTextBuffer *textbuf;
551    GtkTextIter iter, iter2;
552    guint len;
553    int del_chars = MAX_TEXT_CHARS / 4;
554
555    textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text1));
556    len = gtk_text_buffer_get_char_count(textbuf);
557    gtk_text_buffer_get_iter_at_offset (textbuf, &iter, 0);
558    gtk_text_buffer_get_iter_at_offset (textbuf, &iter2, del_chars);
559    gtk_text_buffer_delete (textbuf, &iter, &iter2);
560    text_chars -= del_chars;
561    len = gtk_text_buffer_get_char_count(textbuf);
562    gtk_text_iter_set_offset(&iter, len);
563 }
564
565 void set_textf(char *fmt, ...)
566 {
567    va_list arg_ptr;
568    char buf[1000];
569    int len;
570    va_start(arg_ptr, fmt);
571    len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
572    va_end(arg_ptr);
573    set_text(buf, len);
574 }
575
576 void set_text(char *buf, int len)
577 {
578    GtkTextBuffer *textbuf;
579    GtkTextIter iter;
580    guint buf_len;
581
582    textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text1));
583    buf_len = gtk_text_buffer_get_char_count(textbuf);
584    gtk_text_buffer_get_iter_at_offset(textbuf, &iter, buf_len - 1);
585    gtk_text_buffer_insert(textbuf, &iter, buf, -1);
586    text_chars += len;
587    if (text_chars > MAX_TEXT_CHARS) {
588       truncate_text_chars();
589    }
590    set_scroll_bar_to_end();
591 }
592
593 void set_statusf(char *fmt, ...)
594 {
595    va_list arg_ptr;
596    char buf[1000];
597    int len;
598    va_start(arg_ptr, fmt);
599    len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
600    va_end(arg_ptr);
601    gtk_label_set_text(GTK_LABEL(status1), buf);
602 // set_scroll_bar_to_end();
603    ready = false;
604 }
605
606 void set_status_ready()    
607 {
608    gtk_label_set_text(GTK_LABEL(status1), " Ready");
609    ready = true;
610 // set_scroll_bar_to_end();
611 }
612
613 void set_status(char *buf)
614 {
615    gtk_label_set_text(GTK_LABEL(status1), buf);
616 // set_scroll_bar_to_end();
617    ready = false;
618 }
619
620 static void set_scroll_bar_to_end(void)
621 {
622    GtkTextBuffer* textbuf = NULL;
623    GtkTextIter iter;
624    guint buf_len;
625
626    textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text1));
627    buf_len = gtk_text_buffer_get_char_count(textbuf);
628    gtk_text_buffer_get_iter_at_offset(textbuf, &iter, buf_len - 1);
629    gtk_text_iter_set_offset(&iter, buf_len);
630    gtk_text_buffer_place_cursor(textbuf, &iter);
631    gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(text1),
632               gtk_text_buffer_get_mark(textbuf, "insert"), 
633               0, TRUE, 0.0, 1.0);
634 }