]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/qt-console/console/console.cpp
Move more bnet functions into the BSOCK class.
[bacula/bacula] / bacula / src / qt-console / console / console.cpp
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2007-2007 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version two of the GNU General Public
10    License as published by the Free Software Foundation plus additions
11    that are listed in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of John Walker.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 /*
29  *   Version $Id$
30  *
31  *  Console Class
32  *
33  *   Kern Sibbald, January MMVII
34  *
35  */ 
36
37 #include <QAbstractEventDispatcher>
38 #include "bat.h"
39 #include "console.h"
40 #include "restore.h"
41 #include "select.h"
42 #include "run/run.h"
43
44 Console::Console(QStackedWidget *parent)
45 {
46    QFont font;
47    m_parent = parent;
48    m_closeable = false;
49    m_console = this;
50    (void)parent;
51
52    setupUi(this);
53    m_sock = NULL;
54    m_at_prompt = false;
55    m_at_main_prompt = false;
56    m_textEdit = textEdit;   /* our console screen */
57    m_cursor = new QTextCursor(m_textEdit->document());
58    mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
59
60    /* Check for messages every 5 seconds */
61    m_timer = new QTimer(this);
62    QWidget::connect(m_timer, SIGNAL(timeout()), this, SLOT(poll_messages()));
63    m_timer->start(5000);
64 }
65
66 Console::~Console()
67 {
68 }
69
70 void Console::poll_messages()
71 {
72    m_messages_pending = true;
73    if (m_at_main_prompt) {
74       write(".messages");
75       displayToPrompt();
76    }
77 }
78
79 /* Terminate any open socket */
80 void Console::terminate()
81 {
82    if (m_sock) {
83       m_sock->close();
84       m_sock = NULL;
85    }
86    m_timer->stop();
87 }
88
89 /*
90  * Connect to Director. If there are more than one, put up
91  * a modal dialog so that the user chooses one.
92  */
93 void Console::connect()
94 {
95    JCR jcr;
96    utime_t heart_beat;
97
98    m_textEdit = textEdit;   /* our console screen */
99
100    if (!m_dir) {          
101       mainWin->set_status("No Director found.");
102       return;
103    }
104    if (m_sock) {
105       mainWin->set_status("Already connected.");
106       return;
107    }
108
109    memset(&jcr, 0, sizeof(jcr));
110
111    mainWin->set_statusf(_("Connecting to Director %s:%d"), m_dir->address, m_dir->DIRport);
112    display_textf(_("Connecting to Director %s:%d\n\n"), m_dir->address, m_dir->DIRport);
113
114    /* Give GUI a chance */
115    app->processEvents();
116    
117    LockRes();
118    /* If cons==NULL, default console will be used */
119    CONRES *cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)NULL);
120    UnlockRes();
121
122    if (m_dir->heartbeat_interval) {
123       heart_beat = m_dir->heartbeat_interval;
124    } else if (cons) {
125       heart_beat = cons->heartbeat_interval;
126    } else {
127       heart_beat = 0;
128    }        
129
130    m_sock = bnet_connect(NULL, 5, 15, heart_beat,
131                           _("Director daemon"), m_dir->address,
132                           NULL, m_dir->DIRport, 0);
133    if (m_sock == NULL) {
134       mainWin->set_status("Connection failed");
135       return;
136    } else {
137       /* Update page selector to green to indicate that Console is connected */
138       mainWin->actionConnect->setIcon(QIcon(":images/connected.png"));
139       QBrush greenBrush(Qt::green);
140       QTreeWidgetItem *item = mainWin->getFromHash(this);
141       item->setForeground(0, greenBrush);
142    }
143
144    jcr.dir_bsock = m_sock;
145
146    if (!authenticate_director(&jcr, m_dir, cons)) {
147       display_text(m_sock->msg);
148       return;
149    }
150
151    /* Give GUI a chance */
152    app->processEvents();
153
154    mainWin->set_status(_("Initializing ..."));
155
156    /* Set up input notifier */
157    m_notifier = new QSocketNotifier(m_sock->m_fd, QSocketNotifier::Read, 0);
158    QObject::connect(m_notifier, SIGNAL(activated(int)), this, SLOT(read_dir(int)));
159
160    write(".api 1");
161    displayToPrompt();
162
163    beginNewCommand();
164    dir_cmd(".jobs", job_list);
165    dir_cmd(".clients", client_list);
166    dir_cmd(".filesets", fileset_list);  
167    dir_cmd(".msgs", messages_list);
168    dir_cmd(".pools", pool_list);
169    dir_cmd(".storage", storage_list);
170    dir_cmd(".types", type_list);
171    dir_cmd(".levels", level_list);
172
173    mainWin->set_status(_("Connected"));
174    return;
175 }
176
177 bool Console::dir_cmd(QString &cmd, QStringList &results)
178 {
179    return dir_cmd(cmd.toUtf8().data(), results);
180 }
181
182 /*
183  * Send a command to the Director, and return the
184  *  results in a QStringList.  
185  */
186 bool Console::dir_cmd(const char *cmd, QStringList &results)
187 {
188    int stat;
189
190    notify(false);
191    write(cmd);
192    while ((stat = read()) > 0) {
193       if (mainWin->m_displayAll) display_text(msg());
194       strip_trailing_junk(msg());
195       results << msg();
196    }
197    notify(true);
198    discardToPrompt();
199    return true;              /* ***FIXME*** return any command error */
200 }
201
202 bool Console::sql_cmd(QString &query, QStringList &results)
203 {
204    return sql_cmd(query.toUtf8().data(), results);
205 }
206
207 /*
208  * Send an sql query to the Director, and return the
209  *  results in a QStringList.  
210  */
211 bool Console::sql_cmd(const char *query, QStringList &results)
212 {
213    if (!is_connectedGui())
214       return false;
215    int stat;
216    POOL_MEM cmd(PM_MESSAGE);
217
218    notify(false);
219    
220    pm_strcpy(cmd, ".sql query=\"");
221    pm_strcat(cmd, query);
222    pm_strcat(cmd, "\"");
223    write(cmd.c_str());
224    while ((stat = read()) > 0) {
225       if (mainWin->m_displayAll) display_text(msg());
226       strip_trailing_junk(msg());
227       results << msg();
228    }
229    notify(true);
230    discardToPrompt();
231    return true;              /* ***FIXME*** return any command error */
232 }
233
234
235 /*  
236  * Send a job name to the director, and read all the resulting
237  *  defaults. 
238  */
239 bool Console::get_job_defaults(struct job_defaults &job_defs)
240 {
241    QString scmd;
242    int stat;
243    char *def;
244
245    notify(false);
246    beginNewCommand();
247    scmd = QString(".defaults job=\"%1\"").arg(job_defs.job_name);
248    write(scmd);
249    while ((stat = read()) > 0) {
250       if (mainWin->m_displayAll) display_text(msg());
251       def = strchr(msg(), '=');
252       if (!def) {
253          continue;
254       }
255       /* Pointer to default value */
256       *def++ = 0;
257       strip_trailing_junk(def);
258
259       if (strcmp(msg(), "job") == 0) {
260          if (strcmp(def, job_defs.job_name.toUtf8().data()) != 0) {
261             goto bail_out;
262          }
263          continue;
264       }
265       if (strcmp(msg(), "pool") == 0) {
266          job_defs.pool_name = def;
267          continue;
268       }
269       if (strcmp(msg(), "messages") == 0) {
270          job_defs.messages_name = def;
271          continue;
272       }
273       if (strcmp(msg(), "client") == 0) {
274          job_defs.client_name = def;
275          continue;
276       }
277       if (strcmp(msg(), "storage") == 0) {
278          job_defs.store_name = def;
279          continue;
280       }
281       if (strcmp(msg(), "where") == 0) {
282          job_defs.where = def;
283          continue;
284       }
285       if (strcmp(msg(), "level") == 0) {
286          job_defs.level = def;
287          continue;
288       }
289       if (strcmp(msg(), "type") == 0) {
290          job_defs.type = def;
291          continue;
292       }
293       if (strcmp(msg(), "fileset") == 0) {
294          job_defs.fileset_name = def;
295          continue;
296       }
297       if (strcmp(msg(), "catalog") == 0) {
298          job_defs.catalog_name = def;
299          continue;
300       }
301       if (strcmp(msg(), "enabled") == 0) {
302          job_defs.enabled = *def == '1' ? true : false;
303          continue;
304       }
305    }
306
307 #ifdef xxx
308    bsnprintf(cmd, sizeof(cmd), "job=%s pool=%s client=%s storage=%s where=%s\n"
309       "level=%s type=%s fileset=%s catalog=%s enabled=%d\n",
310       job_defs.job_name.toUtf8().data(), job_defs.pool_name.toUtf8().data(), 
311       job_defs.client_name.toUtf8().data(), 
312       job_defs.pool_name.toUtf8().data(), job_defs.messages_name.toUtf8().data(), 
313       job_defs.store_name.toUtf8().data(),
314       job_defs.where.toUtf8().data(), job_defs.level.toUtf8().data(), 
315       job_defs.type.toUtf8().data(), job_defs.fileset_name.toUtf8().data(),
316       job_defs.catalog_name.toUtf8().data(), job_defs.enabled);
317 #endif
318
319    notify(true);
320    return true;
321
322 bail_out:
323    notify(true);
324    return false;
325 }
326
327
328 /*
329  * Save user settings associated with this console
330  */
331 void Console::writeSettings()
332 {
333    QFont font = get_font();
334
335    QSettings settings(m_dir->name(), "bat");
336    settings.beginGroup("Console");
337    settings.setValue("consoleFont", font.family());
338    settings.setValue("consolePointSize", font.pointSize());
339    settings.setValue("consoleFixedPitch", font.fixedPitch());
340    settings.endGroup();
341 }
342
343 /*
344  * Read and restore user settings associated with this console
345  */
346 void Console::readSettings()
347
348    QFont font = get_font();
349
350    QSettings settings(m_dir->name(), "bat");
351    settings.beginGroup("Console");
352    font.setFamily(settings.value("consoleFont", "Courier").value<QString>());
353    font.setPointSize(settings.value("consolePointSize", 10).toInt());
354    font.setFixedPitch(settings.value("consoleFixedPitch", true).toBool());
355    settings.endGroup();
356    m_textEdit->setFont(font);
357 }
358
359 /*
360  * Set the console textEdit font
361  */
362 void Console::set_font()
363 {
364    bool ok;
365    QFont font = QFontDialog::getFont(&ok, QFont(m_textEdit->font()), this);
366    if (ok) {
367       m_textEdit->setFont(font);
368    }
369 }
370
371 /*
372  * Get the console text edit font
373  */
374 const QFont Console::get_font()
375 {
376    return m_textEdit->font();
377 }
378
379 /*
380  * Slot for responding to status dir button on button bar
381  */
382 void Console::status_dir()
383 {
384    QString cmd("status dir");
385    consoleCommand(cmd);
386 }
387
388 /*
389  * Slot for responding to messages button on button bar
390  */
391 void Console::messages()
392 {
393    QString cmd(".messages");
394    consoleCommand(cmd);
395 }
396
397 /*
398  * Put text into the console window
399  */
400 void Console::display_textf(const char *fmt, ...)
401 {
402    va_list arg_ptr;
403    char buf[1000];
404    int len;
405    va_start(arg_ptr, fmt);
406    len = bvsnprintf(buf, sizeof(buf), fmt, arg_ptr);
407    va_end(arg_ptr);
408    display_text(buf);
409 }
410
411 void Console::display_text(const QString buf)
412 {
413    m_cursor->movePosition(QTextCursor::End);
414    m_cursor->insertText(buf);
415 }
416
417
418 void Console::display_text(const char *buf)
419 {
420    m_cursor->movePosition(QTextCursor::End);
421    m_cursor->insertText(buf);
422 }
423
424 void Console::display_html(const QString buf)
425 {
426    m_cursor->movePosition(QTextCursor::End);
427    m_cursor->insertHtml(buf);
428 }
429
430 /* Position cursor to end of screen */
431 void Console::update_cursor()
432 {
433    QApplication::restoreOverrideCursor();
434    m_textEdit->moveCursor(QTextCursor::End);
435    m_textEdit->ensureCursorVisible();
436 }
437
438 /* 
439  * This should be moved into a bSocket class 
440  */
441 char *Console::msg()
442 {
443    if (m_sock) {
444       return m_sock->msg;
445    }
446    return NULL;
447 }
448
449 /* Send a command to the Director */
450 void Console::write_dir(const char *msg)
451 {
452    if (m_sock) {
453       mainWin->set_status(_("Processing command ..."));
454       QApplication::setOverrideCursor(Qt::WaitCursor);
455       write(msg);
456    } else {
457       mainWin->set_status(" Director not connected. Click on connect button.");
458       mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
459       QBrush redBrush(Qt::red);
460       QTreeWidgetItem *item = mainWin->getFromHash(this);
461       item->setForeground(0, redBrush);
462       m_at_prompt = false;
463       m_at_main_prompt = false;
464    }
465 }
466
467 int Console::write(const QString msg)
468 {
469    return write(msg.toUtf8().data());
470 }
471
472 int Console::write(const char *msg)
473 {
474    m_sock->msglen = pm_strcpy(m_sock->msg, msg);
475    m_at_prompt = false;
476    m_at_main_prompt = false;
477    if (mainWin->m_commDebug) Pmsg1(000, "send: %s\n", msg);
478    return m_sock->send();
479 }
480
481 /*
482  * Get to main command prompt -- i.e. abort any subcommand
483  */
484 void Console::beginNewCommand()
485 {
486    for (int i=0; i < 3; i++) {
487       write(".\n");
488       while (read() > 0) {
489          if (mainWin->m_displayAll) display_text(msg());
490       }
491       if (m_at_main_prompt) {
492          break;
493       }
494    }
495    display_text("\n");
496 }
497
498 void Console::displayToPrompt()
499
500    int stat = 0;
501    if (mainWin->m_commDebug) Pmsg0(000, "DisplaytoPrompt\n");
502    while (!m_at_prompt) {
503       if ((stat=read()) > 0) {
504          display_text(msg());
505       }
506    }
507    if (mainWin->m_commDebug) Pmsg1(000, "endDisplaytoPrompt=%d\n", stat);
508 }
509
510 void Console::discardToPrompt()
511
512    int stat = 0;
513    if (mainWin->m_commDebug) Pmsg0(000, "discardToPrompt\n");
514    while (!m_at_prompt) {
515       if ((stat=read()) > 0) {
516          if (mainWin->m_displayAll) display_text(msg());
517       }
518    }
519    if (mainWin->m_commDebug) Pmsg1(000, "endDisplayToPrompt=%d\n", stat);
520 }
521
522
523 /* 
524  * Blocking read from director
525  */
526 int Console::read()
527 {
528    int stat = 0;
529    while (m_sock) {
530       for (;;) {
531          stat = bnet_wait_data_intr(m_sock, 1);
532          if (stat > 0) {
533             break;
534          } 
535          app->processEvents();
536          if (m_api_set && m_messages_pending) {
537             write_dir(".messages");
538             m_messages_pending = false;
539          }
540       }
541       stat = m_sock->recv();
542       if (stat >= 0) {
543          if (m_at_prompt) {
544             display_text("\n");
545             m_at_prompt = false;
546             m_at_main_prompt = false;
547          }
548          if (mainWin->m_commDebug) Pmsg1(000, "got: %s", m_sock->msg);
549       }
550       switch (m_sock->msglen) {
551       case BNET_MSGS_PENDING:
552          if (mainWin->m_commDebug) Pmsg0(000, "MSGS PENDING\n");
553          write_dir(".messages");
554          displayToPrompt();
555          m_messages_pending = false;
556          continue;
557       case BNET_CMD_OK:
558          if (mainWin->m_commDebug) Pmsg0(000, "CMD OK\n");
559          m_at_prompt = false;
560          m_at_main_prompt = false;
561          continue;
562       case BNET_CMD_BEGIN:
563          if (mainWin->m_commDebug) Pmsg0(000, "CMD BEGIN\n");
564          m_at_prompt = false;
565          m_at_main_prompt = false;
566          continue;
567       case BNET_MAIN_PROMPT:
568          if (mainWin->m_commDebug) Pmsg0(000, "MAIN PROMPT\n");
569          m_at_prompt = true;
570          m_at_main_prompt = true;
571          mainWin->set_status(_("At prompt waiting for input ..."));
572          update_cursor();
573          QApplication::restoreOverrideCursor();
574          break;
575       case BNET_PROMPT:
576          if (mainWin->m_commDebug) Pmsg0(000, "PROMPT\n");
577          m_at_prompt = true;
578          m_at_main_prompt = false;
579          mainWin->set_status(_("At prompt waiting for input ..."));
580          update_cursor();
581          QApplication::restoreOverrideCursor();
582          break;
583       case BNET_CMD_FAILED:
584          if (mainWin->m_commDebug) Pmsg0(000, "CMD FAILED\n");
585          mainWin->set_status(_("Command failed. At prompt waiting for input ..."));
586          update_cursor();
587          QApplication::restoreOverrideCursor();
588          break;
589       /* We should not get this one */
590       case BNET_EOD:
591          if (mainWin->m_commDebug) Pmsg0(000, "EOD\n");
592          mainWin->set_status_ready();
593          update_cursor();
594          QApplication::restoreOverrideCursor();
595          if (!m_api_set) {
596             break;
597          }
598          continue;
599       case BNET_START_SELECT:
600          if (mainWin->m_commDebug) Pmsg0(000, "START SELECT\n");
601          new selectDialog(this);    
602          break;
603       case BNET_RUN_CMD:
604          if (mainWin->m_commDebug) Pmsg0(000, "RUN CMD\n");
605          new runCmdPage();
606          break;
607       case BNET_ERROR_MSG:
608          if (mainWin->m_commDebug) Pmsg0(000, "ERROR MSG\n");
609          m_sock->recv();              /* get the message */
610          display_text(msg());
611          QMessageBox::critical(this, "Error", msg(), QMessageBox::Ok);
612          break;
613       case BNET_WARNING_MSG:
614          if (mainWin->m_commDebug) Pmsg0(000, "WARNING MSG\n");
615          m_sock->recv();              /* get the message */
616          display_text(msg());
617          QMessageBox::critical(this, "Warning", msg(), QMessageBox::Ok);
618          break;
619       case BNET_INFO_MSG:
620          if (mainWin->m_commDebug) Pmsg0(000, "INFO MSG\n");
621          m_sock->recv();              /* get the message */
622          display_text(msg());
623          mainWin->set_status(msg());
624          break;
625       }
626       if (is_bnet_stop(m_sock)) {         /* error or term request */
627          if (mainWin->m_commDebug) Pmsg0(000, "BNET STOP\n");
628          m_sock->close();
629          m_sock = NULL;
630          mainWin->actionConnect->setIcon(QIcon(":images/disconnected.png"));
631          QBrush redBrush(Qt::red);
632          QTreeWidgetItem *item = mainWin->getFromHash(this);
633          item->setForeground(0, redBrush);
634          m_notifier->setEnabled(false);
635          delete m_notifier;
636          m_notifier = NULL;
637          mainWin->set_status(_("Director disconnected."));
638          QApplication::restoreOverrideCursor();
639          stat = BNET_HARDEOF;
640       }
641       break;
642    } 
643    return stat;
644 }
645
646 /* Called by signal when the Director has output for us */
647 void Console::read_dir(int /* fd */)
648 {
649    if (mainWin->m_commDebug) Pmsg0(000, "read_dir\n");
650    while (read() >= 0) {
651       display_text(msg());
652    }
653 }
654
655 /*
656  * When the notifier is enabled, read_dir() will automatically be
657  * called by the Qt event loop when ever there is any output 
658  * from the Directory, and read_dir() will then display it on
659  * the console.
660  *
661  * When we are in a bat dialog, we want to control *all* output
662  * from the Directory, so we set notify to off.
663  *    m_console->notifiy(false);
664  */
665 void Console::notify(bool enable) 
666
667    m_notifier->setEnabled(enable);   
668 }
669
670 void Console::setDirectorTreeItem(QTreeWidgetItem *item)
671 {
672    m_directorTreeItem = item;
673 }
674
675 void Console::setDirRes(DIRRES *dir) 
676
677    m_dir = dir;
678 }
679
680 /*
681  * To have the ability to get the name of the director resource.
682  */
683 void Console::getDirResName(QString &name_returned)
684 {
685    name_returned = m_dir->name();
686 }
687
688 bool Console::is_connectedGui()
689 {
690    if (is_connected()) {
691       return true;
692    } else {
693       QString message("Director ");
694       message += " is curerntly disconnected\n  Please reconnect!!";
695       QMessageBox::warning(this, tr("Bat"),
696          tr(message.toUtf8().data()), QMessageBox::Ok );
697       return false;
698    }
699 }
700
701 /*
702  * A temporary function to prevent connecting to the director if the director
703  * is busy with a restore.
704  */
705 bool Console::preventInUseConnect()
706 {
707    if (!is_connected()) {
708       QString message("Director ");
709       message += m_dir->name();
710       message += " is curerntly disconnected\n  Please reconnect!!";
711       QMessageBox::warning(this, tr("Bat"),
712          tr(message.toUtf8().data()), QMessageBox::Ok );
713       return false;
714    } else if (!m_at_main_prompt){
715       QString message("Director ");
716       message += m_dir->name();
717       message += " is curerntly busy\n  Please complete restore or other "
718 " operation !!  This is a limitation that will be resolved before a beta"
719 " release.  This is currently an alpa release.";
720       QMessageBox::warning(this, tr("Bat"),
721          tr(message.toUtf8().data()), QMessageBox::Ok );
722       return false;
723    } else if (!m_at_prompt){
724       QString message("Director ");
725       message += m_dir->name();
726       message += " is curerntly not at a prompt\n  Please try again!!";
727       QMessageBox::warning(this, tr("Bat"),
728          tr(message.toUtf8().data()), QMessageBox::Ok );
729       return false;
730    } else {
731       return true;
732    }
733 }