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
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 three of the GNU Affero General Public
10 License as published by the Free Software Foundation and included
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.
18 You should have received a copy of the GNU Affero 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
23 Bacula® is a registered trademark of Kern Sibbald.
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.
31 * bRestore Class (Eric's brestore)
33 * Kern Sibbald, January MMVII
39 #include "util/fmtwidgetitem.h"
41 bRestore::bRestore() : Pages()
43 m_name = tr("bRestore");
47 QTreeWidgetItem* thisitem = mainWin->getFromHash(this);
48 thisitem->setIcon(0, QIcon(QString::fromUtf8(":images/browse.png")));
52 RestoreList->setAcceptDrops(true);
55 // Populate client table and job associated
56 void bRestore::setClient()
58 // Select the same client, don't touch
59 if (m_client == ClientList->currentText()) {
62 m_client = ClientList->currentText();
63 FileList->clearContents();
64 FileRevisions->clearContents();
66 JobList->setEnabled(true);
67 LocationEntry->clear();
71 if (ClientList->currentIndex() < 1) {
72 JobList->setEnabled(false);
76 JobList->addItem("Job list for " + m_client);
79 "SELECT Job.Jobid AS JobId, Job.StartTime AS StartTime,"
80 " Job.Level AS Level,"
82 " FROM Job JOIN Client USING (ClientId)"
84 " Job.JobStatus IN ('T','W') AND Job.Type='B' AND"
85 " Client.Name='" + m_client + "' ORDER BY StartTime DESC" ;
89 QStringList fieldlist;
90 if (m_console->sql_cmd(jobQuery, results)) {
91 /* Iterate through the record returned from the query */
92 foreach (QString resultline, results) {
94 // JobId, StartTime, Level, Name
95 fieldlist = resultline.split("\t");
96 job = fieldlist[1] + " " + fieldlist[3] + "(" + fieldlist[2] + ") " + fieldlist[0];
97 JobList->addItem(job, QVariant(fieldlist[0])); // set also private value
102 // Compute job associated and update the job cache if needed
103 void bRestore::setJob()
105 if (JobList->currentIndex() < 1) {
106 FileList->clearContents();
107 FileList->setRowCount(0);
108 FileRevisions->clearContents();
109 FileRevisions->setRowCount(0);
113 QVariant tmp = JobList->itemData(JobList->currentIndex(), Qt::UserRole);
115 m_jobids = tmp.toString();
116 QString cmd = ".bvfs_get_jobids jobid=" + m_jobids;
117 if (MergeChk->checkState() == Qt::Checked) {
121 m_console->dir_cmd(cmd, results);
123 if (results.size() < 1) {
124 FileList->clearContents();
125 FileList->setRowCount(0);
126 FileRevisions->clearContents();
127 FileRevisions->setRowCount(0);
131 // TODO: Can take some time if the job contains many dirs
132 m_jobids = results.at(0);
133 cmd = ".bvfs_update jobid=" + m_jobids;
134 m_console->dir_cmd(cmd, results);
136 Pmsg1(0, "jobids=%s\n", m_jobids.toLocal8Bit().constData());
138 displayFiles(m_pathid, QString(""));
139 Pmsg0(000, "update done\n");
142 extern int decode_stat(char *buf, struct stat *statp, int stat_size, int32_t *LinkFI);
144 // refresh button with a filter or limit/offset change
145 void bRestore::refreshView()
147 displayFiles(m_pathid, m_path);
150 void bRestore::displayFiles(int64_t pathid, QString path)
154 QStringList fieldlist;
159 Freeze frz_lst(*FileList); /* disable updating*/
160 Freeze frz_rev(*FileRevisions); /* disable updating*/
161 FileList->clearContents();
162 FileRevisions->clearContents();
163 FileRevisions->setRowCount(0);
165 // If we provide pathid, use it (path can be altered by encoding conversion)
167 arg = " pathid=" + QString().setNum(pathid);
169 // Choose .. update current path to parent dir
174 m_path.remove(QRegExp("[^/]+/$"));
177 } else if (path == "/" && m_path == "") {
180 } else if (path != "/" && path != ".") {
185 arg = " path=\"" + m_path + "\"";
188 // If a filter is set, add it to the current query
189 if (FilterEntry->text() != "") {
190 QString tmp = FilterEntry->text();
191 tmp.replace("\"", "."); // basic escape of "
192 arg += " pattern=\"" + tmp + "\"";
195 LocationEntry->setText(m_path);
196 QString offset = QString().setNum(Offset1Spin->value());
197 QString limit=QString().setNum(Offset2Spin->value() - Offset1Spin->value());
198 QString q = ".bvfs_lsdir jobid=" + m_jobids + arg
199 + " limit=" + limit + " offset=" + offset ;
200 if (mainWin->m_miscDebug) qDebug() << q;
201 if (m_console->dir_cmd(q, results)) {
203 FileList->setRowCount(nb);
204 foreach (QString resultline, results) {
206 //PathId, FilenameId, fileid, jobid, lstat, path
207 fieldlist = resultline.split("\t");
209 * Note, the next line zaps variable "item", probably
210 * because the input data in fieldlist is bad.
212 decode_stat(fieldlist.at(4).toLocal8Bit().data(), &statp, sizeof(statp), &LinkFI);
213 TableItemFormatter item(*FileList, row++);
214 item.setFileType(col++, QString("folder")); // folder or file
215 item.setTextFld(col++, fieldlist.at(5)); // path
216 item.setBytesFld(col++, QString().setNum(statp.st_size));
217 item.setDateFld(col++, statp.st_mtime); // date
218 fieldlist.replace(3, m_jobids); // use current jobids selection
219 // keep original info on the first cel that is never empty
220 item.widget(1)->setData(Qt::UserRole, fieldlist.join("\t"));
225 q = ".bvfs_lsfiles jobid=" + m_jobids + arg
226 + " limit=" + limit + " offset=" + offset ;
227 if (m_console->dir_cmd(q, results)) {
228 FileList->setRowCount(results.size() + nb);
229 foreach (QString resultline, results) {
230 int col=1; // skip icon
231 //PathId, FilenameId, fileid, jobid, lstat, name
232 fieldlist = resultline.split("\t");
233 TableItemFormatter item(*FileList, row++);
234 item.setTextFld(col++, fieldlist.at(5)); // name
235 decode_stat(fieldlist.at(4).toLocal8Bit().data(),
236 &statp, sizeof(statp), &LinkFI);
237 item.setBytesFld(col++, QString().setNum(statp.st_size));
238 item.setDateFld(col++, statp.st_mtime);
239 // keep original info on the first cel that is never empty
240 item.widget(1)->setData(Qt::UserRole, fieldlist.join("\t")); // keep info
243 FileList->verticalHeader()->hide();
244 FileList->resizeColumnsToContents();
245 FileList->resizeRowsToContents();
246 FileList->setEditTriggers(QAbstractItemView::NoEditTriggers);
249 void bRestore::PgSeltreeWidgetClicked()
254 if (!isOnceDocked()) {
259 // Display all versions of a file for this client
260 void bRestore::displayFileVersion(QString pathid, QString fnid,
261 QString client, QString filename)
266 Freeze frz_rev(*FileRevisions); /* disable updating*/
267 FileRevisions->clearContents();
269 QString q = ".bvfs_versions jobid=" + m_jobids +
270 " pathid=" + pathid +
274 if (VersionsChk->checkState() == Qt::Checked) {
275 q.append(" versions");
279 QStringList fieldlist;
281 if (m_console->dir_cmd(q, results)) {
282 FileRevisions->setRowCount(results.size());
283 foreach (QString resultline, results) {
286 //PathId, FilenameId, fileid, jobid, lstat, Md5, VolName, Inchanger
287 fieldlist = resultline.split("\t");
288 TableItemFormatter item(*FileRevisions, row++);
289 item.setInChanger(col++, fieldlist.at(7)); // inchanger
290 item.setTextFld(col++, fieldlist.at(6)); // Volume
291 item.setNumericFld(col++, fieldlist.at(3)); // JobId
292 decode_stat(fieldlist.at(4).toLocal8Bit().data(),
293 &statp, sizeof(statp), &LinkFI);
294 item.setBytesFld(col++, QString().setNum(statp.st_size)); // size
295 item.setDateFld(col++, statp.st_mtime); // date
296 item.setTextFld(col++, fieldlist.at(5)); // chksum
298 // Adjust the fieldlist for drag&drop
299 fieldlist.removeLast(); // inchanger
300 fieldlist.removeLast(); // volname
301 fieldlist.removeLast(); // md5
302 fieldlist << m_path + filename;
304 // keep original info on the first cel that is never empty
305 item.widget(1)->setData(Qt::UserRole, fieldlist.join("\t"));
308 FileRevisions->verticalHeader()->hide();
309 FileRevisions->resizeColumnsToContents();
310 FileRevisions->resizeRowsToContents();
311 FileRevisions->setEditTriggers(QAbstractItemView::NoEditTriggers);
314 void bRestore::showInfoForFile(QTableWidgetItem *widget)
317 QTableWidgetItem *first = FileList->item(widget->row(), 1);
318 QStringList lst = first->data(Qt::UserRole).toString().split("\t");
319 if (lst.at(1) == "0") { // no filenameid, should be a path
320 displayFiles(lst.at(0).toLongLong(), lst.at(5));
322 displayFileVersion(lst.at(0), lst.at(1), m_client, lst.at(5));
326 void bRestore::applyLocation()
328 displayFiles(0, LocationEntry->text());
331 void bRestore::clearVersions(QTableWidgetItem *item)
333 if (item != m_current) {
334 FileRevisions->clearContents();
335 FileRevisions->setRowCount(0);
340 void bRestore::clearRestoreList()
342 RestoreList->clearContents();
343 RestoreList->setRowCount(0);
346 void bRestore::runRestore()
348 bRunRestore *r = new bRunRestore(this);
352 void bRestore::setupPage()
354 ClientList->addItem("Client list");
355 ClientList->addItems(m_console->client_list);
356 connect(ClientList, SIGNAL(currentIndexChanged(int)), this, SLOT(setClient()));
357 connect(JobList, SIGNAL(currentIndexChanged(int)), this, SLOT(setJob()));
358 connect(FileList, SIGNAL(itemClicked(QTableWidgetItem*)),
359 this, SLOT(clearVersions(QTableWidgetItem *)));
360 connect(FileList, SIGNAL(itemDoubleClicked(QTableWidgetItem*)),
361 this, SLOT(showInfoForFile(QTableWidgetItem *)));
362 connect(LocationBp, SIGNAL(pressed()), this, SLOT(applyLocation()));
363 connect(MergeChk, SIGNAL(clicked()), this, SLOT(setJob()));
364 connect(ClearBp, SIGNAL(clicked()), this, SLOT(clearRestoreList()));
365 connect(RestoreBp, SIGNAL(clicked()), this, SLOT(runRestore()));
366 connect(FilterBp, SIGNAL(clicked()), this, SLOT(refreshView()));
370 bRestore::~bRestore()
374 // Drag & Drop handling, not so easy...
375 void bRestoreTable::mousePressEvent(QMouseEvent *event)
377 QTableWidget::mousePressEvent(event);
379 if (event->button() == Qt::LeftButton) {
380 dragStartPosition = event->pos();
384 // This event permits to send set custom data on drag&drop
385 // Don't forget to call original class if we are not interested
386 void bRestoreTable::mouseMoveEvent(QMouseEvent *event)
390 // Look just for drag&drop
391 if (!(event->buttons() & Qt::LeftButton)) {
392 QTableWidget::mouseMoveEvent(event);
395 if ((event->pos() - dragStartPosition).manhattanLength()
396 < QApplication::startDragDistance())
398 QTableWidget::mouseMoveEvent(event);
402 QList<QTableWidgetItem *> lst = selectedItems();
403 if (mainWin->m_miscDebug) qDebug() << this << " selectedItems: " << lst;
408 QDrag *drag = new QDrag(this);
409 QMimeData *mimeData = new QMimeData;
410 for (int i=0; i < lst.size(); i++) {
411 if (lastrow != lst[i]->row()) {
412 lastrow = lst[i]->row();
413 QTableWidgetItem *it = item(lastrow, 1);
414 mimeData->setText(it->data(Qt::UserRole).toString());
415 break; // at this time, we do it one by one
418 drag->setMimeData(mimeData);
422 // This event is called when the drag item enters in the destination area
423 void bRestoreTable::dragEnterEvent(QDragEnterEvent *event)
425 if (event->source() == this) {
429 if (event->mimeData()->hasText()) {
430 event->acceptProposedAction();
436 // It should not be essential to redefine this event, but it
437 // doesn't work if not defined
438 void bRestoreTable::dragMoveEvent(QDragMoveEvent *event)
440 if (event->mimeData()->hasText()) {
441 event->acceptProposedAction();
447 // When user releases the button
448 void bRestoreTable::dropEvent(QDropEvent *event)
453 if (event->mimeData()->hasText()) {
454 TableItemFormatter item(*this, rowCount());
455 setRowCount(rowCount() + 1);
456 QStringList fields = event->mimeData()->text().split("\t");
457 if (fields.size() != 6) {
461 if (fields.at(1) == "0") {
462 item.setFileType(0, "folder");
464 item.setTextFld(col++, fields.at(5)); // filename
465 decode_stat(fields.at(4).toLocal8Bit().data(),
466 &statp, sizeof(statp), &LinkFI);
467 item.setBytesFld(col++, QString().setNum(statp.st_size)); // size
468 item.setDateFld(col++, statp.st_mtime); // date
469 item.setNumericFld(col++, fields.at(3)); // jobid
470 item.setNumericFld(col++, fields.at(2)); // fileid
471 // keep original info on the first cel that is never empty
472 item.widget(1)->setData(Qt::UserRole, event->mimeData()->text());
473 event->acceptProposedAction();
479 // Use File Relocation bp
480 void bRunRestore::UFRcb()
482 if (UseFileRelocationChk->checkState() == Qt::Checked) {
483 WhereEntry->setEnabled(false);
484 UseRegexpChk->setEnabled(true);
485 if (UseRegexpChk->checkState() == Qt::Checked) {
486 AddSuffixEntry->setEnabled(false);
487 AddPrefixEntry->setEnabled(false);
488 StripPrefixEntry->setEnabled(false);
489 WhereRegexpEntry->setEnabled(true);
491 AddSuffixEntry->setEnabled(true);
492 AddPrefixEntry->setEnabled(true);
493 StripPrefixEntry->setEnabled(true);
494 WhereRegexpEntry->setEnabled(false);
497 WhereEntry->setEnabled(true);
498 AddSuffixEntry->setEnabled(false);
499 AddPrefixEntry->setEnabled(false);
500 StripPrefixEntry->setEnabled(false);
501 UseRegexpChk->setEnabled(false);
502 WhereRegexpEntry->setEnabled(false);
506 // Expert mode for file relocation
507 void bRunRestore::useRegexp()
509 if (UseRegexpChk->checkState() == Qt::Checked) {
510 AddSuffixEntry->setEnabled(false);
511 AddPrefixEntry->setEnabled(false);
512 StripPrefixEntry->setEnabled(false);
513 WhereRegexpEntry->setEnabled(true);
515 AddSuffixEntry->setEnabled(true);
516 AddPrefixEntry->setEnabled(true);
517 StripPrefixEntry->setEnabled(true);
518 WhereRegexpEntry->setEnabled(false);
522 // Display Form to run the restore job
523 bRunRestore::bRunRestore(bRestore *parent)
527 ClientCb->addItems(parent->console()->client_list);
528 int i = ClientCb->findText(parent->m_client);
530 ClientCb->setCurrentIndex(i);
532 StorageCb->addItem(QString(""));
533 RestoreCb->addItems(parent->console()->restore_list);
534 WhenEditor->setDateTime(QDateTime::currentDateTime());
535 StorageCb->addItems(parent->console()->storage_list);
536 connect(UseFileRelocationChk, SIGNAL(clicked()), this, SLOT(UFRcb()));
537 connect(UseRegexpChk, SIGNAL(clicked()), this, SLOT(useRegexp()));
538 connect(ActionBp, SIGNAL(accepted()), this, SLOT(computeRestore()));
539 // TODO: handle multiple restore job
540 struct job_defaults jd;
541 if (parent->console()->restore_list.size() > 0) {
542 jd.job_name = parent->console()->restore_list[0];
543 brestore->console()->get_job_defaults(jd);
544 WhereEntry->setText(jd.where);
549 void bRestore::get_info_from_selection(QStringList &fileids,
552 QStringList &findexes)
556 for (int i=0; i < RestoreList->rowCount(); i++) {
557 QTableWidgetItem *item = RestoreList->item(i, 1);
558 QString data = item->data(Qt::UserRole).toString();
559 QStringList lst = data.split("\t");
560 if (lst.at(1) != "0") { // skip path
561 fileids << lst.at(2);
563 decode_stat(lst.at(4).toLocal8Bit().data(),
564 &statp, sizeof(statp), &LinkFI);
566 findexes << lst.at(3) + "," + QString().setNum(LinkFI);
570 jobids << lst.at(3).split(","); // Can have multiple jobids
573 fileids.removeDuplicates();
574 jobids.removeDuplicates();
575 dirids.removeDuplicates();
576 findexes.removeDuplicates();
579 // To compute volume list with directories, query is much slower
580 void bRunRestore::computeVolumeList()
582 brestore->get_info_from_selection(m_fileids, m_jobids, m_dirids, m_findexes);
583 if (m_fileids.size() == 0) {
587 Freeze frz_lst(*TableMedia); /* disable updating*/
589 " SELECT DISTINCT VolumeName, Enabled, InChanger "
591 " ( " // -- Get all media from this job
592 " SELECT MIN(FirstIndex) AS FirstIndex, MAX(LastIndex) AS LastIndex, "
593 " VolumeName, Enabled, Inchanger "
594 " FROM JobMedia JOIN Media USING (MediaId) "
595 " WHERE JobId IN (" + m_jobids.join(",") + ") "
596 " GROUP BY VolumeName,Enabled,InChanger "
598 " WHERE File.FileId IN (" + m_fileids.join(",") + ") "
599 " AND File.FileIndex >= allmedia.FirstIndex "
600 " AND File.FileIndex <= allmedia.LastIndex ";
603 if (brestore->console()->sql_cmd(q, results)) {
604 QStringList fieldlist;
605 TableMedia->setRowCount(results.size());
606 /* Iterate through the record returned from the query */
607 foreach (QString resultline, results) {
609 //volname, enabled, inchanger
610 fieldlist = resultline.split("\t");
612 TableItemFormatter item(*TableMedia, row++);
613 item.setInChanger(col++, fieldlist.at(2)); // inchanger
614 item.setTextFld(col++, fieldlist.at(0)); // Volume
617 TableMedia->verticalHeader()->hide();
618 TableMedia->resizeColumnsToContents();
619 TableMedia->resizeRowsToContents();
620 TableMedia->setEditTriggers(QAbstractItemView::NoEditTriggers);
623 int64_t bRunRestore::runRestore(QString tablename)
628 tmp = ClientCb->currentText();
632 q = "restore client=" + tmp;
634 tmp = CommentEntry->text();
636 tmp.replace("\"", " ");
637 q += " comment=\"" + tmp + "\"";
640 tmp = StorageCb->currentText();
642 q += " storage=" + tmp;
645 if (UseFileRelocationChk->checkState() == Qt::Checked) {
646 if (UseRegexpChk->checkState() == Qt::Checked) {
647 tmp = WhereRegexpEntry->text();
649 tmp.replace("\"", "");
650 q += " regexwhere=\"" + tmp + "\"";
654 tmp = StripPrefixEntry->text();
656 tmp.replace("\"", "");
657 lst.append("!" + tmp + "!!i");
659 tmp = AddPrefixEntry->text();
661 tmp.replace("\"", "");
662 lst.append("!^!" + tmp + "!");
664 tmp = AddSuffixEntry->text();
666 tmp.replace("\"", "");
667 lst.append("!([^/])$!$1" + tmp + "!");
669 if (lst.size() > 0) {
670 q += " regexwhere=\"" + lst.join(",") + "\"";
674 tmp = WhereEntry->text();
676 tmp.replace("\"", "");
677 q += " where=\"" + tmp + "\"";
681 // q += " priority=" + tmp.setNum(PrioritySb->value());
682 // q += " job=\"" + RestoreCb->currentText() + "\"";
683 q += " file=\"?" + tablename + "\"";
684 q += " when=\"" + WhenEditor->dateTime().toString("yyyy-MM-dd hh:mm:ss") + "\"";
687 if (mainWin->m_miscDebug) qDebug() << q;
689 if (brestore->console()->dir_cmd(q, results)) {
690 foreach (QString resultline, results) {
691 QStringList fieldlist = resultline.split("=");
692 if (fieldlist.size() == 2) {
693 return fieldlist.at(1).toLongLong();
700 void bRunRestore::computeRestore()
702 QString q = ".bvfs_restore path=b2123 jobid=" + m_jobids.join(",");
703 if (m_fileids.size() > 0) {
704 q += " fileid=" + m_fileids.join(",");
706 if (m_dirids.size() > 0) {
707 q += " dirid=" + m_dirids.join(",");
709 if (m_findexes.size() > 0) {
710 q += " hardlink=" + m_findexes.join(",");
712 if (mainWin->m_miscDebug) qDebug() << q;
715 if (brestore->console()->dir_cmd(q, results)) {
716 if (results.size() == 1 && results[0] == "OK") {
717 int64_t jobid = runRestore("b2123");
718 if (mainWin->m_miscDebug) qDebug() << "jobid=" << jobid;
719 q = ".bvfs_cleanup path=b2123";
720 brestore->console()->dir_cmd(q, results);