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