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