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