2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2015 Kern Sibbald
5 Copyright (C) 2007-2011 Free Software Foundation Europe e.V.
7 The original author of Bacula is Kern Sibbald, with contributions
8 from many others, a complete list can be found in the file AUTHORS.
10 You may use this file and others of this release according to the
11 license defined in the LICENSE file, which includes the Affero General
12 Public License, v3.0 ("AGPLv3") and some additional permissions and
13 terms pursuant to its AGPLv3 Section 7.
15 This notice must be preserved when any source code is
16 conveyed and/or propagated.
18 Bacula(R) is a registered trademark of Kern Sibbald.
23 * bRestore Class (Eric's brestore)
25 * Kern Sibbald, January MMVII
31 #include "util/fmtwidgetitem.h"
33 bRestore::bRestore() : Pages()
35 m_name = tr("bRestore");
39 QTreeWidgetItem* thisitem = mainWin->getFromHash(this);
40 thisitem->setIcon(0, QIcon(QString::fromUtf8(":images/browse.png")));
44 RestoreList->setAcceptDrops(true);
47 // Populate client table and job associated
48 void bRestore::setClient()
50 // Select the same client, don't touch
51 if (m_client == ClientList->currentText()) {
54 m_client = ClientList->currentText();
55 FileList->clearContents();
56 FileRevisions->clearContents();
58 JobList->setEnabled(true);
59 LocationEntry->clear();
63 if (ClientList->currentIndex() < 1) {
64 JobList->setEnabled(false);
68 JobList->addItem("Job list for " + m_client);
71 "SELECT Job.Jobid AS JobId, Job.StartTime AS StartTime,"
72 " Job.Level AS Level,"
74 " FROM Job JOIN Client USING (ClientId)"
76 " Job.JobStatus IN ('T','W') AND Job.Type='B' AND"
77 " Client.Name='" + m_client + "' ORDER BY StartTime DESC" ;
81 QStringList fieldlist;
82 if (m_console->sql_cmd(jobQuery, results)) {
83 /* Iterate through the record returned from the query */
84 foreach (QString resultline, results) {
86 // JobId, StartTime, Level, Name
87 fieldlist = resultline.split("\t");
88 job = fieldlist[1] + " " + fieldlist[3] + "(" + fieldlist[2] + ") " + fieldlist[0];
89 JobList->addItem(job, QVariant(fieldlist[0])); // set also private value
94 // Compute job associated and update the job cache if needed
95 void bRestore::setJob()
97 if (JobList->currentIndex() < 1) {
98 FileList->clearContents();
99 FileList->setRowCount(0);
100 FileRevisions->clearContents();
101 FileRevisions->setRowCount(0);
105 QVariant tmp = JobList->itemData(JobList->currentIndex(), Qt::UserRole);
107 m_jobids = tmp.toString();
108 QString cmd = ".bvfs_get_jobids jobid=" + m_jobids;
109 if (MergeChk->checkState() == Qt::Checked) {
113 m_console->dir_cmd(cmd, results);
115 if (results.size() < 1) {
116 FileList->clearContents();
117 FileList->setRowCount(0);
118 FileRevisions->clearContents();
119 FileRevisions->setRowCount(0);
123 // TODO: Can take some time if the job contains many dirs
124 m_jobids = results.at(0);
125 cmd = ".bvfs_update jobid=" + m_jobids;
126 m_console->dir_cmd(cmd, results);
128 Pmsg1(0, "jobids=%s\n", m_jobids.toLocal8Bit().constData());
130 displayFiles(m_pathid, QString(""));
131 Pmsg0(000, "update done\n");
134 extern int decode_stat(char *buf, struct stat *statp, int stat_size, int32_t *LinkFI);
136 // refresh button with a filter or limit/offset change
137 void bRestore::refreshView()
139 displayFiles(m_pathid, m_path);
142 void bRestore::displayFiles(int64_t pathid, QString path)
146 QStringList fieldlist;
151 Freeze frz_lst(*FileList); /* disable updating*/
152 Freeze frz_rev(*FileRevisions); /* disable updating*/
153 FileList->clearContents();
154 FileRevisions->clearContents();
155 FileRevisions->setRowCount(0);
157 // If we provide pathid, use it (path can be altered by encoding conversion)
159 arg = " pathid=" + QString().setNum(pathid);
161 // Choose .. update current path to parent dir
166 m_path.remove(QRegExp("[^/]+/$"));
169 } else if (path == "/" && m_path == "") {
172 } else if (path != "/" && path != ".") {
177 arg = " path=\"" + m_path + "\"";
180 // If a filter is set, add it to the current query
181 if (FilterEntry->text() != "") {
182 QString tmp = FilterEntry->text();
183 tmp.replace("\"", "."); // basic escape of "
184 arg += " pattern=\"" + tmp + "\"";
187 LocationEntry->setText(m_path);
188 QString offset = QString().setNum(Offset1Spin->value());
189 QString limit=QString().setNum(Offset2Spin->value() - Offset1Spin->value());
190 QString q = ".bvfs_lsdir jobid=" + m_jobids + arg
191 + " limit=" + limit + " offset=" + offset ;
192 if (mainWin->m_miscDebug) qDebug() << q;
193 if (m_console->dir_cmd(q, results)) {
195 FileList->setRowCount(nb);
196 foreach (QString resultline, results) {
198 //PathId, FilenameId, fileid, jobid, lstat, path
199 fieldlist = resultline.split("\t");
201 * Note, the next line zaps variable "item", probably
202 * because the input data in fieldlist is bad.
204 decode_stat(fieldlist.at(4).toLocal8Bit().data(), &statp, sizeof(statp), &LinkFI);
205 TableItemFormatter item(*FileList, row++);
206 item.setFileType(col++, QString("folder")); // folder or file
207 item.setTextFld(col++, fieldlist.at(5)); // path
208 item.setBytesFld(col++, QString().setNum(statp.st_size));
209 item.setDateFld(col++, statp.st_mtime); // date
210 fieldlist.replace(3, m_jobids); // use current jobids selection
211 // keep original info on the first cel that is never empty
212 item.widget(1)->setData(Qt::UserRole, fieldlist.join("\t"));
217 q = ".bvfs_lsfiles jobid=" + m_jobids + arg
218 + " limit=" + limit + " offset=" + offset ;
219 if (m_console->dir_cmd(q, results)) {
220 FileList->setRowCount(results.size() + nb);
221 foreach (QString resultline, results) {
222 int col=1; // skip icon
223 //PathId, FilenameId, fileid, jobid, lstat, name
224 fieldlist = resultline.split("\t");
225 TableItemFormatter item(*FileList, row++);
226 item.setTextFld(col++, fieldlist.at(5)); // name
227 decode_stat(fieldlist.at(4).toLocal8Bit().data(),
228 &statp, sizeof(statp), &LinkFI);
229 item.setBytesFld(col++, QString().setNum(statp.st_size));
230 item.setDateFld(col++, statp.st_mtime);
231 // keep original info on the first cel that is never empty
232 item.widget(1)->setData(Qt::UserRole, fieldlist.join("\t")); // keep info
235 FileList->verticalHeader()->hide();
236 FileList->resizeColumnsToContents();
237 FileList->resizeRowsToContents();
238 FileList->setEditTriggers(QAbstractItemView::NoEditTriggers);
241 void bRestore::PgSeltreeWidgetClicked()
246 if (!isOnceDocked()) {
251 // Display all versions of a file for this client
252 void bRestore::displayFileVersion(QString pathid, QString fnid,
253 QString client, QString filename)
258 Freeze frz_rev(*FileRevisions); /* disable updating*/
259 FileRevisions->clearContents();
261 QString q = ".bvfs_versions jobid=" + m_jobids +
262 " pathid=" + pathid +
266 if (VersionsChk->checkState() == Qt::Checked) {
267 q.append(" versions");
271 QStringList fieldlist;
273 if (m_console->dir_cmd(q, results)) {
274 FileRevisions->setRowCount(results.size());
275 foreach (QString resultline, results) {
278 //PathId, FilenameId, fileid, jobid, lstat, Md5, VolName, Inchanger
279 fieldlist = resultline.split("\t");
280 TableItemFormatter item(*FileRevisions, row++);
281 item.setInChanger(col++, fieldlist.at(7)); // inchanger
282 item.setTextFld(col++, fieldlist.at(6)); // Volume
283 item.setNumericFld(col++, fieldlist.at(3)); // JobId
284 decode_stat(fieldlist.at(4).toLocal8Bit().data(),
285 &statp, sizeof(statp), &LinkFI);
286 item.setBytesFld(col++, QString().setNum(statp.st_size)); // size
287 item.setDateFld(col++, statp.st_mtime); // date
288 item.setTextFld(col++, fieldlist.at(5)); // chksum
290 // Adjust the fieldlist for drag&drop
291 fieldlist.removeLast(); // inchanger
292 fieldlist.removeLast(); // volname
293 fieldlist.removeLast(); // md5
294 fieldlist << m_path + filename;
296 // keep original info on the first cel that is never empty
297 item.widget(1)->setData(Qt::UserRole, fieldlist.join("\t"));
300 FileRevisions->verticalHeader()->hide();
301 FileRevisions->resizeColumnsToContents();
302 FileRevisions->resizeRowsToContents();
303 FileRevisions->setEditTriggers(QAbstractItemView::NoEditTriggers);
306 void bRestore::showInfoForFile(QTableWidgetItem *widget)
309 QTableWidgetItem *first = FileList->item(widget->row(), 1);
310 QStringList lst = first->data(Qt::UserRole).toString().split("\t");
311 if (lst.at(1) == "0") { // no filenameid, should be a path
312 displayFiles(lst.at(0).toLongLong(), lst.at(5));
314 displayFileVersion(lst.at(0), lst.at(1), m_client, lst.at(5));
318 void bRestore::applyLocation()
320 displayFiles(0, LocationEntry->text());
323 void bRestore::clearVersions(QTableWidgetItem *item)
325 if (item != m_current) {
326 FileRevisions->clearContents();
327 FileRevisions->setRowCount(0);
332 void bRestore::clearRestoreList()
334 RestoreList->clearContents();
335 RestoreList->setRowCount(0);
338 void bRestore::runRestore()
340 bRunRestore *r = new bRunRestore(this);
344 void bRestore::setupPage()
346 ClientList->addItem("Client list");
347 ClientList->addItems(m_console->client_list);
348 connect(ClientList, SIGNAL(currentIndexChanged(int)), this, SLOT(setClient()));
349 connect(JobList, SIGNAL(currentIndexChanged(int)), this, SLOT(setJob()));
350 connect(FileList, SIGNAL(itemClicked(QTableWidgetItem*)),
351 this, SLOT(clearVersions(QTableWidgetItem *)));
352 connect(FileList, SIGNAL(itemDoubleClicked(QTableWidgetItem*)),
353 this, SLOT(showInfoForFile(QTableWidgetItem *)));
354 connect(LocationBp, SIGNAL(pressed()), this, SLOT(applyLocation()));
355 connect(MergeChk, SIGNAL(clicked()), this, SLOT(setJob()));
356 connect(ClearBp, SIGNAL(clicked()), this, SLOT(clearRestoreList()));
357 connect(RestoreBp, SIGNAL(clicked()), this, SLOT(runRestore()));
358 connect(FilterBp, SIGNAL(clicked()), this, SLOT(refreshView()));
362 bRestore::~bRestore()
366 // Drag & Drop handling, not so easy...
367 void bRestoreTable::mousePressEvent(QMouseEvent *event)
369 QTableWidget::mousePressEvent(event);
371 if (event->button() == Qt::LeftButton) {
372 dragStartPosition = event->pos();
376 // This event permits to send set custom data on drag&drop
377 // Don't forget to call original class if we are not interested
378 void bRestoreTable::mouseMoveEvent(QMouseEvent *event)
382 // Look just for drag&drop
383 if (!(event->buttons() & Qt::LeftButton)) {
384 QTableWidget::mouseMoveEvent(event);
387 if ((event->pos() - dragStartPosition).manhattanLength()
388 < QApplication::startDragDistance())
390 QTableWidget::mouseMoveEvent(event);
394 QList<QTableWidgetItem *> lst = selectedItems();
395 if (mainWin->m_miscDebug) qDebug() << this << " selectedItems: " << lst;
400 QDrag *drag = new QDrag(this);
401 QMimeData *mimeData = new QMimeData;
402 for (int i=0; i < lst.size(); i++) {
403 if (lastrow != lst[i]->row()) {
404 lastrow = lst[i]->row();
405 QTableWidgetItem *it = item(lastrow, 1);
406 mimeData->setText(it->data(Qt::UserRole).toString());
407 break; // at this time, we do it one by one
410 drag->setMimeData(mimeData);
414 // This event is called when the drag item enters in the destination area
415 void bRestoreTable::dragEnterEvent(QDragEnterEvent *event)
417 if (event->source() == this) {
421 if (event->mimeData()->hasText()) {
422 event->acceptProposedAction();
428 // It should not be essential to redefine this event, but it
429 // doesn't work if not defined
430 void bRestoreTable::dragMoveEvent(QDragMoveEvent *event)
432 if (event->mimeData()->hasText()) {
433 event->acceptProposedAction();
439 // When user releases the button
440 void bRestoreTable::dropEvent(QDropEvent *event)
445 if (event->mimeData()->hasText()) {
446 TableItemFormatter item(*this, rowCount());
447 setRowCount(rowCount() + 1);
448 QStringList fields = event->mimeData()->text().split("\t");
449 if (fields.size() != 6) {
453 if (fields.at(1) == "0") {
454 item.setFileType(0, "folder");
456 item.setTextFld(col++, fields.at(5)); // filename
457 decode_stat(fields.at(4).toLocal8Bit().data(),
458 &statp, sizeof(statp), &LinkFI);
459 item.setBytesFld(col++, QString().setNum(statp.st_size)); // size
460 item.setDateFld(col++, statp.st_mtime); // date
461 item.setNumericFld(col++, fields.at(3)); // jobid
462 item.setNumericFld(col++, fields.at(2)); // fileid
463 // keep original info on the first cel that is never empty
464 item.widget(1)->setData(Qt::UserRole, event->mimeData()->text());
465 event->acceptProposedAction();
471 // Use File Relocation bp
472 void bRunRestore::UFRcb()
474 if (UseFileRelocationChk->checkState() == Qt::Checked) {
475 WhereEntry->setEnabled(false);
476 UseRegexpChk->setEnabled(true);
477 if (UseRegexpChk->checkState() == Qt::Checked) {
478 AddSuffixEntry->setEnabled(false);
479 AddPrefixEntry->setEnabled(false);
480 StripPrefixEntry->setEnabled(false);
481 WhereRegexpEntry->setEnabled(true);
483 AddSuffixEntry->setEnabled(true);
484 AddPrefixEntry->setEnabled(true);
485 StripPrefixEntry->setEnabled(true);
486 WhereRegexpEntry->setEnabled(false);
489 WhereEntry->setEnabled(true);
490 AddSuffixEntry->setEnabled(false);
491 AddPrefixEntry->setEnabled(false);
492 StripPrefixEntry->setEnabled(false);
493 UseRegexpChk->setEnabled(false);
494 WhereRegexpEntry->setEnabled(false);
498 // Expert mode for file relocation
499 void bRunRestore::useRegexp()
501 if (UseRegexpChk->checkState() == Qt::Checked) {
502 AddSuffixEntry->setEnabled(false);
503 AddPrefixEntry->setEnabled(false);
504 StripPrefixEntry->setEnabled(false);
505 WhereRegexpEntry->setEnabled(true);
507 AddSuffixEntry->setEnabled(true);
508 AddPrefixEntry->setEnabled(true);
509 StripPrefixEntry->setEnabled(true);
510 WhereRegexpEntry->setEnabled(false);
514 // Display Form to run the restore job
515 bRunRestore::bRunRestore(bRestore *parent)
519 ClientCb->addItems(parent->console()->client_list);
520 int i = ClientCb->findText(parent->m_client);
522 ClientCb->setCurrentIndex(i);
524 StorageCb->addItem(QString(""));
525 RestoreCb->addItems(parent->console()->restore_list);
526 WhenEditor->setDateTime(QDateTime::currentDateTime());
527 StorageCb->addItems(parent->console()->storage_list);
528 connect(UseFileRelocationChk, SIGNAL(clicked()), this, SLOT(UFRcb()));
529 connect(UseRegexpChk, SIGNAL(clicked()), this, SLOT(useRegexp()));
530 connect(ActionBp, SIGNAL(accepted()), this, SLOT(computeRestore()));
531 // TODO: handle multiple restore job
532 struct job_defaults jd;
533 if (parent->console()->restore_list.size() > 0) {
534 jd.job_name = parent->console()->restore_list[0];
535 brestore->console()->get_job_defaults(jd);
536 WhereEntry->setText(jd.where);
541 void bRestore::get_info_from_selection(QStringList &fileids,
544 QStringList &findexes)
548 for (int i=0; i < RestoreList->rowCount(); i++) {
549 QTableWidgetItem *item = RestoreList->item(i, 1);
550 QString data = item->data(Qt::UserRole).toString();
551 QStringList lst = data.split("\t");
552 if (lst.at(1) != "0") { // skip path
553 fileids << lst.at(2);
555 decode_stat(lst.at(4).toLocal8Bit().data(),
556 &statp, sizeof(statp), &LinkFI);
558 findexes << lst.at(3) + "," + QString().setNum(LinkFI);
562 jobids << lst.at(3).split(","); // Can have multiple jobids
565 fileids.removeDuplicates();
566 jobids.removeDuplicates();
567 dirids.removeDuplicates();
568 findexes.removeDuplicates();
571 // To compute volume list with directories, query is much slower
572 void bRunRestore::computeVolumeList()
574 brestore->get_info_from_selection(m_fileids, m_jobids, m_dirids, m_findexes);
575 if (m_fileids.size() == 0) {
579 Freeze frz_lst(*TableMedia); /* disable updating*/
581 " SELECT DISTINCT VolumeName, Enabled, InChanger "
583 " ( " // -- Get all media from this job
584 " SELECT MIN(FirstIndex) AS FirstIndex, MAX(LastIndex) AS LastIndex, "
585 " VolumeName, Enabled, Inchanger "
586 " FROM JobMedia JOIN Media USING (MediaId) "
587 " WHERE JobId IN (" + m_jobids.join(",") + ") "
588 " GROUP BY VolumeName,Enabled,InChanger "
590 " WHERE File.FileId IN (" + m_fileids.join(",") + ") "
591 " AND File.FileIndex >= allmedia.FirstIndex "
592 " AND File.FileIndex <= allmedia.LastIndex ";
595 if (brestore->console()->sql_cmd(q, results)) {
596 QStringList fieldlist;
597 TableMedia->setRowCount(results.size());
598 /* Iterate through the record returned from the query */
599 foreach (QString resultline, results) {
601 //volname, enabled, inchanger
602 fieldlist = resultline.split("\t");
604 TableItemFormatter item(*TableMedia, row++);
605 item.setInChanger(col++, fieldlist.at(2)); // inchanger
606 item.setTextFld(col++, fieldlist.at(0)); // Volume
609 TableMedia->verticalHeader()->hide();
610 TableMedia->resizeColumnsToContents();
611 TableMedia->resizeRowsToContents();
612 TableMedia->setEditTriggers(QAbstractItemView::NoEditTriggers);
615 int64_t bRunRestore::runRestore(QString tablename)
620 tmp = ClientCb->currentText();
624 q = "restore client=" + tmp;
626 tmp = CommentEntry->text();
628 tmp.replace("\"", " ");
629 q += " comment=\"" + tmp + "\"";
632 tmp = StorageCb->currentText();
634 q += " storage=" + tmp;
637 if (UseFileRelocationChk->checkState() == Qt::Checked) {
638 if (UseRegexpChk->checkState() == Qt::Checked) {
639 tmp = WhereRegexpEntry->text();
641 tmp.replace("\"", "");
642 q += " regexwhere=\"" + tmp + "\"";
646 tmp = StripPrefixEntry->text();
648 tmp.replace("\"", "");
649 lst.append("!" + tmp + "!!i");
651 tmp = AddPrefixEntry->text();
653 tmp.replace("\"", "");
654 lst.append("!^!" + tmp + "!");
656 tmp = AddSuffixEntry->text();
658 tmp.replace("\"", "");
659 lst.append("!([^/])$!$1" + tmp + "!");
661 if (lst.size() > 0) {
662 q += " regexwhere=\"" + lst.join(",") + "\"";
666 tmp = WhereEntry->text();
668 tmp.replace("\"", "");
669 q += " where=\"" + tmp + "\"";
673 // q += " priority=" + tmp.setNum(PrioritySb->value());
674 // q += " job=\"" + RestoreCb->currentText() + "\"";
675 q += " file=\"?" + tablename + "\"";
676 q += " when=\"" + WhenEditor->dateTime().toString("yyyy-MM-dd hh:mm:ss") + "\"";
679 if (mainWin->m_miscDebug) qDebug() << q;
681 if (brestore->console()->dir_cmd(q, results)) {
682 foreach (QString resultline, results) {
683 QStringList fieldlist = resultline.split("=");
684 if (fieldlist.size() == 2) {
685 return fieldlist.at(1).toLongLong();
692 void bRunRestore::computeRestore()
694 QString q = ".bvfs_restore path=b2123 jobid=" + m_jobids.join(",");
695 if (m_fileids.size() > 0) {
696 q += " fileid=" + m_fileids.join(",");
698 if (m_dirids.size() > 0) {
699 q += " dirid=" + m_dirids.join(",");
701 if (m_findexes.size() > 0) {
702 q += " hardlink=" + m_findexes.join(",");
704 if (mainWin->m_miscDebug) qDebug() << q;
707 if (brestore->console()->dir_cmd(q, results)) {
708 if (results.size() == 1 && results[0] == "OK") {
709 int64_t jobid = runRestore("b2123");
710 if (mainWin->m_miscDebug) qDebug() << "jobid=" << jobid;
711 q = ".bvfs_cleanup path=b2123";
712 brestore->console()->dir_cmd(q, results);