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