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