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