]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/tray-monitor/tray-monitor.c
be1d003ef0b146c47861bd7ebf6ee5cf01c19652
[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_Blocked:
634          g_string_append_printf(*str, _("Job status: Blocked (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
635          ret = warn;
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_Error:
646          ret = (joberrors > 0) ? warn : running;
647          g_string_append_printf(*str, _("Job status: Error (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
648          break;
649       case JS_FatalError:
650          g_string_append_printf(*str, _("Job status: Fatal error (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
651          ret = error;
652          break;
653       case JS_Differences:
654          g_string_append_printf(*str, _("Job status: Verify differences (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
655          ret = warn;
656          break;
657       case JS_Canceled:
658          g_string_append_printf(*str, _("Job status: Canceled (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
659          ret = warn;
660          break;
661       case JS_WaitFD:
662          g_string_append_printf(*str, _("Job status: Waiting on File daemon (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
663          ret = warn;
664          break;
665       case JS_WaitSD:
666          g_string_append_printf(*str, _("Job status: Waiting on the Storage daemon (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
667          ret = warn;
668          break;
669       case JS_WaitMedia:
670          g_string_append_printf(*str, _("Job status: Waiting for new media (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
671          ret = warn;
672          break;
673       case JS_WaitMount:
674          g_string_append_printf(*str, _("Job status: Waiting for Mount (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
675          ret = warn;
676          break;
677       case JS_WaitStoreRes:
678          g_string_append_printf(*str, _("Job status: Waiting for storage resource (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
679          ret = warn;
680          break;
681       case JS_WaitJobRes:
682          g_string_append_printf(*str, _("Job status: Waiting for job resource (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
683          ret = warn;
684          break;
685       case JS_WaitClientRes:
686          g_string_append_printf(*str, _("Job status: Waiting for Client resource (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
687          ret = warn;
688          break;
689       case JS_WaitMaxJobs:
690          g_string_append_printf(*str, _("Job status: Waiting for maximum jobs (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
691          ret = warn;
692          break;
693       case JS_WaitStartTime:
694          g_string_append_printf(*str, _("Job status: Waiting for start time (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
695          ret = warn;
696          break;
697       case JS_WaitPriority:
698          g_string_append_printf(*str, _("Job status: Waiting for higher priority jobs to finish (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
699          ret = warn;
700          break;
701       default:
702          g_warning("Unknown job status %c.", jobstatus);
703          g_string_append_printf(*str, _("Job status: Unknown(%c) (%d error%s)"), jobstatus, joberrors, (joberrors > 1) ? "s" : "");
704          ret = warn;
705          break;
706       }
707    }
708    else {
709       fprintf(stderr, "Bad scan : '%s' %d\n", (it == NULL) ? "" : ((GString*)it->data)->str, k);
710       ret = error;
711    }
712       
713    it = list;
714    do {
715       if (it->data) g_string_free((GString*)it->data, TRUE);
716    } while ((it = it->next) != NULL);
717    
718    g_slist_free(list);
719    
720    if (current) {
721       item->state = ret;
722    }
723    else {
724       item->oldstate = ret;
725    }
726 }
727
728 int docmd(monitoritem* item, const char* command, GSList** list) {
729    int stat;
730    GString* str = NULL;
731    
732    *list = g_slist_alloc();
733
734    //str = g_string_sized_new(64);
735    
736    if (!item->D_sock) {
737       memset(&jcr, 0, sizeof(jcr));
738       
739       DIRRES* dird;
740       CLIENT* filed;
741       STORE* stored;
742       
743       switch (item->type) {
744       case R_DIRECTOR:
745          dird = (DIRRES*)item->resource;      
746          trayMessage("Connecting to Director %s:%d\n", dird->address, dird->DIRport);
747          changeStatusMessage(item, "Connecting to Director %s:%d", dird->address, dird->DIRport);
748          item->D_sock = bnet_connect(NULL, 0, 0, "Director daemon", dird->address, NULL, dird->DIRport, 0);
749          jcr.dir_bsock = item->D_sock;
750          break;
751       case R_CLIENT:
752          filed = (CLIENT*)item->resource;      
753          trayMessage("Connecting to Client %s:%d\n", filed->address, filed->FDport);
754          changeStatusMessage(item, "Connecting to Client %s:%d", filed->address, filed->FDport);
755          item->D_sock = bnet_connect(NULL, 0, 0, "File daemon", filed->address, NULL, filed->FDport, 0);
756          jcr.file_bsock = item->D_sock;
757          break;
758       case R_STORAGE:
759          stored = (STORE*)item->resource;      
760          trayMessage("Connecting to Storage %s:%d\n", stored->address, stored->SDport);
761          changeStatusMessage(item, "Connecting to Storage %s:%d", stored->address, stored->SDport);
762          item->D_sock = bnet_connect(NULL, 0, 0, "Storage daemon", stored->address, NULL, stored->SDport, 0);
763          jcr.store_bsock = item->D_sock;
764          break;
765       default:
766          printf("Error, currentitem is not a Client, a Storage or a Director..\n");
767          gtk_main_quit();
768          return 0;
769       }
770       
771       if (item->D_sock == NULL) {
772          g_slist_append(*list, g_string_new("Cannot connect to daemon.\n"));
773          changeStatusMessage(item, "Cannot connect to daemon.");
774          item->state = error;
775          item->oldstate = error;
776          return 0;
777       }
778       
779       if (!authenticate_daemon(item, &jcr)) {
780          str = g_string_sized_new(64);
781          g_string_printf(str, "ERR=%s\n", item->D_sock->msg);
782          g_slist_append(*list, str);
783          item->state = error;
784          item->oldstate = error;
785          changeStatusMessage(item, "Authentication error : %s", item->D_sock->msg);
786          item->D_sock = NULL;
787          return 0;
788       }
789       
790       switch (item->type) {
791       case R_DIRECTOR:
792          trayMessage("Opened connection with Director daemon.\n");
793          changeStatusMessage(item, "Opened connection with Director daemon.");
794          break;
795       case R_CLIENT:
796          trayMessage("Opened connection with File daemon.\n");
797          changeStatusMessage(item, "Opened connection with File daemon.");
798          break;
799       case R_STORAGE:
800          trayMessage("Opened connection with Storage daemon.\n");
801          changeStatusMessage(item, "Opened connection with Storage daemon.");
802          break;
803       default:
804          printf("Error, currentitem is not a Client, a Storage or a Director..\n");
805          gtk_main_quit();
806          return 0;
807          break;
808       }
809       
810       if (item->type == R_DIRECTOR) { /* Read connection messages... */
811          GSList *list, *it;
812          docmd(item, "", &list); /* Usually invalid, but no matter */
813          it = list;
814          do {
815             if (it->data) g_string_free((GString*)it->data, TRUE);
816          } while ((it = it->next) != NULL);
817          
818          g_slist_free(list);         
819       }
820    }
821    
822    if (command[0] != 0)
823       writecmd(item, command);
824    
825    while(1) {
826       if ((stat = bnet_recv(item->D_sock)) >= 0) {
827          g_slist_append(*list, g_string_new(item->D_sock->msg));
828       }
829       else if (stat == BNET_SIGNAL) {
830          if (item->D_sock->msglen == BNET_EOD) {
831             //fprintf(stderr, "<< EOD >>\n");
832             return 1;
833          }
834          else if (item->D_sock->msglen == BNET_PROMPT) {
835             //fprintf(stderr, "<< PROMPT >>\n");
836             g_slist_append(*list, g_string_new("<< Error: BNET_PROMPT signal received. >>\n"));
837             return 0;
838          }
839          else if (item->D_sock->msglen == BNET_HEARTBEAT) {
840             bnet_sig(item->D_sock, BNET_HB_RESPONSE);
841             g_slist_append(*list, g_string_new("<< Heartbeat signal received, answered. >>\n"));
842          }
843          else {
844             str = g_string_sized_new(64);
845             g_string_printf(str, "<< Unexpected signal received : %s >>\n", bnet_sig_to_ascii(item->D_sock));
846             g_slist_append(*list, str);
847          }
848       }
849       else { /* BNET_HARDEOF || BNET_ERROR */
850          g_slist_append(*list, g_string_new("<ERROR>\n"));
851          item->D_sock = NULL;
852          item->state = error;
853          item->oldstate = error;
854          changeStatusMessage(item, "Error : BNET_HARDEOF or BNET_ERROR");
855          //fprintf(stderr, "<< ERROR >>\n");
856          return 0;
857       }
858            
859       if (is_bnet_stop(item->D_sock)) {
860          g_string_append_printf(str, "<STOP>\n");
861          item->D_sock = NULL;
862          item->state = error;
863          item->oldstate = error;
864          changeStatusMessage(item, "Error : Connection closed.");
865          //fprintf(stderr, "<< STOP >>\n");
866          return 0;            /* error or term */
867       }
868    }
869 }
870
871 void writecmd(monitoritem* item, const char* command) {
872    if (item->D_sock) {
873       item->D_sock->msglen = strlen(command);
874       pm_strcpy(&item->D_sock->msg, command);
875       bnet_send(item->D_sock);
876    }
877 }
878
879 /* Note: Does not seem to work either on Gnome nor KDE... */
880 void trayMessage(const char *fmt,...) {
881    char buf[512];
882    va_list arg_ptr;
883    
884    va_start(arg_ptr, fmt);
885    bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
886    va_end(arg_ptr);
887    
888    fprintf(stderr, buf);
889    
890    egg_tray_icon_send_message(egg_status_icon_get_tray_icon(mTrayIcon), 5000, (const char*)&buf, -1);
891 }
892
893 void changeStatusMessage(monitoritem* item, const char *fmt,...) {
894    char buf[512];
895    va_list arg_ptr;
896    
897    va_start(arg_ptr, fmt);
898    bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
899    va_end(arg_ptr);
900
901    gtk_label_set_text(GTK_LABEL(item->label), buf);
902 }
903
904 void updateStatusIcon(monitoritem* item) {
905    const char** xpm;
906    
907    if (item == NULL) {
908       /* For the current status, select the two worse for blinking,
909          but never blink a D_Sock == NULL error with idle. */
910       stateenum state1, state2, oldstate;
911       gboolean onenull = FALSE;
912       state1 = idle;
913       state2 = idle;
914       oldstate = idle;
915       for (int i = 0; i < nitems; i++) {
916          if (items[i].D_sock == NULL) onenull = TRUE;
917          if (items[i].state >= state1) {
918             state2 = state1;
919             state1 = items[i].state;
920          }
921          else if (items[i].state > state2) {
922             state2 = items[i].state;
923          }
924          if (items[i].oldstate > oldstate) oldstate = items[i].oldstate;
925       }
926       
927       if ((onenull == TRUE) && (state2 == idle)) {
928          state2 = error;
929       }
930                   
931       xpm = generateXPM(blinkstate ? state1 : state2, oldstate);
932    }
933    else {
934       if ((blinkstate) && (item->D_sock != NULL)) {
935          if (item->state > 1) { //Warning or error while running
936             xpm = generateXPM(running, item->oldstate);
937          }
938          else {
939             xpm = generateXPM(idle, item->oldstate);
940          }
941       }
942       else {
943          xpm = generateXPM(item->state, item->oldstate);
944       }
945    }
946    
947    GdkPixbuf* pixbuf = gdk_pixbuf_new_from_xpm_data(xpm);
948    if (item == NULL) {
949       egg_status_icon_set_from_pixbuf(mTrayIcon, pixbuf);
950       gtk_window_set_icon(GTK_WINDOW(window), pixbuf);
951    }
952    else {
953       gtk_image_set_from_pixbuf(GTK_IMAGE(item->image), pixbuf);
954    }
955 }
956
957 /* Note: result should not be stored, as it is a reference to xpm_generic_var */
958 static const char** generateXPM(stateenum newstate, stateenum oldstate) {
959    char* address = &xpm_generic_var[xpm_generic_first_color][xpm_generic_column];
960    switch (newstate) {
961    case error:
962       strcpy(address, "ff0000");
963       break;
964    case idle:
965       strcpy(address, "ffffff");
966       break;
967    case running:
968       strcpy(address, "00ff00");
969       break;
970    case warn:
971       strcpy(address, "ffff00");
972       break;
973    }
974    
975    address = &xpm_generic_var[xpm_generic_second_color][xpm_generic_column];
976    switch (oldstate) {
977    case error:
978       strcpy(address, "ff0000");
979       break;
980    case idle:
981       strcpy(address, "ffffff");
982       break;
983    case running:
984       strcpy(address, "00ff00");
985       break;
986    case warn:
987       strcpy(address, "ffff00");
988       break;
989    }
990    
991    return (const char**)xpm_generic_var;
992 }