1 /*
2 Bacula(R) - The Network Backup Solution
3
4 Copyright (C) 2000-2020 Kern Sibbald
5
6 The original author of Bacula is Kern Sibbald, with contributions
7 from many others, a complete list can be found in the file AUTHORS.
8
9 You may use this file and others of this release according to the
10 license defined in the LICENSE file, which includes the Affero General
11 Public License, v3.0 ("AGPLv3") and some additional permissions and
12 terms pursuant to its AGPLv3 Section 7.
13
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
16
17 Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19
20 /*
21 * Restore Class
22 *
23 * Kern Sibbald, February MMVII
24 *
25 */
26
27 #include "bat.h"
28 #include "restore.h"
29
30 static const int dbglvl = 100;
31
restorePage(int conn)32 restorePage::restorePage(int conn) : Pages()
33 {
34 Dmsg1(dbglvl, "Construcing restorePage Instance connection %i\n", conn);
35 m_conn = conn;
36 QStringList titles;
37
38 setupUi(this);
39 m_name = tr("Restore Select");
40 pgInitialize();
41 QTreeWidgetItem* thisitem = mainWin->getFromHash(this);
42 thisitem->setIcon(0,QIcon(QString::fromUtf8(":images/restore.png")));
43
44 m_console->notify(m_conn, false); /* this should already be off */
45
46 connect(fileWidget, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)),
47 this, SLOT(fileDoubleClicked(QTreeWidgetItem *, int)));
48 connect(directoryWidget, SIGNAL(
49 currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)),
50 this, SLOT(directoryItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)));
51 connect(upButton, SIGNAL(pressed()), this, SLOT(upButtonPushed()));
52 connect(markButton, SIGNAL(pressed()), this, SLOT(markButtonPushed()));
53 connect(unmarkButton, SIGNAL(pressed()), this, SLOT(unmarkButtonPushed()));
54 connect(okButton, SIGNAL(pressed()), this, SLOT(okButtonPushed()));
55 connect(cancelButton, SIGNAL(pressed()), this, SLOT(cancelButtonPushed()));
56
57 fileWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
58 fileWidget->addAction(actionMark);
59 fileWidget->addAction(actionUnMark);
60 connect(actionMark, SIGNAL(triggered()), this, SLOT(markButtonPushed()));
61 connect(actionUnMark, SIGNAL(triggered()), this, SLOT(unmarkButtonPushed()));
62
63 setFont(m_console->get_font());
64 m_console->displayToPrompt(m_conn);
65
66 titles << tr("Mark") << tr("File") << tr("Mode") << tr("User")
67 << tr("Group") << tr("Size") << tr("Date");
68 fileWidget->setHeaderLabels(titles);
69
70 get_cwd();
71
72 readSettings();
73 /* wait was entered from pre-restore
74 * will exit, but will reenter in fillDirectory */
75 mainWin->waitExit();
76 fillDirectory();
77 dockPage();
78 setCurrent();
79 this->show();
80 if (mainWin->m_miscDebug) Pmsg0(000, "Leave restorePage\n");
81 }
82
~restorePage()83 restorePage::~restorePage()
84 {
85 writeSettings();
86 }
87
88 /*
89 * Fill the fileWidget box with the contents of the current directory
90 */
fillDirectory()91 void restorePage::fillDirectory()
92 {
93 mainWin->waitEnter();
94 char modes[20], user[20], group[20], size[20], date[30];
95 char marked[10];
96 int pnl, fnl;
97 POOLMEM *file = get_pool_memory(PM_FNAME);
98 POOLMEM *path = get_pool_memory(PM_FNAME);
99
100 fileWidget->clear();
101 m_console->write_dir(m_conn, "dir", false);
102 QList<QTreeWidgetItem *> treeItemList;
103 QStringList item;
104 m_rx.setPattern("has no children\\.$");
105 bool first = true;
106 while (m_console->read(m_conn) > 0) {
107 char *p = m_console->msg(m_conn);
108 char *l;
109 strip_trailing_junk(p);
110 if (*p == '$' || !*p) { continue; }
111 if (first) {
112 if (m_rx.indexIn(QString(p)) != -1) { continue; }
113 first = false;
114 }
115 l = p;
116 skip_nonspaces(&p); /* permissions */
117 *p++ = 0;
118 bstrncpy(modes, l, sizeof(modes));
119 skip_spaces(&p);
120 skip_nonspaces(&p); /* link count */
121 *p++ = 0;
122 skip_spaces(&p);
123 l = p;
124 skip_nonspaces(&p); /* user */
125 *p++ = 0;
126 skip_spaces(&p);
127 bstrncpy(user, l, sizeof(user));
128 l = p;
129 skip_nonspaces(&p); /* group */
130 *p++ = 0;
131 bstrncpy(group, l, sizeof(group));
132 skip_spaces(&p);
133 l = p;
134 skip_nonspaces(&p); /* size */
135 *p++ = 0;
136 bstrncpy(size, l, sizeof(size));
137 skip_spaces(&p);
138 l = p;
139 skip_nonspaces(&p); /* date/time */
140 skip_spaces(&p);
141 skip_nonspaces(&p);
142 *p++ = 0;
143 bstrncpy(date, l, sizeof(date));
144 skip_spaces(&p);
145 if (*p == '*') {
146 bstrncpy(marked, "*", sizeof(marked));
147 p++;
148 } else {
149 bstrncpy(marked, " ", sizeof(marked));
150 }
151 split_path_and_filename(p, &path, &pnl, &file, &fnl);
152 item.clear();
153 item << "" << file << modes << user << group << size << date;
154 if (item[1].endsWith("/")) {
155 addDirectory(item[1]);
156 }
157 QTreeWidgetItem *ti = new QTreeWidgetItem((QTreeWidget *)0, item);
158 ti->setTextAlignment(5, Qt::AlignRight); /* right align size */
159 if (strcmp(marked, "*") == 0) {
160 ti->setIcon(0, QIcon(QString::fromUtf8(":images/check.png")));
161 ti->setData(0, Qt::UserRole, true);
162 } else {
163 ti->setIcon(0, QIcon(QString::fromUtf8(":images/unchecked.png")));
164 ti->setData(0, Qt::UserRole, false);
165 }
166 treeItemList.append(ti);
167 }
168 fileWidget->clear();
169 fileWidget->insertTopLevelItems(0, treeItemList);
170 for (int i=0; i<7; i++) {
171 fileWidget->resizeColumnToContents(i);
172 }
173
174 free_pool_memory(file);
175 free_pool_memory(path);
176 mainWin->waitExit();
177 }
178
179 /*
180 * Function called from fill directory when a directory is found to see if this
181 * directory exists in the directory pane and then add it to the directory pane
182 */
addDirectory(QString & newdirr)183 void restorePage::addDirectory(QString &newdirr)
184 {
185 QString newdir = newdirr;
186 QString fullpath = m_cwd + newdirr;
187 bool ok = true;
188
189 if (mainWin->m_miscDebug) {
190 QString msg = QString(tr("In addDirectory cwd \"%1\" newdir \"%2\" fullpath \"%3\"\n"))
191 .arg(m_cwd)
192 .arg(newdir)
193 .arg(fullpath);
194 Pmsg1(dbglvl, "%s\n", msg.toUtf8().data());
195 }
196
197 if (isWin32Path(fullpath)) {
198 if (mainWin->m_miscDebug) Pmsg0(dbglvl, "Windows drive\n");
199 if (fullpath.left(1) == "/") {
200 fullpath.replace(0, 1, ""); /* strip leading / */
201 }
202 /* If drive and not already in add it */
203 if (fullpath.length() == 3 && !m_dirPaths.contains(fullpath)) {
204 QTreeWidgetItem *item = new QTreeWidgetItem(directoryWidget);
205 item->setIcon(0,QIcon(QString::fromUtf8(":images/folder.png")));
206 item->setText(0, fullpath.toUtf8().data());
207 if (mainWin->m_miscDebug) {
208 Pmsg1(dbglvl, "Pre Inserting %s\n",fullpath.toUtf8().data());
209 }
210 m_dirPaths.insert(fullpath, item);
211 m_dirTreeItems.insert(item, fullpath);
212 directoryWidget->setCurrentItem(NULL);
213 }
214 } else {
215 // Unix add / first if not already there
216 if (m_dirPaths.empty()) {
217 QTreeWidgetItem *item = new QTreeWidgetItem(directoryWidget);
218 item->setIcon(0,QIcon(QString::fromUtf8(":images/folder.png")));
219
220 QString text("/");
221 item->setText(0, text.toUtf8().data());
222 if (mainWin->m_miscDebug) {
223 Pmsg1(dbglvl, "Pre Inserting %s\n",text.toUtf8().data());
224 }
225 m_dirPaths.insert(text, item);
226 m_dirTreeItems.insert(item, text);
227 }
228 }
229
230 /* Does it already exist ?? */
231 if (!m_dirPaths.contains(fullpath)) {
232 QTreeWidgetItem *item = NULL;
233 if (isWin32Path(fullpath)) {
234 /* this is the base widget */
235 item = new QTreeWidgetItem(directoryWidget);
236 item->setText(0, fullpath.toUtf8().data());
237 if (mainWin->m_miscDebug) Pmsg1(dbglvl, "Windows: %s\n", fullpath.toUtf8().data());
238 item->setIcon(0,QIcon(QString::fromUtf8(":images/folder.png")));
239 } else {
240 QTreeWidgetItem *parent = m_dirPaths.value(m_cwd);
241 if (parent) {
242 /* new directories to add */
243 item = new QTreeWidgetItem(parent);
244 item->setText(0, newdir.toUtf8().data());
245 item->setIcon(0,QIcon(QString::fromUtf8(":images/folder.png")));
246 directoryWidget->expandItem(parent);
247 if (mainWin->m_miscDebug) {
248 Pmsg1(dbglvl, "%s\n", newdir.toUtf8().data());
249 }
250 } else {
251 ok = false;
252 if (mainWin->m_miscDebug) {
253 QString msg = QString(tr("In else of if parent cwd \"%1\" newdir \"%2\"\n"))
254 .arg(m_cwd)
255 .arg(newdir);
256 Pmsg1(dbglvl, "%s\n", msg.toUtf8().data());
257 }
258 }
259 }
260 /* insert into both forward and reverse hash */
261 if (ok) {
262 if (mainWin->m_miscDebug) {
263 Pmsg1(dbglvl, "Inserting %s\n",fullpath.toUtf8().data());
264 }
265 m_dirPaths.insert(fullpath, item);
266 m_dirTreeItems.insert(item, fullpath);
267 }
268 }
269 }
270
271 /*
272 * Executed when the tree item in the directory pane is changed. This will
273 * allow us to populate the file pane and make this the cwd.
274 */
directoryItemChanged(QTreeWidgetItem * currentitem,QTreeWidgetItem *)275 void restorePage::directoryItemChanged(QTreeWidgetItem *currentitem,
276 QTreeWidgetItem * /*previousitem*/)
277 {
278 QString fullpath = m_dirTreeItems.value(currentitem);
279 statusLine->setText("");
280 if (fullpath != "") {
281 cwd(fullpath.toUtf8().data());
282 fillDirectory();
283 }
284 }
285
okButtonPushed()286 void restorePage::okButtonPushed()
287 {
288 this->hide();
289 m_console->write(m_conn, "done");
290 m_console->notify(m_conn, true);
291 setConsoleCurrent();
292 closeStackPage();
293 mainWin->resetFocus();
294 }
295
296
cancelButtonPushed()297 void restorePage::cancelButtonPushed()
298 {
299 this->hide();
300 m_console->write(m_conn, "quit");
301 m_console->displayToPrompt(m_conn);
302 mainWin->set_status(tr("Canceled"));
303 closeStackPage();
304 m_console->notify(m_conn, true);
305 mainWin->resetFocus();
306 }
307
fileDoubleClicked(QTreeWidgetItem * item,int column)308 void restorePage::fileDoubleClicked(QTreeWidgetItem *item, int column)
309 {
310 char cmd[1000];
311 statusLine->setText("");
312 if (column == 0) { /* mark/unmark */
313 mainWin->waitEnter();
314 if (item->data(0, Qt::UserRole).toBool()) {
315 bsnprintf(cmd, sizeof(cmd), "unmark \"%s\"", item->text(1).toUtf8().data());
316 item->setIcon(0, QIcon(QString::fromUtf8(":images/unchecked.png")));
317 item->setData(0, Qt::UserRole, false);
318 } else {
319 bsnprintf(cmd, sizeof(cmd), "mark \"%s\"", item->text(1).toUtf8().data());
320 item->setIcon(0, QIcon(QString::fromUtf8(":images/check.png")));
321 item->setData(0, Qt::UserRole, true);
322 }
323 m_console->write_dir(m_conn, cmd, false);
324 if (m_console->read(m_conn) > 0) {
325 strip_trailing_junk(m_console->msg(m_conn));
326 statusLine->setText(m_console->msg(m_conn));
327 }
328 m_console->displayToPrompt(m_conn);
329 mainWin->waitExit();
330 return;
331 }
332 /*
333 * Double clicking other than column 0 means to decend into
334 * the directory -- or nothing if it is not a directory.
335 */
336 if (item->text(1).endsWith("/")) {
337 QString fullpath = m_cwd + item->text(1);
338 QTreeWidgetItem *item = m_dirPaths.value(fullpath);
339 if (mainWin->m_miscDebug) {
340 Pmsg1(dbglvl, "%s\n", fullpath.toUtf8().data());
341 }
342 if (item) {
343 directoryWidget->setCurrentItem(item);
344 } else {
345 QString msg = QString("DoubleClick else of item column %1 fullpath %2\n")
346 .arg(column,10)
347 .arg(fullpath);
348 if (mainWin->m_miscDebug) Pmsg1(dbglvl, "%s\n", msg.toUtf8().data());
349 }
350 }
351 }
352
353 /*
354 * If up button pushed, making the parent tree widget current will call fill
355 * directory.
356 */
upButtonPushed()357 void restorePage::upButtonPushed()
358 {
359 cwd("..");
360 QTreeWidgetItem *item = m_dirPaths.value(m_cwd);
361 if (item) {
362 directoryWidget->setCurrentItem(item);
363 }
364 statusLine->setText("");
365 }
366
367 /*
368 * Mark selected items
369 */
markButtonPushed()370 void restorePage::markButtonPushed()
371 {
372 mainWin->waitEnter();
373 QList<QTreeWidgetItem *> treeItemList = fileWidget->selectedItems();
374 QTreeWidgetItem *item;
375 char cmd[1000];
376 int count = 0;
377 statusLine->setText("");
378 foreach (item, treeItemList) {
379 count++;
380 bsnprintf(cmd, sizeof(cmd), "mark \"%s\"", item->text(1).toUtf8().data());
381 item->setIcon(0, QIcon(QString::fromUtf8(":images/check.png")));
382 m_console->write_dir(m_conn, cmd, false);
383 if (m_console->read(m_conn) > 0) {
384 strip_trailing_junk(m_console->msg(m_conn));
385 statusLine->setText(m_console->msg(m_conn));
386 }
387 Dmsg1(dbglvl, "cmd=%s\n", cmd);
388 m_console->discardToPrompt(m_conn);
389 }
390 if (count == 0) {
391 mainWin->set_status("Nothing selected, nothing done");
392 statusLine->setText("Nothing selected, nothing done");
393 }
394 mainWin->waitExit();
395 }
396
397 /*
398 * Unmark selected items
399 */
unmarkButtonPushed()400 void restorePage::unmarkButtonPushed()
401 {
402 mainWin->waitEnter();
403 QList<QTreeWidgetItem *> treeItemList = fileWidget->selectedItems();
404 QTreeWidgetItem *item;
405 char cmd[1000];
406 int count = 0;
407 statusLine->setText("");
408 foreach (item, treeItemList) {
409 count++;
410 bsnprintf(cmd, sizeof(cmd), "unmark \"%s\"", item->text(1).toUtf8().data());
411 item->setIcon(0, QIcon(QString::fromUtf8(":images/unchecked.png")));
412 m_console->write_dir(m_conn, cmd, false);
413 if (m_console->read(m_conn) > 0) {
414 strip_trailing_junk(m_console->msg(m_conn));
415 statusLine->setText(m_console->msg(m_conn));
416 }
417 Dmsg1(dbglvl, "cmd=%s\n", cmd);
418 m_console->discardToPrompt(m_conn);
419 }
420 if (count == 0) {
421 mainWin->set_status(tr("Nothing selected, nothing done"));
422 statusLine->setText(tr("Nothing selected, nothing done"));
423 }
424 mainWin->waitExit();
425 }
426
427 /*
428 * Change current working directory
429 */
cwd(const char * dir)430 bool restorePage::cwd(const char *dir)
431 {
432 int stat;
433 char cd_cmd[MAXSTRING];
434
435 mainWin->waitEnter();
436 statusLine->setText("");
437 bsnprintf(cd_cmd, sizeof(cd_cmd), "cd \"%s\"", dir);
438 Dmsg2(dbglvl, "dir=%s cmd=%s\n", dir, cd_cmd);
439 m_console->write_dir(m_conn, cd_cmd, false);
440 lineEdit->clear();
441 if ((stat = m_console->read(m_conn)) > 0) {
442 m_cwd = m_console->msg(m_conn);
443 lineEdit->insert(m_cwd);
444 Dmsg2(dbglvl, "cwd=%s msg=%s\n", m_cwd.toUtf8().data(), m_console->msg(m_conn));
445 } else {
446 Dmsg1(dbglvl, "stat=%d\n", stat);
447 QMessageBox::critical(this, "Error", tr("cd command failed"), QMessageBox::Ok);
448 }
449 m_console->discardToPrompt(m_conn);
450 mainWin->waitExit();
451 return true; /* ***FIXME*** return real status */
452 }
453
454 /*
455 * Return cwd when in tree restore mode
456 */
get_cwd()457 char *restorePage::get_cwd()
458 {
459 int stat;
460 mainWin->waitEnter();
461 m_console->write_dir(m_conn, ".pwd", false);
462 Dmsg0(dbglvl, "send: .pwd\n");
463 if ((stat = m_console->read(m_conn)) > 0) {
464 m_cwd = m_console->msg(m_conn);
465 Dmsg2(dbglvl, "cwd=%s msg=%s\n", m_cwd.toUtf8().data(), m_console->msg(m_conn));
466 } else {
467 Dmsg1(dbglvl, "Something went wrong read stat=%d\n", stat);
468 QMessageBox::critical(this, "Error", tr(".pwd command failed"), QMessageBox::Ok);
469 }
470 m_console->discardToPrompt(m_conn);
471 mainWin->waitExit();
472 return m_cwd.toUtf8().data();
473 }
474
475 /*
476 * Save user settings associated with this page
477 */
writeSettings()478 void restorePage::writeSettings()
479 {
480 QSettings settings(m_console->m_dir->name(), "bat");
481 settings.beginGroup("RestorePage");
482 settings.setValue(m_splitText, splitter->saveState());
483 settings.endGroup();
484 }
485
486 /*
487 * Read and restore user settings associated with this page
488 */
readSettings()489 void restorePage::readSettings()
490 {
491 m_splitText = "splitterSizes_2";
492 QSettings settings(m_console->m_dir->name(), "bat");
493 settings.beginGroup("RestorePage");
494 if (settings.contains(m_splitText)) {
495 splitter->restoreState(settings.value(m_splitText).toByteArray());
496 }
497 settings.endGroup();
498 }
499