2 Bacula® - The Network Backup Solution
4 Copyright (C) 2007-2010 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"
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, 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 ;
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");
208 TableItemFormatter item(*FileList, row++);
209 item.setFileType(col++, QString("folder")); // folder or file
210 item.setTextFld(col++, fieldlist.at(5)); // path
211 decode_stat(fieldlist.at(4).toLocal8Bit().data(),
213 item.setBytesFld(col++, QString().setNum(statp.st_size));
214 item.setDateFld(col++, statp.st_mtime); // date
215 fieldlist.replace(3, m_jobids); // use current jobids selection
216 // keep original info on the first cel that is never empty
217 item.widget(1)->setData(Qt::UserRole, fieldlist.join("\t"));
222 q = ".bvfs_lsfiles jobid=" + m_jobids + arg
223 + " limit=" + limit + " offset=" + offset ;
224 if (m_console->dir_cmd(q, results)) {
225 FileList->setRowCount(results.size() + nb);
226 foreach (QString resultline, results) {
227 int col=1; // skip icon
228 //PathId, FilenameId, fileid, jobid, lstat, name
229 fieldlist = resultline.split("\t");
230 TableItemFormatter item(*FileList, row++);
231 item.setTextFld(col++, fieldlist.at(5)); // name
232 decode_stat(fieldlist.at(4).toLocal8Bit().data(),
234 item.setBytesFld(col++, QString().setNum(statp.st_size));
235 item.setDateFld(col++, statp.st_mtime);
236 // keep original info on the first cel that is never empty
237 item.widget(1)->setData(Qt::UserRole, fieldlist.join("\t")); // keep info
240 FileList->verticalHeader()->hide();
241 FileList->resizeColumnsToContents();
242 FileList->resizeRowsToContents();
243 FileList->setEditTriggers(QAbstractItemView::NoEditTriggers);
246 void bRestore::PgSeltreeWidgetClicked()
251 if (!isOnceDocked()) {
256 // Display all versions of a file for this client
257 void bRestore::displayFileVersion(QString pathid, QString fnid,
258 QString client, QString filename)
263 Freeze frz_rev(*FileRevisions); /* disable updating*/
264 FileRevisions->clearContents();
266 QString q = ".bvfs_versions jobid=" + m_jobids +
267 " pathid=" + pathid +
271 if (VersionsChk->checkState() == Qt::Checked) {
272 q.append(" versions");
276 QStringList fieldlist;
278 if (m_console->dir_cmd(q, results)) {
279 FileRevisions->setRowCount(results.size());
280 foreach (QString resultline, results) {
283 //PathId, FilenameId, fileid, jobid, lstat, Md5, VolName, Inchanger
284 fieldlist = resultline.split("\t");
285 TableItemFormatter item(*FileRevisions, row++);
286 item.setInChanger(col++, fieldlist.at(7)); // inchanger
287 item.setTextFld(col++, fieldlist.at(6)); // Volume
288 item.setNumericFld(col++, fieldlist.at(3)); // JobId
289 decode_stat(fieldlist.at(4).toLocal8Bit().data(),
291 item.setBytesFld(col++, QString().setNum(statp.st_size)); // size
292 item.setDateFld(col++, statp.st_mtime); // date
293 item.setTextFld(col++, fieldlist.at(5)); // chksum
295 // Adjust the fieldlist for drag&drop
296 fieldlist.removeLast(); // inchanger
297 fieldlist.removeLast(); // volname
298 fieldlist.removeLast(); // md5
299 fieldlist << m_path + filename;
301 // keep original info on the first cel that is never empty
302 item.widget(1)->setData(Qt::UserRole, fieldlist.join("\t"));
305 FileRevisions->verticalHeader()->hide();
306 FileRevisions->resizeColumnsToContents();
307 FileRevisions->resizeRowsToContents();
308 FileRevisions->setEditTriggers(QAbstractItemView::NoEditTriggers);
311 void bRestore::showInfoForFile(QTableWidgetItem *widget)
314 QTableWidgetItem *first = FileList->item(widget->row(), 1);
315 QStringList lst = first->data(Qt::UserRole).toString().split("\t");
316 if (lst.at(1) == "0") { // no filenameid, should be a path
317 displayFiles(lst.at(0).toLongLong(), lst.at(5));
319 displayFileVersion(lst.at(0), lst.at(1), m_client, lst.at(5));
323 void bRestore::applyLocation()
325 displayFiles(0, LocationEntry->text());
328 void bRestore::clearVersions(QTableWidgetItem *item)
330 if (item != m_current) {
331 FileRevisions->clearContents();
332 FileRevisions->setRowCount(0);
337 void bRestore::clearRestoreList()
339 RestoreList->clearContents();
340 RestoreList->setRowCount(0);
343 void bRestore::runRestore()
345 bRunRestore *r = new bRunRestore(this);
349 void bRestore::setupPage()
351 ClientList->addItem("Client list");
352 ClientList->addItems(m_console->client_list);
353 connect(ClientList, SIGNAL(currentIndexChanged(int)), this, SLOT(setClient()));
354 connect(JobList, SIGNAL(currentIndexChanged(int)), this, SLOT(setJob()));
355 connect(FileList, SIGNAL(itemClicked(QTableWidgetItem*)),
356 this, SLOT(clearVersions(QTableWidgetItem *)));
357 connect(FileList, SIGNAL(itemDoubleClicked(QTableWidgetItem*)),
358 this, SLOT(showInfoForFile(QTableWidgetItem *)));
359 connect(LocationBp, SIGNAL(pressed()), this, SLOT(applyLocation()));
360 connect(MergeChk, SIGNAL(clicked()), this, SLOT(setJob()));
361 connect(ClearBp, SIGNAL(clicked()), this, SLOT(clearRestoreList()));
362 connect(RestoreBp, SIGNAL(clicked()), this, SLOT(runRestore()));
363 connect(FilterBp, SIGNAL(clicked()), this, SLOT(refreshView()));
367 bRestore::~bRestore()
371 // Drag & Drop handling, not so easy...
372 void bRestoreTable::mousePressEvent(QMouseEvent *event)
374 QTableWidget::mousePressEvent(event);
376 if (event->button() == Qt::LeftButton) {
377 dragStartPosition = event->pos();
381 // This event permits to send set custom data on drag&drop
382 // Don't forget to call original class if we are not interested
383 void bRestoreTable::mouseMoveEvent(QMouseEvent *event)
387 // Look just for drag&drop
388 if (!(event->buttons() & Qt::LeftButton)) {
389 QTableWidget::mouseMoveEvent(event);
392 if ((event->pos() - dragStartPosition).manhattanLength()
393 < QApplication::startDragDistance())
395 QTableWidget::mouseMoveEvent(event);
399 QList<QTableWidgetItem *> lst = selectedItems();
400 qDebug() << this << " selectedItems: " << lst;
405 QDrag *drag = new QDrag(this);
406 QMimeData *mimeData = new QMimeData;
407 for (int i=0; i < lst.size(); i++) {
408 if (lastrow != lst[i]->row()) {
409 lastrow = lst[i]->row();
410 QTableWidgetItem *it = item(lastrow, 1);
411 mimeData->setText(it->data(Qt::UserRole).toString());
412 break; // at this time, we do it one by one
415 drag->setMimeData(mimeData);
419 // This event is called when the drag item enters in the destination area
420 void bRestoreTable::dragEnterEvent(QDragEnterEvent *event)
422 if (event->source() == this) {
426 if (event->mimeData()->hasText()) {
427 event->acceptProposedAction();
433 // It should not be essential to redefine this event, but it
434 // doesn't work if not defined
435 void bRestoreTable::dragMoveEvent(QDragMoveEvent *event)
437 if (event->mimeData()->hasText()) {
438 event->acceptProposedAction();
444 // When user releases the button
445 void bRestoreTable::dropEvent(QDropEvent *event)
450 if (event->mimeData()->hasText()) {
451 TableItemFormatter item(*this, rowCount());
452 setRowCount(rowCount() + 1);
453 QStringList fields = event->mimeData()->text().split("\t");
454 if (fields.size() != 6) {
458 if (fields.at(1) == "0") {
459 item.setFileType(0, "folder");
461 item.setTextFld(col++, fields.at(5)); // filename
462 decode_stat(fields.at(4).toLocal8Bit().data(),
464 item.setBytesFld(col++, QString().setNum(statp.st_size)); // size
465 item.setDateFld(col++, statp.st_mtime); // date
466 item.setNumericFld(col++, fields.at(3)); // jobid
467 item.setNumericFld(col++, fields.at(2)); // fileid
468 // keep original info on the first cel that is never empty
469 item.widget(1)->setData(Qt::UserRole, event->mimeData()->text());
470 event->acceptProposedAction();
476 // Use File Relocation bp
477 void bRunRestore::UFRcb()
479 if (UseFileRelocationChk->checkState() == Qt::Checked) {
480 WhereEntry->setEnabled(false);
481 UseRegexpChk->setEnabled(true);
482 if (UseRegexpChk->checkState() == Qt::Checked) {
483 AddSuffixEntry->setEnabled(false);
484 AddPrefixEntry->setEnabled(false);
485 StripPrefixEntry->setEnabled(false);
486 WhereRegexpEntry->setEnabled(true);
488 AddSuffixEntry->setEnabled(true);
489 AddPrefixEntry->setEnabled(true);
490 StripPrefixEntry->setEnabled(true);
491 WhereRegexpEntry->setEnabled(false);
494 WhereEntry->setEnabled(true);
495 AddSuffixEntry->setEnabled(false);
496 AddPrefixEntry->setEnabled(false);
497 StripPrefixEntry->setEnabled(false);
498 UseRegexpChk->setEnabled(false);
499 WhereRegexpEntry->setEnabled(false);
503 // Expert mode for file relocation
504 void bRunRestore::useRegexp()
506 if (UseRegexpChk->checkState() == Qt::Checked) {
507 AddSuffixEntry->setEnabled(false);
508 AddPrefixEntry->setEnabled(false);
509 StripPrefixEntry->setEnabled(false);
510 WhereRegexpEntry->setEnabled(true);
512 AddSuffixEntry->setEnabled(true);
513 AddPrefixEntry->setEnabled(true);
514 StripPrefixEntry->setEnabled(true);
515 WhereRegexpEntry->setEnabled(false);
519 // Display Form to run the restore job
520 bRunRestore::bRunRestore(bRestore *parent)
524 ClientCb->addItems(parent->console()->client_list);
525 int i = ClientCb->findText(parent->m_client);
527 ClientCb->setCurrentIndex(i);
529 StorageCb->addItem(QString(""));
530 RestoreCb->addItems(parent->console()->restore_list);
531 WhenEditor->setDateTime(QDateTime::currentDateTime());
532 StorageCb->addItems(parent->console()->storage_list);
533 connect(UseFileRelocationChk, SIGNAL(clicked()), this, SLOT(UFRcb()));
534 connect(UseRegexpChk, SIGNAL(clicked()), this, SLOT(useRegexp()));
535 connect(ActionBp, SIGNAL(accepted()), this, SLOT(computeRestore()));
536 // TODO: handle multiple restore job
537 struct job_defaults jd;
538 if (parent->console()->restore_list.size() > 0) {
539 jd.job_name = parent->console()->restore_list[0];
540 brestore->console()->get_job_defaults(jd);
541 WhereEntry->setText(jd.where);
546 void bRestore::get_info_from_selection(QStringList &fileids,
549 QStringList &findexes)
553 for (int i=0; i < RestoreList->rowCount(); i++) {
554 QTableWidgetItem *item = RestoreList->item(i, 1);
555 QString data = item->data(Qt::UserRole).toString();
556 QStringList lst = data.split("\t");
557 if (lst.at(1) != "0") { // skip path
558 fileids << lst.at(2);
560 decode_stat(lst.at(4).toLocal8Bit().data(),
563 findexes << lst.at(3) + "," + QString().setNum(LinkFI);
567 jobids << lst.at(3).split(","); // Can have multiple jobids
570 fileids.removeDuplicates();
571 jobids.removeDuplicates();
572 dirids.removeDuplicates();
573 findexes.removeDuplicates();
576 // To compute volume list with directories, query is much slower
577 void bRunRestore::computeVolumeList()
579 brestore->get_info_from_selection(m_fileids, m_jobids, m_dirids, m_findexes);
580 if (m_fileids.size() == 0) {
584 Freeze frz_lst(*TableMedia); /* disable updating*/
586 " SELECT DISTINCT VolumeName, Enabled, InChanger "
588 " ( " // -- Get all media from this job
589 " SELECT MIN(FirstIndex) AS FirstIndex, MAX(LastIndex) AS LastIndex, "
590 " VolumeName, Enabled, Inchanger "
591 " FROM JobMedia JOIN Media USING (MediaId) "
592 " WHERE JobId IN (" + m_jobids.join(",") + ") "
593 " GROUP BY VolumeName,Enabled,InChanger "
595 " WHERE File.FileId IN (" + m_fileids.join(",") + ") "
596 " AND File.FileIndex >= allmedia.FirstIndex "
597 " AND File.FileIndex <= allmedia.LastIndex ";
600 if (brestore->console()->sql_cmd(q, results)) {
601 QStringList fieldlist;
602 TableMedia->setRowCount(results.size());
603 /* Iterate through the record returned from the query */
604 foreach (QString resultline, results) {
606 //volname, enabled, inchanger
607 fieldlist = resultline.split("\t");
609 TableItemFormatter item(*TableMedia, row++);
610 item.setInChanger(col++, fieldlist.at(2)); // inchanger
611 item.setTextFld(col++, fieldlist.at(0)); // Volume
614 TableMedia->verticalHeader()->hide();
615 TableMedia->resizeColumnsToContents();
616 TableMedia->resizeRowsToContents();
617 TableMedia->setEditTriggers(QAbstractItemView::NoEditTriggers);
620 int64_t bRunRestore::runRestore(QString tablename)
625 tmp = ClientCb->currentText();
629 q = "restore client=" + tmp;
631 tmp = CommentEntry->text();
633 tmp.replace("\"", " ");
634 q += " comment=\"" + tmp + "\"";
637 tmp = StorageCb->currentText();
639 q += " storage=" + tmp;
642 if (UseFileRelocationChk->checkState() == Qt::Checked) {
643 if (UseRegexpChk->checkState() == Qt::Checked) {
644 tmp = WhereRegexpEntry->text();
646 tmp.replace("\"", "");
647 q += " regexwhere=\"" + tmp + "\"";
651 tmp = StripPrefixEntry->text();
653 tmp.replace("\"", "");
654 lst.append("!" + tmp + "!!i");
656 tmp = AddPrefixEntry->text();
658 tmp.replace("\"", "");
659 lst.append("!^!" + tmp + "!");
661 tmp = AddSuffixEntry->text();
663 tmp.replace("\"", "");
664 lst.append("!([^/])$!$1" + tmp + "!");
666 if (lst.size() > 0) {
667 q += " regexwhere=\"" + lst.join(",") + "\"";
671 tmp = WhereEntry->text();
673 tmp.replace("\"", "");
674 q += " where=\"" + tmp + "\"";
678 // q += " priority=" + tmp.setNum(PrioritySb->value());
679 // q += " job=\"" + RestoreCb->currentText() + "\"";
680 q += " file=\"?" + tablename + "\"";
681 q += " when=\"" + WhenEditor->dateTime().toString("yyyy-MM-dd hh:mm:ss") + "\"";
686 if (brestore->console()->dir_cmd(q, results)) {
687 foreach (QString resultline, results) {
688 QStringList fieldlist = resultline.split("=");
689 if (fieldlist.size() == 2) {
690 return fieldlist.at(1).toLongLong();
697 void bRunRestore::computeRestore()
699 QString q = ".bvfs_restore path=b2123 jobid=" + m_jobids.join(",");
700 if (m_fileids.size() > 0) {
701 q += " fileid=" + m_fileids.join(",");
703 if (m_dirids.size() > 0) {
704 q += " dirid=" + m_dirids.join(",");
706 if (m_findexes.size() > 0) {
707 q += " hardlink=" + m_findexes.join(",");
712 if (brestore->console()->dir_cmd(q, results)) {
713 if (results.size() == 1 && results[0] == "OK") {
714 qDebug() << "jobid=" << runRestore("b2123");
715 q = ".bvfs_cleanup path=b2123";
716 brestore->console()->dir_cmd(q, results);