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