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