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