]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/tray-monitor/tray-monitor.c
Fix bug # 746 - Windows FD crashes when job canceled
[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       PROG_COPYRIGHT
508       "%s"
509       "\n<small>%s: %s (%s) %s %s %s</small>",
510       _("Bacula Tray Monitor"),
511         2004,
512       _("Written by Nicolas Boichat\n"),
513       _("Version"),
514       VERSION, BDATE, HOST_OS, DISTNAME, DISTVER);
515 #else
516    GtkWidget* about = gtk_message_dialog_new(GTK_WINDOW(window),GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE,   
517       "%s\n\n"
518       PROG_COPYRIGHT
519       "%s"
520       "\n%s: %s (%s) %s %s %s",
521       _("Bacula Tray Monitor"),
522         2004,
523       _("Written by Nicolas Boichat\n"),
524       _("Version"),
525       VERSION, BDATE, HOST_OS, DISTNAME, DISTVER);
526 #endif
527    gtk_dialog_run(GTK_DIALOG(about));
528    gtk_widget_destroy(about);
529 }
530
531 static void MonitorRefresh(GtkWidget *widget, gpointer data) {
532    for (int i = 0; i < nitems; i++) {
533       fd_read(NULL);
534    }
535 }
536
537 static gboolean delete_event( GtkWidget *widget,
538                               GdkEvent  *event,
539                               gpointer   data ) {
540    gtk_widget_hide(window);
541    return TRUE; /* do not destroy the window */
542 }
543
544 static void TrayIconActivate(GtkWidget *widget, gpointer data) {
545     gtk_widget_show(window);
546 }
547
548 static void TrayIconPopupMenu(unsigned int activateTime, unsigned int button) {
549   gtk_menu_popup(GTK_MENU(mTrayMenu), NULL, NULL, NULL, NULL, 1, 0);
550   gtk_widget_show_all(mTrayMenu);
551 }
552
553 static void TrayIconExit(unsigned int activateTime, unsigned int button) {
554    gtk_main_quit();
555 }
556
557 static void IntervalChanged(GtkWidget *widget, gpointer data) {
558    g_source_remove(timerTag);
559    timerTag = g_timeout_add(
560       (guint)(
561          gtk_spin_button_get_value(GTK_SPIN_BUTTON(timeoutspinner))*1000/nitems
562       ), fd_read, NULL );
563 }
564
565 static void DaemonChanged(GtkWidget *widget, monitoritem* data) {
566    if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
567       fullitem = -1;
568       for (int i = 0; i < nitems; i++) {
569          if (data == &(items[i])) {
570             fullitem = i;
571             break;
572          }
573       }
574       g_return_if_fail(fullitem != -1);
575
576       int oldlastupdated = lastupdated;
577       lastupdated = fullitem-1;
578       fd_read(NULL);
579       lastupdated = oldlastupdated;
580    }
581 }
582
583 static int authenticate_daemon(monitoritem* item, JCR *jcr) {
584    switch (item->type) {
585    case R_DIRECTOR:
586       return authenticate_director(jcr, monitor, (DIRRES*)item->resource);
587    case R_CLIENT:
588       return authenticate_file_daemon(jcr, monitor, (CLIENT*)item->resource);
589    case R_STORAGE:
590       return authenticate_storage_daemon(jcr, monitor, (STORE*)item->resource);
591    default:
592       printf(_("Error, currentitem is not a Client or a Storage..\n"));
593       gtk_main_quit();
594       return FALSE;
595    }
596    return false;
597 }
598
599 static gboolean blink(gpointer data) {
600    blinkstate = !blinkstate;
601    for (int i = 0; i < nitems; i++) {
602       updateStatusIcon(&items[i]);
603    }
604    updateStatusIcon(NULL);
605    return true;
606 }
607
608 static gboolean fd_read(gpointer data)
609 {
610    sm_line++;
611 #if TRAY_DEBUG_MEMORY
612    printf("sm_line=%d\n", sm_line);
613 #endif
614    GtkTextBuffer *newbuffer;
615    GtkTextIter start, stop, nstart, nstop;
616
617    GSList *list, *it;
618
619    GString *strlast, *strcurrent;
620
621    lastupdated++;
622    if (lastupdated == nitems) {
623       lastupdated = 0;
624    }
625
626    if (lastupdated == fullitem) {
627       newbuffer = gtk_text_buffer_new(NULL);
628       
629       if (items[lastupdated].type == R_DIRECTOR)
630          docmd(&items[lastupdated], "status Director\n", &list);
631       else
632          docmd(&items[lastupdated], "status\n", &list);
633
634       it = list->next;
635       do {
636          gtk_text_buffer_get_end_iter(newbuffer, &stop);
637          gtk_text_buffer_insert (newbuffer, &stop, ((GString*)it->data)->str, -1);
638          if (it->data) g_string_free((GString*)it->data, TRUE);
639       } while ((it = it->next) != NULL);
640
641       g_slist_free(list);
642             
643       /* Keep the selection if necessary */
644       if (gtk_text_buffer_get_selection_bounds(buffer, &start, &stop)) {
645          gtk_text_buffer_get_iter_at_offset(newbuffer, &nstart, gtk_text_iter_get_offset(&start));
646          gtk_text_buffer_get_iter_at_offset(newbuffer, &nstop,  gtk_text_iter_get_offset(&stop ));
647
648    #if HAVE_GTK_2_4
649          gtk_text_buffer_select_range(newbuffer, &nstart, &nstop);
650    #else
651          gtk_text_buffer_move_mark(newbuffer, gtk_text_buffer_get_mark(newbuffer, "insert"), &nstart);
652          gtk_text_buffer_move_mark(newbuffer, gtk_text_buffer_get_mark(newbuffer, "selection_bound"), &nstop);
653    #endif
654       }
655
656       g_object_unref(buffer);
657
658       buffer = newbuffer;
659       gtk_text_view_set_buffer(GTK_TEXT_VIEW(textview), buffer);
660    }
661
662    getstatus(&items[lastupdated], 1, &strcurrent);
663    getstatus(&items[lastupdated], 0, &strlast);
664    updateStatusIcon(&items[lastupdated]);
665
666    changeStatusMessage(&items[lastupdated], _("Current job: %s\nLast job: %s"), strcurrent->str, strlast->str);
667
668    updateStatusIcon(NULL);
669
670    g_string_free(strcurrent, TRUE);
671    g_string_free(strlast, TRUE);
672
673    return 1;
674 }
675
676 void append_error_string(GString* str, int joberrors) {
677    if (joberrors > 1) {
678       g_string_append_printf(str, _(" (%d errors)"), joberrors);
679    }
680    else {
681       g_string_append_printf(str, _(" (%d error)"), joberrors);
682    }
683 }
684
685 void getstatus(monitoritem* item, int current, GString** str)
686 {
687    GSList *list, *it;
688    stateenum ret = error;
689    int jobid = 0, joberrors = 0;
690    char jobstatus = JS_ErrorTerminated;
691    char num;
692    int k;
693
694    *str = g_string_sized_new(128);
695
696    if (current) {
697       if (item->type == R_DIRECTOR)
698          docmd(&items[lastupdated], ".status dir current\n", &list);
699       else
700          docmd(&items[lastupdated], ".status current\n", &list);
701    }
702    else {
703       if (item->type == R_DIRECTOR)
704          docmd(&items[lastupdated], ".status dir last\n", &list);
705       else
706          docmd(&items[lastupdated], ".status last\n", &list);
707    }
708
709    it = list->next;
710    if ((it == NULL) || (sscanf(((GString*)it->data)->str, OKqstatus, &num) != 1)) {
711       g_string_append_printf(*str, ".status error : %s", (it == NULL) ? "" : ((GString*)it->data)->str);
712       while (((*str)->str[(*str)->len-1] == '\n') || ((*str)->str[(*str)->len-1] == '\r')) {
713          g_string_set_size(*str, (*str)->len-1);
714       }
715       ret = error;
716    }
717    else if ((it = it->next) == NULL) {
718       if (current) {
719          g_string_append(*str, _("No current job."));
720       }
721       else {
722          g_string_append(*str, _("No last job."));
723       }
724       ret = idle;
725    }
726    else if ((k = sscanf(((GString*)it->data)->str, DotStatusJob, &jobid, &jobstatus, &joberrors)) == 3) {
727       switch (jobstatus) {
728       case JS_Created:
729          ret = (joberrors > 0) ? warn : running;
730          g_string_append_printf(*str, _("Job status: Created"));
731          append_error_string(*str, joberrors);
732          break;
733       case JS_Running:
734          ret = (joberrors > 0) ? warn : running;
735          g_string_append_printf(*str, _("Job status: Running"));
736          append_error_string(*str, joberrors);
737          break;
738       case JS_Blocked:
739          g_string_append_printf(*str, _("Job status: Blocked"));
740          append_error_string(*str, joberrors);
741          ret = warn;
742          break;
743       case JS_Terminated:
744          g_string_append_printf(*str, _("Job status: Terminated"));
745          append_error_string(*str, joberrors);
746          ret = (joberrors > 0) ? warn : idle;
747          break;
748       case JS_ErrorTerminated:
749          g_string_append_printf(*str, _("Job status: Terminated in error"));
750          append_error_string(*str, joberrors);
751          ret = error;
752          break;
753       case JS_Error:
754          ret = (joberrors > 0) ? warn : running;
755          g_string_append_printf(*str, _("Job status: Error"));
756          append_error_string(*str, joberrors);
757          break;
758       case JS_FatalError:
759          g_string_append_printf(*str, _("Job status: Fatal error"));
760          append_error_string(*str, joberrors);
761          ret = error;
762          break;
763       case JS_Differences:
764          g_string_append_printf(*str, _("Job status: Verify differences"));
765          append_error_string(*str, joberrors);
766          ret = warn;
767          break;
768       case JS_Canceled:
769          g_string_append_printf(*str, _("Job status: Canceled"));
770          append_error_string(*str, joberrors);
771          ret = warn;
772          break;
773       case JS_WaitFD:
774          g_string_append_printf(*str, _("Job status: Waiting on File daemon"));
775          append_error_string(*str, joberrors);
776          ret = warn;
777          break;
778       case JS_WaitSD:
779          g_string_append_printf(*str, _("Job status: Waiting on the Storage daemon"));
780          append_error_string(*str, joberrors);
781          ret = warn;
782          break;
783       case JS_WaitMedia:
784          g_string_append_printf(*str, _("Job status: Waiting for new media"));
785          append_error_string(*str, joberrors);
786          ret = warn;
787          break;
788       case JS_WaitMount:
789          g_string_append_printf(*str, _("Job status: Waiting for Mount"));
790          append_error_string(*str, joberrors);
791          ret = warn;
792          break;
793       case JS_WaitStoreRes:
794          g_string_append_printf(*str, _("Job status: Waiting for storage resource"));
795          append_error_string(*str, joberrors);
796          ret = warn;
797          break;
798       case JS_WaitJobRes:
799          g_string_append_printf(*str, _("Job status: Waiting for job resource"));
800          append_error_string(*str, joberrors);
801          ret = warn;
802          break;
803       case JS_WaitClientRes:
804          g_string_append_printf(*str, _("Job status: Waiting for Client resource"));
805          append_error_string(*str, joberrors);
806          ret = warn;
807          break;
808       case JS_WaitMaxJobs:
809          g_string_append_printf(*str, _("Job status: Waiting for maximum jobs"));
810          append_error_string(*str, joberrors);
811          ret = warn;
812          break;
813       case JS_WaitStartTime:
814          g_string_append_printf(*str, _("Job status: Waiting for start time"));
815          append_error_string(*str, joberrors);
816          ret = warn;
817          break;
818       case JS_WaitPriority:
819          g_string_append_printf(*str, _("Job status: Waiting for higher priority jobs to finish"));
820          append_error_string(*str, joberrors);
821          ret = warn;
822          break;
823       default:
824          g_warning(_("Unknown job status %c."), jobstatus);
825          g_string_append_printf(*str, _("Job status: Unknown(%c)"), jobstatus);
826          append_error_string(*str, joberrors);
827          ret = warn;
828          break;
829       }
830    }
831    else {
832       fprintf(stderr, _("Bad scan : '%s' %d\n"), (it == NULL) ? "" : ((GString*)it->data)->str, k);
833       ret = error;
834    }
835
836    it = list;
837    do {
838       if (it->data) {
839          g_string_free((GString*)it->data, TRUE);
840       }
841    } while ((it = it->next) != NULL);
842
843    g_slist_free(list);
844
845    if (current) {
846       item->state = ret;
847    }
848    else {
849       item->oldstate = ret;
850    }
851 }
852
853 int docmd(monitoritem* item, const char* command, GSList** list) 
854 {
855    int stat;
856    GString* str = NULL;
857
858    *list = g_slist_alloc();
859
860    //str = g_string_sized_new(64);
861
862    if (!item->D_sock) {
863       memset(&jcr, 0, sizeof(jcr));
864
865       DIRRES* dird;
866       CLIENT* filed;
867       STORE* stored;
868
869       switch (item->type) {
870       case R_DIRECTOR:
871          dird = (DIRRES*)item->resource;
872          trayMessage(_("Connecting to Director %s:%d\n"), dird->address, dird->DIRport);
873          changeStatusMessage(item, _("Connecting to Director %s:%d"), dird->address, dird->DIRport);
874          item->D_sock = bnet_connect(NULL, 0, 0, _("Director daemon"), dird->address, NULL, dird->DIRport, 0);
875          jcr.dir_bsock = item->D_sock;
876          break;
877       case R_CLIENT:
878          filed = (CLIENT*)item->resource;
879          trayMessage(_("Connecting to Client %s:%d\n"), filed->address, filed->FDport);
880          changeStatusMessage(item, _("Connecting to Client %s:%d"), filed->address, filed->FDport);
881          item->D_sock = bnet_connect(NULL, 0, 0, _("File daemon"), filed->address, NULL, filed->FDport, 0);
882          jcr.file_bsock = item->D_sock;
883          break;
884       case R_STORAGE:
885          stored = (STORE*)item->resource;
886          trayMessage(_("Connecting to Storage %s:%d\n"), stored->address, stored->SDport);
887          changeStatusMessage(item, _("Connecting to Storage %s:%d"), stored->address, stored->SDport);
888          item->D_sock = bnet_connect(NULL, 0, 0, _("Storage daemon"), stored->address, NULL, stored->SDport, 0);
889          jcr.store_bsock = item->D_sock;
890          break;
891       default:
892          printf(_("Error, currentitem is not a Client, a Storage or a Director..\n"));
893          gtk_main_quit();
894          return 0;
895       }
896
897       if (item->D_sock == NULL) {
898          *list = g_slist_append(*list, g_string_new(_("Cannot connect to daemon.\n")));
899          changeStatusMessage(item, _("Cannot connect to daemon."));
900          item->state = error;
901          item->oldstate = error;
902          return 0;
903       }
904
905       if (!authenticate_daemon(item, &jcr)) {
906          str = g_string_sized_new(64);
907          g_string_printf(str, "ERR=%s\n", item->D_sock->msg);
908          *list = g_slist_append(*list, str);
909          item->state = error;
910          item->oldstate = error;
911          changeStatusMessage(item, _("Authentication error : %s"), item->D_sock->msg);
912          item->D_sock = NULL;
913          return 0;
914       }
915
916       switch (item->type) {
917       case R_DIRECTOR:
918          trayMessage(_("Opened connection with Director daemon.\n"));
919          changeStatusMessage(item, _("Opened connection with Director daemon."));
920          break;
921       case R_CLIENT:
922          trayMessage(_("Opened connection with File daemon.\n"));
923          changeStatusMessage(item, _("Opened connection with File daemon."));
924          break;
925       case R_STORAGE:
926          trayMessage(_("Opened connection with Storage daemon.\n"));
927          changeStatusMessage(item, _("Opened connection with Storage daemon."));
928          break;
929       default:
930          printf(_("Error, currentitem is not a Client, a Storage or a Director..\n"));
931          gtk_main_quit();
932          return 0;
933          break;
934       }
935
936       if (item->type == R_DIRECTOR) { /* Read connection messages... */
937          GSList *tlist, *it;
938          docmd(item, "", &tlist); /* Usually invalid, but no matter */
939          it = tlist;
940          do {
941             if (it->data) {
942                g_string_free((GString*)it->data, TRUE);
943             }
944          } while ((it = it->next) != NULL);
945
946          g_slist_free(tlist);
947       }
948    }
949
950    if (command[0] != 0)
951       writecmd(item, command);
952
953    while(1) {
954       if ((stat = bnet_recv(item->D_sock)) >= 0) {
955          *list = g_slist_append(*list, g_string_new(item->D_sock->msg));
956       }
957       else if (stat == BNET_SIGNAL) {
958          if (item->D_sock->msglen == BNET_EOD) {
959             //fprintf(stderr, "<< EOD >>\n");
960             return 1;
961          }
962          else if (item->D_sock->msglen == BNET_PROMPT) {
963             //fprintf(stderr, "<< PROMPT >>\n");
964             *list = g_slist_append(*list, g_string_new(_("<< Error: BNET_PROMPT signal received. >>\n")));
965             return 0;
966          }
967          else if (item->D_sock->msglen == BNET_HEARTBEAT) {
968             bnet_sig(item->D_sock, BNET_HB_RESPONSE);
969             *list = g_slist_append(*list, g_string_new(_("<< Heartbeat signal received, answered. >>\n")));
970          }
971          else {
972             str = g_string_sized_new(64);
973             g_string_printf(str, _("<< Unexpected signal received : %s >>\n"), bnet_sig_to_ascii(item->D_sock));
974             *list = g_slist_append(*list, str);
975          }
976       }
977       else { /* BNET_HARDEOF || BNET_ERROR */
978          *list = g_slist_append(*list, g_string_new(_("<ERROR>\n")));
979          item->D_sock = NULL;
980          item->state = error;
981          item->oldstate = error;
982          changeStatusMessage(item, _("Error : BNET_HARDEOF or BNET_ERROR"));
983          //fprintf(stderr, _("<< ERROR >>\n"));
984          return 0;
985       }
986
987       if (is_bnet_stop(item->D_sock)) {
988          g_string_append_printf(str, _("<STOP>\n"));
989          item->D_sock = NULL;
990          item->state = error;
991          item->oldstate = error;
992          changeStatusMessage(item, _("Error : Connection closed."));
993          //fprintf(stderr, "<< STOP >>\n");
994          return 0;            /* error or term */
995       }
996    }
997 }
998
999 void writecmd(monitoritem* item, const char* command) {
1000    if (item->D_sock) {
1001       item->D_sock->msglen = strlen(command);
1002       pm_strcpy(&item->D_sock->msg, command);
1003       bnet_send(item->D_sock);
1004    }
1005 }
1006
1007 /* Note: Does not seem to work either on Gnome nor KDE... */
1008 void trayMessage(const char *fmt,...)
1009 {
1010    char buf[512];
1011    va_list arg_ptr;
1012
1013    va_start(arg_ptr, fmt);
1014    bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
1015    va_end(arg_ptr);
1016
1017    fprintf(stderr, buf);
1018
1019    egg_tray_icon_send_message(egg_status_icon_get_tray_icon(mTrayIcon), 5000, (const char*)&buf, -1);
1020 }
1021
1022 void changeStatusMessage(monitoritem* item, const char *fmt,...) {
1023    char buf[512];
1024    va_list arg_ptr;
1025
1026    va_start(arg_ptr, fmt);
1027    bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
1028    va_end(arg_ptr);
1029
1030    gtk_label_set_text(GTK_LABEL(item->label), buf);
1031 }
1032
1033 void updateStatusIcon(monitoritem* item) {
1034    const char** xpm;
1035
1036    if (item == NULL) {
1037       /* For the current status, select the two worse for blinking,
1038          but never blink a D_Sock == NULL error with idle. */
1039       stateenum state1, state2, oldstate;
1040       gboolean onenull = FALSE;
1041       state1 = idle;
1042       state2 = idle;
1043       oldstate = idle;
1044       for (int i = 0; i < nitems; i++) {
1045          if (items[i].D_sock == NULL) onenull = TRUE;
1046          if (items[i].state >= state1) {
1047             state2 = state1;
1048             state1 = items[i].state;
1049          }
1050          else if (items[i].state > state2) {
1051             state2 = items[i].state;
1052          }
1053          if (items[i].oldstate > oldstate) oldstate = items[i].oldstate;
1054       }
1055
1056       if ((onenull == TRUE) && (state2 == idle)) {
1057          state2 = error;
1058       }
1059
1060       xpm = generateXPM(blinkstate ? state1 : state2, oldstate);
1061    }
1062    else {
1063       if ((blinkstate) && (item->D_sock != NULL)) {
1064          if (item->state > 1) { //Warning or error while running
1065             xpm = generateXPM(running, item->oldstate);
1066          }
1067          else {
1068             xpm = generateXPM(idle, item->oldstate);
1069          }
1070       }
1071       else {
1072          xpm = generateXPM(item->state, item->oldstate);
1073       }
1074    }
1075
1076    GdkPixbuf* pixbuf = gdk_pixbuf_new_from_xpm_data(xpm);
1077    if (item == NULL) {
1078       egg_status_icon_set_from_pixbuf(mTrayIcon, pixbuf);
1079       gtk_window_set_icon(GTK_WINDOW(window), pixbuf);
1080    }
1081    else {
1082       gtk_image_set_from_pixbuf(GTK_IMAGE(item->image), pixbuf);
1083    }
1084    g_object_unref(G_OBJECT(pixbuf));
1085 }
1086
1087 /* Note: result should not be stored, as it is a reference to xpm_generic_var */
1088 static const char** generateXPM(stateenum newstate, stateenum oldstate) {
1089    char* address = &xpm_generic_var[xpm_generic_first_color][xpm_generic_column];
1090    switch (newstate) {
1091    case error:
1092       strcpy(address, "ff0000");
1093       break;
1094    case idle:
1095       strcpy(address, "ffffff");
1096       break;
1097    case running:
1098       strcpy(address, "00ff00");
1099       break;
1100    case warn:
1101       strcpy(address, "ffff00");
1102       break;
1103    }
1104
1105    address = &xpm_generic_var[xpm_generic_second_color][xpm_generic_column];
1106    switch (oldstate) {
1107    case error:
1108       strcpy(address, "ff0000");
1109       break;
1110    case idle:
1111       strcpy(address, "ffffff");
1112       break;
1113    case running:
1114       strcpy(address, "00ff00");
1115       break;
1116    case warn:
1117       strcpy(address, "ffff00");
1118       break;
1119    }
1120
1121    return (const char**)xpm_generic_var;
1122 }