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