2 Bacula® - The Network Backup Solution
4 Copyright (C) 2007-2011 Free Software Foundation Europe e.V.
6 The main author of Bacula is Kern Sibbald, with contributions from many
7 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 Bacula® is a registered trademark of Kern Sibbald.
19 * bRestore Class (Eric's brestore)
21 * Kern Sibbald, January MMVII
27 #include "util/fmtwidgetitem.h"
29 bRestore::bRestore() : Pages()
31 m_name = tr("bRestore");
35 QTreeWidgetItem* thisitem = mainWin->getFromHash(this);
36 thisitem->setIcon(0, QIcon(QString::fromUtf8(":images/browse.png")));
40 RestoreList->setAcceptDrops(true);
43 // Populate client table and job associated
44 void bRestore::setClient()
46 // Select the same client, don't touch
47 if (m_client == ClientList->currentText()) {
50 m_client = ClientList->currentText();
51 FileList->clearContents();
52 FileRevisions->clearContents();
54 JobList->setEnabled(true);
55 LocationEntry->clear();
59 if (ClientList->currentIndex() < 1) {
60 JobList->setEnabled(false);
64 JobList->addItem("Job list for " + m_client);
67 "SELECT Job.Jobid AS JobId, Job.StartTime AS StartTime,"
68 " Job.Level AS Level,"
70 " FROM Job JOIN Client USING (ClientId)"
72 " Job.JobStatus IN ('T','W') AND Job.Type='B' AND"
73 " Client.Name='" + m_client + "' ORDER BY StartTime DESC" ;
77 QStringList fieldlist;
78 if (m_console->sql_cmd(jobQuery, results)) {
79 /* Iterate through the record returned from the query */
80 foreach (QString resultline, results) {
82 // JobId, StartTime, Level, Name
83 fieldlist = resultline.split("\t");
84 job = fieldlist[1] + " " + fieldlist[3] + "(" + fieldlist[2] + ") " + fieldlist[0];
85 JobList->addItem(job, QVariant(fieldlist[0])); // set also private value
90 // Compute job associated and update the job cache if needed
91 void bRestore::setJob()
93 if (JobList->currentIndex() < 1) {
94 FileList->clearContents();
95 FileList->setRowCount(0);
96 FileRevisions->clearContents();
97 FileRevisions->setRowCount(0);
101 QVariant tmp = JobList->itemData(JobList->currentIndex(), Qt::UserRole);
103 m_jobids = tmp.toString();
104 QString cmd = ".bvfs_get_jobids jobid=" + m_jobids;
105 if (MergeChk->checkState() == Qt::Checked) {
109 m_console->dir_cmd(cmd, results);
111 if (results.size() < 1) {
112 FileList->clearContents();
113 FileList->setRowCount(0);
114 FileRevisions->clearContents();
115 FileRevisions->setRowCount(0);
119 // TODO: Can take some time if the job contains many dirs
120 m_jobids = results.at(0);
121 cmd = ".bvfs_update jobid=" + m_jobids;
122 m_console->dir_cmd(cmd, results);
124 Pmsg1(0, "jobids=%s\n", m_jobids.toLocal8Bit().constData());
126 displayFiles(m_pathid, QString(""));
127 Pmsg0(000, "update done\n");
130 extern int decode_stat(char *buf, struct stat *statp, int stat_size, int32_t *LinkFI);
132 // refresh button with a filter or limit/offset change
133 void bRestore::refreshView()
135 displayFiles(m_pathid, m_path);
138 void bRestore::displayFiles(int64_t pathid, QString path)
142 QStringList fieldlist;
147 Freeze frz_lst(*FileList); /* disable updating*/
148 Freeze frz_rev(*FileRevisions); /* disable updating*/
149 FileList->clearContents();
150 FileRevisions->clearContents();
151 FileRevisions->setRowCount(0);
153 // If we provide pathid, use it (path can be altered by encoding conversion)
155 arg = " pathid=" + QString().setNum(pathid);
157 // Choose .. update current path to parent dir
162 m_path.remove(QRegExp("[^/]+/$"));
165 } else if (path == "/" && m_path == "") {
168 } else if (path != "/" && path != ".") {
173 arg = " path=\"" + m_path + "\"";
176 // If a filter is set, add it to the current query
177 if (FilterEntry->text() != "") {
178 QString tmp = FilterEntry->text();
179 tmp.replace("\"", "."); // basic escape of "
180 arg += " pattern=\"" + tmp + "\"";
183 LocationEntry->setText(m_path);
184 QString offset = QString().setNum(Offset1Spin->value());
185 QString limit=QString().setNum(Offset2Spin->value() - Offset1Spin->value());
186 QString q = ".bvfs_lsdir jobid=" + m_jobids + arg
187 + " limit=" + limit + " offset=" + offset ;
188 if (mainWin->m_miscDebug) qDebug() << q;
189 if (m_console->dir_cmd(q, results)) {
191 FileList->setRowCount(nb);
192 foreach (QString resultline, results) {
194 //PathId, FilenameId, fileid, jobid, lstat, path
195 fieldlist = resultline.split("\t");
197 * Note, the next line zaps variable "item", probably
198 * because the input data in fieldlist is bad.
200 decode_stat(fieldlist.at(4).toLocal8Bit().data(), &statp, sizeof(statp), &LinkFI);
201 TableItemFormatter item(*FileList, row++);
202 item.setFileType(col++, QString("folder")); // folder or file
203 item.setTextFld(col++, fieldlist.at(5)); // path
204 item.setBytesFld(col++, QString().setNum(statp.st_size));
205 item.setDateFld(col++, statp.st_mtime); // date
206 fieldlist.replace(3, m_jobids); // use current jobids selection
207 // keep original info on the first cel that is never empty
208 item.widget(1)->setData(Qt::UserRole, fieldlist.join("\t"));
213 q = ".bvfs_lsfiles jobid=" + m_jobids + arg
214 + " limit=" + limit + " offset=" + offset ;
215 if (m_console->dir_cmd(q, results)) {
216 FileList->setRowCount(results.size() + nb);
217 foreach (QString resultline, results) {
218 int col=1; // skip icon
219 //PathId, FilenameId, fileid, jobid, lstat, name
220 fieldlist = resultline.split("\t");
221 TableItemFormatter item(*FileList, row++);
222 item.setTextFld(col++, fieldlist.at(5)); // name
223 decode_stat(fieldlist.at(4).toLocal8Bit().data(),
224 &statp, sizeof(statp), &LinkFI);
225 item.setBytesFld(col++, QString().setNum(statp.st_size));
226 item.setDateFld(col++, statp.st_mtime);
227 // keep original info on the first cel that is never empty
228 item.widget(1)->setData(Qt::UserRole, fieldlist.join("\t")); // keep info
231 FileList->verticalHeader()->hide();
232 FileList->resizeColumnsToContents();
233 FileList->resizeRowsToContents();
234 FileList->setEditTriggers(QAbstractItemView::NoEditTriggers);
237 void bRestore::PgSeltreeWidgetClicked()
242 if (!isOnceDocked()) {
247 // Display all versions of a file for this client
248 void bRestore::displayFileVersion(QString pathid, QString fnid,
249 QString client, QString filename)
254 Freeze frz_rev(*FileRevisions); /* disable updating*/
255 FileRevisions->clearContents();
257 QString q = ".bvfs_versions jobid=" + m_jobids +
258 " pathid=" + pathid +
262 if (VersionsChk->checkState() == Qt::Checked) {
263 q.append(" versions");
267 QStringList fieldlist;
269 if (m_console->dir_cmd(q, results)) {
270 FileRevisions->setRowCount(results.size());
271 foreach (QString resultline, results) {
274 //PathId, FilenameId, fileid, jobid, lstat, Md5, VolName, Inchanger
275 fieldlist = resultline.split("\t");
276 TableItemFormatter item(*FileRevisions, row++);
277 item.setInChanger(col++, fieldlist.at(7)); // inchanger
278 item.setTextFld(col++, fieldlist.at(6)); // Volume
279 item.setNumericFld(col++, fieldlist.at(3)); // JobId
280 decode_stat(fieldlist.at(4).toLocal8Bit().data(),
281 &statp, sizeof(statp), &LinkFI);
282 item.setBytesFld(col++, QString().setNum(statp.st_size)); // size
283 item.setDateFld(col++, statp.st_mtime); // date
284 item.setTextFld(col++, fieldlist.at(5)); // chksum
286 // Adjust the fieldlist for drag&drop
287 fieldlist.removeLast(); // inchanger
288 fieldlist.removeLast(); // volname
289 fieldlist.removeLast(); // md5
290 fieldlist << m_path + filename;
292 // keep original info on the first cel that is never empty
293 item.widget(1)->setData(Qt::UserRole, fieldlist.join("\t"));
296 FileRevisions->verticalHeader()->hide();
297 FileRevisions->resizeColumnsToContents();
298 FileRevisions->resizeRowsToContents();
299 FileRevisions->setEditTriggers(QAbstractItemView::NoEditTriggers);
302 void bRestore::showInfoForFile(QTableWidgetItem *widget)
305 QTableWidgetItem *first = FileList->item(widget->row(), 1);
306 QStringList lst = first->data(Qt::UserRole).toString().split("\t");
307 if (lst.at(1) == "0") { // no filenameid, should be a path
308 displayFiles(lst.at(0).toLongLong(), lst.at(5));
310 displayFileVersion(lst.at(0), lst.at(1), m_client, lst.at(5));
314 void bRestore::applyLocation()
316 displayFiles(0, LocationEntry->text());
319 void bRestore::clearVersions(QTableWidgetItem *item)
321 if (item != m_current) {
322 FileRevisions->clearContents();
323 FileRevisions->setRowCount(0);
328 void bRestore::clearRestoreList()
330 RestoreList->clearContents();
331 RestoreList->setRowCount(0);
334 void bRestore::runRestore()
336 bRunRestore *r = new bRunRestore(this);
340 void bRestore::setupPage()
342 ClientList->addItem("Client list");
343 ClientList->addItems(m_console->client_list);
344 connect(ClientList, SIGNAL(currentIndexChanged(int)), this, SLOT(setClient()));
345 connect(JobList, SIGNAL(currentIndexChanged(int)), this, SLOT(setJob()));
346 connect(FileList, SIGNAL(itemClicked(QTableWidgetItem*)),
347 this, SLOT(clearVersions(QTableWidgetItem *)));
348 connect(FileList, SIGNAL(itemDoubleClicked(QTableWidgetItem*)),
349 this, SLOT(showInfoForFile(QTableWidgetItem *)));
350 connect(LocationBp, SIGNAL(pressed()), this, SLOT(applyLocation()));
351 connect(MergeChk, SIGNAL(clicked()), this, SLOT(setJob()));
352 connect(ClearBp, SIGNAL(clicked()), this, SLOT(clearRestoreList()));
353 connect(RestoreBp, SIGNAL(clicked()), this, SLOT(runRestore()));
354 connect(FilterBp, SIGNAL(clicked()), this, SLOT(refreshView()));
358 bRestore::~bRestore()
362 // Drag & Drop handling, not so easy...
363 void bRestoreTable::mousePressEvent(QMouseEvent *event)
365 QTableWidget::mousePressEvent(event);
367 if (event->button() == Qt::LeftButton) {
368 dragStartPosition = event->pos();
372 // This event permits to send set custom data on drag&drop
373 // Don't forget to call original class if we are not interested
374 void bRestoreTable::mouseMoveEvent(QMouseEvent *event)
378 // Look just for drag&drop
379 if (!(event->buttons() & Qt::LeftButton)) {
380 QTableWidget::mouseMoveEvent(event);
383 if ((event->pos() - dragStartPosition).manhattanLength()
384 < QApplication::startDragDistance())
386 QTableWidget::mouseMoveEvent(event);
390 QList<QTableWidgetItem *> lst = selectedItems();
391 if (mainWin->m_miscDebug) qDebug() << this << " selectedItems: " << lst;
396 QDrag *drag = new QDrag(this);
397 QMimeData *mimeData = new QMimeData;
398 for (int i=0; i < lst.size(); i++) {
399 if (lastrow != lst[i]->row()) {
400 lastrow = lst[i]->row();
401 QTableWidgetItem *it = item(lastrow, 1);
402 mimeData->setText(it->data(Qt::UserRole).toString());
403 break; // at this time, we do it one by one
406 drag->setMimeData(mimeData);
410 // This event is called when the drag item enters in the destination area
411 void bRestoreTable::dragEnterEvent(QDragEnterEvent *event)
413 if (event->source() == this) {
417 if (event->mimeData()->hasText()) {
418 event->acceptProposedAction();
424 // It should not be essential to redefine this event, but it
425 // doesn't work if not defined
426 void bRestoreTable::dragMoveEvent(QDragMoveEvent *event)
428 if (event->mimeData()->hasText()) {
429 event->acceptProposedAction();
435 // When user releases the button
436 void bRestoreTable::dropEvent(QDropEvent *event)
441 if (event->mimeData()->hasText()) {
442 TableItemFormatter item(*this, rowCount());
443 setRowCount(rowCount() + 1);
444 QStringList fields = event->mimeData()->text().split("\t");
445 if (fields.size() != 6) {
449 if (fields.at(1) == "0") {
450 item.setFileType(0, "folder");
452 item.setTextFld(col++, fields.at(5)); // filename
453 decode_stat(fields.at(4).toLocal8Bit().data(),
454 &statp, sizeof(statp), &LinkFI);
455 item.setBytesFld(col++, QString().setNum(statp.st_size)); // size
456 item.setDateFld(col++, statp.st_mtime); // date
457 item.setNumericFld(col++, fields.at(3)); // jobid
458 item.setNumericFld(col++, fields.at(2)); // fileid
459 // keep original info on the first cel that is never empty
460 item.widget(1)->setData(Qt::UserRole, event->mimeData()->text());
461 event->acceptProposedAction();
467 // Use File Relocation bp
468 void bRunRestore::UFRcb()
470 if (UseFileRelocationChk->checkState() == Qt::Checked) {
471 WhereEntry->setEnabled(false);
472 UseRegexpChk->setEnabled(true);
473 if (UseRegexpChk->checkState() == Qt::Checked) {
474 AddSuffixEntry->setEnabled(false);
475 AddPrefixEntry->setEnabled(false);
476 StripPrefixEntry->setEnabled(false);
477 WhereRegexpEntry->setEnabled(true);
479 AddSuffixEntry->setEnabled(true);
480 AddPrefixEntry->setEnabled(true);
481 StripPrefixEntry->setEnabled(true);
482 WhereRegexpEntry->setEnabled(false);
485 WhereEntry->setEnabled(true);
486 AddSuffixEntry->setEnabled(false);
487 AddPrefixEntry->setEnabled(false);
488 StripPrefixEntry->setEnabled(false);
489 UseRegexpChk->setEnabled(false);
490 WhereRegexpEntry->setEnabled(false);
494 // Expert mode for file relocation
495 void bRunRestore::useRegexp()
497 if (UseRegexpChk->checkState() == Qt::Checked) {
498 AddSuffixEntry->setEnabled(false);
499 AddPrefixEntry->setEnabled(false);
500 StripPrefixEntry->setEnabled(false);
501 WhereRegexpEntry->setEnabled(true);
503 AddSuffixEntry->setEnabled(true);
504 AddPrefixEntry->setEnabled(true);
505 StripPrefixEntry->setEnabled(true);
506 WhereRegexpEntry->setEnabled(false);
510 // Display Form to run the restore job
511 bRunRestore::bRunRestore(bRestore *parent)
515 ClientCb->addItems(parent->console()->client_list);
516 int i = ClientCb->findText(parent->m_client);
518 ClientCb->setCurrentIndex(i);
520 StorageCb->addItem(QString(""));
521 RestoreCb->addItems(parent->console()->restore_list);
522 WhenEditor->setDateTime(QDateTime::currentDateTime());
523 StorageCb->addItems(parent->console()->storage_list);
524 connect(UseFileRelocationChk, SIGNAL(clicked()), this, SLOT(UFRcb()));
525 connect(UseRegexpChk, SIGNAL(clicked()), this, SLOT(useRegexp()));
526 connect(ActionBp, SIGNAL(accepted()), this, SLOT(computeRestore()));
527 // TODO: handle multiple restore job
528 struct job_defaults jd;
529 if (parent->console()->restore_list.size() > 0) {
530 jd.job_name = parent->console()->restore_list[0];
531 brestore->console()->get_job_defaults(jd);
532 WhereEntry->setText(jd.where);
537 void bRestore::get_info_from_selection(QStringList &fileids,
540 QStringList &findexes)
544 for (int i=0; i < RestoreList->rowCount(); i++) {
545 QTableWidgetItem *item = RestoreList->item(i, 1);
546 QString data = item->data(Qt::UserRole).toString();
547 QStringList lst = data.split("\t");
548 if (lst.at(1) != "0") { // skip path
549 fileids << lst.at(2);
551 decode_stat(lst.at(4).toLocal8Bit().data(),
552 &statp, sizeof(statp), &LinkFI);
554 findexes << lst.at(3) + "," + QString().setNum(LinkFI);
558 jobids << lst.at(3).split(","); // Can have multiple jobids
561 fileids.removeDuplicates();
562 jobids.removeDuplicates();
563 dirids.removeDuplicates();
564 findexes.removeDuplicates();
567 // To compute volume list with directories, query is much slower
568 void bRunRestore::computeVolumeList()
570 brestore->get_info_from_selection(m_fileids, m_jobids, m_dirids, m_findexes);
571 if (m_fileids.size() == 0) {
575 Freeze frz_lst(*TableMedia); /* disable updating*/
577 " SELECT DISTINCT VolumeName, Enabled, InChanger "
579 " ( " // -- Get all media from this job
580 " SELECT MIN(FirstIndex) AS FirstIndex, MAX(LastIndex) AS LastIndex, "
581 " VolumeName, Enabled, Inchanger "
582 " FROM JobMedia JOIN Media USING (MediaId) "
583 " WHERE JobId IN (" + m_jobids.join(",") + ") "
584 " GROUP BY VolumeName,Enabled,InChanger "
586 " WHERE File.FileId IN (" + m_fileids.join(",") + ") "
587 " AND File.FileIndex >= allmedia.FirstIndex "
588 " AND File.FileIndex <= allmedia.LastIndex ";
591 if (brestore->console()->sql_cmd(q, results)) {
592 QStringList fieldlist;
593 TableMedia->setRowCount(results.size());
594 /* Iterate through the record returned from the query */
595 foreach (QString resultline, results) {
597 //volname, enabled, inchanger
598 fieldlist = resultline.split("\t");
600 TableItemFormatter item(*TableMedia, row++);
601 item.setInChanger(col++, fieldlist.at(2)); // inchanger
602 item.setTextFld(col++, fieldlist.at(0)); // Volume
605 TableMedia->verticalHeader()->hide();
606 TableMedia->resizeColumnsToContents();
607 TableMedia->resizeRowsToContents();
608 TableMedia->setEditTriggers(QAbstractItemView::NoEditTriggers);
611 int64_t bRunRestore::runRestore(QString tablename)
616 tmp = ClientCb->currentText();
620 q = "restore client=" + tmp;
622 tmp = CommentEntry->text();
624 tmp.replace("\"", " ");
625 q += " comment=\"" + tmp + "\"";
628 tmp = StorageCb->currentText();
630 q += " storage=" + tmp;
633 if (UseFileRelocationChk->checkState() == Qt::Checked) {
634 if (UseRegexpChk->checkState() == Qt::Checked) {
635 tmp = WhereRegexpEntry->text();
637 tmp.replace("\"", "");
638 q += " regexwhere=\"" + tmp + "\"";
642 tmp = StripPrefixEntry->text();
644 tmp.replace("\"", "");
645 lst.append("!" + tmp + "!!i");
647 tmp = AddPrefixEntry->text();
649 tmp.replace("\"", "");
650 lst.append("!^!" + tmp + "!");
652 tmp = AddSuffixEntry->text();
654 tmp.replace("\"", "");
655 lst.append("!([^/])$!$1" + tmp + "!");
657 if (lst.size() > 0) {
658 q += " regexwhere=\"" + lst.join(",") + "\"";
662 tmp = WhereEntry->text();
664 tmp.replace("\"", "");
665 q += " where=\"" + tmp + "\"";
669 // q += " priority=" + tmp.setNum(PrioritySb->value());
670 // q += " job=\"" + RestoreCb->currentText() + "\"";
671 q += " file=\"?" + tablename + "\"";
672 q += " when=\"" + WhenEditor->dateTime().toString("yyyy-MM-dd hh:mm:ss") + "\"";
675 if (mainWin->m_miscDebug) qDebug() << q;
677 if (brestore->console()->dir_cmd(q, results)) {
678 foreach (QString resultline, results) {
679 QStringList fieldlist = resultline.split("=");
680 if (fieldlist.size() == 2) {
681 return fieldlist.at(1).toLongLong();
688 void bRunRestore::computeRestore()
690 QString q = ".bvfs_restore path=b2123 jobid=" + m_jobids.join(",");
691 if (m_fileids.size() > 0) {
692 q += " fileid=" + m_fileids.join(",");
694 if (m_dirids.size() > 0) {
695 q += " dirid=" + m_dirids.join(",");
697 if (m_findexes.size() > 0) {
698 q += " hardlink=" + m_findexes.join(",");
700 if (mainWin->m_miscDebug) qDebug() << q;
703 if (brestore->console()->dir_cmd(q, results)) {
704 if (results.size() == 1 && results[0] == "OK") {
705 int64_t jobid = runRestore("b2123");
706 if (mainWin->m_miscDebug) qDebug() << "jobid=" << jobid;
707 q = ".bvfs_cleanup path=b2123";
708 brestore->console()->dir_cmd(q, results);