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