2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2016 Kern Sibbald
6 The original author of Bacula is Kern Sibbald, with contributions
7 from many others, a complete list can be found in the file AUTHORS.
9 You may use this file and others of this release according to the
10 license defined in the LICENSE file, which includes the Affero General
11 Public License, v3.0 ("AGPLv3") and some additional permissions and
12 terms pursuant to its AGPLv3 Section 7.
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
17 Bacula(R) is a registered trademark of Kern Sibbald.
22 * bRestore Class (Eric's brestore)
24 * Kern Sibbald, January MMVII
30 #include "util/fmtwidgetitem.h"
32 bRestore::bRestore() : Pages()
34 m_name = tr("bRestore");
38 QTreeWidgetItem* thisitem = mainWin->getFromHash(this);
39 thisitem->setIcon(0, QIcon(QString::fromUtf8(":images/browse.png")));
43 RestoreList->setAcceptDrops(true);
46 // Populate client table and job associated
47 void bRestore::setClient()
49 // Select the same client, don't touch
50 if (m_client == ClientList->currentText()) {
53 m_client = ClientList->currentText();
54 FileList->clearContents();
55 FileRevisions->clearContents();
57 JobList->setEnabled(true);
58 LocationEntry->clear();
62 if (ClientList->currentIndex() < 1) {
63 JobList->setEnabled(false);
67 JobList->addItem("Job list for " + m_client);
70 "SELECT Job.Jobid AS JobId, Job.StartTime AS StartTime,"
71 " Job.Level AS Level,"
73 " FROM Job JOIN Client USING (ClientId)"
75 " Job.JobStatus IN ('T','W') AND Job.Type='B' AND"
76 " Client.Name='" + m_client + "' ORDER BY StartTime DESC" ;
80 QStringList fieldlist;
81 if (m_console->sql_cmd(jobQuery, results)) {
82 /* Iterate through the record returned from the query */
83 foreach (QString resultline, results) {
85 // JobId, StartTime, Level, Name
86 fieldlist = resultline.split("\t");
87 job = fieldlist[1] + " " + fieldlist[3] + "(" + fieldlist[2] + ") " + fieldlist[0];
88 JobList->addItem(job, QVariant(fieldlist[0])); // set also private value
93 // Compute job associated and update the job cache if needed
94 void bRestore::setJob()
96 if (JobList->currentIndex() < 1) {
97 FileList->clearContents();
98 FileList->setRowCount(0);
99 FileRevisions->clearContents();
100 FileRevisions->setRowCount(0);
104 QVariant tmp = JobList->itemData(JobList->currentIndex(), Qt::UserRole);
106 m_jobids = tmp.toString();
107 QString cmd = ".bvfs_get_jobids jobid=" + m_jobids;
108 if (MergeChk->checkState() == Qt::Checked) {
112 m_console->dir_cmd(cmd, results);
114 if (results.size() < 1) {
115 FileList->clearContents();
116 FileList->setRowCount(0);
117 FileRevisions->clearContents();
118 FileRevisions->setRowCount(0);
122 // TODO: Can take some time if the job contains many dirs
123 m_jobids = results.at(0);
124 cmd = ".bvfs_update jobid=" + m_jobids;
125 m_console->dir_cmd(cmd, results);
127 Pmsg1(0, "jobids=%s\n", m_jobids.toLocal8Bit().constData());
129 displayFiles(m_pathid, QString(""));
130 Pmsg0(000, "update done\n");
133 extern int decode_stat(char *buf, struct stat *statp, int stat_size, int32_t *LinkFI);
135 // refresh button with a filter or limit/offset change
136 void bRestore::refreshView()
138 displayFiles(m_pathid, m_path);
141 void bRestore::displayFiles(int64_t pathid, QString path)
145 QStringList fieldlist;
150 Freeze frz_lst(*FileList); /* disable updating*/
151 Freeze frz_rev(*FileRevisions); /* disable updating*/
152 FileList->clearContents();
153 FileRevisions->clearContents();
154 FileRevisions->setRowCount(0);
156 // If we provide pathid, use it (path can be altered by encoding conversion)
158 arg = " pathid=" + QString().setNum(pathid);
160 // Choose .. update current path to parent dir
165 m_path.remove(QRegExp("[^/]+/$"));
168 } else if (path == "/" && m_path == "") {
171 } else if (path != "/" && path != ".") {
176 arg = " path=\"" + m_path + "\"";
179 // If a filter is set, add it to the current query
180 if (FilterEntry->text() != "") {
181 QString tmp = FilterEntry->text();
182 tmp.replace("\"", "."); // basic escape of "
183 arg += " pattern=\"" + tmp + "\"";
186 LocationEntry->setText(m_path);
187 QString offset = QString().setNum(Offset1Spin->value());
188 QString limit=QString().setNum(Offset2Spin->value() - Offset1Spin->value());
189 QString q = ".bvfs_lsdir jobid=" + m_jobids + arg
190 + " limit=" + limit + " offset=" + offset ;
191 if (mainWin->m_miscDebug) qDebug() << q;
192 if (m_console->dir_cmd(q, results)) {
194 FileList->setRowCount(nb);
195 foreach (QString resultline, results) {
197 //PathId, FilenameId, fileid, jobid, lstat, path
198 fieldlist = resultline.split("\t");
200 * Note, the next line zaps variable "item", probably
201 * because the input data in fieldlist is bad.
203 decode_stat(fieldlist.at(4).toLocal8Bit().data(), &statp, sizeof(statp), &LinkFI);
204 TableItemFormatter item(*FileList, row++);
205 item.setFileType(col++, QString("folder")); // folder or file
206 item.setTextFld(col++, fieldlist.at(5)); // path
207 item.setBytesFld(col++, QString().setNum(statp.st_size));
208 item.setDateFld(col++, statp.st_mtime); // date
209 fieldlist.replace(3, m_jobids); // use current jobids selection
210 // keep original info on the first cel that is never empty
211 item.widget(1)->setData(Qt::UserRole, fieldlist.join("\t"));
216 q = ".bvfs_lsfiles jobid=" + m_jobids + arg
217 + " limit=" + limit + " offset=" + offset ;
218 if (m_console->dir_cmd(q, results)) {
219 FileList->setRowCount(results.size() + nb);
220 foreach (QString resultline, results) {
221 int col=1; // skip icon
222 //PathId, FilenameId, fileid, jobid, lstat, name
223 fieldlist = resultline.split("\t");
224 TableItemFormatter item(*FileList, row++);
225 item.setTextFld(col++, fieldlist.at(5)); // name
226 decode_stat(fieldlist.at(4).toLocal8Bit().data(),
227 &statp, sizeof(statp), &LinkFI);
228 item.setBytesFld(col++, QString().setNum(statp.st_size));
229 item.setDateFld(col++, statp.st_mtime);
230 // keep original info on the first cel that is never empty
231 item.widget(1)->setData(Qt::UserRole, fieldlist.join("\t")); // keep info
234 FileList->verticalHeader()->hide();
235 FileList->resizeColumnsToContents();
236 FileList->resizeRowsToContents();
237 FileList->setEditTriggers(QAbstractItemView::NoEditTriggers);
240 void bRestore::PgSeltreeWidgetClicked()
245 if (!isOnceDocked()) {
250 // Display all versions of a file for this client
251 void bRestore::displayFileVersion(QString pathid, QString fnid,
252 QString client, QString filename)
257 Freeze frz_rev(*FileRevisions); /* disable updating*/
258 FileRevisions->clearContents();
260 QString q = ".bvfs_versions jobid=" + m_jobids +
261 " pathid=" + pathid +
265 if (VersionsChk->checkState() == Qt::Checked) {
266 q.append(" versions");
270 QStringList fieldlist;
272 if (m_console->dir_cmd(q, results)) {
273 FileRevisions->setRowCount(results.size());
274 foreach (QString resultline, results) {
277 //PathId, FilenameId, fileid, jobid, lstat, Md5, VolName, Inchanger
278 fieldlist = resultline.split("\t");
279 TableItemFormatter item(*FileRevisions, row++);
280 item.setInChanger(col++, fieldlist.at(7)); // inchanger
281 item.setTextFld(col++, fieldlist.at(6)); // Volume
282 item.setNumericFld(col++, fieldlist.at(3)); // JobId
283 decode_stat(fieldlist.at(4).toLocal8Bit().data(),
284 &statp, sizeof(statp), &LinkFI);
285 item.setBytesFld(col++, QString().setNum(statp.st_size)); // size
286 item.setDateFld(col++, statp.st_mtime); // date
287 item.setTextFld(col++, fieldlist.at(5)); // chksum
289 // Adjust the fieldlist for drag&drop
290 fieldlist.removeLast(); // inchanger
291 fieldlist.removeLast(); // volname
292 fieldlist.removeLast(); // md5
293 fieldlist << m_path + filename;
295 // keep original info on the first cel that is never empty
296 item.widget(1)->setData(Qt::UserRole, fieldlist.join("\t"));
299 FileRevisions->verticalHeader()->hide();
300 FileRevisions->resizeColumnsToContents();
301 FileRevisions->resizeRowsToContents();
302 FileRevisions->setEditTriggers(QAbstractItemView::NoEditTriggers);
305 void bRestore::showInfoForFile(QTableWidgetItem *widget)
308 QTableWidgetItem *first = FileList->item(widget->row(), 1);
309 QStringList lst = first->data(Qt::UserRole).toString().split("\t");
310 if (lst.at(1) == "0") { // no filenameid, should be a path
311 displayFiles(lst.at(0).toLongLong(), lst.at(5));
313 displayFileVersion(lst.at(0), lst.at(1), m_client, lst.at(5));
317 void bRestore::applyLocation()
319 displayFiles(0, LocationEntry->text());
322 void bRestore::clearVersions(QTableWidgetItem *item)
324 if (item != m_current) {
325 FileRevisions->clearContents();
326 FileRevisions->setRowCount(0);
331 void bRestore::clearRestoreList()
333 RestoreList->clearContents();
334 RestoreList->setRowCount(0);
337 void bRestore::runRestore()
339 bRunRestore *r = new bRunRestore(this);
343 void bRestore::setupPage()
345 ClientList->addItem("Client list");
346 ClientList->addItems(m_console->client_list);
347 connect(ClientList, SIGNAL(currentIndexChanged(int)), this, SLOT(setClient()));
348 connect(JobList, SIGNAL(currentIndexChanged(int)), this, SLOT(setJob()));
349 connect(FileList, SIGNAL(itemClicked(QTableWidgetItem*)),
350 this, SLOT(clearVersions(QTableWidgetItem *)));
351 connect(FileList, SIGNAL(itemDoubleClicked(QTableWidgetItem*)),
352 this, SLOT(showInfoForFile(QTableWidgetItem *)));
353 connect(LocationBp, SIGNAL(pressed()), this, SLOT(applyLocation()));
354 connect(MergeChk, SIGNAL(clicked()), this, SLOT(setJob()));
355 connect(ClearBp, SIGNAL(clicked()), this, SLOT(clearRestoreList()));
356 connect(RestoreBp, SIGNAL(clicked()), this, SLOT(runRestore()));
357 connect(FilterBp, SIGNAL(clicked()), this, SLOT(refreshView()));
361 bRestore::~bRestore()
365 // Drag & Drop handling, not so easy...
366 void bRestoreTable::mousePressEvent(QMouseEvent *event)
368 QTableWidget::mousePressEvent(event);
370 if (event->button() == Qt::LeftButton) {
371 dragStartPosition = event->pos();
375 // This event permits to send set custom data on drag&drop
376 // Don't forget to call original class if we are not interested
377 void bRestoreTable::mouseMoveEvent(QMouseEvent *event)
381 // Look just for drag&drop
382 if (!(event->buttons() & Qt::LeftButton)) {
383 QTableWidget::mouseMoveEvent(event);
386 if ((event->pos() - dragStartPosition).manhattanLength()
387 < QApplication::startDragDistance())
389 QTableWidget::mouseMoveEvent(event);
393 QList<QTableWidgetItem *> lst = selectedItems();
394 if (mainWin->m_miscDebug) qDebug() << this << " selectedItems: " << lst;
399 QDrag *drag = new QDrag(this);
400 QMimeData *mimeData = new QMimeData;
401 for (int i=0; i < lst.size(); i++) {
402 if (lastrow != lst[i]->row()) {
403 lastrow = lst[i]->row();
404 QTableWidgetItem *it = item(lastrow, 1);
405 mimeData->setText(it->data(Qt::UserRole).toString());
406 break; // at this time, we do it one by one
409 drag->setMimeData(mimeData);
413 // This event is called when the drag item enters in the destination area
414 void bRestoreTable::dragEnterEvent(QDragEnterEvent *event)
416 if (event->source() == this) {
420 if (event->mimeData()->hasText()) {
421 event->acceptProposedAction();
427 // It should not be essential to redefine this event, but it
428 // doesn't work if not defined
429 void bRestoreTable::dragMoveEvent(QDragMoveEvent *event)
431 if (event->mimeData()->hasText()) {
432 event->acceptProposedAction();
438 // When user releases the button
439 void bRestoreTable::dropEvent(QDropEvent *event)
444 if (event->mimeData()->hasText()) {
445 TableItemFormatter item(*this, rowCount());
446 setRowCount(rowCount() + 1);
447 QStringList fields = event->mimeData()->text().split("\t");
448 if (fields.size() != 6) {
452 if (fields.at(1) == "0") {
453 item.setFileType(0, "folder");
455 item.setTextFld(col++, fields.at(5)); // filename
456 decode_stat(fields.at(4).toLocal8Bit().data(),
457 &statp, sizeof(statp), &LinkFI);
458 item.setBytesFld(col++, QString().setNum(statp.st_size)); // size
459 item.setDateFld(col++, statp.st_mtime); // date
460 item.setNumericFld(col++, fields.at(3)); // jobid
461 item.setNumericFld(col++, fields.at(2)); // fileid
462 // keep original info on the first cel that is never empty
463 item.widget(1)->setData(Qt::UserRole, event->mimeData()->text());
464 event->acceptProposedAction();
470 // Use File Relocation bp
471 void bRunRestore::UFRcb()
473 if (UseFileRelocationChk->checkState() == Qt::Checked) {
474 WhereEntry->setEnabled(false);
475 UseRegexpChk->setEnabled(true);
476 if (UseRegexpChk->checkState() == Qt::Checked) {
477 AddSuffixEntry->setEnabled(false);
478 AddPrefixEntry->setEnabled(false);
479 StripPrefixEntry->setEnabled(false);
480 WhereRegexpEntry->setEnabled(true);
482 AddSuffixEntry->setEnabled(true);
483 AddPrefixEntry->setEnabled(true);
484 StripPrefixEntry->setEnabled(true);
485 WhereRegexpEntry->setEnabled(false);
488 WhereEntry->setEnabled(true);
489 AddSuffixEntry->setEnabled(false);
490 AddPrefixEntry->setEnabled(false);
491 StripPrefixEntry->setEnabled(false);
492 UseRegexpChk->setEnabled(false);
493 WhereRegexpEntry->setEnabled(false);
497 // Expert mode for file relocation
498 void bRunRestore::useRegexp()
500 if (UseRegexpChk->checkState() == Qt::Checked) {
501 AddSuffixEntry->setEnabled(false);
502 AddPrefixEntry->setEnabled(false);
503 StripPrefixEntry->setEnabled(false);
504 WhereRegexpEntry->setEnabled(true);
506 AddSuffixEntry->setEnabled(true);
507 AddPrefixEntry->setEnabled(true);
508 StripPrefixEntry->setEnabled(true);
509 WhereRegexpEntry->setEnabled(false);
513 // Display Form to run the restore job
514 bRunRestore::bRunRestore(bRestore *parent)
518 ClientCb->addItems(parent->console()->client_list);
519 int i = ClientCb->findText(parent->m_client);
521 ClientCb->setCurrentIndex(i);
523 StorageCb->addItem(QString(""));
524 RestoreCb->addItems(parent->console()->restore_list);
525 WhenEditor->setDateTime(QDateTime::currentDateTime());
526 StorageCb->addItems(parent->console()->storage_list);
527 connect(UseFileRelocationChk, SIGNAL(clicked()), this, SLOT(UFRcb()));
528 connect(UseRegexpChk, SIGNAL(clicked()), this, SLOT(useRegexp()));
529 connect(ActionBp, SIGNAL(accepted()), this, SLOT(computeRestore()));
530 // TODO: handle multiple restore job
531 struct job_defaults jd;
532 if (parent->console()->restore_list.size() > 0) {
533 jd.job_name = parent->console()->restore_list[0];
534 brestore->console()->get_job_defaults(jd);
535 WhereEntry->setText(jd.where);
540 void bRestore::get_info_from_selection(QStringList &fileids,
543 QStringList &findexes)
547 for (int i=0; i < RestoreList->rowCount(); i++) {
548 QTableWidgetItem *item = RestoreList->item(i, 1);
549 QString data = item->data(Qt::UserRole).toString();
550 QStringList lst = data.split("\t");
551 if (lst.at(1) != "0") { // skip path
552 fileids << lst.at(2);
554 decode_stat(lst.at(4).toLocal8Bit().data(),
555 &statp, sizeof(statp), &LinkFI);
557 findexes << lst.at(3) + "," + QString().setNum(LinkFI);
561 jobids << lst.at(3).split(","); // Can have multiple jobids
564 fileids.removeDuplicates();
565 jobids.removeDuplicates();
566 dirids.removeDuplicates();
567 findexes.removeDuplicates();
570 // To compute volume list with directories, query is much slower
571 void bRunRestore::computeVolumeList()
573 brestore->get_info_from_selection(m_fileids, m_jobids, m_dirids, m_findexes);
574 if (m_fileids.size() == 0) {
578 Freeze frz_lst(*TableMedia); /* disable updating*/
580 " SELECT DISTINCT VolumeName, Enabled, InChanger "
582 " ( " // -- Get all media from this job
583 " SELECT MIN(FirstIndex) AS FirstIndex, MAX(LastIndex) AS LastIndex, "
584 " VolumeName, Enabled, Inchanger "
585 " FROM JobMedia JOIN Media USING (MediaId) "
586 " WHERE JobId IN (" + m_jobids.join(",") + ") "
587 " GROUP BY VolumeName,Enabled,InChanger "
589 " WHERE File.FileId IN (" + m_fileids.join(",") + ") "
590 " AND File.FileIndex >= allmedia.FirstIndex "
591 " AND File.FileIndex <= allmedia.LastIndex ";
594 if (brestore->console()->sql_cmd(q, results)) {
595 QStringList fieldlist;
596 TableMedia->setRowCount(results.size());
597 /* Iterate through the record returned from the query */
598 foreach (QString resultline, results) {
600 //volname, enabled, inchanger
601 fieldlist = resultline.split("\t");
603 TableItemFormatter item(*TableMedia, row++);
604 item.setInChanger(col++, fieldlist.at(2)); // inchanger
605 item.setTextFld(col++, fieldlist.at(0)); // Volume
608 TableMedia->verticalHeader()->hide();
609 TableMedia->resizeColumnsToContents();
610 TableMedia->resizeRowsToContents();
611 TableMedia->setEditTriggers(QAbstractItemView::NoEditTriggers);
614 int64_t bRunRestore::runRestore(QString tablename)
619 tmp = ClientCb->currentText();
623 q = "restore client=" + tmp;
625 tmp = CommentEntry->text();
627 tmp.replace("\"", " ");
628 q += " comment=\"" + tmp + "\"";
631 tmp = StorageCb->currentText();
633 q += " storage=" + tmp;
636 if (UseFileRelocationChk->checkState() == Qt::Checked) {
637 if (UseRegexpChk->checkState() == Qt::Checked) {
638 tmp = WhereRegexpEntry->text();
640 tmp.replace("\"", "");
641 q += " regexwhere=\"" + tmp + "\"";
645 tmp = StripPrefixEntry->text();
647 tmp.replace("\"", "");
648 lst.append("!" + tmp + "!!i");
650 tmp = AddPrefixEntry->text();
652 tmp.replace("\"", "");
653 lst.append("!^!" + tmp + "!");
655 tmp = AddSuffixEntry->text();
657 tmp.replace("\"", "");
658 lst.append("!([^/])$!$1" + tmp + "!");
660 if (lst.size() > 0) {
661 q += " regexwhere=\"" + lst.join(",") + "\"";
665 tmp = WhereEntry->text();
667 tmp.replace("\"", "");
668 q += " where=\"" + tmp + "\"";
672 // q += " priority=" + tmp.setNum(PrioritySb->value());
673 // q += " job=\"" + RestoreCb->currentText() + "\"";
674 q += " file=\"?" + tablename + "\"";
675 q += " when=\"" + WhenEditor->dateTime().toString("yyyy-MM-dd hh:mm:ss") + "\"";
678 if (mainWin->m_miscDebug) qDebug() << q;
680 if (brestore->console()->dir_cmd(q, results)) {
681 foreach (QString resultline, results) {
682 QStringList fieldlist = resultline.split("=");
683 if (fieldlist.size() == 2) {
684 return fieldlist.at(1).toLongLong();
691 void bRunRestore::computeRestore()
693 QString q = ".bvfs_restore path=b2123 jobid=" + m_jobids.join(",");
694 if (m_fileids.size() > 0) {
695 q += " fileid=" + m_fileids.join(",");
697 if (m_dirids.size() > 0) {
698 q += " dirid=" + m_dirids.join(",");
700 if (m_findexes.size() > 0) {
701 q += " hardlink=" + m_findexes.join(",");
703 if (mainWin->m_miscDebug) qDebug() << q;
706 if (brestore->console()->dir_cmd(q, results)) {
707 if (results.size() == 1 && results[0] == "OK") {
708 int64_t jobid = runRestore("b2123");
709 if (mainWin->m_miscDebug) qDebug() << "jobid=" << jobid;
710 q = ".bvfs_cleanup path=b2123";
711 brestore->console()->dir_cmd(q, results);