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