X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=bacula%2Fsrc%2Fqt-console%2Frestore%2Fbrestore.cpp;h=baa3a27479efcbde38d2c06b7b29e2539ea43611;hb=23f1411adf9a9afc8259ca284b89c5135bd2e426;hp=7a4310076a7aa27fc7ede81a20d481591795487f;hpb=095ae896b3fa96e6c8db2105519552b785e6b005;p=bacula%2Fbacula diff --git a/bacula/src/qt-console/restore/brestore.cpp b/bacula/src/qt-console/restore/brestore.cpp index 7a4310076a..baa3a27479 100644 --- a/bacula/src/qt-console/restore/brestore.cpp +++ b/bacula/src/qt-console/restore/brestore.cpp @@ -1,12 +1,12 @@ /* Bacula® - The Network Backup Solution - Copyright (C) 2007-2007 Free Software Foundation Europe e.V. + Copyright (C) 2007-2010 Free Software Foundation Europe e.V. The main author of Bacula is Kern Sibbald, with contributions from many others, a complete list can be found in the file AUTHORS. This program is Free Software; you can redistribute it and/or - modify it under the terms of version two of the GNU General Public + modify it under the terms of version three of the GNU Affero General Public License as published by the Free Software Foundation and included in the file LICENSE. @@ -15,19 +15,18 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License + You should have received a copy of the GNU Affero General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - Bacula® is a registered trademark of John Walker. + Bacula® is a registered trademark of Kern Sibbald. The licensor of Bacula is the Free Software Foundation Europe (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich, Switzerland, email:ftf@fsfeurope.org. */ /* - * Version $Id$ * * bRestore Class (Eric's brestore) * @@ -37,16 +36,685 @@ #include "bat.h" #include "restore.h" +#include "util/fmtwidgetitem.h" bRestore::bRestore() { - m_name = "bRestore"; + m_name = tr("bRestore"); + m_client = ""; setupUi(this); pgInitialize(); - m_closeable = true; - dockPage(); + QTreeWidgetItem* thisitem = mainWin->getFromHash(this); + thisitem->setIcon(0, QIcon(QString::fromUtf8(":images/browse.png"))); + m_populated = false; + m_closeable = false; + m_current = NULL; + RestoreList->setAcceptDrops(true); +} + +// Populate client table and job associated +void bRestore::setClient() +{ + // Select the same client, don't touch + if (m_client == ClientList->currentText()) { + return; + } + m_client = ClientList->currentText(); + FileList->clearContents(); + FileRevisions->clearContents(); + JobList->clear(); + JobList->setEnabled(true); + LocationEntry->clear(); + m_path = ""; + m_pathid = 0; + + if (ClientList->currentIndex() < 1) { + JobList->setEnabled(false); + return; + } + + JobList->addItem("Job list for " + m_client); + + QString jobQuery = + "SELECT Job.Jobid AS JobId, Job.StartTime AS StartTime," + " Job.Level AS Level," + " Job.Name AS Name" + " FROM Job JOIN Client USING (ClientId)" + " WHERE" + " Job.JobStatus IN ('T','W') AND Job.Type='B' AND" + " Client.Name='" + m_client + "' ORDER BY StartTime DESC" ; + + QString job; + QStringList results; + QStringList fieldlist; + if (m_console->sql_cmd(jobQuery, results)) { + /* Iterate through the record returned from the query */ + foreach (QString resultline, results) { + // 0 1 2 3 + // JobId, StartTime, Level, Name + fieldlist = resultline.split("\t"); + job = fieldlist[1] + " " + fieldlist[3] + "(" + fieldlist[2] + ") " + fieldlist[0]; + JobList->addItem(job, QVariant(fieldlist[0])); // set also private value + } + } +} + +// Compute job associated and update the job cache if needed +void bRestore::setJob() +{ + if (JobList->currentIndex() < 1) { + FileList->clearContents(); + FileList->setRowCount(0); + FileRevisions->clearContents(); + FileRevisions->setRowCount(0); + return ; + } + QStringList results; + QVariant tmp = JobList->itemData(JobList->currentIndex(), Qt::UserRole); + + m_jobids = tmp.toString(); + QString cmd = ".bvfs_get_jobids jobid=" + m_jobids; + if (MergeChk->checkState() == Qt::Checked) { + cmd.append(" all"); + } + + m_console->dir_cmd(cmd, results); + + if (results.size() < 1) { + FileList->clearContents(); + FileList->setRowCount(0); + FileRevisions->clearContents(); + FileRevisions->setRowCount(0); + return; + } + + // TODO: Can take some time if the job contains many dirs + m_jobids = results.at(0); + cmd = ".bvfs_update jobid=" + m_jobids; + m_console->dir_cmd(cmd, results); + + Pmsg1(0, "jobids=%s\n", m_jobids.toLocal8Bit().constData()); + + displayFiles(m_pathid, QString("")); + Pmsg0(000, "update done\n"); +} + +extern int decode_stat(char *buf, struct stat *statp, int32_t *LinkFI); + +// refresh button with a filter or limit/offset change +void bRestore::refreshView() +{ + displayFiles(m_pathid, m_path); +} + +void bRestore::displayFiles(int64_t pathid, QString path) +{ + QString arg; + QStringList results; + QStringList fieldlist; + struct stat statp; + int32_t LinkFI; + int nb = 0; + int row = 0; + Freeze frz_lst(*FileList); /* disable updating*/ + Freeze frz_rev(*FileRevisions); /* disable updating*/ + FileList->clearContents(); + FileRevisions->clearContents(); + FileRevisions->setRowCount(0); + + // If we provide pathid, use it (path can be altered by encoding conversion) + if (pathid > 0) { + arg = " pathid=" + QString().setNum(pathid); + + // Choose .. update current path to parent dir + if (path == "..") { + if (m_path == "/") { + m_path = ""; + } else { + m_path.remove(QRegExp("[^/]+/$")); + } + + } else if (path == "/" && m_path == "") { + m_path += path; + + } else if (path != "/" && path != ".") { + m_path += path; + } + } else { + m_path = path; + arg = " path=\"" + m_path + "\""; + } + + // If a filter is set, add it to the current query + if (FilterEntry->text() != "") { + QString tmp = FilterEntry->text(); + tmp.replace("\"", "."); // basic escape of " + arg += " pattern=\"" + tmp + "\""; + } + + LocationEntry->setText(m_path); + QString offset = QString().setNum(Offset1Spin->value()); + QString limit=QString().setNum(Offset2Spin->value() - Offset1Spin->value()); + QString q = ".bvfs_lsdir jobid=" + m_jobids + arg + + " limit=" + limit + " offset=" + offset ; + if (mainWin->m_miscDebug) qDebug() << q; + if (m_console->dir_cmd(q, results)) { + nb = results.size(); + FileList->setRowCount(nb); + foreach (QString resultline, results) { + int col=0; + //PathId, FilenameId, fileid, jobid, lstat, path + fieldlist = resultline.split("\t"); + TableItemFormatter item(*FileList, row++); + item.setFileType(col++, QString("folder")); // folder or file + item.setTextFld(col++, fieldlist.at(5)); // path + decode_stat(fieldlist.at(4).toLocal8Bit().data(), + &statp, &LinkFI); + item.setBytesFld(col++, QString().setNum(statp.st_size)); + item.setDateFld(col++, statp.st_mtime); // date + fieldlist.replace(3, m_jobids); // use current jobids selection + // keep original info on the first cel that is never empty + item.widget(1)->setData(Qt::UserRole, fieldlist.join("\t")); + } + } + + results.clear(); + q = ".bvfs_lsfiles jobid=" + m_jobids + arg + + " limit=" + limit + " offset=" + offset ; + if (m_console->dir_cmd(q, results)) { + FileList->setRowCount(results.size() + nb); + foreach (QString resultline, results) { + int col=1; // skip icon + //PathId, FilenameId, fileid, jobid, lstat, name + fieldlist = resultline.split("\t"); + TableItemFormatter item(*FileList, row++); + item.setTextFld(col++, fieldlist.at(5)); // name + decode_stat(fieldlist.at(4).toLocal8Bit().data(), + &statp, &LinkFI); + item.setBytesFld(col++, QString().setNum(statp.st_size)); + item.setDateFld(col++, statp.st_mtime); + // keep original info on the first cel that is never empty + item.widget(1)->setData(Qt::UserRole, fieldlist.join("\t")); // keep info + } + } + FileList->verticalHeader()->hide(); + FileList->resizeColumnsToContents(); + FileList->resizeRowsToContents(); + FileList->setEditTriggers(QAbstractItemView::NoEditTriggers); +} + +void bRestore::PgSeltreeWidgetClicked() +{ + if(!m_populated) { + setupPage(); + } + if (!isOnceDocked()) { + dockPage(); + } +} + +// Display all versions of a file for this client +void bRestore::displayFileVersion(QString pathid, QString fnid, + QString client, QString filename) +{ + int row=0; + struct stat statp; + int32_t LinkFI; + Freeze frz_rev(*FileRevisions); /* disable updating*/ + FileRevisions->clearContents(); + + QString q = ".bvfs_versions jobid=" + m_jobids + + " pathid=" + pathid + + " fnid=" + fnid + + " client=" + client; + + if (VersionsChk->checkState() == Qt::Checked) { + q.append(" versions"); + } + + QStringList results; + QStringList fieldlist; + QString tmp; + if (m_console->dir_cmd(q, results)) { + FileRevisions->setRowCount(results.size()); + foreach (QString resultline, results) { + int col=0; + // 0 1 2 3 4 5 6 7 + //PathId, FilenameId, fileid, jobid, lstat, Md5, VolName, Inchanger + fieldlist = resultline.split("\t"); + TableItemFormatter item(*FileRevisions, row++); + item.setInChanger(col++, fieldlist.at(7)); // inchanger + item.setTextFld(col++, fieldlist.at(6)); // Volume + item.setNumericFld(col++, fieldlist.at(3)); // JobId + decode_stat(fieldlist.at(4).toLocal8Bit().data(), + &statp, &LinkFI); + item.setBytesFld(col++, QString().setNum(statp.st_size)); // size + item.setDateFld(col++, statp.st_mtime); // date + item.setTextFld(col++, fieldlist.at(5)); // chksum + + // Adjust the fieldlist for drag&drop + fieldlist.removeLast(); // inchanger + fieldlist.removeLast(); // volname + fieldlist.removeLast(); // md5 + fieldlist << m_path + filename; + + // keep original info on the first cel that is never empty + item.widget(1)->setData(Qt::UserRole, fieldlist.join("\t")); + } + } + FileRevisions->verticalHeader()->hide(); + FileRevisions->resizeColumnsToContents(); + FileRevisions->resizeRowsToContents(); + FileRevisions->setEditTriggers(QAbstractItemView::NoEditTriggers); +} + +void bRestore::showInfoForFile(QTableWidgetItem *widget) +{ + m_current = widget; + QTableWidgetItem *first = FileList->item(widget->row(), 1); + QStringList lst = first->data(Qt::UserRole).toString().split("\t"); + if (lst.at(1) == "0") { // no filenameid, should be a path + displayFiles(lst.at(0).toLongLong(), lst.at(5)); + } else { + displayFileVersion(lst.at(0), lst.at(1), m_client, lst.at(5)); + } +} + +void bRestore::applyLocation() +{ + displayFiles(0, LocationEntry->text()); +} + +void bRestore::clearVersions(QTableWidgetItem *item) +{ + if (item != m_current) { + FileRevisions->clearContents(); + FileRevisions->setRowCount(0); + } + m_current = item ; +} + +void bRestore::clearRestoreList() +{ + RestoreList->clearContents(); + RestoreList->setRowCount(0); +} + +void bRestore::runRestore() +{ + bRunRestore *r = new bRunRestore(this); + r->setVisible(true); +} + +void bRestore::setupPage() +{ + ClientList->addItem("Client list"); + ClientList->addItems(m_console->client_list); + connect(ClientList, SIGNAL(currentIndexChanged(int)), this, SLOT(setClient())); + connect(JobList, SIGNAL(currentIndexChanged(int)), this, SLOT(setJob())); + connect(FileList, SIGNAL(itemClicked(QTableWidgetItem*)), + this, SLOT(clearVersions(QTableWidgetItem *))); + connect(FileList, SIGNAL(itemDoubleClicked(QTableWidgetItem*)), + this, SLOT(showInfoForFile(QTableWidgetItem *))); + connect(LocationBp, SIGNAL(pressed()), this, SLOT(applyLocation())); + connect(MergeChk, SIGNAL(clicked()), this, SLOT(setJob())); + connect(ClearBp, SIGNAL(clicked()), this, SLOT(clearRestoreList())); + connect(RestoreBp, SIGNAL(clicked()), this, SLOT(runRestore())); + connect(FilterBp, SIGNAL(clicked()), this, SLOT(refreshView())); + m_populated = true; } bRestore::~bRestore() { } + +// Drag & Drop handling, not so easy... +void bRestoreTable::mousePressEvent(QMouseEvent *event) +{ + QTableWidget::mousePressEvent(event); + + if (event->button() == Qt::LeftButton) { + dragStartPosition = event->pos(); + } +} + +// This event permits to send set custom data on drag&drop +// Don't forget to call original class if we are not interested +void bRestoreTable::mouseMoveEvent(QMouseEvent *event) +{ + int lastrow=-1; + + // Look just for drag&drop + if (!(event->buttons() & Qt::LeftButton)) { + QTableWidget::mouseMoveEvent(event); + return; + } + if ((event->pos() - dragStartPosition).manhattanLength() + < QApplication::startDragDistance()) + { + QTableWidget::mouseMoveEvent(event); + return; + } + + QList lst = selectedItems(); + if (mainWin->m_miscDebug) qDebug() << this << " selectedItems: " << lst; + if (lst.isEmpty()) { + return; + } + + QDrag *drag = new QDrag(this); + QMimeData *mimeData = new QMimeData; + for (int i=0; i < lst.size(); i++) { + if (lastrow != lst[i]->row()) { + lastrow = lst[i]->row(); + QTableWidgetItem *it = item(lastrow, 1); + mimeData->setText(it->data(Qt::UserRole).toString()); + break; // at this time, we do it one by one + } + } + drag->setMimeData(mimeData); + drag->exec(); +} + +// This event is called when the drag item enters in the destination area +void bRestoreTable::dragEnterEvent(QDragEnterEvent *event) +{ + if (event->source() == this) { + event->ignore(); + return; + } + if (event->mimeData()->hasText()) { + event->acceptProposedAction(); + } else { + event->ignore(); + } +} + +// It should not be essential to redefine this event, but it +// doesn't work if not defined +void bRestoreTable::dragMoveEvent(QDragMoveEvent *event) +{ + if (event->mimeData()->hasText()) { + event->acceptProposedAction(); + } else { + event->ignore(); + } +} + +// When user releases the button +void bRestoreTable::dropEvent(QDropEvent *event) +{ + int col=1; + struct stat statp; + int32_t LinkFI; + if (event->mimeData()->hasText()) { + TableItemFormatter item(*this, rowCount()); + setRowCount(rowCount() + 1); + QStringList fields = event->mimeData()->text().split("\t"); + if (fields.size() != 6) { + event->ignore(); + return; + } + if (fields.at(1) == "0") { + item.setFileType(0, "folder"); + } + item.setTextFld(col++, fields.at(5)); // filename + decode_stat(fields.at(4).toLocal8Bit().data(), + &statp, &LinkFI); + item.setBytesFld(col++, QString().setNum(statp.st_size)); // size + item.setDateFld(col++, statp.st_mtime); // date + item.setNumericFld(col++, fields.at(3)); // jobid + item.setNumericFld(col++, fields.at(2)); // fileid + // keep original info on the first cel that is never empty + item.widget(1)->setData(Qt::UserRole, event->mimeData()->text()); + event->acceptProposedAction(); + } else { + event->ignore(); + } +} + +// Use File Relocation bp +void bRunRestore::UFRcb() +{ + if (UseFileRelocationChk->checkState() == Qt::Checked) { + WhereEntry->setEnabled(false); + UseRegexpChk->setEnabled(true); + if (UseRegexpChk->checkState() == Qt::Checked) { + AddSuffixEntry->setEnabled(false); + AddPrefixEntry->setEnabled(false); + StripPrefixEntry->setEnabled(false); + WhereRegexpEntry->setEnabled(true); + } else { + AddSuffixEntry->setEnabled(true); + AddPrefixEntry->setEnabled(true); + StripPrefixEntry->setEnabled(true); + WhereRegexpEntry->setEnabled(false); + } + } else { + WhereEntry->setEnabled(true); + AddSuffixEntry->setEnabled(false); + AddPrefixEntry->setEnabled(false); + StripPrefixEntry->setEnabled(false); + UseRegexpChk->setEnabled(false); + WhereRegexpEntry->setEnabled(false); + } +} + +// Expert mode for file relocation +void bRunRestore::useRegexp() +{ + if (UseRegexpChk->checkState() == Qt::Checked) { + AddSuffixEntry->setEnabled(false); + AddPrefixEntry->setEnabled(false); + StripPrefixEntry->setEnabled(false); + WhereRegexpEntry->setEnabled(true); + } else { + AddSuffixEntry->setEnabled(true); + AddPrefixEntry->setEnabled(true); + StripPrefixEntry->setEnabled(true); + WhereRegexpEntry->setEnabled(false); + } +} + +// Display Form to run the restore job +bRunRestore::bRunRestore(bRestore *parent) +{ + brestore = parent; + setupUi(this); + ClientCb->addItems(parent->console()->client_list); + int i = ClientCb->findText(parent->m_client); + if (i >= 0) { + ClientCb->setCurrentIndex(i); + } + StorageCb->addItem(QString("")); + RestoreCb->addItems(parent->console()->restore_list); + WhenEditor->setDateTime(QDateTime::currentDateTime()); + StorageCb->addItems(parent->console()->storage_list); + connect(UseFileRelocationChk, SIGNAL(clicked()), this, SLOT(UFRcb())); + connect(UseRegexpChk, SIGNAL(clicked()), this, SLOT(useRegexp())); + connect(ActionBp, SIGNAL(accepted()), this, SLOT(computeRestore())); + // TODO: handle multiple restore job + struct job_defaults jd; + if (parent->console()->restore_list.size() > 0) { + jd.job_name = parent->console()->restore_list[0]; + brestore->console()->get_job_defaults(jd); + WhereEntry->setText(jd.where); + } + computeVolumeList(); +} + +void bRestore::get_info_from_selection(QStringList &fileids, + QStringList &jobids, + QStringList &dirids, + QStringList &findexes) +{ + struct stat statp; + int32_t LinkFI; + for (int i=0; i < RestoreList->rowCount(); i++) { + QTableWidgetItem *item = RestoreList->item(i, 1); + QString data = item->data(Qt::UserRole).toString(); + QStringList lst = data.split("\t"); + if (lst.at(1) != "0") { // skip path + fileids << lst.at(2); + jobids << lst.at(3); + decode_stat(lst.at(4).toLocal8Bit().data(), + &statp, &LinkFI); + if (LinkFI) { + findexes << lst.at(3) + "," + QString().setNum(LinkFI); + } + } else { + dirids << lst.at(0); + jobids << lst.at(3).split(","); // Can have multiple jobids + } + } + fileids.removeDuplicates(); + jobids.removeDuplicates(); + dirids.removeDuplicates(); + findexes.removeDuplicates(); +} + +// To compute volume list with directories, query is much slower +void bRunRestore::computeVolumeList() +{ + brestore->get_info_from_selection(m_fileids, m_jobids, m_dirids, m_findexes); + if (m_fileids.size() == 0) { + return; + } + + Freeze frz_lst(*TableMedia); /* disable updating*/ + QString q = +" SELECT DISTINCT VolumeName, Enabled, InChanger " + " FROM File, " + " ( " // -- Get all media from this job + " SELECT MIN(FirstIndex) AS FirstIndex, MAX(LastIndex) AS LastIndex, " + " VolumeName, Enabled, Inchanger " + " FROM JobMedia JOIN Media USING (MediaId) " + " WHERE JobId IN (" + m_jobids.join(",") + ") " + " GROUP BY VolumeName,Enabled,InChanger " + " ) AS allmedia " + " WHERE File.FileId IN (" + m_fileids.join(",") + ") " + " AND File.FileIndex >= allmedia.FirstIndex " + " AND File.FileIndex <= allmedia.LastIndex "; + int row=0; + QStringList results; + if (brestore->console()->sql_cmd(q, results)) { + QStringList fieldlist; + TableMedia->setRowCount(results.size()); + /* Iterate through the record returned from the query */ + foreach (QString resultline, results) { + // 0 1 2 + //volname, enabled, inchanger + fieldlist = resultline.split("\t"); + int col=0; + TableItemFormatter item(*TableMedia, row++); + item.setInChanger(col++, fieldlist.at(2)); // inchanger + item.setTextFld(col++, fieldlist.at(0)); // Volume + } + } + TableMedia->verticalHeader()->hide(); + TableMedia->resizeColumnsToContents(); + TableMedia->resizeRowsToContents(); + TableMedia->setEditTriggers(QAbstractItemView::NoEditTriggers); +} + +int64_t bRunRestore::runRestore(QString tablename) +{ + QString q; + QString tmp; + + tmp = ClientCb->currentText(); + if (tmp == "") { + return 0; + } + q = "restore client=" + tmp; + + tmp = CommentEntry->text(); + if (tmp != "") { + tmp.replace("\"", " "); + q += " comment=\"" + tmp + "\""; + } + + tmp = StorageCb->currentText(); + if (tmp != "") { + q += " storage=" + tmp; + } + + if (UseFileRelocationChk->checkState() == Qt::Checked) { + if (UseRegexpChk->checkState() == Qt::Checked) { + tmp = WhereRegexpEntry->text(); + if (tmp != "") { + tmp.replace("\"", ""); + q += " regexwhere=\"" + tmp + "\""; + } + } else { + QStringList lst; + tmp = StripPrefixEntry->text(); + if (tmp != "") { + tmp.replace("\"", ""); + lst.append("!" + tmp + "!!i"); + } + tmp = AddPrefixEntry->text(); + if (tmp != "") { + tmp.replace("\"", ""); + lst.append("!^!" + tmp + "!"); + } + tmp = AddSuffixEntry->text(); + if (tmp != "") { + tmp.replace("\"", ""); + lst.append("!([^/])$!$1" + tmp + "!"); + } + if (lst.size() > 0) { + q += " regexwhere=\"" + lst.join(",") + "\""; + } + } + } else { + tmp = WhereEntry->text(); + if (tmp != "") { + tmp.replace("\"", ""); + q += " where=\"" + tmp + "\""; + } + } + +// q += " priority=" + tmp.setNum(PrioritySb->value()); +// q += " job=\"" + RestoreCb->currentText() + "\""; + q += " file=\"?" + tablename + "\""; + q += " when=\"" + WhenEditor->dateTime().toString("yyyy-MM-dd hh:mm:ss") + "\""; + q += " done yes"; + + if (mainWin->m_miscDebug) qDebug() << q; + QStringList results; + if (brestore->console()->dir_cmd(q, results)) { + foreach (QString resultline, results) { + QStringList fieldlist = resultline.split("="); + if (fieldlist.size() == 2) { + return fieldlist.at(1).toLongLong(); + } + } + } + return 0; +} + +void bRunRestore::computeRestore() +{ + QString q = ".bvfs_restore path=b2123 jobid=" + m_jobids.join(","); + if (m_fileids.size() > 0) { + q += " fileid=" + m_fileids.join(","); + } + if (m_dirids.size() > 0) { + q += " dirid=" + m_dirids.join(","); + } + if (m_findexes.size() > 0) { + q += " hardlink=" + m_findexes.join(","); + } + if (mainWin->m_miscDebug) qDebug() << q; + + QStringList results; + if (brestore->console()->dir_cmd(q, results)) { + if (results.size() == 1 && results[0] == "OK") { + int64_t jobid = runRestore("b2123"); + if (mainWin->m_miscDebug) qDebug() << "jobid=" << jobid; + q = ".bvfs_cleanup path=b2123"; + brestore->console()->dir_cmd(q, results); + } + } +}