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