]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/qt-console/joblist/joblist.cpp
Add Fileset column. This is the commit that adds "LEFT OUTER JOIN" sql syntax.
[bacula/bacula] / bacula / src / qt-console / joblist / joblist.cpp
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2000-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: joblist.h 4230 2007-02-21 20:07:37Z kerns $
30  *
31  *   Dirk Bartley, March 2007
32  */
33  
34 #include <QAbstractEventDispatcher>
35 #include <QTableWidgetItem>
36 #include "bat.h"
37 #include "joblist.h"
38 #include "restore.h"
39 #include "joblog/joblog.h"
40
41 /*
42  * Constructor for the class
43  */
44 JobList::JobList(QString &mediaName, QString &clientname,
45          QTreeWidgetItem *parentTreeWidgetItem)
46 {
47    setupUi(this);
48    m_name = "Clients";
49    m_mediaName = mediaName;
50    m_clientName = clientname;
51    pgInitialize(parentTreeWidgetItem);
52    m_resultCount = 0;
53    m_populated = false;
54    m_closeable = false;
55    if ((m_mediaName != "") || (m_clientName != "")) { m_closeable=true; }
56    m_checkCurrentWidget = true;
57    createConnections();
58
59    /* Set Defaults for check and spin for limits */
60    limitCheckBox->setCheckState(Qt::Checked);
61    limitSpinBox->setValue(150);
62    daysCheckBox->setCheckState(Qt::Unchecked);
63    daysSpinBox->setValue(30);
64 }
65
66 /*
67  * The Meat of the class.
68  * This function will populate the QTableWidget, mp_tablewidget, with
69  * QTableWidgetItems representing the results of a query for what jobs exist on
70  * the media name passed from the constructor stored in m_mediaName.
71  */
72 void JobList::populateTable()
73 {
74    QStringList results;
75    QString resultline;
76    QBrush blackBrush(Qt::black);
77
78    if (!m_console->preventInUseConnect())
79        return;
80
81    /* Can't do this in constructor because not neccesarily conected in constructor */
82    if (!m_populated) {
83       clientsComboBox->addItem("Any");
84       clientsComboBox->addItems(m_console->client_list);
85       int clientIndex = clientsComboBox->findText(m_clientName, Qt::MatchExactly);
86       if (clientIndex != -1)
87          clientsComboBox->setCurrentIndex(clientIndex);
88
89       /* Not m_console->volume_list will query database */
90       QString query("SELECT VolumeName AS Media FROM Media ORDER BY Media");
91       QStringList results, volumeList;
92       if (m_console->sql_cmd(query, results)) {
93          QString field;
94          QStringList fieldlist;
95          /* Iterate through the lines of results. */
96          foreach (QString resultline, results) {
97             fieldlist = resultline.split("\t");
98             volumeList.append(fieldlist[0]);
99          } /* foreach resultline */
100       } /* if results from query */
101       volumeComboBox->addItem("Any");
102       volumeComboBox->addItems(volumeList);
103       int volumeIndex = volumeComboBox->findText(m_mediaName, Qt::MatchExactly);
104       if (volumeIndex != -1) {
105          volumeComboBox->setCurrentIndex(volumeIndex);
106       }
107       jobComboBox->addItem("Any");
108       jobComboBox->addItems(m_console->job_list);
109       levelComboBox->addItem("Any");
110       levelComboBox->addItems( QStringList() << "F" << "D" << "I");
111       purgedComboBox->addItem("Any");
112       purgedComboBox->addItems( QStringList() << "0" << "1");
113       statusComboBox->addItem("Any");
114       fileSetComboBox->addItem("Any");
115       fileSetComboBox->addItems(m_console->fileset_list);
116       QString statusQuery("SELECT JobStatusLong FROM Status");
117       QStringList statusResults, statusLongList;
118       if (m_console->sql_cmd(statusQuery, statusResults)) {
119          QString field;
120          QStringList fieldlist;
121          /* Iterate through the lines of results. */
122          foreach (QString resultline, statusResults) {
123             fieldlist = resultline.split("\t");
124             statusLongList.append(fieldlist[0]);
125          } /* foreach resultline */
126       } /* if results from statusquery */
127       statusComboBox->addItems(statusLongList);
128    }
129
130    /* Set up query */
131    QString query("");
132    int volumeIndex = volumeComboBox->currentIndex();
133    if (volumeIndex != -1)
134       m_mediaName = volumeComboBox->itemText(volumeIndex);
135    query += "SELECT DISTINCT Job.Jobid AS Id, Job.Name AS JobName, Client.Name AS Client,"
136             " Job.Starttime AS JobStart, Job.Type AS JobType,"
137             " Job.Level AS BackupLevel, Job.Jobfiles AS FileCount,"
138             " Job.JobBytes AS Bytes,"
139             " Job.JobStatus AS Status, Status.JobStatusLong AS StatusLong,"
140             " Job.PurgedFiles AS Purged, FileSet.FileSet"
141             " FROM Job"
142             " LEFT OUTER JOIN Client ON (Client.ClientId=Job.ClientId)"
143             " LEFT OUTER JOIN FileSet ON (FileSet.FileSetId=Job.FileSetId)"
144             " LEFT OUTER JOIN Status ON (Job.JobStatus=Status.JobStatus)"
145             " LEFT OUTER JOIN JobMedia ON (JobMedia.JobId=Job.JobId)"
146             " LEFT OUTER JOIN Media ON (JobMedia.MediaId=Media.MediaId)";
147    QStringList conditions;
148    if (m_mediaName != "Any") {
149       conditions.append("Media.VolumeName='" + m_mediaName + "'");
150    }
151    int clientIndex = clientsComboBox->currentIndex();
152    if (clientIndex != -1)
153       m_clientName = clientsComboBox->itemText(clientIndex);
154    if (m_clientName != "Any") {
155       conditions.append("Client.Name='" + m_clientName + "'");
156    }
157    int jobIndex = jobComboBox->currentIndex();
158    if ((jobIndex != -1) && (jobComboBox->itemText(jobIndex) != "Any")) {
159       conditions.append("Job.Name='" + jobComboBox->itemText(jobIndex) + "'");
160    }
161    int levelIndex = levelComboBox->currentIndex();
162    if ((levelIndex != -1) && (levelComboBox->itemText(levelIndex) != "Any")) {
163       conditions.append("Job.Level='" + levelComboBox->itemText(levelIndex) + "'");
164    }
165    int statusIndex = statusComboBox->currentIndex();
166    if ((statusIndex != -1) && (statusComboBox->itemText(statusIndex) != "Any")) {
167       conditions.append("Status.JobStatusLong='" + statusComboBox->itemText(statusIndex) + "'");
168    }
169    int purgedIndex = purgedComboBox->currentIndex();
170    if ((purgedIndex != -1) && (purgedComboBox->itemText(purgedIndex) != "Any")) {
171       conditions.append("Job.PurgedFiles='" + purgedComboBox->itemText(purgedIndex) + "'");
172    }
173    int fileSetIndex = fileSetComboBox->currentIndex();
174    if ((fileSetIndex != -1) && (fileSetComboBox->itemText(fileSetIndex) != "Any")) {
175       conditions.append("FileSet.FileSet='" + fileSetComboBox->itemText(fileSetIndex) + "'");
176    }
177    /* If Limit check box For limit by days is checked  */
178    if (daysCheckBox->checkState() == Qt::Checked) {
179       QDateTime stamp = QDateTime::currentDateTime().addDays(-daysSpinBox->value());
180       QString since = stamp.toString(Qt::ISODate);
181       conditions.append("Job.Starttime>'" + since + "'");
182    }
183    bool first = true;
184    foreach (QString condition, conditions) {
185       if (first) {
186          query += " WHERE " + condition;
187          first = false;
188       } else {
189          query += " AND " + condition;
190       }
191    }
192    /* Descending */
193    query += " ORDER BY Job.Starttime DESC, Job.JobId DESC";
194    /* If Limit check box for limit records returned is checked  */
195    if (limitCheckBox->checkState() == Qt::Checked) {
196       QString limit;
197       limit.setNum(limitSpinBox->value());
198       query += " LIMIT " + limit;
199    }
200
201    /* Set up the Header for the table */
202    QStringList headerlist = (QStringList()
203       << "Job Id" << "Job Name" << "Client" << "Job Starttime" << "Job Type" 
204       << "Job Level" << "Job Files" << "Job Bytes" << "Job Status"  << "Purged" << "File Set" );
205    m_purgedIndex = headerlist.indexOf("Purged");
206    m_typeIndex = headerlist.indexOf("Job Type");
207    statusIndex = headerlist.indexOf("Job Status");
208
209    /* Initialize the QTableWidget */
210    m_checkCurrentWidget = false;
211    mp_tableWidget->clear();
212    m_checkCurrentWidget = true;
213    mp_tableWidget->setColumnCount(headerlist.size());
214    mp_tableWidget->setHorizontalHeaderLabels(headerlist);
215
216    /*  This could be a user preference debug message?? */
217    printf("Query cmd : %s\n",query.toUtf8().data());
218    if (m_console->sql_cmd(query, results)) {
219       m_resultCount = results.count();
220
221       QTableWidgetItem* p_tableitem;
222       QString field;
223       QStringList fieldlist;
224       mp_tableWidget->setRowCount(results.size());
225
226       int row = 0;
227       /* Iterate through the record returned from the query */
228       foreach (resultline, results) {
229          fieldlist = resultline.split("\t");
230          int column = 0;
231          bool statusIndexDone = false;
232          QString statusCode("");
233          /* Iterate through fields in the record */
234          foreach (field, fieldlist) {
235             field = field.trimmed();  /* strip leading & trailing spaces */
236             if ((column == statusIndex) && (!statusIndexDone)){
237                statusIndexDone = true;
238                statusCode = field;
239             } else {
240                p_tableitem = new QTableWidgetItem(field,1);
241                p_tableitem->setFlags(0);
242                p_tableitem->setForeground(blackBrush);
243                mp_tableWidget->setItem(row, column, p_tableitem);
244                if (column == statusIndex)
245                   setStatusColor(p_tableitem, statusCode);
246                column++;
247             }
248          }
249          row++;
250       }
251    } 
252    /* Resize the columns */
253    mp_tableWidget->resizeColumnsToContents();
254    mp_tableWidget->resizeRowsToContents();
255    mp_tableWidget->verticalHeader()->hide();
256    if ((m_mediaName != "Any") && (m_resultCount == 0)){
257       /* for context sensitive searches, let the user know if there were no
258        * results */
259       QMessageBox::warning(this, tr("Bat"),
260           tr("The Jobs query returned no results.\n"
261          "Press OK to continue?"), QMessageBox::Ok );
262    }
263 }
264
265 void JobList::setStatusColor(QTableWidgetItem *item, QString &field)
266 {
267    QString greenchars("TCR");
268    QString redchars("BEf");
269    QString yellowchars("eDAFSMmsjdctp");
270    if (greenchars.contains(field, Qt::CaseSensitive)) {
271       item->setBackground(Qt::green);
272    } else if (redchars.contains(field, Qt::CaseSensitive)) {
273       item->setBackground(Qt::red);
274    } else if (yellowchars.contains(field, Qt::CaseSensitive)){ 
275       item->setBackground(Qt::yellow);
276    }
277 }
278
279 /*
280  * When the treeWidgetItem in the page selector tree is singleclicked, Make sure
281  * The tree has been populated.
282  */
283 void JobList::PgSeltreeWidgetClicked()
284 {
285    if (!m_populated) {
286       populateTable();
287       m_populated=true;
288    }
289 }
290
291 /*
292  *  Virtual function override of pages function which is called when this page
293  *  is visible on the stack
294  */
295 void JobList::currentStackItem()
296 {
297    populateTable();
298    if (!m_populated) {
299       m_contextActions.append(actionRefreshJobList);
300       m_populated=true;
301    }
302 }
303
304 /*
305  * Virtual Function to return the name for the medialist tree widget
306  */
307 void JobList::treeWidgetName(QString &desc)
308 {
309    if ((m_mediaName == "") && (m_clientName == "")) {
310       desc = "Jobs";
311    } else {
312       desc = "Jobs ";
313       if (m_mediaName != "" ) {
314          desc += "on Volume " + m_mediaName;
315       }
316       if (m_clientName != "" ) {
317          desc += "of Client " + m_clientName;
318       }
319    }
320 }
321
322 /*
323  * This functions much line tableItemChanged for trees like the page selector,
324  * but I will do much less here
325  */
326 void JobList::tableItemChanged(QTableWidgetItem *currentItem, QTableWidgetItem * /*previousItem*/)
327 {
328    if (m_checkCurrentWidget) {
329       int row = currentItem->row();
330       QTableWidgetItem* jobitem = mp_tableWidget->item(row, 0);
331       m_currentJob = jobitem->text();
332       jobitem = mp_tableWidget->item(row, m_purgedIndex);
333       QString purged = jobitem->text();
334       mp_tableWidget->removeAction(actionPurgeFiles);
335       if (purged == "0") {
336          mp_tableWidget->addAction(actionPurgeFiles);
337       }
338       jobitem = mp_tableWidget->item(row, m_typeIndex);
339       QString status = jobitem->text();
340       mp_tableWidget->removeAction(actionRestoreFromJob);
341       mp_tableWidget->removeAction(actionRestoreFromTime);
342       if (status == "B") {
343          mp_tableWidget->addAction(actionRestoreFromJob);
344          mp_tableWidget->addAction(actionRestoreFromTime);
345       }
346    }
347 }
348
349 /*
350  * Function to create connections for context sensitive menu for this and
351  * the page selector
352  */
353 void JobList::createConnections()
354 {
355    /* connect to the action specific to this pages class that shows up in the 
356     * page selector tree */
357    connect(actionRefreshJobList, SIGNAL(triggered()), this,
358                 SLOT(populateTable()));
359    connect(refreshButton, SIGNAL(pressed()), this, SLOT(populateTable()));
360    /* for the tableItemChanged to maintain m_currentJob */
361    connect(mp_tableWidget, SIGNAL(
362            currentItemChanged(QTableWidgetItem *, QTableWidgetItem *)),
363            this, SLOT(tableItemChanged(QTableWidgetItem *, QTableWidgetItem *)));
364
365    /* Do what is required for the local context sensitive menu */
366
367
368    /* setContextMenuPolicy is required */
369    mp_tableWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
370
371    /* Add Actions */
372    mp_tableWidget->addAction(actionRefreshJobList);
373    mp_tableWidget->addAction(actionLongListJob);
374    mp_tableWidget->addAction(actionListJobid);
375    mp_tableWidget->addAction(actionListFilesOnJob);
376    mp_tableWidget->addAction(actionListJobMedia);
377    mp_tableWidget->addAction(actionListVolumes);
378    mp_tableWidget->addAction(actionDeleteJob);
379    mp_tableWidget->addAction(actionPurgeFiles);
380    mp_tableWidget->addAction(actionRestoreFromJob);
381    mp_tableWidget->addAction(actionRestoreFromTime);
382    mp_tableWidget->addAction(actionShowLogForJob);
383
384    /* Make Connections */
385    connect(actionLongListJob, SIGNAL(triggered()), this,
386                 SLOT(consoleLongListJob()));
387    connect(actionListJobid, SIGNAL(triggered()), this,
388                 SLOT(consoleListJobid()));
389    connect(actionListFilesOnJob, SIGNAL(triggered()), this,
390                 SLOT(consoleListFilesOnJob()));
391    connect(actionListJobMedia, SIGNAL(triggered()), this,
392                 SLOT(consoleListJobMedia()));
393    connect(actionListVolumes, SIGNAL(triggered()), this,
394                 SLOT(consoleListVolumes()));
395    connect(actionDeleteJob, SIGNAL(triggered()), this,
396                 SLOT(consoleDeleteJob()));
397    connect(actionPurgeFiles, SIGNAL(triggered()), this,
398                 SLOT(consolePurgeFiles()));
399    connect(actionRestoreFromJob, SIGNAL(triggered()), this,
400                 SLOT(preRestoreFromJob()));
401    connect(actionRestoreFromTime, SIGNAL(triggered()), this,
402                 SLOT(preRestoreFromTime()));
403    connect(actionShowLogForJob, SIGNAL(triggered()), this,
404                 SLOT(showLogForJob()));
405 }
406
407 /*
408  * Functions to respond to local context sensitive menu sending console commands
409  * If I could figure out how to make these one function passing a string, Yaaaaaa
410  */
411 void JobList::consoleLongListJob()
412 {
413    QString cmd("llist jobid=");
414    cmd += m_currentJob;
415    consoleCommand(cmd);
416 }
417 void JobList::consoleListJobid()
418 {
419    QString cmd("list jobid=");
420    cmd += m_currentJob;
421    consoleCommand(cmd);
422 }
423 void JobList::consoleListFilesOnJob()
424 {
425    QString cmd("list files jobid=");
426    cmd += m_currentJob;
427    consoleCommand(cmd);
428 }
429 void JobList::consoleListJobMedia()
430 {
431    QString cmd("list jobmedia jobid=");
432    cmd += m_currentJob;
433    consoleCommand(cmd);
434 }
435 void JobList::consoleListVolumes()
436 {
437    QString cmd("list volumes jobid=");
438    cmd += m_currentJob;
439    consoleCommand(cmd);
440 }
441 void JobList::consoleDeleteJob()
442 {
443    if (QMessageBox::warning(this, tr("Bat"),
444       tr("Are you sure you want to delete??  !!!.\n"
445 "This delete command is used to delete a Job record and all associated catalog"
446 " records that were created. This command operates only on the Catalog"
447 " database and has no effect on the actual data written to a Volume. This"
448 " command can be dangerous and we strongly recommend that you do not use"
449 " it unless you know what you are doing.  The Job and all its associated"
450 " records (File and JobMedia) will be deleted from the catalog."
451       "Press OK to proceed with delete operation.?"),
452       QMessageBox::Ok | QMessageBox::Cancel)
453       == QMessageBox::Cancel) { return; }
454
455    QString cmd("delete job jobid=");
456    cmd += m_currentJob;
457    consoleCommand(cmd);
458 }
459 void JobList::consolePurgeFiles()
460 {
461    if (QMessageBox::warning(this, tr("Bat"),
462       tr("Are you sure you want to purge ??  !!!.\n"
463 "The Purge command will delete associated Catalog database records from Jobs and"
464 " Volumes without considering the retention period. Purge  works only on the"
465 " Catalog database and does not affect data written to Volumes. This command can"
466 " be dangerous because you can delete catalog records associated with current"
467 " backups of files, and we recommend that you do not use it unless you know what"
468 " you are doing.\n"
469       "Press OK to proceed with the purge operation?"),
470       QMessageBox::Ok | QMessageBox::Cancel)
471       == QMessageBox::Cancel) { return; }
472
473    QString cmd("purge files jobid=");
474    cmd += m_currentJob;
475    consoleCommand(cmd);
476 }
477
478 /*
479  * Subroutine to call preRestore to restore from a select job
480  */
481 void JobList::preRestoreFromJob()
482 {
483    new prerestorePage(m_currentJob, R_JOBIDLIST);
484 }
485
486 /*
487  * Subroutine to call preRestore to restore from a select job
488  */
489 void JobList::preRestoreFromTime()
490 {
491    new prerestorePage(m_currentJob, R_JOBDATETIME);
492 }
493
494 /*
495  * Subroutine to call class to show the log in the database from that job
496  */
497 void JobList::showLogForJob()
498 {
499    QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
500    new JobLog(m_currentJob, pageSelectorTreeWidgetItem);
501 }