3 * Bacula Gnome Tray Monitor
5 * Nicolas Boichat, August MMIV
11 Copyright (C) 2004 Kern Sibbald and John Walker
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.
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.
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,
31 #include "tray-monitor.h"
33 #include "eggstatusicon.h"
37 #include "running.xpm"
38 //#include "saving.xpm"
41 /* Imported functions */
42 int authenticate_director(JCR *jcr, MONITOR *monitor, DIRRES *director);
43 int authenticate_file_daemon(JCR *jcr, MONITOR *monitor, CLIENT* client);
44 int authenticate_storage_daemon(JCR *jcr, MONITOR *monitor, STORE* store);
46 /* Forward referenced functions */
47 void writecmd(monitoritem* item, const char* command);
48 int docmd(monitoritem* item, const char* command, GSList** list);
49 stateenum getstatus(monitoritem* item, int current, GString** str);
51 /* Static variables */
52 static char *configfile = NULL;
53 static MONITOR *monitor;
56 static int nitems = 0;
57 static int fullitem = -1; //Item to be display in detailled status window
58 static int lastupdated = -1; //Last item updated
59 static monitoritem items[32];
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";
65 /* UI variables and functions */
67 stateenum currentstatus = warn;
69 static gboolean fd_read(gpointer data);
70 void trayMessage(const char *fmt,...);
71 void changeStatus(monitoritem* item, stateenum status);
72 void changeStatusMessage(monitoritem* item, const char *fmt,...);
75 static void TrayIconActivate(GtkWidget *widget, gpointer data);
76 static void TrayIconExit(unsigned int activateTime, unsigned int button);
77 static void TrayIconPopupMenu(unsigned int button, unsigned int activateTime);
78 static void MonitorAbout(GtkWidget *widget, gpointer data);
79 static gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data);
82 static EggStatusIcon *mTrayIcon;
83 static GtkWidget *mTrayMenu;
84 static GtkWidget *window;
85 static GtkWidget *textview;
86 static GtkTextBuffer *buffer;
88 #define CONFIG_FILE "./tray-monitor.conf" /* default configuration file */
93 "Copyright (C) 2000-2004 Kern Sibbald and John Walker\n"
94 "Written by Nicolas Boichat (2004)\n"
95 "\nVersion: " VERSION " (" BDATE ") %s %s %s\n\n"
96 "Usage: tray-monitor [-c config_file] [-d debug_level]\n"
97 " -c <file> set configuration file to file\n"
98 " -dnn set debug level to nn\n"
99 " -t test - read configuration and exit\n"
100 " -? print this message.\n"
101 "\n"), HOST_OS, DISTNAME, DISTVER);
104 static GtkWidget *new_image_button(const gchar *stock_id,
105 const gchar *label_text) {
111 button = gtk_button_new();
113 box = gtk_hbox_new(FALSE, 0);
114 gtk_container_set_border_width(GTK_CONTAINER(box), 2);
115 image = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_BUTTON);
116 label = gtk_label_new(label_text);
118 gtk_box_pack_start(GTK_BOX(box), image, FALSE, FALSE, 3);
119 gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 3);
121 gtk_widget_show(image);
122 gtk_widget_show(label);
124 gtk_widget_show(box);
126 gtk_container_add(GTK_CONTAINER(button), box);
131 /*********************************************************************
133 * Main Bacula Tray Monitor -- User Interface Program
136 int main(int argc, char *argv[])
139 bool test_config = false;
145 my_name_is(argc, argv, "tray-monitor");
146 textdomain("bacula");
147 init_msg(NULL, NULL);
148 working_directory = "/tmp";
149 args = get_pool_memory(PM_FNAME);
151 while ((ch = getopt(argc, argv, "bc:d:th?f:s:")) != -1) {
153 case 'c': /* configuration file */
154 if (configfile != NULL) {
157 configfile = bstrdup(optarg);
161 debug_level = atoi(optarg);
162 if (debug_level <= 0) {
186 if (configfile == NULL) {
187 configfile = bstrdup(CONFIG_FILE);
190 parse_config(configfile);
194 foreach_res(dird, R_DIRECTOR) {
195 items[nitems].type = R_DIRECTOR;
196 items[nitems].resource = dird;
197 items[nitems].D_sock = NULL;
198 items[nitems].state = warn;
201 foreach_res(filed, R_CLIENT) {
202 items[nitems].type = R_CLIENT;
203 items[nitems].resource = filed;
204 items[nitems].D_sock = NULL;
205 items[nitems].state = warn;
208 foreach_res(stored, R_STORAGE) {
209 items[nitems].type = R_STORAGE;
210 items[nitems].resource = stored;
211 items[nitems].D_sock = NULL;
212 items[nitems].state = warn;
218 Emsg1(M_ERROR_TERM, 0, _("No Client nor Storage resource defined in %s\n\
219 Without that I don't how to get status from the File or Storage Daemon :-(\n"), configfile);
226 (void)WSA_Init(); /* Initialize Windows sockets */
229 monitor = (MONITOR*)GetNextRes(R_MONITOR, (RES *)NULL);
232 gtk_init (&argc, &argv);
234 GdkPixbuf* pixbuf = gdk_pixbuf_new_from_xpm_data(xpm_warn);
235 // This should be ideally replaced by a completely libpr0n-based icon rendering.
236 mTrayIcon = egg_status_icon_new_from_pixbuf(pixbuf);
237 g_signal_connect(G_OBJECT(mTrayIcon), "activate", G_CALLBACK(TrayIconActivate), NULL);
238 g_signal_connect(G_OBJECT(mTrayIcon), "popup-menu", G_CALLBACK(TrayIconPopupMenu), NULL);
239 g_object_unref(G_OBJECT(pixbuf));
241 mTrayMenu = gtk_menu_new();
245 entry = gtk_menu_item_new_with_label("Open status window...");
246 g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(TrayIconActivate), NULL);
247 gtk_menu_shell_append(GTK_MENU_SHELL(mTrayMenu), entry);
249 gtk_menu_shell_append(GTK_MENU_SHELL(mTrayMenu), gtk_separator_menu_item_new());
251 entry = gtk_menu_item_new_with_label("Exit");
252 g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(TrayIconExit), NULL);
253 gtk_menu_shell_append(GTK_MENU_SHELL(mTrayMenu), entry);
255 gtk_widget_show_all(mTrayMenu);
257 timerTag = g_timeout_add( 5000/nitems, fd_read, NULL );
259 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
261 gtk_window_set_title(GTK_WINDOW(window), "Bacula tray monitor");
263 g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(delete_event), NULL);
264 //g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy), NULL);
266 gtk_container_set_border_width(GTK_CONTAINER(window), 10);
268 GtkWidget* vbox = gtk_vbox_new(FALSE, 10);
270 /*textview = gtk_text_view_new();
272 buffer = gtk_text_buffer_new(NULL);
274 gtk_text_buffer_set_text(buffer, "", -1);
276 PangoFontDescription *font_desc = pango_font_description_from_string ("Fixed 10");
277 gtk_widget_modify_font(textview, font_desc);
278 pango_font_description_free(font_desc);
280 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview), 20);
281 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(textview), 20);
283 gtk_text_view_set_editable(GTK_TEXT_VIEW(textview), FALSE);
285 gtk_text_view_set_buffer(GTK_TEXT_VIEW(textview), buffer);
287 gtk_box_pack_start(GTK_BOX(vbox), textview, TRUE, FALSE, 0);*/
289 GtkWidget* daemon_table = gtk_table_new((nitems*2)+1, 3, FALSE);
291 gtk_table_set_col_spacings(GTK_TABLE(daemon_table), 8);
293 GtkWidget* separator = gtk_hseparator_new();
294 gtk_table_attach_defaults(GTK_TABLE(daemon_table), separator, 0, 3, 0, 1);
297 for (int i = 0; i < nitems; i++) {
298 switch (items[i].type) {
300 str = g_string_new(((DIRRES*)(items[i].resource))->hdr.name);
301 g_string_append(str, _(" (DIR)"));
304 str = g_string_new(((CLIENT*)(items[i].resource))->hdr.name);
305 g_string_append(str, _(" (FD)"));
308 str = g_string_new(((STORE*)(items[i].resource))->hdr.name);
309 g_string_append(str, _(" (SD)"));
315 GtkWidget* label = gtk_label_new(str->str);
316 GdkPixbuf* pixbuf = gdk_pixbuf_new_from_xpm_data(xpm_warn);
317 items[i].image = gtk_image_new_from_pixbuf(pixbuf);
319 items[i].label = gtk_label_new(_("Unknown status."));
320 GtkWidget* align = gtk_alignment_new(0.0, 0.5, 0.0, 1.0);
321 gtk_container_add(GTK_CONTAINER(align), items[i].label);
323 gtk_table_attach_defaults(GTK_TABLE(daemon_table), label, 0, 1, (i*2)+1, (i*2)+2);
324 gtk_table_attach_defaults(GTK_TABLE(daemon_table), items[i].image, 1, 2, (i*2)+1, (i*2)+2);
325 gtk_table_attach_defaults(GTK_TABLE(daemon_table), align, 2, 3, (i*2)+1, (i*2)+2);
327 GtkWidget* separator = gtk_hseparator_new();
328 gtk_table_attach_defaults(GTK_TABLE(daemon_table), separator, 0, 3, (i*2)+2, (i*2)+3);
331 gtk_box_pack_start(GTK_BOX(vbox), daemon_table, TRUE, FALSE, 0);
333 GtkWidget* hbox = gtk_hbox_new(FALSE, 10);
335 GtkWidget* button = new_image_button("gtk-help", "About");
336 g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(MonitorAbout), NULL);
338 gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, FALSE, 0);
340 button = new_image_button("gtk-close", "Close");
341 g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(gtk_widget_hide), G_OBJECT(window));
343 gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, FALSE, 0);
345 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
347 gtk_container_add(GTK_CONTAINER (window), vbox);
349 gtk_widget_show_all(vbox);
353 for (i = 0; i < nitems; i++) {
354 if (items[i].D_sock) {
355 writecmd(&items[i], "quit");
356 bnet_sig(items[i].D_sock, BNET_TERMINATE); /* send EOF */
357 bnet_close(items[i].D_sock);
361 free_pool_memory(args);
362 (void)WSACleanup(); /* Cleanup Windows sockets */
366 static void MonitorAbout(GtkWidget *widget, gpointer data) {
368 GtkWidget* about = gtk_message_dialog_new_with_markup(GTK_WINDOW(window),GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, _(
369 "<span size='x-large' weight='bold'>Bacula Tray Monitor</span>\n\n"
370 "Copyright (C) 2004 Kern Sibbald and John Walker\n"
371 "Written by Nicolas Boichat\n"
372 "\n<small>Version: " VERSION " (" BDATE ") %s %s %s</small>"
373 ), HOST_OS, DISTNAME, DISTVER);
375 GtkWidget* about = gtk_message_dialog_new(GTK_WINDOW(window),GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, _(
376 "Bacula Tray Monitor\n\n"
377 "Copyright (C) 2004 Kern Sibbald and John Walker\n"
378 "Written by Nicolas Boichat\n"
379 "\nVersion: " VERSION " (" BDATE ") %s %s %s"
380 ), HOST_OS, DISTNAME, DISTVER);
382 gtk_dialog_run(GTK_DIALOG(about));
383 gtk_widget_destroy(about);
386 static gboolean delete_event( GtkWidget *widget,
389 gtk_widget_hide(window);
390 return TRUE; /* do not destroy the window */
393 static void TrayIconActivate(GtkWidget *widget, gpointer data) {
394 gtk_widget_show(window);
397 static void TrayIconPopupMenu(unsigned int activateTime, unsigned int button) {
398 gtk_menu_popup(GTK_MENU(mTrayMenu), NULL, NULL, NULL, NULL, 1, 0);
399 gtk_widget_show_all(mTrayMenu);
402 static void TrayIconExit(unsigned int activateTime, unsigned int button) {
406 static int authenticate_daemon(monitoritem* item, JCR *jcr) {
407 switch (item->type) {
409 return authenticate_director(jcr, monitor, (DIRRES*)item->resource);
412 return authenticate_file_daemon(jcr, monitor, (CLIENT*)item->resource);
415 return authenticate_storage_daemon(jcr, monitor, (STORE*)item->resource);
418 printf("Error, currentitem is not a Client or a Storage..\n");
424 static gboolean fd_read(gpointer data) {
425 GtkTextBuffer *newbuffer = gtk_text_buffer_new(NULL);
426 GtkTextIter start, stop, nstart, nstop;
429 stateenum stat = idle, nstat;
431 GString *strlast, *strcurrent;
434 if (lastupdated == nitems) {
438 nstat = getstatus(&items[lastupdated], 1, &strcurrent);
439 if (nstat > stat) stat = nstat;
440 nstat = getstatus(&items[lastupdated], 0, &strlast);
441 if (nstat > stat) stat = nstat;
443 changeStatusMessage(&items[lastupdated], "Current job: %s\nLast job: %s", strcurrent->str, strlast->str);
444 changeStatus(&items[lastupdated], stat);
446 g_string_free(strcurrent, TRUE);
447 g_string_free(strlast, TRUE);
449 if (lastupdated == fullitem) {
450 docmd(&items[lastupdated], "status", &list);
454 gtk_text_buffer_get_end_iter(newbuffer, &stop);
455 gtk_text_buffer_insert (newbuffer, &stop, ((GString*)it->data)->str, -1);
456 if (it->data) g_string_free((GString*)it->data, TRUE);
457 } while ((it = it->next) != NULL);
459 /* Keep the selection if necessary */
460 if (gtk_text_buffer_get_selection_bounds(buffer, &start, &stop)) {
461 gtk_text_buffer_get_iter_at_offset(newbuffer, &nstart, gtk_text_iter_get_offset(&start));
462 gtk_text_buffer_get_iter_at_offset(newbuffer, &nstop, gtk_text_iter_get_offset(&stop ));
465 gtk_text_buffer_select_range(newbuffer, &nstart, &nstop);
467 gtk_text_buffer_move_mark(newbuffer, gtk_text_buffer_get_mark(newbuffer, "insert"), &nstart);
468 gtk_text_buffer_move_mark(newbuffer, gtk_text_buffer_get_mark(newbuffer, "selection_bound"), &nstop);
472 g_object_unref(buffer);
475 gtk_text_view_set_buffer(GTK_TEXT_VIEW(textview), buffer);
481 stateenum getstatus(monitoritem* item, int current, GString** str) {
483 stateenum ret = error;
484 int jobid = 0, joberrors = 0;
485 char jobstatus = JS_ErrorTerminated;
489 *str = g_string_sized_new(128);
492 if (item->type == R_DIRECTOR)
493 docmd(&items[lastupdated], ".status dir current", &list);
495 docmd(&items[lastupdated], ".status current", &list);
498 if (item->type == R_DIRECTOR)
499 docmd(&items[lastupdated], ".status dir last", &list);
501 docmd(&items[lastupdated], ".status last", &list);
505 if ((it == NULL) || (sscanf(((GString*)it->data)->str, OKqstatus, &num) != 1)) {
506 g_string_append_printf(*str, ".status error : %s", (it == NULL) ? "" : ((GString*)it->data)->str);
509 else if ((it = it->next) == NULL) {
511 g_string_append(*str, _("No current job."));
514 g_string_append(*str, _("No last job."));
518 else if ((k = sscanf(((GString*)it->data)->str, DotStatusJob, &jobid, &jobstatus, &joberrors)) == 3) {
521 ret = (joberrors > 0) ? warn : running;
522 g_string_append_printf(*str, _("Job status: Created (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
525 ret = (joberrors > 0) ? warn : running;
526 g_string_append_printf(*str, _("Job status: Running (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
529 ret = (joberrors > 0) ? warn : running;
530 g_string_append_printf(*str, _("Job status: Error (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
533 g_string_append_printf(*str, _("Job status: Terminated (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
534 ret = (joberrors > 0) ? warn : idle;
536 case JS_ErrorTerminated:
537 g_string_append_printf(*str, _("Job status: Terminated in error (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
541 g_string_append_printf(*str, _("Job status: Fatal error (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
545 g_string_append_printf(*str, _("Job status: Blocked (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
549 g_string_append_printf(*str, _("Job status: Canceled (%d error%s)"), joberrors, (joberrors > 1) ? "s" : "");
553 g_string_append_printf(*str, _("Job status: Unknown(%c) (%d error%s)"), jobstatus, joberrors, (joberrors > 1) ? "s" : "");
559 fprintf(stderr, "Bad scan : '%s' %d\n", (it == NULL) ? "" : ((GString*)it->data)->str, k);
565 if (it->data) g_string_free((GString*)it->data, TRUE);
566 } while ((it = it->next) != NULL);
573 int docmd(monitoritem* item, const char* command, GSList** list) {
577 *list = g_slist_alloc();
579 //str = g_string_sized_new(64);
582 memset(&jcr, 0, sizeof(jcr));
588 switch (item->type) {
590 dird = (DIRRES*)item->resource;
591 trayMessage("Connecting to Director %s:%d\n", dird->address, dird->DIRport);
592 changeStatusMessage(item, "Connecting to Director %s:%d", dird->address, dird->DIRport);
593 item->D_sock = bnet_connect(NULL, 0, 0, "Director daemon", dird->address, NULL, dird->DIRport, 0);
594 jcr.dir_bsock = item->D_sock;
597 filed = (CLIENT*)item->resource;
598 trayMessage("Connecting to Client %s:%d\n", filed->address, filed->FDport);
599 changeStatusMessage(item, "Connecting to Client %s:%d", filed->address, filed->FDport);
600 item->D_sock = bnet_connect(NULL, 0, 0, "File daemon", filed->address, NULL, filed->FDport, 0);
601 jcr.file_bsock = item->D_sock;
604 stored = (STORE*)item->resource;
605 trayMessage("Connecting to Storage %s:%d\n", stored->address, stored->SDport);
606 changeStatusMessage(item, "Connecting to Storage %s:%d", stored->address, stored->SDport);
607 item->D_sock = bnet_connect(NULL, 0, 0, "Storage daemon", stored->address, NULL, stored->SDport, 0);
608 jcr.store_bsock = item->D_sock;
611 printf("Error, currentitem is not a Client, a Storage or a Director..\n");
616 if (item->D_sock == NULL) {
617 g_slist_append(*list, g_string_new("Cannot connect to daemon.\n"));
618 changeStatusMessage(item, "Cannot connect to daemon.");
619 changeStatus(NULL, error);
623 if (!authenticate_daemon(item, &jcr)) {
624 str = g_string_sized_new(64);
625 g_string_printf(str, "ERR=%s\n", item->D_sock->msg);
626 g_slist_append(*list, str);
627 changeStatus(NULL, error);
628 changeStatusMessage(item, "Authentication error : %s", item->D_sock->msg);
633 switch (item->type) {
635 trayMessage("Opened connection with Director daemon.\n");
636 changeStatusMessage(item, "Opened connection with Director daemon.");
639 trayMessage("Opened connection with File daemon.\n");
640 changeStatusMessage(item, "Opened connection with File daemon.");
643 trayMessage("Opened connection with Storage daemon.\n");
644 changeStatusMessage(item, "Opened connection with Storage daemon.");
647 printf("Error, currentitem is not a Client, a Storage or a Director..\n");
654 writecmd(item, command);
657 if ((stat = bnet_recv(item->D_sock)) >= 0) {
658 g_slist_append(*list, g_string_new(item->D_sock->msg));
660 else if (stat == BNET_SIGNAL) {
661 if (item->D_sock->msglen == BNET_EOD) {
664 else if (item->D_sock->msglen == BNET_HEARTBEAT) {
665 bnet_sig(item->D_sock, BNET_HB_RESPONSE);
666 g_slist_append(*list, g_string_new("<< Heartbeat signal received, answered. >>\n"));
669 str = g_string_sized_new(64);
670 g_string_printf(str, "<< Unexpected signal received : %s >>\n", bnet_sig_to_ascii(item->D_sock));
671 g_slist_append(*list, str);
674 else { /* BNET_HARDEOF || BNET_ERROR */
675 g_slist_append(*list, g_string_new("<ERROR>\n"));
677 changeStatus(NULL, error);
678 changeStatusMessage(item, "Error : BNET_HARDEOF or BNET_ERROR");
682 if (is_bnet_stop(item->D_sock)) {
683 g_string_append_printf(str, "<STOP>\n");
685 changeStatus(NULL, error);
686 changeStatusMessage(item, "Error : Connection closed.");
687 return 0; /* error or term */
692 void writecmd(monitoritem* item, const char* command) {
694 item->D_sock->msglen = strlen(command);
695 pm_strcpy(&item->D_sock->msg, command);
696 bnet_send(item->D_sock);
700 /* Note: Does not seem to work either on Gnome nor KDE... */
701 void trayMessage(const char *fmt,...) {
705 va_start(arg_ptr, fmt);
706 bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
709 fprintf(stderr, buf);
711 egg_tray_icon_send_message(egg_status_icon_get_tray_icon(mTrayIcon), 5000, (const char*)&buf, -1);
714 void changeStatusMessage(monitoritem* item, const char *fmt,...) {
718 va_start(arg_ptr, fmt);
719 bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
722 gtk_label_set_text(GTK_LABEL(item->label), buf);
725 void changeStatus(monitoritem* item, stateenum status) {
727 if (status == currentstatus)
731 if (status == item->state)
739 xpm = (const char**)&xpm_error;
742 xpm = (const char**)&xpm_idle;
745 xpm = (const char**)&xpm_running;
748 xpm = (const char**)&xpm_saving;
751 xpm = (const char**)&xpm_warn;
758 GdkPixbuf* pixbuf = gdk_pixbuf_new_from_xpm_data(xpm);
760 egg_status_icon_set_from_pixbuf(mTrayIcon, pixbuf);
761 gtk_window_set_icon(GTK_WINDOW(window), pixbuf);
762 currentstatus = status;
765 gtk_image_set_from_pixbuf(GTK_IMAGE(item->image), pixbuf);
766 item->state = status;