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")));
51 RestoreList->setAcceptDrops(true);
54 // Populate client table and job associated
55 void bRestore::setClient()
57 // Select the same client, don't touch
58 if (m_client == ClientList->currentText()) {
61 m_client = ClientList->currentText();
62 FileList->clearContents();
63 FileRevisions->clearContents();
65 JobList->setEnabled(true);
66 LocationEntry->clear();
70 if (ClientList->currentIndex() < 1) {
71 JobList->setEnabled(false);
75 JobList->addItem("Job list for " + m_client);
78 "SELECT Job.Jobid AS JobId, Job.StartTime AS StartTime,"
79 " Job.Level AS Level,"
81 " FROM Job JOIN Client USING (ClientId)"
83 " Job.JobStatus IN ('T','W') AND Job.Type='B' AND"
84 " Client.Name='" + m_client + "' ORDER BY StartTime DESC" ;
88 QStringList fieldlist;
89 if (m_console->sql_cmd(jobQuery, results)) {
90 /* Iterate through the record returned from the query */
91 foreach (QString resultline, results) {
93 // JobId, StartTime, Level, Name
94 fieldlist = resultline.split("\t");
95 job = fieldlist[1] + " " + fieldlist[3] + "(" + fieldlist[2] + ") " + fieldlist[0];
96 JobList->addItem(job, QVariant(fieldlist[0])); // set also private value
101 // Compute job associated and update the job cache if needed
102 void bRestore::setJob()
104 if (JobList->currentIndex() < 1) {
105 FileList->clearContents();
106 FileList->setRowCount(0);
107 FileRevisions->clearContents();
108 FileRevisions->setRowCount(0);
112 QVariant tmp = JobList->itemData(JobList->currentIndex(), Qt::UserRole);
114 m_jobids = tmp.toString();
115 QString cmd = ".bvfs_get_jobids jobid=" + m_jobids;
116 if (MergeChk->checkState() == Qt::Checked) {
120 m_console->dir_cmd(cmd, results);
122 if (results.size() < 1) {
123 FileList->clearContents();
124 FileList->setRowCount(0);
125 FileRevisions->clearContents();
126 FileRevisions->setRowCount(0);
130 // TODO: Can take some time if the job contains many dirs
131 m_jobids = results.at(0);
132 cmd = ".bvfs_update jobid=" + m_jobids;
133 m_console->dir_cmd(cmd, results);
135 Pmsg1(0, "jobids=%s\n", m_jobids.toLocal8Bit().constData());
137 displayFiles(m_pathid, QString(""));
138 Pmsg0(000, "update done\n");
141 extern int decode_stat(char *buf, struct stat *statp, int32_t *LinkFI);
143 // refresh button with a filter or limit/offset change
144 void bRestore::refreshView()
146 displayFiles(m_pathid, m_path);
149 void bRestore::displayFiles(int64_t pathid, QString path)
153 QStringList fieldlist;
158 Freeze frz_lst(*FileList); /* disable updating*/
159 Freeze frz_rev(*FileRevisions); /* disable updating*/
160 FileList->clearContents();
161 FileRevisions->clearContents();
162 FileRevisions->setRowCount(0);
164 // If we provide pathid, use it (path can be altered by encoding conversion)
166 arg = " pathid=" + QString().setNum(pathid);
168 // Choose .. update current path to parent dir
173 m_path.remove(QRegExp("[^/]+/$"));
176 } else if (path == "/" && m_path == "") {
179 } else if (path != "/" && path != ".") {
184 arg = " path=\"" + m_path + "\"";
187 // If a filter is set, add it to the current query
188 if (FilterEntry->text() != "") {
189 QString tmp = FilterEntry->text();
190 tmp.replace("\"", "."); // basic escape of "
191 arg += " pattern=\"" + tmp + "\"";
194 LocationEntry->setText(m_path);
195 QString offset = QString().setNum(Offset1Spin->value());
196 QString limit=QString().setNum(Offset2Spin->value() - Offset1Spin->value());
197 QString q = ".bvfs_lsdir jobid=" + m_jobids + arg
198 + " limit=" + limit + " offset=" + offset ;
200 if (m_console->dir_cmd(q, results)) {
202 FileList->setRowCount(nb);
203 foreach (QString resultline, results) {
205 //PathId, FilenameId, fileid, jobid, lstat, path
206 fieldlist = resultline.split("\t");
207 TableItemFormatter item(*FileList, row++);
208 item.setFileType(col++, QString("folder")); // folder or file
209 item.setTextFld(col++, fieldlist.at(5)); // path
210 decode_stat(fieldlist.at(4).toLocal8Bit().data(),
212 item.setBytesFld(col++, QString().setNum(statp.st_size));
213 item.setDateFld(col++, statp.st_mtime); // date
214 fieldlist.replace(3, m_jobids); // use current jobids selection
215 // keep original info on the first cel that is never empty
216 item.widget(1)->setData(Qt::UserRole, fieldlist.join("\t"));
221 q = ".bvfs_lsfiles jobid=" + m_jobids + arg
222 + " limit=" + limit + " offset=" + offset ;
223 if (m_console->dir_cmd(q, results)) {
224 FileList->setRowCount(results.size() + nb);
225 foreach (QString resultline, results) {
226 int col=1; // skip icon
227 //PathId, FilenameId, fileid, jobid, lstat, name
228 fieldlist = resultline.split("\t");
229 TableItemFormatter item(*FileList, row++);
230 item.setTextFld(col++, fieldlist.at(5)); // name
231 decode_stat(fieldlist.at(4).toLocal8Bit().data(),
233 item.setBytesFld(col++, QString().setNum(statp.st_size));
234 item.setDateFld(col++, statp.st_mtime);
235 // keep original info on the first cel that is never empty
236 item.widget(1)->setData(Qt::UserRole, fieldlist.join("\t")); // keep info
239 FileList->verticalHeader()->hide();
240 FileList->resizeColumnsToContents();
241 FileList->resizeRowsToContents();
242 FileList->setEditTriggers(QAbstractItemView::NoEditTriggers);
245 void bRestore::PgSeltreeWidgetClicked()
250 if (!isOnceDocked()) {
255 // Display all versions of a file for this client
256 void bRestore::displayFileVersion(QString pathid, QString fnid,
257 QString client, QString filename)
262 Freeze frz_rev(*FileRevisions); /* disable updating*/
263 FileRevisions->clearContents();
265 QString q = ".bvfs_versions jobid=" + m_jobids +
266 " pathid=" + pathid +
270 if (VersionsChk->checkState() == Qt::Checked) {
271 q.append(" versions");
275 QStringList fieldlist;
277 if (m_console->dir_cmd(q, results)) {
278 FileRevisions->setRowCount(results.size());
279 foreach (QString resultline, results) {
282 //PathId, FilenameId, fileid, jobid, lstat, Md5, VolName, Inchanger
283 fieldlist = resultline.split("\t");
284 TableItemFormatter item(*FileRevisions, row++);
285 item.setInChanger(col++, fieldlist.at(7)); // inchanger
286 item.setTextFld(col++, fieldlist.at(6)); // Volume
287 item.setNumericFld(col++, fieldlist.at(3)); // JobId
288 decode_stat(fieldlist.at(4).toLocal8Bit().data(),
290 item.setBytesFld(col++, QString().setNum(statp.st_size)); // size
291 item.setDateFld(col++, statp.st_mtime); // date
292 item.setTextFld(col++, fieldlist.at(5)); // chksum
294 // Adjust the fieldlist for drag&drop
295 fieldlist.removeLast(); // inchanger
296 fieldlist.removeLast(); // volname
297 fieldlist.removeLast(); // md5
298 fieldlist << m_path + filename;
300 // keep original info on the first cel that is never empty
301 item.widget(1)->setData(Qt::UserRole, fieldlist.join("\t"));
304 FileRevisions->verticalHeader()->hide();
305 FileRevisions->resizeColumnsToContents();
306 FileRevisions->resizeRowsToContents();
307 FileRevisions->setEditTriggers(QAbstractItemView::NoEditTriggers);
310 void bRestore::showInfoForFile(QTableWidgetItem *widget)
313 QTableWidgetItem *first = FileList->item(widget->row(), 1);
314 QStringList lst = first->data(Qt::UserRole).toString().split("\t");
315 if (lst.at(1) == "0") { // no filenameid, should be a path
316 displayFiles(lst.at(0).toLongLong(), lst.at(5));
318 displayFileVersion(lst.at(0), lst.at(1), m_client, lst.at(5));
322 void bRestore::applyLocation()
324 displayFiles(0, LocationEntry->text());
327 void bRestore::clearVersions(QTableWidgetItem *item)
329 if (item != m_current) {
330 FileRevisions->clearContents();
331 FileRevisions->setRowCount(0);
336 void bRestore::clearRestoreList()
338 RestoreList->clearContents();
339 RestoreList->setRowCount(0);
342 void bRestore::runRestore()
344 bRunRestore *r = new bRunRestore(this);
348 void bRestore::setupPage()
350 ClientList->addItem("Client list");
351 ClientList->addItems(m_console->client_list);
352 connect(ClientList, SIGNAL(currentIndexChanged(int)), this, SLOT(setClient()));
353 connect(JobList, SIGNAL(currentIndexChanged(int)), this, SLOT(setJob()));
354 connect(FileList, SIGNAL(itemClicked(QTableWidgetItem*)),
355 this, SLOT(clearVersions(QTableWidgetItem *)));
356 connect(FileList, SIGNAL(itemDoubleClicked(QTableWidgetItem*)),
357 this, SLOT(showInfoForFile(QTableWidgetItem *)));
358 connect(LocationBp, SIGNAL(pressed()), this, SLOT(applyLocation()));
359 connect(MergeChk, SIGNAL(clicked()), this, SLOT(setJob()));
360 connect(ClearBp, SIGNAL(clicked()), this, SLOT(clearRestoreList()));
361 connect(RestoreBp, SIGNAL(clicked()), this, SLOT(runRestore()));
362 connect(FilterBp, SIGNAL(clicked()), this, SLOT(refreshView()));
366 bRestore::~bRestore()
370 // Drag & Drop handling, not so easy...
371 void bRestoreTable::mousePressEvent(QMouseEvent *event)
373 QTableWidget::mousePressEvent(event);
375 if (event->button() == Qt::LeftButton) {
376 dragStartPosition = event->pos();
380 // This event permits to send set custom data on drag&drop
381 // Don't forget to call original class if we are not interested
382 void bRestoreTable::mouseMoveEvent(QMouseEvent *event)
386 // Look just for drag&drop
387 if (!(event->buttons() & Qt::LeftButton)) {
388 QTableWidget::mouseMoveEvent(event);
391 if ((event->pos() - dragStartPosition).manhattanLength()
392 < QApplication::startDragDistance())
394 QTableWidget::mouseMoveEvent(event);
398 QList<QTableWidgetItem *> lst = selectedItems();
399 qDebug() << this << " selectedItems: " << lst;
404 QDrag *drag = new QDrag(this);
405 QMimeData *mimeData = new QMimeData;
406 for (int i=0; i < lst.size(); i++) {
407 if (lastrow != lst[i]->row()) {
408 lastrow = lst[i]->row();
409 QTableWidgetItem *it = item(lastrow, 1);
410 mimeData->setText(it->data(Qt::UserRole).toString());
411 break; // at this time, we do it one by one
414 drag->setMimeData(mimeData);
418 // This event is called when the drag item enters in the destination area
419 void bRestoreTable::dragEnterEvent(QDragEnterEvent *event)
421 if (event->source() == this) {
425 if (event->mimeData()->hasText()) {
426 event->acceptProposedAction();
432 // It should not be essential to redefine this event, but it
433 // doesn't work if not defined
434 void bRestoreTable::dragMoveEvent(QDragMoveEvent *event)
436 if (event->mimeData()->hasText()) {
437 event->acceptProposedAction();
443 // When user releases the button
444 void bRestoreTable::dropEvent(QDropEvent *event)
449 if (event->mimeData()->hasText()) {
450 TableItemFormatter item(*this, rowCount());
451 setRowCount(rowCount() + 1);
452 QStringList fields = event->mimeData()->text().split("\t");
453 if (fields.size() != 6) {
457 if (fields.at(1) == "0") {
458 item.setFileType(0, "folder");
460 item.setTextFld(col++, fields.at(5)); // filename
461 decode_stat(fields.at(4).toLocal8Bit().data(),
463 item.setBytesFld(col++, QString().setNum(statp.st_size)); // size
464 item.setDateFld(col++, statp.st_mtime); // date
465 item.setNumericFld(col++, fields.at(3)); // jobid
466 item.setNumericFld(col++, fields.at(2)); // fileid
467 // keep original info on the first cel that is never empty
468 item.widget(1)->setData(Qt::UserRole, event->mimeData()->text());
469 event->acceptProposedAction();
475 // Use File Relocation bp
476 void bRunRestore::UFRcb()
478 if (UseFileRelocationChk->checkState() == Qt::Checked) {
479 WhereEntry->setEnabled(false);
480 UseRegexpChk->setEnabled(true);
481 if (UseRegexpChk->checkState() == Qt::Checked) {
482 AddSuffixEntry->setEnabled(false);
483 AddPrefixEntry->setEnabled(false);
484 StripPrefixEntry->setEnabled(false);
485 WhereRegexpEntry->setEnabled(true);
487 AddSuffixEntry->setEnabled(true);
488 AddPrefixEntry->setEnabled(true);
489 StripPrefixEntry->setEnabled(true);
490 WhereRegexpEntry->setEnabled(false);
493 WhereEntry->setEnabled(true);
494 AddSuffixEntry->setEnabled(false);
495 AddPrefixEntry->setEnabled(false);
496 StripPrefixEntry->setEnabled(false);
497 UseRegexpChk->setEnabled(false);
498 WhereRegexpEntry->setEnabled(false);
502 // Expert mode for file relocation
503 void bRunRestore::useRegexp()
505 if (UseRegexpChk->checkState() == Qt::Checked) {
506 AddSuffixEntry->setEnabled(false);
507 AddPrefixEntry->setEnabled(false);
508 StripPrefixEntry->setEnabled(false);
509 WhereRegexpEntry->setEnabled(true);
511 AddSuffixEntry->setEnabled(true);
512 AddPrefixEntry->setEnabled(true);
513 StripPrefixEntry->setEnabled(true);
514 WhereRegexpEntry->setEnabled(false);
518 // Display Form to run the restore job
519 bRunRestore::bRunRestore(bRestore *parent)
523 ClientCb->addItems(parent->console()->client_list);
524 int i = ClientCb->findText(parent->m_client);
526 ClientCb->setCurrentIndex(i);
528 StorageCb->addItem(QString(""));
529 RestoreCb->addItems(parent->console()->restore_list);
530 WhenEditor->setDateTime(QDateTime::currentDateTime());
531 StorageCb->addItems(parent->console()->storage_list);
532 connect(UseFileRelocationChk, SIGNAL(clicked()), this, SLOT(UFRcb()));
533 connect(UseRegexpChk, SIGNAL(clicked()), this, SLOT(useRegexp()));
534 connect(ActionBp, SIGNAL(accepted()), this, SLOT(computeRestore()));
535 // TODO: handle multiple restore job
536 struct job_defaults jd;
537 if (parent->console()->restore_list.size() > 0) {
538 jd.job_name = parent->console()->restore_list[0];
539 brestore->console()->get_job_defaults(jd);
540 WhereEntry->setText(jd.where);
545 void bRestore::get_info_from_selection(QStringList &fileids,
548 QStringList &findexes)
552 for (int i=0; i < RestoreList->rowCount(); i++) {
553 QTableWidgetItem *item = RestoreList->item(i, 1);
554 QString data = item->data(Qt::UserRole).toString();
555 QStringList lst = data.split("\t");
556 if (lst.at(1) != "0") { // skip path
557 fileids << lst.at(2);
559 decode_stat(lst.at(4).toLocal8Bit().data(),
562 findexes << lst.at(3) + "," + QString().setNum(LinkFI);
566 jobids << lst.at(3).split(","); // Can have multiple jobids
569 fileids.removeDuplicates();
570 jobids.removeDuplicates();
571 dirids.removeDuplicates();
572 findexes.removeDuplicates();
575 // To compute volume list with directories, query is much slower
576 void bRunRestore::computeVolumeList()
578 brestore->get_info_from_selection(m_fileids, m_jobids, m_dirids, m_findexes);
579 if (m_fileids.size() == 0) {
583 Freeze frz_lst(*TableMedia); /* disable updating*/
585 " SELECT DISTINCT VolumeName, Enabled, InChanger "
587 " ( " // -- Get all media from this job
588 " SELECT MIN(FirstIndex) AS FirstIndex, MAX(LastIndex) AS LastIndex, "
589 " VolumeName, Enabled, Inchanger "
590 " FROM JobMedia JOIN Media USING (MediaId) "
591 " WHERE JobId IN (" + m_jobids.join(",") + ") "
592 " GROUP BY VolumeName,Enabled,InChanger "
594 " WHERE File.FileId IN (" + m_fileids.join(",") + ") "
595 " AND File.FileIndex >= allmedia.FirstIndex "
596 " AND File.FileIndex <= allmedia.LastIndex ";
599 if (brestore->console()->sql_cmd(q, results)) {
600 QStringList fieldlist;
601 TableMedia->setRowCount(results.size());
602 /* Iterate through the record returned from the query */
603 foreach (QString resultline, results) {
605 //volname, enabled, inchanger
606 fieldlist = resultline.split("\t");
608 TableItemFormatter item(*TableMedia, row++);
609 item.setInChanger(col++, fieldlist.at(2)); // inchanger
610 item.setTextFld(col++, fieldlist.at(0)); // Volume
613 TableMedia->verticalHeader()->hide();
614 TableMedia->resizeColumnsToContents();
615 TableMedia->resizeRowsToContents();
616 TableMedia->setEditTriggers(QAbstractItemView::NoEditTriggers);
619 int64_t bRunRestore::runRestore(QString tablename)
624 tmp = ClientCb->currentText();
628 q = "restore client=" + tmp;
630 tmp = CommentEntry->text();
632 tmp.replace("\"", " ");
633 q += " comment=\"" + tmp + "\"";
636 tmp = StorageCb->currentText();
638 q += " storage=" + tmp;
641 if (UseFileRelocationChk->checkState() == Qt::Checked) {
642 if (UseRegexpChk->checkState() == Qt::Checked) {
643 tmp = WhereRegexpEntry->text();
645 tmp.replace("\"", "");
646 q += " regexwhere=\"" + tmp + "\"";
650 tmp = StripPrefixEntry->text();
652 tmp.replace("\"", "");
653 lst.append("!" + tmp + "!!i");
655 tmp = AddPrefixEntry->text();
657 tmp.replace("\"", "");
658 lst.append("!^!" + tmp + "!");
660 tmp = AddSuffixEntry->text();
662 tmp.replace("\"", "");
663 lst.append("!([^/])$!$1" + tmp + "!");
665 if (lst.size() > 0) {
666 q += " regexwhere=\"" + lst.join(",") + "\"";
670 tmp = WhereEntry->text();
672 tmp.replace("\"", "");
673 q += " where=\"" + tmp + "\"";
677 // q += " priority=" + tmp.setNum(PrioritySb->value());
678 // q += " job=\"" + RestoreCb->currentText() + "\"";
679 q += " file=\"?" + tablename + "\"";
680 q += " when=\"" + WhenEditor->dateTime().toString("yyyy-MM-dd hh:mm:ss") + "\"";
685 if (brestore->console()->dir_cmd(q, results)) {
686 foreach (QString resultline, results) {
687 QStringList fieldlist = resultline.split("=");
688 if (fieldlist.size() == 2) {
689 return fieldlist.at(1).toLongLong();
696 void bRunRestore::computeRestore()
698 QString q = ".bvfs_restore path=b2123 jobid=" + m_jobids.join(",");
699 if (m_fileids.size() > 0) {
700 q += " fileid=" + m_fileids.join(",");
702 if (m_dirids.size() > 0) {
703 q += " dirid=" + m_dirids.join(",");
705 if (m_findexes.size() > 0) {
706 q += " hardlink=" + m_findexes.join(",");
711 if (brestore->console()->dir_cmd(q, results)) {
712 if (results.size() == 1 && results[0] == "OK") {
713 qDebug() << "jobid=" << runRestore("b2123");
714 q = ".bvfs_cleanup path=b2123";
715 brestore->console()->dir_cmd(q, results);