]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/tray-monitor/tray-monitor.c
Cleanup Python build so that Python is not dragged
[bacula/bacula] / bacula / src / tray-monitor / tray-monitor.c
1 /*
2  *
3  *   Bacula Gnome Tray Monitor
4  *
5  *     Nicolas Boichat, August MMIV
6  *
7  *     Version $Id$
8  */
9
10 /*
11    Copyright (C) 2004-2005 Kern Sibbald
12
13    This library is free software; you can redistribute it and/or
14    modify it under the terms of the GNU Lesser General Public
15    License as published by the Free Software Foundation; either
16    version 2.1 of the License, or (at your option) any later version.
17
18    This library 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 GNU
21    Lesser General Public License for more details.
22
23    You should have received a copy of the GNU Lesser General Public
24    License along with this library; if not, write to the Free
25    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
26    MA 02111-1307, USA.
27
28  */
29
30 #include "bacula.h"
31 #include "tray-monitor.h"
32
33 #include "eggstatusicon.h"
34
35 #include "generic.xpm"
36
37 #define TRAY_DEBUG_MEMORY 0
38
39 /* Imported functions */
40 int authenticate_director(JCR *jcr, MONITOR *monitor, DIRRES *director);
41 int authenticate_file_daemon(JCR *jcr, MONITOR *monitor, CLIENT* client);
42 int authenticate_storage_daemon(JCR *jcr, MONITOR *monitor, STORE* store);
43
44 /* Dummy functions */
45 int generate_daemon_event(JCR *jcr, const char *event) { return 1; }
46
47 /* Forward referenced functions */
48 void writecmd(monitoritem* item, const char* command);
49 int docmd(monitoritem* item, const char* command, GSList** list);
50 void getstatus(monitoritem* item, int current, GString** str);
51
52 /* Static variables */
53 static char *configfile = NULL;
54 static MONITOR *monitor;
55 static JCR jcr;
56 static int nitems = 0;
57 static int fullitem = 0; //Item to be display in detailled status window
58 static int lastupdated = -1; //Last item updated
59 static monitoritem items[32];
60
61 /* Data received from DIR/FD/SD */
62 static char OKqstatus[]   = "%c000 OK .status\n";
63 static char DotStatusJob[] = "JobId=%d JobStatus=%c JobErrors=%d\n";
64
65 /* UI variables and functions */
66
67 extern "C" {
68 static gboolean fd_read(gpointer data);
69 static gboolean blink(gpointer data);
70 }
71
72 void trayMessage(const char *fmt,...);
73 void updateStatusIcon(monitoritem* item);
74 void changeStatusMessage(monitoritem* item, const char *fmt,...);
75 static const char** generateXPM(stateenum newstate, stateenum oldstate);
76
77 /* Callbacks */
78 static void TrayIconActivate(GtkWidget *widget, gpointer data);
79 static void TrayIconExit(unsigned int activateTime, unsigned int button);
80 static void TrayIconPopupMenu(unsigned int button, unsigned int activateTime);
81 static void MonitorAbout(GtkWidget *widget, gpointer data);
82 static void MonitorRefresh(GtkWidget *widget, gpointer data);
83 static void IntervalChanged(GtkWidget *widget, gpointer data);
84 static void DaemonChanged(GtkWidget *widget, monitoritem* data);
85 static gboolean delete_event(GtkWidget *widget, GdkEvent  *event, gpointer   data);
86
87 static guint timerTag;
88 static EggStatusIcon *mTrayIcon;
89 static GtkWidget *mTrayMenu;
90 static GtkWidget *window;
91 static GtkWidget *textview;
92 static GtkTextBuffer *buffer;
93 static GtkWidget *timeoutspinner;
94 char** xpm_generic_var;
95 static gboolean blinkstate = TRUE;
96
97 #define CONFIG_FILE "./tray-monitor.conf"   /* default configuration file */
98
99 static void usage()
100 {
101    fprintf(stderr, _(
102 "Copyright (C) 2000-2004 Kern Sibbald and John Walker\n"
103 "Written by Nicolas Boichat (2004)\n"
104 "\nVersion: " VERSION " (" BDATE ") %s %s %s\n\n"
105 "Usage: tray-monitor [-c config_file] [-d debug_level]\n"
106 "       -c <file>     set configuration file to file\n"
107 "       -dnn          set debug level to nn\n"
108 "       -t            test - read configuration and exit\n"
109 "       -?            print this message.\n"
110 "\n"), HOST_OS, DISTNAME, DISTVER);
111 }
112
113 static GtkWidget *new_image_button(const gchar *stock_id,
114                                    const gchar *label_text) {
115     GtkWidget *button;
116     GtkWidget *box;
117     GtkWidget *label;
118     GtkWidget *image;
119
120     button = gtk_button_new();
121
122     box = gtk_hbox_new(FALSE, 0);
123     gtk_container_set_border_width(GTK_CONTAINER(box), 2);
124     image = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_BUTTON);
125     label = gtk_label_new(label_text);
126
127     gtk_box_pack_start(GTK_BOX(box), image, FALSE, FALSE, 3);
128     gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 3);
129
130     gtk_widget_show(image);
131     gtk_widget_show(label);
132
133     gtk_widget_show(box);
134
135     gtk_container_add(GTK_CONTAINER(button), box);
136
137     return button;
138 }
139
140 int sm_line = 0;
141
142 #if TRAY_DEBUG_MEMORY
143 gpointer smt_malloc(gsize    n_bytes) {
144    return sm_malloc("GLib", sm_line, n_bytes);
145 }
146   
147 gpointer smt_realloc(gpointer mem, gsize    n_bytes) {
148    return sm_realloc("GLib", sm_line, mem, n_bytes);
149 }
150
151 gpointer smt_calloc(gsize    n_blocks,
152                     gsize    n_block_bytes) {
153    return sm_calloc("GLib", sm_line, n_blocks, n_block_bytes);
154 }
155
156 void     smt_free(gpointer mem) {
157    sm_free("Glib", sm_line, mem);
158 }
159 #endif
160
161 /*********************************************************************
162  *
163  *         Main Bacula Tray Monitor -- User Interface Program
164  *
165  */
166 int main(int argc, char *argv[])
167 {
168 #if TRAY_DEBUG_MEMORY
169    GMemVTable smvtable;
170    smvtable.malloc = &smt_malloc;
171    smvtable.realloc = &smt_realloc;
172    smvtable.free = &smt_free;
173    smvtable.calloc = &smt_calloc;
174    smvtable.try_malloc = NULL;
175    smvtable.try_realloc = NULL;
176    g_mem_set_vtable(&smvtable);
177 #endif
178    
179    int ch, i;
180    bool test_config = false;
181    DIRRES* dird;
182    CLIENT* filed;
183    STORE* stored;
184
185    init_stack_dump();
186    my_name_is(argc, argv, "tray-monitor");
187    textdomain("bacula");
188    init_msg(NULL, NULL);
189    working_directory = "/tmp";
190
191    struct sigaction sigignore;
192    sigignore.sa_flags = 0;
193    sigignore.sa_handler = SIG_IGN;
194    sigfillset(&sigignore.sa_mask);
195    sigaction(SIGPIPE, &sigignore, NULL);
196
197    gtk_init (&argc, &argv);
198
199    while ((ch = getopt(argc, argv, "bc:d:th?f:s:")) != -1) {
200       switch (ch) {
201       case 'c':                    /* configuration file */
202          if (configfile != NULL) {
203             free(configfile);
204          }
205          configfile = bstrdup(optarg);
206          break;
207
208       case 'd':
209          debug_level = atoi(optarg);
210          if (debug_level <= 0) {
211             debug_level = 1;
212          }
213          break;
214
215       case 't':
216          test_config = true;
217          break;
218
219       case 'h':
220       case '?':
221       default:
222          usage();
223          exit(1);
224       }
225    }
226    argc -= optind;
227    //argv += optind;
228
229    if (argc) {
230       usage();
231       exit(1);
232    }
233
234    if (configfile == NULL) {
235       configfile = bstrdup(CONFIG_FILE);
236    }
237
238    parse_config(configfile);
239
240    LockRes();
241    nitems = 0;
242    foreach_res(monitor, R_MONITOR) {
243       nitems++;
244    }
245
246    if (nitems != 1) {
247       Emsg2(M_ERROR_TERM, 0,
248          _("Error: %d Monitor resource defined in %s. You must define one and only one Monitor resource.\n"), nitems, configfile);
249    }
250
251    nitems = 0;
252    foreach_res(dird, R_DIRECTOR) {
253       items[nitems].type = R_DIRECTOR;
254       items[nitems].resource = dird;
255       items[nitems].D_sock = NULL;
256       items[nitems].state = warn;
257       items[nitems].oldstate = warn;
258       nitems++;
259    }
260    foreach_res(filed, R_CLIENT) {
261       items[nitems].type = R_CLIENT;
262       items[nitems].resource = filed;
263       items[nitems].D_sock = NULL;
264       items[nitems].state = warn;
265       items[nitems].oldstate = warn;
266       nitems++;
267    }
268    foreach_res(stored, R_STORAGE) {
269       items[nitems].type = R_STORAGE;
270       items[nitems].resource = stored;
271       items[nitems].D_sock = NULL;
272       items[nitems].state = warn;
273       items[nitems].oldstate = warn;
274       nitems++;
275    }
276    UnlockRes();
277
278    if (nitems == 0) {
279       Emsg1(M_ERROR_TERM, 0, _("No Client, Storage nor Director resource defined in %s\n"
280 "Without that I don't how to get status from the File, Storage or Director Daemon :-(\n"), configfile);
281    }
282
283    if (test_config) {
284       exit(0);
285    }
286
287    //Copy the content of xpm_generic in xpm_generic_var to be able to modify it
288    g_assert((xpm_generic_var = (char**)g_malloc(sizeof(xpm_generic))));
289    for (i = 0; i < (int)(sizeof(xpm_generic)/sizeof(const char*)); i++) {
290       g_assert((xpm_generic_var[i] = (char*)g_malloc((strlen(xpm_generic[i])+1)*sizeof(char))));
291       strcpy(xpm_generic_var[i], xpm_generic[i]);
292    }
293
294    (void)WSA_Init();                /* Initialize Windows sockets */
295
296    LockRes();
297    monitor = (MONITOR*)GetNextRes(R_MONITOR, (RES *)NULL);
298    UnlockRes();
299
300    if ((monitor->RefreshInterval < 1) || (monitor->RefreshInterval > 600)) {
301       Emsg2(M_ERROR_TERM, 0, _("Invalid refresh interval defined in %s\n"
302 "This value must be greater or equal to 1 second and less or equal to 10 minutes (read value: %d).\n"), configfile, monitor->RefreshInterval);
303    }
304
305    GdkPixbuf* pixbuf = gdk_pixbuf_new_from_xpm_data(generateXPM(warn, warn));
306    // This should be ideally replaced by a completely libpr0n-based icon rendering.
307    mTrayIcon = egg_status_icon_new_from_pixbuf(pixbuf);
308    g_signal_connect(G_OBJECT(mTrayIcon), "activate", G_CALLBACK(TrayIconActivate), NULL);
309    g_signal_connect(G_OBJECT(mTrayIcon), "popup-menu", G_CALLBACK(TrayIconPopupMenu), NULL);
310    g_object_unref(G_OBJECT(pixbuf));
311
312    mTrayMenu = gtk_menu_new();
313
314    GtkWidget *entry;
315
316    entry = gtk_menu_item_new_with_label("Open status window...");
317    g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(TrayIconActivate), NULL);
318    gtk_menu_shell_append(GTK_MENU_SHELL(mTrayMenu), entry);
319
320    gtk_menu_shell_append(GTK_MENU_SHELL(mTrayMenu), gtk_separator_menu_item_new());
321
322    entry = gtk_menu_item_new_with_label("Exit");
323    g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(TrayIconExit), NULL);
324    gtk_menu_shell_append(GTK_MENU_SHELL(mTrayMenu), entry);
325
326    gtk_widget_show_all(mTrayMenu);
327
328    timerTag = g_timeout_add( 1000*monitor->RefreshInterval/nitems, fd_read, NULL );
329
330    g_timeout_add( 1000, blink, NULL );
331
332    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
333
334    gtk_window_set_title(GTK_WINDOW(window), "Bacula tray monitor");
335
336    g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(delete_event), NULL);
337    //g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy), NULL);
338
339    gtk_container_set_border_width(GTK_CONTAINER(window), 10);
340
341    GtkWidget* vbox = gtk_vbox_new(FALSE, 10);
342
343    GtkWidget* daemon_table = gtk_table_new((nitems*2)+2, 3, FALSE);
344
345    gtk_table_set_col_spacings(GTK_TABLE(daemon_table), 8);
346
347    GtkWidget* separator = gtk_hseparator_new();
348    gtk_table_attach_defaults(GTK_TABLE(daemon_table), separator, 0, 3, 0, 1);
349
350    GString *str;
351    GSList *group = NULL;
352    GtkWidget* radio;
353    GtkWidget* align;
354
355    for (int i = 0; i < nitems; i++) {
356       switch (items[i].type) {
357       case R_DIRECTOR:
358          str = g_string_new(((DIRRES*)(items[i].resource))->hdr.name);
359          g_string_append(str, _(" (DIR)"));
360          break;
361       case R_CLIENT:
362          str = g_string_new(((CLIENT*)(items[i].resource))->hdr.name);
363          g_string_append(str, _(" (FD)"));
364          break;
365       case R_STORAGE:
366          str = g_string_new(((STORE*)(items[i].resource))->hdr.name);
367          g_string_append(str, _(" (SD)"));
368          break;
369       default:
370          continue;
371       }
372
373       radio = gtk_radio_button_new_with_label(group, str->str);
374       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), i == 0);
375       g_signal_connect(G_OBJECT(radio), "toggled", G_CALLBACK(DaemonChanged), &(items[i]));
376
377       pixbuf = gdk_pixbuf_new_from_xpm_data(generateXPM(warn, warn));
378       items[i].image = gtk_image_new_from_pixbuf(pixbuf);
379
380       items[i].label =  gtk_label_new(_("Unknown status."));
381       align = gtk_alignment_new(0.0, 0.5, 0.0, 1.0);
382       gtk_container_add(GTK_CONTAINER(align), items[i].label);
383
384       gtk_table_attach(GTK_TABLE(daemon_table), radio, 0, 1, (i*2)+1, (i*2)+2,
385          GTK_FILL, (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), 0, 0);
386       gtk_table_attach(GTK_TABLE(daemon_table), items[i].image, 1, 2, (i*2)+1, (i*2)+2,
387          GTK_FILL, (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), 0, 0);
388       gtk_table_attach(GTK_TABLE(daemon_table), align, 2, 3, (i*2)+1, (i*2)+2,
389          (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), 0, 0);
390
391       separator = gtk_hseparator_new();
392       gtk_table_attach_defaults(GTK_TABLE(daemon_table), separator, 0, 3, (i*2)+2, (i*2)+3);
393
394       group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(radio));
395    }
396
397    gtk_box_pack_start(GTK_BOX(vbox), daemon_table, FALSE, FALSE, 0);
398
399    textview = gtk_text_view_new();
400
401    buffer = gtk_text_buffer_new(NULL);
402
403    gtk_text_buffer_set_text(buffer, "", -1);
404
405    PangoFontDescription *font_desc = pango_font_description_from_string ("Fixed 10");
406    gtk_widget_modify_font(textview, font_desc);
407    pango_font_description_free(font_desc);
408
409    gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview), 20);
410    gtk_text_view_set_right_margin(GTK_TEXT_VIEW(textview), 20);
411
412    gtk_text_view_set_editable(GTK_TEXT_VIEW(textview), FALSE);
413
414    gtk_text_view_set_buffer(GTK_TEXT_VIEW(textview), buffer);
415
416    gtk_box_pack_start(GTK_BOX(vbox), textview, TRUE, TRUE, 0);
417
418    GtkWidget* hbox = gtk_hbox_new(FALSE, 10);
419
420    GtkWidget* hbox2 = gtk_hbox_new(FALSE, 0);
421    GtkWidget* label = gtk_label_new(_("Refresh interval in seconds: "));
422    gtk_box_pack_start(GTK_BOX(hbox2), label, TRUE, FALSE, 0);
423    GtkAdjustment *spinner_adj = (GtkAdjustment *) gtk_adjustment_new (monitor->RefreshInterval, 1.0, 600.0, 1.0, 5.0, 5.0);
424    timeoutspinner = gtk_spin_button_new (spinner_adj, 1.0, 0);
425    g_signal_connect(G_OBJECT(timeoutspinner), "value-changed", G_CALLBACK(IntervalChanged), NULL);
426    gtk_box_pack_start(GTK_BOX(hbox2), timeoutspinner, TRUE, FALSE, 0);
427    gtk_box_pack_start(GTK_BOX(hbox), hbox2, TRUE, FALSE, 0);
428
429    GtkWidget* button = new_image_button("gtk-refresh", _("Refresh now"));
430    g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(MonitorRefresh), NULL);
431    gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, FALSE, 0);
432
433    button = new_image_button("gtk-help", _("About"));
434    g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(MonitorAbout), NULL);
435    gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, FALSE, 0);
436
437    button = new_image_button("gtk-close", _("Close"));
438    g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(gtk_widget_hide), G_OBJECT(window));
439    gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, FALSE, 0);
440
441    gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
442
443    gtk_container_add(GTK_CONTAINER (window), vbox);
444
445    gtk_widget_show_all(vbox);
446
447    gtk_main();
448    
449    g_source_remove(timerTag);
450    
451    sm_line = 0;
452
453    for (i = 0; i < nitems; i++) {
454       if (items[i].D_sock) {
455          writecmd(&items[i], "quit");
456          bnet_sig(items[i].D_sock, BNET_TERMINATE); /* send EOF */
457          bnet_close(items[i].D_sock);
458       }
459    }
460
461    (void)WSACleanup();               /* Cleanup Windows sockets */
462
463    //Free xpm_generic_var
464    for (i = 0; i < (int)(sizeof(xpm_generic)/sizeof(const char*)); i++) {
465       g_free(xpm_generic_var[i]);
466    }
467    g_free(xpm_generic_var);
468    
469    gtk_object_destroy(GTK_OBJECT(window));
470    gtk_object_destroy(GTK_OBJECT(mTrayMenu));
471    term_msg();
472
473 #if TRAY_DEBUG_MEMORY
474    sm_dump(false);
475 #endif
476    
477    return 0;
478 }
479
480 static void MonitorAbout(GtkWidget *widget, gpointer data) {
481 #if HAVE_GTK_2_4
482    GtkWidget* about = gtk_message_dialog_new_with_markup(GTK_WINDOW(window),GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, _(
483       "<span size='x-large' weight='bold'>Bacula Tray Monitor</span>\n\n"
484       "Copyright (C) 2004 Kern Sibbald and John Walker\n"
485       "Written by Nicolas Boichat\n"
486       "\n<small>Version: " VERSION " (" BDATE ") %s %s %s</small>"
487    ), HOST_OS, DISTNAME, DISTVER);
488 #else
489    GtkWidget* about = gtk_message_dialog_new(GTK_WINDOW(window),GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, _(
490       "Bacula Tray Monitor\n\n"
491       "Copyright (C) 2004 Kern Sibbald and John Walker\n"
492       "Written by Nicolas Boichat\n"
493       "\nVersion: " VERSION " (" BDATE ") %s %s %s"
494    ), HOST_OS, DISTNAME, DISTVER);
495 #endif
496    gtk_dialog_run(GTK_DIALOG(about));
497    gtk_widget_destroy(about);
498 }
499
500 static void MonitorRefresh(GtkWidget *widget, gpointer data) {
501    for (int i = 0; i < nitems; i++) {
502       fd_read(NULL);
503    }
504 }
505
506 static gboolean delete_event( GtkWidget *widget,
507                               GdkEvent  *event,
508                               gpointer   data ) {
509    gtk_widget_hide(window);
510    return TRUE; /* do not destroy the window */
511 }
512
513 static void TrayIconActivate(GtkWidget *widget, gpointer data) {
514     gtk_widget_show(window);
515 }
516
517 static void TrayIconPopupMenu(unsigned int activateTime, unsigned int button) {
518   gtk_menu_popup(GTK_MENU(mTrayMenu), NULL, NULL, NULL, NULL, 1, 0);
519   gtk_widget_show_all(mTrayMenu);
520 }
521
522 static void TrayIconExit(unsigned int activateTime, unsigned int button) {
523    gtk_main_quit();
524 }
525
526 static void IntervalChanged(GtkWidget *widget, gpointer data) {
527    g_source_remove(timerTag);
528    timerTag = g_timeout_add(
529       (guint)(
530          gtk_spin_button_get_value(GTK_SPIN_BUTTON(timeoutspinner))*1000/nitems
531       ), fd_read, NULL );
532 }
533
534 static void DaemonChanged(GtkWidget *widget, monitoritem* data) {
535    if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
536       fullitem = -1;
537       for (int i = 0; i < nitems; i++) {
538          if (data == &(items[i])) {
539             fullitem = i;
540             break;
541          }
542       }
543       g_return_if_fail(fullitem != -1);
544
545       int oldlastupdated = lastupdated;
546       lastupdated = fullitem-1;
547       fd_read(NULL);
548       lastupdated = oldlastupdated;
549    }
550 }
551
552 static int authenticate_daemon(monitoritem* item, JCR *jcr) {
553    switch (item->type) {
554    case R_DIRECTOR:
555       return authenticate_director(jcr, monitor, (DIRRES*)item->resource);
556       break;
557    case R_CLIENT:
558       return authenticate_file_daemon(jcr, monitor, (CLIENT*)item->resource);
559       break;
560    case R_STORAGE:
561       return authenticate_storage_daemon(jcr, monitor, (STORE*)item->resource);
562       break;
563    default:
564       printf("Error, currentitem is not a Client or a Storage..\n");
565       gtk_main_quit();
566       return FALSE;
567    }
568 }
569
570 static gboolean blink(gpointer data) {
571    blinkstate = !blinkstate;
572    for (int i = 0; i < nitems; i++) {
573       updateStatusIcon(&items[i]);
574    }
575    updateStatusIcon(NULL);
576    return true;
577 }
578
579 static gboolean fd_read(gpointer data) {
580    sm_line++;
581 #if TRAY_DEBUG_MEMORY
582    printf("sm_line=%d\n", sm_line);
583 #endif
584    GtkTextBuffer *newbuffer;
585    GtkTextIter start, stop, nstart, nstop;
586
587    GSList *list, *it;
588
589    GString *strlast, *strcurrent;
590
591    lastupdated++;
592    if (lastupdated == nitems) {
593       lastupdated = 0;
594    }
595
596    if (lastupdated == fullitem) {
597       newbuffer = gtk_text_buffer_new(NULL);
598       
599       if (items[lastupdated].type == R_DIRECTOR)
600          docmd(&items[lastupdated], "status Director\n", &list);
601       else
602          docmd(&items[lastupdated], "status\n", &list);
603
604       it = list->next;
605       do {
606          gtk_text_buffer_get_end_iter(newbuffer, &stop);
607          gtk_text_buffer_insert (newbuffer, &stop, ((GString*)it->data)->str, -1);
608          if (it->data) g_string_free((GString*)it->data, TRUE);
609       } while ((it = it->next) != NULL);
610
611       g_slist_free(list);
612             
613       /* Keep the selection if necessary */
614       if (gtk_text_buffer_get_selection_bounds(buffer, &start, &stop)) {
615          gtk_text_buffer_get_iter_at_offset(newbuffer, &nstart, gtk_text_iter_get_offset(&start));
616          gtk_text_buffer_get_iter_at_offset(newbuffer, &nstop,  gtk_text_iter_get_offset(&stop ));
617
618    #if HAVE_GTK_2_4
619          gtk_text_buffer_select_range(newbuffer, &nstart, &nstop);
620    #else
621          gtk_text_buffer_move_mark(newbuffer, gtk_text_buffer_get_mark(newbuffer, "insert"), &nstart);
622          gtk_text_buffer_move_mark(newbuffer, gtk_text_buffer_get_mark(newbuffer, "selection_bound"), &nstop);
623    #endif
624       }
625
626       g_object_unref(buffer);
627
628       buffer = newbuffer;
629       gtk_text_view_set_buffer(GTK_TEXT_VIEW(textview), buffer);
630    }
631
632    getstatus(&items[lastupdated], 1, &strcurrent);
633    getstatus(&items[lastupdated], 0, &strlast);
634    updateStatusIcon(&items[lastupdated]);
635
636    changeStatusMessage(&items[lastupdated], "Current job: %s\nLast job: %s", strcurrent->str, strlast->str);
637
638    updateStatusIcon(NULL);
639
640    g_string_free(strcurrent, TRUE);
641    g_string_free(strlast, TRUE);
642
643    return 1;
644 }
645
646 void getstatus(monitoritem* item, int current, GString** str) {
647    GSList *list, *it;
648    stateenum ret = error;
649    int jobid = 0, joberrors = 0;
650    char jobstatus = JS_ErrorTerminated;
651    char num;
652    int k;
653
654    *str = g_string_sized_new(128);
655
656    if (current) {
657       if (item->type == R_DIRECTOR)
658          docmd(&items[lastupdated], ".status dir current\n", &list);
659       else
660          docmd(&items[lastupdated], ".status current\n", &list);
661    }
662    else {
663       if (item->type == R_DIRECTOR)
664          docmd(&items[lastupdated], ".status dir last\n", &list);
665       else
666          docmd(&items[lastupdated], ".status last\n", &list);
667    }
668
669    it = list->next;
670    if ((it == NULL) || (sscanf(((GString*)it->data)->str, OKqstatus, &num) != 1)) {
671       g_string_append_printf(*str, ".status error : %s", (it == NULL) ? "" : ((GString*)it->data)->str);
672       while (((*str)->str[(*str)->len-1] == '\n') || ((*str)->str[(*str)->len-1] == '\r')) {
673          g_string_set_size(*str, (*str)->len-1);
674       }
675       ret = error;
676    }
677    else if ((it = it->next) == NULL) {
678       if (current) {
679          g_string_append(*str, _("No current job."));
680       }
681       else {
682          g_string_append(*str, _("No last job."));
683       }
684       ret = idle;
685    }
686    else if ((k = sscanf(((GString*)it->data)->str, DotStatusJob, &jobid, &jobstatus, &joberrors)) == 3) {
687       switch (jobstatus) {
688       case JS_Created:
689          ret = (joberrors > 0) ? warn : running;
690          g_string_append_printf(*str, _("Job status: Created (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
691          break;
692       case JS_Running:
693          ret = (joberrors > 0) ? warn : running;
694          g_string_append_printf(*str, _("Job status: Running (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
695          break;
696       case JS_Blocked:
697          g_string_append_printf(*str, _("Job status: Blocked (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
698          ret = warn;
699          break;
700       case JS_Terminated:
701          g_string_append_printf(*str, _("Job status: Terminated (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
702          ret = (joberrors > 0) ? warn : idle;
703          break;
704       case JS_ErrorTerminated:
705          g_string_append_printf(*str, _("Job status: Terminated in error (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
706          ret = error;
707          break;
708       case JS_Error:
709          ret = (joberrors > 0) ? warn : running;
710          g_string_append_printf(*str, _("Job status: Error (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
711          break;
712       case JS_FatalError:
713          g_string_append_printf(*str, _("Job status: Fatal error (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
714          ret = error;
715          break;
716       case JS_Differences:
717          g_string_append_printf(*str, _("Job status: Verify differences (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
718          ret = warn;
719          break;
720       case JS_Canceled:
721          g_string_append_printf(*str, _("Job status: Canceled (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
722          ret = warn;
723          break;
724       case JS_WaitFD:
725          g_string_append_printf(*str, _("Job status: Waiting on File daemon (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
726          ret = warn;
727          break;
728       case JS_WaitSD:
729          g_string_append_printf(*str, _("Job status: Waiting on the Storage daemon (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
730          ret = warn;
731          break;
732       case JS_WaitMedia:
733          g_string_append_printf(*str, _("Job status: Waiting for new media (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
734          ret = warn;
735          break;
736       case JS_WaitMount:
737          g_string_append_printf(*str, _("Job status: Waiting for Mount (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
738          ret = warn;
739          break;
740       case JS_WaitStoreRes:
741          g_string_append_printf(*str, _("Job status: Waiting for storage resource (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
742          ret = warn;
743          break;
744       case JS_WaitJobRes:
745          g_string_append_printf(*str, _("Job status: Waiting for job resource (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
746          ret = warn;
747          break;
748       case JS_WaitClientRes:
749          g_string_append_printf(*str, _("Job status: Waiting for Client resource (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
750          ret = warn;
751          break;
752       case JS_WaitMaxJobs:
753          g_string_append_printf(*str, _("Job status: Waiting for maximum jobs (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
754          ret = warn;
755          break;
756       case JS_WaitStartTime:
757          g_string_append_printf(*str, _("Job status: Waiting for start time (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
758          ret = warn;
759          break;
760       case JS_WaitPriority:
761          g_string_append_printf(*str, _("Job status: Waiting for higher priority jobs to finish (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
762          ret = warn;
763          break;
764       default:
765          g_warning("Unknown job status %c.", jobstatus);
766          g_string_append_printf(*str, _("Job status: Unknown(%c) (%d error%s)"), jobstatus, joberrors, (joberrors > 1) ? "s" : "");
767          ret = warn;
768          break;
769       }
770    }
771    else {
772       fprintf(stderr, "Bad scan : '%s' %d\n", (it == NULL) ? "" : ((GString*)it->data)->str, k);
773       ret = error;
774    }
775
776    it = list;
777    do {
778       if (it->data) g_string_free((GString*)it->data, TRUE);
779    } while ((it = it->next) != NULL);
780
781    g_slist_free(list);
782
783    if (current) {
784       item->state = ret;
785    }
786    else {
787       item->oldstate = ret;
788    }
789 }
790
791 int docmd(monitoritem* item, const char* command, GSList** list) {
792    int stat;
793    GString* str = NULL;
794
795    *list = g_slist_alloc();
796
797    //str = g_string_sized_new(64);
798
799    if (!item->D_sock) {
800       memset(&jcr, 0, sizeof(jcr));
801
802       DIRRES* dird;
803       CLIENT* filed;
804       STORE* stored;
805
806       switch (item->type) {
807       case R_DIRECTOR:
808          dird = (DIRRES*)item->resource;
809          trayMessage("Connecting to Director %s:%d\n", dird->address, dird->DIRport);
810          changeStatusMessage(item, "Connecting to Director %s:%d", dird->address, dird->DIRport);
811          item->D_sock = bnet_connect(NULL, 0, 0, "Director daemon", dird->address, NULL, dird->DIRport, 0);
812          jcr.dir_bsock = item->D_sock;
813          break;
814       case R_CLIENT:
815          filed = (CLIENT*)item->resource;
816          trayMessage("Connecting to Client %s:%d\n", filed->address, filed->FDport);
817          changeStatusMessage(item, "Connecting to Client %s:%d", filed->address, filed->FDport);
818          item->D_sock = bnet_connect(NULL, 0, 0, "File daemon", filed->address, NULL, filed->FDport, 0);
819          jcr.file_bsock = item->D_sock;
820          break;
821       case R_STORAGE:
822          stored = (STORE*)item->resource;
823          trayMessage("Connecting to Storage %s:%d\n", stored->address, stored->SDport);
824          changeStatusMessage(item, "Connecting to Storage %s:%d", stored->address, stored->SDport);
825          item->D_sock = bnet_connect(NULL, 0, 0, "Storage daemon", stored->address, NULL, stored->SDport, 0);
826          jcr.store_bsock = item->D_sock;
827          break;
828       default:
829          printf("Error, currentitem is not a Client, a Storage or a Director..\n");
830          gtk_main_quit();
831          return 0;
832       }
833
834       if (item->D_sock == NULL) {
835          g_slist_append(*list, g_string_new("Cannot connect to daemon.\n"));
836          changeStatusMessage(item, "Cannot connect to daemon.");
837          item->state = error;
838          item->oldstate = error;
839          return 0;
840       }
841
842       if (!authenticate_daemon(item, &jcr)) {
843          str = g_string_sized_new(64);
844          g_string_printf(str, "ERR=%s\n", item->D_sock->msg);
845          g_slist_append(*list, str);
846          item->state = error;
847          item->oldstate = error;
848          changeStatusMessage(item, "Authentication error : %s", item->D_sock->msg);
849          item->D_sock = NULL;
850          return 0;
851       }
852
853       switch (item->type) {
854       case R_DIRECTOR:
855          trayMessage("Opened connection with Director daemon.\n");
856          changeStatusMessage(item, "Opened connection with Director daemon.");
857          break;
858       case R_CLIENT:
859          trayMessage("Opened connection with File daemon.\n");
860          changeStatusMessage(item, "Opened connection with File daemon.");
861          break;
862       case R_STORAGE:
863          trayMessage("Opened connection with Storage daemon.\n");
864          changeStatusMessage(item, "Opened connection with Storage daemon.");
865          break;
866       default:
867          printf("Error, currentitem is not a Client, a Storage or a Director..\n");
868          gtk_main_quit();
869          return 0;
870          break;
871       }
872
873       if (item->type == R_DIRECTOR) { /* Read connection messages... */
874          GSList *list, *it;
875          docmd(item, "", &list); /* Usually invalid, but no matter */
876          it = list;
877          do {
878             if (it->data) g_string_free((GString*)it->data, TRUE);
879          } while ((it = it->next) != NULL);
880
881          g_slist_free(list);
882       }
883    }
884
885    if (command[0] != 0)
886       writecmd(item, command);
887
888    while(1) {
889       if ((stat = bnet_recv(item->D_sock)) >= 0) {
890          g_slist_append(*list, g_string_new(item->D_sock->msg));
891       }
892       else if (stat == BNET_SIGNAL) {
893          if (item->D_sock->msglen == BNET_EOD) {
894             //fprintf(stderr, "<< EOD >>\n");
895             return 1;
896          }
897          else if (item->D_sock->msglen == BNET_PROMPT) {
898             //fprintf(stderr, "<< PROMPT >>\n");
899             g_slist_append(*list, g_string_new("<< Error: BNET_PROMPT signal received. >>\n"));
900             return 0;
901          }
902          else if (item->D_sock->msglen == BNET_HEARTBEAT) {
903             bnet_sig(item->D_sock, BNET_HB_RESPONSE);
904             g_slist_append(*list, g_string_new("<< Heartbeat signal received, answered. >>\n"));
905          }
906          else {
907             str = g_string_sized_new(64);
908             g_string_printf(str, "<< Unexpected signal received : %s >>\n", bnet_sig_to_ascii(item->D_sock));
909             g_slist_append(*list, str);
910          }
911       }
912       else { /* BNET_HARDEOF || BNET_ERROR */
913          g_slist_append(*list, g_string_new("<ERROR>\n"));
914          item->D_sock = NULL;
915          item->state = error;
916          item->oldstate = error;
917          changeStatusMessage(item, "Error : BNET_HARDEOF or BNET_ERROR");
918          //fprintf(stderr, "<< ERROR >>\n");
919          return 0;
920       }
921
922       if (is_bnet_stop(item->D_sock)) {
923          g_string_append_printf(str, "<STOP>\n");
924          item->D_sock = NULL;
925          item->state = error;
926          item->oldstate = error;
927          changeStatusMessage(item, "Error : Connection closed.");
928          //fprintf(stderr, "<< STOP >>\n");
929          return 0;            /* error or term */
930       }
931    }
932 }
933
934 void writecmd(monitoritem* item, const char* command) {
935    if (item->D_sock) {
936       item->D_sock->msglen = strlen(command);
937       pm_strcpy(&item->D_sock->msg, command);
938       bnet_send(item->D_sock);
939    }
940 }
941
942 /* Note: Does not seem to work either on Gnome nor KDE... */
943 void trayMessage(const char *fmt,...) {
944    char buf[512];
945    va_list arg_ptr;
946
947    va_start(arg_ptr, fmt);
948    bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
949    va_end(arg_ptr);
950
951    fprintf(stderr, buf);
952
953    egg_tray_icon_send_message(egg_status_icon_get_tray_icon(mTrayIcon), 5000, (const char*)&buf, -1);
954 }
955
956 void changeStatusMessage(monitoritem* item, const char *fmt,...) {
957    char buf[512];
958    va_list arg_ptr;
959
960    va_start(arg_ptr, fmt);
961    bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
962    va_end(arg_ptr);
963
964    gtk_label_set_text(GTK_LABEL(item->label), buf);
965 }
966
967 void updateStatusIcon(monitoritem* item) {
968    const char** xpm;
969
970    if (item == NULL) {
971       /* For the current status, select the two worse for blinking,
972          but never blink a D_Sock == NULL error with idle. */
973       stateenum state1, state2, oldstate;
974       gboolean onenull = FALSE;
975       state1 = idle;
976       state2 = idle;
977       oldstate = idle;
978       for (int i = 0; i < nitems; i++) {
979          if (items[i].D_sock == NULL) onenull = TRUE;
980          if (items[i].state >= state1) {
981             state2 = state1;
982             state1 = items[i].state;
983          }
984          else if (items[i].state > state2) {
985             state2 = items[i].state;
986          }
987          if (items[i].oldstate > oldstate) oldstate = items[i].oldstate;
988       }
989
990       if ((onenull == TRUE) && (state2 == idle)) {
991          state2 = error;
992       }
993
994       xpm = generateXPM(blinkstate ? state1 : state2, oldstate);
995    }
996    else {
997       if ((blinkstate) && (item->D_sock != NULL)) {
998          if (item->state > 1) { //Warning or error while running
999             xpm = generateXPM(running, item->oldstate);
1000          }
1001          else {
1002             xpm = generateXPM(idle, item->oldstate);
1003          }
1004       }
1005       else {
1006          xpm = generateXPM(item->state, item->oldstate);
1007       }
1008    }
1009
1010    GdkPixbuf* pixbuf = gdk_pixbuf_new_from_xpm_data(xpm);
1011    if (item == NULL) {
1012       egg_status_icon_set_from_pixbuf(mTrayIcon, pixbuf);
1013       gtk_window_set_icon(GTK_WINDOW(window), pixbuf);
1014    }
1015    else {
1016       gtk_image_set_from_pixbuf(GTK_IMAGE(item->image), pixbuf);
1017    }
1018    g_object_unref(G_OBJECT(pixbuf));
1019 }
1020
1021 /* Note: result should not be stored, as it is a reference to xpm_generic_var */
1022 static const char** generateXPM(stateenum newstate, stateenum oldstate) {
1023    char* address = &xpm_generic_var[xpm_generic_first_color][xpm_generic_column];
1024    switch (newstate) {
1025    case error:
1026       strcpy(address, "ff0000");
1027       break;
1028    case idle:
1029       strcpy(address, "ffffff");
1030       break;
1031    case running:
1032       strcpy(address, "00ff00");
1033       break;
1034    case warn:
1035       strcpy(address, "ffff00");
1036       break;
1037    }
1038
1039    address = &xpm_generic_var[xpm_generic_second_color][xpm_generic_column];
1040    switch (oldstate) {
1041    case error:
1042       strcpy(address, "ff0000");
1043       break;
1044    case idle:
1045       strcpy(address, "ffffff");
1046       break;
1047    case running:
1048       strcpy(address, "00ff00");
1049       break;
1050    case warn:
1051       strcpy(address, "ffff00");
1052       break;
1053    }
1054
1055    return (const char**)xpm_generic_var;
1056 }