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