]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/tray-monitor/tray-monitor.c
- Disable parts of NLS as the configure does not work here.
[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 Kern Sibbald and John Walker\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 Kern Sibbald and John Walker\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    sm_line++;
601 #if TRAY_DEBUG_MEMORY
602    printf("sm_line=%d\n", sm_line);
603 #endif
604    GtkTextBuffer *newbuffer;
605    GtkTextIter start, stop, nstart, nstop;
606
607    GSList *list, *it;
608
609    GString *strlast, *strcurrent;
610
611    lastupdated++;
612    if (lastupdated == nitems) {
613       lastupdated = 0;
614    }
615
616    if (lastupdated == fullitem) {
617       newbuffer = gtk_text_buffer_new(NULL);
618       
619       if (items[lastupdated].type == R_DIRECTOR)
620          docmd(&items[lastupdated], "status Director\n", &list);
621       else
622          docmd(&items[lastupdated], "status\n", &list);
623
624       it = list->next;
625       do {
626          gtk_text_buffer_get_end_iter(newbuffer, &stop);
627          gtk_text_buffer_insert (newbuffer, &stop, ((GString*)it->data)->str, -1);
628          if (it->data) g_string_free((GString*)it->data, TRUE);
629       } while ((it = it->next) != NULL);
630
631       g_slist_free(list);
632             
633       /* Keep the selection if necessary */
634       if (gtk_text_buffer_get_selection_bounds(buffer, &start, &stop)) {
635          gtk_text_buffer_get_iter_at_offset(newbuffer, &nstart, gtk_text_iter_get_offset(&start));
636          gtk_text_buffer_get_iter_at_offset(newbuffer, &nstop,  gtk_text_iter_get_offset(&stop ));
637
638    #if HAVE_GTK_2_4
639          gtk_text_buffer_select_range(newbuffer, &nstart, &nstop);
640    #else
641          gtk_text_buffer_move_mark(newbuffer, gtk_text_buffer_get_mark(newbuffer, "insert"), &nstart);
642          gtk_text_buffer_move_mark(newbuffer, gtk_text_buffer_get_mark(newbuffer, "selection_bound"), &nstop);
643    #endif
644       }
645
646       g_object_unref(buffer);
647
648       buffer = newbuffer;
649       gtk_text_view_set_buffer(GTK_TEXT_VIEW(textview), buffer);
650    }
651
652    getstatus(&items[lastupdated], 1, &strcurrent);
653    getstatus(&items[lastupdated], 0, &strlast);
654    updateStatusIcon(&items[lastupdated]);
655
656    changeStatusMessage(&items[lastupdated], _("Current job: %s\nLast job: %s"), strcurrent->str, strlast->str);
657
658    updateStatusIcon(NULL);
659
660    g_string_free(strcurrent, TRUE);
661    g_string_free(strlast, TRUE);
662
663    return 1;
664 }
665
666 void append_error_string(GString* str, int joberrors) {
667    if (joberrors > 1) {
668       g_string_append_printf(str, _(" (%d errors)"), joberrors);
669    }
670    else {
671       g_string_append_printf(str, _(" (%d error)"), joberrors);
672    }
673 }
674
675 void getstatus(monitoritem* item, int current, GString** str) {
676    GSList *list, *it;
677    stateenum ret = error;
678    int jobid = 0, joberrors = 0;
679    char jobstatus = JS_ErrorTerminated;
680    char num;
681    int k;
682
683    *str = g_string_sized_new(128);
684
685    if (current) {
686       if (item->type == R_DIRECTOR)
687          docmd(&items[lastupdated], ".status dir current\n", &list);
688       else
689          docmd(&items[lastupdated], ".status current\n", &list);
690    }
691    else {
692       if (item->type == R_DIRECTOR)
693          docmd(&items[lastupdated], ".status dir last\n", &list);
694       else
695          docmd(&items[lastupdated], ".status last\n", &list);
696    }
697
698    it = list->next;
699    if ((it == NULL) || (sscanf(((GString*)it->data)->str, OKqstatus, &num) != 1)) {
700       g_string_append_printf(*str, ".status error : %s", (it == NULL) ? "" : ((GString*)it->data)->str);
701       while (((*str)->str[(*str)->len-1] == '\n') || ((*str)->str[(*str)->len-1] == '\r')) {
702          g_string_set_size(*str, (*str)->len-1);
703       }
704       ret = error;
705    }
706    else if ((it = it->next) == NULL) {
707       if (current) {
708          g_string_append(*str, _("No current job."));
709       }
710       else {
711          g_string_append(*str, _("No last job."));
712       }
713       ret = idle;
714    }
715    else if ((k = sscanf(((GString*)it->data)->str, DotStatusJob, &jobid, &jobstatus, &joberrors)) == 3) {
716       switch (jobstatus) {
717       case JS_Created:
718          ret = (joberrors > 0) ? warn : running;
719          g_string_append_printf(*str, _("Job status: Created"));
720          append_error_string(*str, joberrors);
721          break;
722       case JS_Running:
723          ret = (joberrors > 0) ? warn : running;
724          g_string_append_printf(*str, _("Job status: Running"));
725          append_error_string(*str, joberrors);
726          break;
727       case JS_Blocked:
728          g_string_append_printf(*str, _("Job status: Blocked"));
729          append_error_string(*str, joberrors);
730          ret = warn;
731          break;
732       case JS_Terminated:
733          g_string_append_printf(*str, _("Job status: Terminated"));
734          append_error_string(*str, joberrors);
735          ret = (joberrors > 0) ? warn : idle;
736          break;
737       case JS_ErrorTerminated:
738          g_string_append_printf(*str, _("Job status: Terminated in error"));
739          append_error_string(*str, joberrors);
740          ret = error;
741          break;
742       case JS_Error:
743          ret = (joberrors > 0) ? warn : running;
744          g_string_append_printf(*str, _("Job status: Error"));
745          append_error_string(*str, joberrors);
746          break;
747       case JS_FatalError:
748          g_string_append_printf(*str, _("Job status: Fatal error"));
749          append_error_string(*str, joberrors);
750          ret = error;
751          break;
752       case JS_Differences:
753          g_string_append_printf(*str, _("Job status: Verify differences"));
754          append_error_string(*str, joberrors);
755          ret = warn;
756          break;
757       case JS_Canceled:
758          g_string_append_printf(*str, _("Job status: Canceled"));
759          append_error_string(*str, joberrors);
760          ret = warn;
761          break;
762       case JS_WaitFD:
763          g_string_append_printf(*str, _("Job status: Waiting on File daemon"));
764          append_error_string(*str, joberrors);
765          ret = warn;
766          break;
767       case JS_WaitSD:
768          g_string_append_printf(*str, _("Job status: Waiting on the Storage daemon"));
769          append_error_string(*str, joberrors);
770          ret = warn;
771          break;
772       case JS_WaitMedia:
773          g_string_append_printf(*str, _("Job status: Waiting for new media"));
774          append_error_string(*str, joberrors);
775          ret = warn;
776          break;
777       case JS_WaitMount:
778          g_string_append_printf(*str, _("Job status: Waiting for Mount"));
779          append_error_string(*str, joberrors);
780          ret = warn;
781          break;
782       case JS_WaitStoreRes:
783          g_string_append_printf(*str, _("Job status: Waiting for storage resource"));
784          append_error_string(*str, joberrors);
785          ret = warn;
786          break;
787       case JS_WaitJobRes:
788          g_string_append_printf(*str, _("Job status: Waiting for job resource"));
789          append_error_string(*str, joberrors);
790          ret = warn;
791          break;
792       case JS_WaitClientRes:
793          g_string_append_printf(*str, _("Job status: Waiting for Client resource"));
794          append_error_string(*str, joberrors);
795          ret = warn;
796          break;
797       case JS_WaitMaxJobs:
798          g_string_append_printf(*str, _("Job status: Waiting for maximum jobs"));
799          append_error_string(*str, joberrors);
800          ret = warn;
801          break;
802       case JS_WaitStartTime:
803          g_string_append_printf(*str, _("Job status: Waiting for start time"));
804          append_error_string(*str, joberrors);
805          ret = warn;
806          break;
807       case JS_WaitPriority:
808          g_string_append_printf(*str, _("Job status: Waiting for higher priority jobs to finish"));
809          append_error_string(*str, joberrors);
810          ret = warn;
811          break;
812       default:
813          g_warning(_("Unknown job status %c."), jobstatus);
814          g_string_append_printf(*str, _("Job status: Unknown(%c)"), jobstatus);
815          append_error_string(*str, joberrors);
816          ret = warn;
817          break;
818       }
819    }
820    else {
821       fprintf(stderr, _("Bad scan : '%s' %d\n"), (it == NULL) ? "" : ((GString*)it->data)->str, k);
822       ret = error;
823    }
824
825    it = list;
826    do {
827       if (it->data) g_string_free((GString*)it->data, TRUE);
828    } while ((it = it->next) != NULL);
829
830    g_slist_free(list);
831
832    if (current) {
833       item->state = ret;
834    }
835    else {
836       item->oldstate = ret;
837    }
838 }
839
840 int docmd(monitoritem* item, const char* command, GSList** list) {
841    int stat;
842    GString* str = NULL;
843
844    *list = g_slist_alloc();
845
846    //str = g_string_sized_new(64);
847
848    if (!item->D_sock) {
849       memset(&jcr, 0, sizeof(jcr));
850
851       DIRRES* dird;
852       CLIENT* filed;
853       STORE* stored;
854
855       switch (item->type) {
856       case R_DIRECTOR:
857          dird = (DIRRES*)item->resource;
858          trayMessage(_("Connecting to Director %s:%d\n"), dird->address, dird->DIRport);
859          changeStatusMessage(item, _("Connecting to Director %s:%d"), dird->address, dird->DIRport);
860          item->D_sock = bnet_connect(NULL, 0, 0, _("Director daemon"), dird->address, NULL, dird->DIRport, 0);
861          jcr.dir_bsock = item->D_sock;
862          break;
863       case R_CLIENT:
864          filed = (CLIENT*)item->resource;
865          trayMessage(_("Connecting to Client %s:%d\n"), filed->address, filed->FDport);
866          changeStatusMessage(item, _("Connecting to Client %s:%d"), filed->address, filed->FDport);
867          item->D_sock = bnet_connect(NULL, 0, 0, _("File daemon"), filed->address, NULL, filed->FDport, 0);
868          jcr.file_bsock = item->D_sock;
869          break;
870       case R_STORAGE:
871          stored = (STORE*)item->resource;
872          trayMessage(_("Connecting to Storage %s:%d\n"), stored->address, stored->SDport);
873          changeStatusMessage(item, _("Connecting to Storage %s:%d"), stored->address, stored->SDport);
874          item->D_sock = bnet_connect(NULL, 0, 0, _("Storage daemon"), stored->address, NULL, stored->SDport, 0);
875          jcr.store_bsock = item->D_sock;
876          break;
877       default:
878          printf(_("Error, currentitem is not a Client, a Storage or a Director..\n"));
879          gtk_main_quit();
880          return 0;
881       }
882
883       if (item->D_sock == NULL) {
884          g_slist_append(*list, (void *)_("Cannot connect to daemon.\n"));
885          changeStatusMessage(item, _("Cannot connect to daemon."));
886          item->state = error;
887          item->oldstate = error;
888          return 0;
889       }
890
891       if (!authenticate_daemon(item, &jcr)) {
892          str = g_string_sized_new(64);
893          g_string_printf(str, "ERR=%s\n", item->D_sock->msg);
894          g_slist_append(*list, str);
895          item->state = error;
896          item->oldstate = error;
897          changeStatusMessage(item, _("Authentication error : %s"), item->D_sock->msg);
898          item->D_sock = NULL;
899          return 0;
900       }
901
902       switch (item->type) {
903       case R_DIRECTOR:
904          trayMessage(_("Opened connection with Director daemon.\n"));
905          changeStatusMessage(item, _("Opened connection with Director daemon."));
906          break;
907       case R_CLIENT:
908          trayMessage(_("Opened connection with File daemon.\n"));
909          changeStatusMessage(item, _("Opened connection with File daemon."));
910          break;
911       case R_STORAGE:
912          trayMessage(_("Opened connection with Storage daemon.\n"));
913          changeStatusMessage(item, _("Opened connection with Storage daemon."));
914          break;
915       default:
916          printf(_("Error, currentitem is not a Client, a Storage or a Director..\n"));
917          gtk_main_quit();
918          return 0;
919          break;
920       }
921
922       if (item->type == R_DIRECTOR) { /* Read connection messages... */
923          GSList *list, *it;
924          docmd(item, "", &list); /* Usually invalid, but no matter */
925          it = list;
926          do {
927             if (it->data) g_string_free((GString*)it->data, TRUE);
928          } while ((it = it->next) != NULL);
929
930          g_slist_free(list);
931       }
932    }
933
934    if (command[0] != 0)
935       writecmd(item, command);
936
937    while(1) {
938       if ((stat = bnet_recv(item->D_sock)) >= 0) {
939          g_slist_append(*list, g_string_new(item->D_sock->msg));
940       }
941       else if (stat == BNET_SIGNAL) {
942          if (item->D_sock->msglen == BNET_EOD) {
943             //fprintf(stderr, "<< EOD >>\n");
944             return 1;
945          }
946          else if (item->D_sock->msglen == BNET_PROMPT) {
947             //fprintf(stderr, "<< PROMPT >>\n");
948             g_slist_append(*list, g_string_new(_("<< Error: BNET_PROMPT signal received. >>\n")));
949             return 0;
950          }
951          else if (item->D_sock->msglen == BNET_HEARTBEAT) {
952             bnet_sig(item->D_sock, BNET_HB_RESPONSE);
953             g_slist_append(*list, g_string_new(_("<< Heartbeat signal received, answered. >>\n")));
954          }
955          else {
956             str = g_string_sized_new(64);
957             g_string_printf(str, _("<< Unexpected signal received : %s >>\n"), bnet_sig_to_ascii(item->D_sock));
958             g_slist_append(*list, str);
959          }
960       }
961       else { /* BNET_HARDEOF || BNET_ERROR */
962          g_slist_append(*list, g_string_new(_("<ERROR>\n")));
963          item->D_sock = NULL;
964          item->state = error;
965          item->oldstate = error;
966          changeStatusMessage(item, _("Error : BNET_HARDEOF or BNET_ERROR"));
967          //fprintf(stderr, _("<< ERROR >>\n"));
968          return 0;
969       }
970
971       if (is_bnet_stop(item->D_sock)) {
972          g_string_append_printf(str, _("<STOP>\n"));
973          item->D_sock = NULL;
974          item->state = error;
975          item->oldstate = error;
976          changeStatusMessage(item, _("Error : Connection closed."));
977          //fprintf(stderr, "<< STOP >>\n");
978          return 0;            /* error or term */
979       }
980    }
981 }
982
983 void writecmd(monitoritem* item, const char* command) {
984    if (item->D_sock) {
985       item->D_sock->msglen = strlen(command);
986       pm_strcpy(&item->D_sock->msg, command);
987       bnet_send(item->D_sock);
988    }
989 }
990
991 /* Note: Does not seem to work either on Gnome nor KDE... */
992 void trayMessage(const char *fmt,...) {
993    char buf[512];
994    va_list arg_ptr;
995
996    va_start(arg_ptr, fmt);
997    bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
998    va_end(arg_ptr);
999
1000    fprintf(stderr, buf);
1001
1002    egg_tray_icon_send_message(egg_status_icon_get_tray_icon(mTrayIcon), 5000, (const char*)&buf, -1);
1003 }
1004
1005 void changeStatusMessage(monitoritem* item, const char *fmt,...) {
1006    char buf[512];
1007    va_list arg_ptr;
1008
1009    va_start(arg_ptr, fmt);
1010    bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
1011    va_end(arg_ptr);
1012
1013    gtk_label_set_text(GTK_LABEL(item->label), buf);
1014 }
1015
1016 void updateStatusIcon(monitoritem* item) {
1017    const char** xpm;
1018
1019    if (item == NULL) {
1020       /* For the current status, select the two worse for blinking,
1021          but never blink a D_Sock == NULL error with idle. */
1022       stateenum state1, state2, oldstate;
1023       gboolean onenull = FALSE;
1024       state1 = idle;
1025       state2 = idle;
1026       oldstate = idle;
1027       for (int i = 0; i < nitems; i++) {
1028          if (items[i].D_sock == NULL) onenull = TRUE;
1029          if (items[i].state >= state1) {
1030             state2 = state1;
1031             state1 = items[i].state;
1032          }
1033          else if (items[i].state > state2) {
1034             state2 = items[i].state;
1035          }
1036          if (items[i].oldstate > oldstate) oldstate = items[i].oldstate;
1037       }
1038
1039       if ((onenull == TRUE) && (state2 == idle)) {
1040          state2 = error;
1041       }
1042
1043       xpm = generateXPM(blinkstate ? state1 : state2, oldstate);
1044    }
1045    else {
1046       if ((blinkstate) && (item->D_sock != NULL)) {
1047          if (item->state > 1) { //Warning or error while running
1048             xpm = generateXPM(running, item->oldstate);
1049          }
1050          else {
1051             xpm = generateXPM(idle, item->oldstate);
1052          }
1053       }
1054       else {
1055          xpm = generateXPM(item->state, item->oldstate);
1056       }
1057    }
1058
1059    GdkPixbuf* pixbuf = gdk_pixbuf_new_from_xpm_data(xpm);
1060    if (item == NULL) {
1061       egg_status_icon_set_from_pixbuf(mTrayIcon, pixbuf);
1062       gtk_window_set_icon(GTK_WINDOW(window), pixbuf);
1063    }
1064    else {
1065       gtk_image_set_from_pixbuf(GTK_IMAGE(item->image), pixbuf);
1066    }
1067    g_object_unref(G_OBJECT(pixbuf));
1068 }
1069
1070 /* Note: result should not be stored, as it is a reference to xpm_generic_var */
1071 static const char** generateXPM(stateenum newstate, stateenum oldstate) {
1072    char* address = &xpm_generic_var[xpm_generic_first_color][xpm_generic_column];
1073    switch (newstate) {
1074    case error:
1075       strcpy(address, "ff0000");
1076       break;
1077    case idle:
1078       strcpy(address, "ffffff");
1079       break;
1080    case running:
1081       strcpy(address, "00ff00");
1082       break;
1083    case warn:
1084       strcpy(address, "ffff00");
1085       break;
1086    }
1087
1088    address = &xpm_generic_var[xpm_generic_second_color][xpm_generic_column];
1089    switch (oldstate) {
1090    case error:
1091       strcpy(address, "ff0000");
1092       break;
1093    case idle:
1094       strcpy(address, "ffffff");
1095       break;
1096    case running:
1097       strcpy(address, "00ff00");
1098       break;
1099    case warn:
1100       strcpy(address, "ffff00");
1101       break;
1102    }
1103
1104    return (const char**)xpm_generic_var;
1105 }