]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/tray-monitor/tray-monitor.c
- Set WANT_AUTOCONF to 2.5 before running autoconf and autoheader in the main Makefil...
[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 #include <gtk/gtk.h>
35
36 #include "idle.xpm"
37 #include "error.xpm"
38 #include "running.xpm"
39 #include "saving.xpm"
40 #include "warn.xpm"
41
42 /* Imported functions */
43 int authenticate_file_daemon(JCR *jcr, MONITOR *monitor, CLIENT* client);
44 int authenticate_storage_daemon(JCR *jcr, MONITOR *monitor, STORE* store);
45
46 /* Forward referenced functions */
47 void writecmd(const char* command);
48
49 /* Static variables */
50 static char *configfile = NULL;
51 static BSOCK *D_sock = NULL;
52 static MONITOR *monitor;
53 static POOLMEM *args;
54 static JCR jcr;
55 static int nitems = 0;
56 static monitoritem items[32];
57 static monitoritem* currentitem;
58
59 /* UI variables and functions */
60 enum stateenum {
61    error,
62    idle,
63    running,
64    saving,
65    warn
66 };
67
68 stateenum currentstatus = warn;
69
70 static gboolean fd_read(gpointer data);
71 void trayMessage(const char *fmt,...);
72 void changeIcon(stateenum status);
73 void writeToTextBuffer(GtkTextBuffer *buffer, const char *fmt,...);
74
75 /* Callbacks */
76 static void TrayIconDaemonChanged(GtkWidget *widget, monitoritem* data);
77 static void TrayIconActivate(GtkWidget *widget, gpointer data);
78 static void TrayIconExit(unsigned int activateTime, unsigned int button);
79 static void TrayIconPopupMenu(unsigned int button, unsigned int activateTime);
80 static void MonitorAbout(GtkWidget *widget, gpointer data);
81 static gboolean delete_event(GtkWidget *widget, GdkEvent  *event, gpointer   data);
82
83 static gint timerTag;
84 static EggStatusIcon *mTrayIcon;
85 static GtkWidget *mTrayMenu;
86 static GtkWidget *window;
87 static GtkWidget *textview;
88 static GtkTextBuffer *buffer;
89
90 #define CONFIG_FILE "./tray-monitor.conf"   /* default configuration file */
91
92 static void usage()
93 {
94    fprintf(stderr, _(
95 "Copyright (C) 2000-2004 Kern Sibbald and John Walker\n"
96 "Written by Nicolas Boichat (2004)\n"
97 "\nVersion: " VERSION " (" BDATE ") %s %s %s\n\n"
98 "Usage: tray-monitor [-c config_file] [-d debug_level] [-f filed | -s stored]\n"
99 "       -c <file>     set configuration file to file\n"
100 "       -dnn          set debug level to nn\n"
101 "       -t            test - read configuration and exit\n"
102 "       -f <filed>    monitor <filed>\n"
103 "       -s <stored>   monitor <stored>\n"
104 "       -?            print this message.\n"  
105 "\n"), HOST_OS, DISTNAME, DISTVER);
106 }
107
108 static GtkWidget *new_image_button(const gchar *stock_id, 
109                                    const gchar *label_text) {
110     GtkWidget *button;
111     GtkWidget *box;
112     GtkWidget *label;
113     GtkWidget *image;
114
115     button = gtk_button_new();
116    
117     box = gtk_hbox_new(FALSE, 0);
118     gtk_container_set_border_width(GTK_CONTAINER(box), 2);
119     image = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_BUTTON);
120     label = gtk_label_new(label_text);
121     
122     gtk_box_pack_start(GTK_BOX(box), image, FALSE, FALSE, 3);
123     gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 3);
124
125     gtk_widget_show(image);
126     gtk_widget_show(label);
127
128     gtk_widget_show(box);
129
130     gtk_container_add(GTK_CONTAINER(button), box);
131     
132     return button;
133 }
134
135 /*********************************************************************
136  *
137  *         Main Bacula Tray Monitor -- User Interface Program
138  *
139  */
140 int main(int argc, char *argv[])
141 {
142    int ch;
143    bool test_config = false;
144    char* deffiled = NULL;
145    char* defstored = NULL;
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    while ((ch = getopt(argc, argv, "bc:d:th?f:s:")) != -1) {
157       switch (ch) {
158       case 'c':                    /* configuration file */
159          if (configfile != NULL) {
160             free(configfile);
161          }
162          configfile = bstrdup(optarg);
163          break;
164          
165       case 'f':
166          if ((deffiled != NULL) || (defstored != NULL)) {
167             fprintf(stderr, "Error: You can only use one -f <filed> or -s <stored> parameter\n");
168             exit(1);
169          }
170          deffiled = bstrdup(optarg);
171          break;
172          
173       case 's':
174          if ((deffiled != NULL) || (defstored != NULL)) {
175             fprintf(stderr, "Error: You can only use one -f <filed> or -s <stored> parameter\n");
176             exit(1);
177          }
178          defstored = bstrdup(optarg);
179          break;
180
181       case 'd':
182          debug_level = atoi(optarg);
183          if (debug_level <= 0) {
184             debug_level = 1;
185          }
186          break;
187
188       case 't':
189          test_config = true;
190          break;
191
192       case 'h':
193       case '?':
194       default:
195          usage();
196          exit(1);
197       }  
198    }
199    argc -= optind;
200    argv += optind;
201
202    if (argc) {
203       usage();
204       exit(1);
205    }
206
207    if (configfile == NULL) {
208       configfile = bstrdup(CONFIG_FILE);
209    }
210
211    parse_config(configfile);
212
213    currentitem = NULL;
214    
215    LockRes();
216    nitems = 0;
217    foreach_res(filed, R_CLIENT) {
218       items[nitems].type = R_CLIENT;
219       items[nitems].resource = filed;
220       if ((deffiled != NULL) && (strcmp(deffiled, filed->hdr.name) == 0)) {
221          currentitem = &(items[nitems]);
222       }
223       nitems++;
224    }
225    foreach_res(stored, R_STORAGE) {
226       items[nitems].type = R_STORAGE;
227       items[nitems].resource = stored;
228       if ((defstored != NULL) && (strcmp(defstored, stored->hdr.name) == 0)) {
229          currentitem = &(items[nitems]);
230       }
231       nitems++;
232    }
233    UnlockRes();
234      
235    if (nitems == 0) {
236       Emsg1(M_ERROR_TERM, 0, _("No Client nor Storage resource defined in %s\n\
237 Without that I don't how to get status from the File or Storage Daemon :-(\n"), configfile);
238    }
239    
240    if ((deffiled != NULL) || (defstored != NULL)) {
241      if (currentitem == NULL) {
242         fprintf(stderr, "Error: The file or storage daemon specified by parameters doesn't exists on your configuration file. Exiting...\n");
243         exit(1);
244      }
245    }
246    else {
247       currentitem = &(items[0]);
248    }
249
250    if (test_config) {
251       exit(0);
252    }
253    
254    (void)WSA_Init();                        /* Initialize Windows sockets */
255
256    LockRes();
257    monitor = (MONITOR*)GetNextRes(R_MONITOR, (RES *)NULL);
258    UnlockRes();
259    
260    gtk_init (&argc, &argv);
261    
262    GdkPixbuf* pixbuf = gdk_pixbuf_new_from_xpm_data(xpm_warn);
263    // This should be ideally replaced by a completely libpr0n-based icon rendering.
264    mTrayIcon = egg_status_icon_new_from_pixbuf(pixbuf);
265    g_signal_connect(G_OBJECT(mTrayIcon), "activate", G_CALLBACK(TrayIconActivate), NULL);
266    g_signal_connect(G_OBJECT(mTrayIcon), "popup-menu", G_CALLBACK(TrayIconPopupMenu), NULL);
267    g_object_unref(G_OBJECT(pixbuf));
268
269    mTrayMenu = gtk_menu_new();
270    
271    GtkWidget *entry;
272    
273    entry = gtk_menu_item_new_with_label("Open status window...");
274    g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(TrayIconActivate), NULL);
275    gtk_menu_shell_append(GTK_MENU_SHELL(mTrayMenu), entry);
276    
277    gtk_menu_shell_append(GTK_MENU_SHELL(mTrayMenu), gtk_separator_menu_item_new());
278    
279    GtkWidget *submenu = gtk_menu_new();
280    
281    entry = gtk_menu_item_new_with_label("Set monitored daemon");
282    gtk_menu_item_set_submenu(GTK_MENU_ITEM(entry), submenu);
283    gtk_menu_shell_append(GTK_MENU_SHELL(mTrayMenu), entry);
284       
285    GSList *group = NULL;
286    
287    GString *str;   
288    for (int i = 0; i < nitems; i++) {
289       switch (items[i].type) {
290       case R_CLIENT:
291          str = g_string_new(((CLIENT*)(items[i].resource))->hdr.name);
292          g_string_append(str, " (FD)");
293          break;
294       case R_STORAGE:
295          str = g_string_new(((STORE*)(items[i].resource))->hdr.name);
296          g_string_append(str, " (SD)");
297          break;
298       default:
299          continue;
300       }
301       entry = gtk_radio_menu_item_new_with_label(group, str->str);
302       g_string_free(str, TRUE);
303       
304       group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(entry));
305       if (currentitem == &(items[i])) {
306          gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry), TRUE);
307       }
308       
309       g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(TrayIconDaemonChanged), &(items[i]));
310       gtk_menu_shell_append(GTK_MENU_SHELL(submenu), entry);         
311    }
312    
313    gtk_menu_shell_append(GTK_MENU_SHELL(mTrayMenu), gtk_separator_menu_item_new());
314    
315    entry = gtk_menu_item_new_with_label("Exit");
316    g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(TrayIconExit), NULL);
317    gtk_menu_shell_append(GTK_MENU_SHELL(mTrayMenu), entry);
318    
319    gtk_widget_show_all(mTrayMenu);
320    
321    timerTag = g_timeout_add( 5000, fd_read, NULL );
322       
323    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
324    
325    gtk_window_set_title(GTK_WINDOW(window), "Bacula tray monitor");
326    
327    g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(delete_event), NULL);
328    //g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy), NULL);
329    
330    gtk_container_set_border_width(GTK_CONTAINER(window), 10);
331    
332    GtkWidget* vbox = gtk_vbox_new(FALSE, 10);
333    
334    textview = gtk_text_view_new();
335
336    buffer = gtk_text_buffer_new(NULL);
337
338    gtk_text_buffer_set_text(buffer, "", -1);
339
340    PangoFontDescription *font_desc = pango_font_description_from_string ("Fixed 10");
341    gtk_widget_modify_font(textview, font_desc);
342    pango_font_description_free(font_desc);
343    
344    gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview), 20);
345    gtk_text_view_set_right_margin(GTK_TEXT_VIEW(textview), 20);
346    
347    gtk_text_view_set_editable(GTK_TEXT_VIEW(textview), FALSE);
348    
349    gtk_text_view_set_buffer(GTK_TEXT_VIEW(textview), buffer);
350    
351    gtk_box_pack_start(GTK_BOX(vbox), textview, TRUE, FALSE, 0);
352       
353    GtkWidget* hbox = gtk_hbox_new(FALSE, 10);
354          
355    GtkWidget* button = new_image_button("gtk-help", "About");
356    g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(MonitorAbout), NULL);
357    
358    gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, FALSE, 0);
359    
360    button = new_image_button("gtk-close", "Close");
361    g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(gtk_widget_hide), G_OBJECT(window));
362    
363    gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, FALSE, 0);
364    
365    gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
366    
367    gtk_container_add(GTK_CONTAINER (window), vbox);
368    
369    gtk_widget_show_all(vbox);
370    
371    fd_read(NULL);
372    
373    gtk_main();
374       
375    if (D_sock) {
376       writecmd("quit");
377       bnet_sig(D_sock, BNET_TERMINATE); /* send EOF */
378       bnet_close(D_sock);
379    }
380
381    free_pool_memory(args);
382    (void)WSACleanup();               /* Cleanup Windows sockets */
383    return 0;
384 }
385
386 static void MonitorAbout(GtkWidget *widget, gpointer data) {
387 #if HAVE_GTK_2_4
388    GtkWidget* about = gtk_message_dialog_new_with_markup(GTK_WINDOW(window),GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, _(
389       "<span size='x-large' weight='bold'>Bacula Tray Monitor</span>\n\n"
390       "Copyright (C) 2004 Kern Sibbald and John Walker\n"
391       "Written by Nicolas Boichat\n"
392       "\n<small>Version: " VERSION " (" BDATE ") %s %s %s</small>"
393    ), HOST_OS, DISTNAME, DISTVER);
394 #else
395    GtkWidget* about = gtk_message_dialog_new(GTK_WINDOW(window),GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, _(
396       "Bacula Tray Monitor\n\n"
397       "Copyright (C) 2004 Kern Sibbald and John Walker\n"
398       "Written by Nicolas Boichat\n"
399       "\nVersion: " VERSION " (" BDATE ") %s %s %s"
400    ), HOST_OS, DISTNAME, DISTVER); 
401 #endif
402    gtk_dialog_run(GTK_DIALOG(about));
403    gtk_widget_destroy(about);
404 }
405
406 static gboolean delete_event( GtkWidget *widget,
407                               GdkEvent  *event,
408                               gpointer   data ) {
409    gtk_widget_hide(window);
410    return TRUE; /* do not destroy the window */
411 }
412
413 static void TrayIconDaemonChanged(GtkWidget *widget, monitoritem* data) {
414    if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) { // && (data != currentitem)
415       printf("!!%s\n", ((STORE*)data->resource)->hdr.name);
416       if (D_sock) {
417          writecmd("quit");
418          bnet_sig(D_sock, BNET_TERMINATE); /* send EOF */
419          bnet_close(D_sock);
420          D_sock = NULL;
421       }
422       currentitem = data;
423       fd_read(NULL);
424    }
425 }
426
427 static void TrayIconActivate(GtkWidget *widget, gpointer data) {
428     gtk_widget_show(window);
429 }
430
431 static void TrayIconPopupMenu(unsigned int activateTime, unsigned int button) {
432   gtk_menu_popup(GTK_MENU(mTrayMenu), NULL, NULL, NULL, NULL, 1, 0);
433   gtk_widget_show_all(mTrayMenu);
434 }
435
436 static void TrayIconExit(unsigned int activateTime, unsigned int button) {
437    gtk_main_quit();
438 }
439
440 static int authenticate_daemon(JCR *jcr) {
441    switch (currentitem->type) {
442    case R_CLIENT:
443       return authenticate_file_daemon(jcr, monitor, (CLIENT*)currentitem->resource);
444       break;
445    case R_STORAGE:
446       return authenticate_storage_daemon(jcr, monitor, (STORE*)currentitem->resource);
447       break;
448    default:
449       printf("Error, currentitem is not a Client or a Storage..\n");
450       gtk_main_quit();
451       return FALSE;
452    }
453 }
454
455 static gboolean fd_read(gpointer data) {
456    int stat;
457    int statuschanged = 0;
458    GtkTextBuffer *newbuffer = gtk_text_buffer_new(NULL);
459    GtkTextIter start, stop, nstart, nstop;
460    
461    gtk_text_buffer_set_text (newbuffer, "", -1);
462       
463    if (!D_sock) {
464       memset(&jcr, 0, sizeof(jcr));
465       
466       CLIENT* filed;
467       STORE* stored;
468       
469       switch (currentitem->type) {
470       case R_CLIENT:
471          filed = (CLIENT*)currentitem->resource;      
472          trayMessage("Connecting to Client %s:%d\n", filed->address, filed->FDport);
473          D_sock = bnet_connect(NULL, 3, 3, "File daemon", filed->address, NULL, filed->FDport, 0);
474          jcr.file_bsock = D_sock;
475          break;
476       case R_STORAGE:
477          stored = (STORE*)currentitem->resource;      
478          trayMessage("Connecting to Storage %s:%d\n", stored->address, stored->SDport);
479          D_sock = bnet_connect(NULL, 3, 3, "Storage daemon", stored->address, NULL, stored->SDport, 0);
480          jcr.store_bsock = D_sock;
481          break;
482       default:
483          printf("Error, currentitem is not a Client or a Storage..\n");
484          gtk_main_quit();
485          return FALSE;
486       }
487       
488       if (D_sock == NULL) {
489          writeToTextBuffer(newbuffer, "Cannot connect to daemon.\n");
490          changeIcon(error);
491          return 1;
492       }
493       
494       if (!authenticate_daemon(&jcr)) {
495          writeToTextBuffer(newbuffer, "ERR=%s\n", D_sock->msg);
496          D_sock = NULL;
497          changeIcon(error);
498          return 0;
499       }
500    
501       trayMessage("Opened connection with File daemon.\n");
502    }
503       
504    writecmd("status");
505    
506    while(1) {
507       if ((stat = bnet_recv(D_sock)) >= 0) {
508          writeToTextBuffer(newbuffer, D_sock->msg);
509          if (strstr(D_sock->msg, " is running.") != NULL) {
510             changeIcon(running);
511             statuschanged = 1;
512          }
513          else if (strstr(D_sock->msg, "No Jobs running.") != NULL) {
514             changeIcon(idle);
515             statuschanged = 1;
516          }
517       }
518       else if (stat == BNET_SIGNAL) {
519          if (D_sock->msglen == BNET_EOD) {
520             if (statuschanged == 0) {
521                changeIcon(warn);
522             }
523             break;
524          }
525          else if (D_sock->msglen == BNET_HEARTBEAT) {
526             bnet_sig(D_sock, BNET_HB_RESPONSE);
527             writeToTextBuffer(newbuffer, "<< Heartbeat signal received, answered. >>");
528          }
529          else {
530             writeToTextBuffer(newbuffer, "<< Unexpected signal received : %s >>", bnet_sig_to_ascii(D_sock));
531          }
532       }
533       else { /* BNET_HARDEOF || BNET_ERROR */
534          writeToTextBuffer(newbuffer, "<ERROR>\n");
535          D_sock = NULL;
536          changeIcon(error);
537          break;
538       }
539            
540       if (is_bnet_stop(D_sock)) {
541          writeToTextBuffer(newbuffer, "<STOP>\n");
542          D_sock = NULL;
543          changeIcon(error);
544          break;            /* error or term */
545       }
546    }
547    
548    /* Keep the selection if necessary */
549    if (gtk_text_buffer_get_selection_bounds(buffer, &start, &stop)) {
550       gtk_text_buffer_get_iter_at_offset(newbuffer, &nstart, gtk_text_iter_get_offset(&start));
551       gtk_text_buffer_get_iter_at_offset(newbuffer, &nstop,  gtk_text_iter_get_offset(&stop ));
552       
553 #if HAVE_GTK_2_4
554       gtk_text_buffer_select_range(newbuffer, &nstart, &nstop);
555 #else
556       gtk_text_buffer_move_mark(newbuffer, gtk_text_buffer_get_mark(newbuffer, "insert"), &nstart);
557       gtk_text_buffer_move_mark(newbuffer, gtk_text_buffer_get_mark(newbuffer, "selection_bound"), &nstop);
558 #endif
559    }
560
561    g_object_unref(buffer);
562    
563    buffer = newbuffer;
564    gtk_text_view_set_buffer(GTK_TEXT_VIEW(textview), buffer);
565       
566    return 1;
567 }
568
569 void writecmd(const char* command) {
570    if (D_sock) {
571       D_sock->msglen = strlen(command);
572       pm_strcpy(&D_sock->msg, command);
573       bnet_send(D_sock);
574    }
575 }
576
577 /* Note: Does not seem to work either on Gnome nor KDE... */
578 void trayMessage(const char *fmt,...) {
579    char buf[3000];
580    va_list arg_ptr;
581    
582    va_start(arg_ptr, fmt);
583    bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
584    va_end(arg_ptr);
585    
586    egg_tray_icon_send_message(egg_status_icon_get_tray_icon(mTrayIcon), 5000, (const char*)&buf, -1);
587 }
588
589 void changeIcon(stateenum status) {
590    if (status == currentstatus)
591       return;
592
593    const char** xpm;
594
595    switch (status) {
596    case error:
597       xpm = (const char**)&xpm_error;
598       break;
599    case idle:
600       xpm = (const char**)&xpm_idle;
601       break;
602    case running:
603       xpm = (const char**)&xpm_running;
604       break;
605    case saving:
606       xpm = (const char**)&xpm_saving;
607       break;
608    case warn:
609       xpm = (const char**)&xpm_warn;
610       break;
611    default:
612       xpm = NULL;
613       break;
614    }
615    
616    GdkPixbuf* pixbuf = gdk_pixbuf_new_from_xpm_data(xpm);
617    egg_status_icon_set_from_pixbuf(mTrayIcon, pixbuf);
618    
619    gtk_window_set_icon(GTK_WINDOW(window), pixbuf);
620    
621    currentstatus = status;
622 }
623
624 void writeToTextBuffer(GtkTextBuffer *buffer, const char *fmt,...) {
625    char buf[3000];
626    va_list arg_ptr;
627    GtkTextIter iter;
628    
629    va_start(arg_ptr, fmt);
630    bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
631    va_end(arg_ptr);
632    
633    gtk_text_buffer_get_end_iter(buffer, &iter);
634    gtk_text_buffer_insert(buffer, &iter, buf, -1);
635 }
636