1 /* ---------------------------------- managewizard.cpp ---------------------------------------------------------------------------
2 Class to launch a wizard for restore, delete a backup
3
4 ===============================================================================================================================
5 ===============================================================================================================================
6 This file is part of "luckyBackup" project
7 Copyright, Loukas Avgeriou
8 luckyBackup is distributed under the terms of the GNU General Public License
9 luckyBackup is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 luckyBackup is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with luckyBackup. If not, see <http://www.gnu.org/licenses/>.
21
22 project version : Please see "main.cpp" for project version
23
24 developer : luckyb
25 last modified : 22 May 2016
26 ===============================================================================================================================
27 ===============================================================================================================================
28 */
29 #include "manageWizard.h"
30
31 #include <QCloseEvent>
32 #include <QFileDialog>
33 #include <QProcess>
34 #include <QTextCodec>
35
36 #include "global.h"
37 #include "operationClass.h"
38
39 QProcess *commandProcess;
40
41 // class manageWizard Constructor=================================================================================================
42 // Launches a wizard
manageWizard(QString type,QString SOURCE,QString DEST,int snapshot,QWidget * parent)43 manageWizard::manageWizard (QString type, QString SOURCE, QString DEST, int snapshot, QWidget *parent) : QDialog (parent)
44 {
45 // initialize variables
46 currentSnap = snapshot; // this is the current snapshot number
47 snapshotsNo = Operation[currentOperation] -> GetSnapshotsListSize(); // this is the number of snapshots
48 wizard_type = type;
49 snapToRestore = snapshotsNo-2; // This means the previous of the very last snapshot
50
51 procRunning = false;
52 procKilled = false;
53 writeToLog = false;
54 DeleteAfter = false;
55 firstScroll = true;
56 MainRun = true;
57
58 QStringList arguments = Operation[currentOperation] -> GetArgs();
59 source = SOURCE; // the full path of the source
60 if (Operation[currentOperation] -> GetTypeDirContents())
61 sourceLast = "";
62 else
63 sourceLast = calculateLastPath(source) + SLASH; // This is the lowest dir of the source
64
65 dest = DEST; // the full path of the destination
66 time = Operation[currentOperation] -> GetSnapshotsListItem(currentSnap);
67 if (time == "")
68 timeReadable = tr("not available","refers to a date-time");
69 else
70 timeReadable = time.mid(0,4) + "/" + time.mid(4,2) + "/" + time.mid(6,2) + " - " +
71 time.mid(8,2) + ":" + time.mid(10,2) + ":" + time.mid(12,2); //QString
72
73 QDir snapSpecificDir(dest + snapDefaultDir + time + SLASH);
74 if (snapSpecificDir.exists())
75 snapSpecificDirExists = true;
76 else
77 snapSpecificDirExists = false;
78
79 // delete backup;if this are more than one snapshots available, set dir to delete as snapSpecificDir
80 if ((wizard_type == "deleteBackup") && (snapshotsNo > 1))
81 dest = snapSpecificDir.absolutePath();
82
83 if (WINrunning && Operation[currentOperation] -> GetRemote())
84 {
85 dest.replace("/",XnixSLASH);
86 if (dest.endsWith(XnixSLASH+XnixSLASH))
87 dest.chop(1);
88 }
89 else if (notXnixRunning) // OS2 actually !!
90 {
91 dest.replace("/",SLASH);
92 if (dest.endsWith(SLASH+SLASH))
93 dest.chop(1);
94 }
95
96 errorCount = 0 ;
97 errorsFound = 0;
98 outputString = "";
99 outputError = "";
100
101 uiW.setupUi(this);
102
103 // initialize the gui
104 this -> resize(580,300);
105 guiInitialize();
106
107 //connections ----------------
108 connect ( uiW.button_cancel, SIGNAL( clicked() ), this, SLOT( cancelPressed() ) );
109 connect ( uiW.button_start, SIGNAL( clicked() ), this, SLOT( startAction() ) );
110 connect ( uiW.button_abort, SIGNAL( clicked() ), this, SLOT( abortAction() ) );
111 connect ( uiW.checkBox_DeleteAfter, SIGNAL( stateChanged(int) ), this, SLOT( deleteAfterChanged() ) );
112
113 commandProcess = new QProcess(this); //create a new qprocess (for rsync) & connect signals
114 connect(commandProcess, SIGNAL(readyReadStandardError()), this, SLOT(appendCommandOutput()));
115 connect(commandProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(appendCommandOutput()));
116 connect(commandProcess, SIGNAL (finished(int, QProcess::ExitStatus)), this, SLOT(procFinished()));
117
118 connect ( uiW.button_previous, SIGNAL( clicked() ), this, SLOT( prevError() ) ); //connect previous pushButton SLOT
119 connect ( uiW.button_next, SIGNAL( clicked() ), this, SLOT( nextError() ) ); //connect next pushButton SLOT
120 connect ( uiW.pushButton_changeRestore, SIGNAL( clicked() ), this, SLOT( changeRestorePath() ) ); //connect change pushButton SLOT
121 }
122 // SLOTS-------------------------------------------------------------------------------------
123
124 // cancel button pressed=====================================================================================================
125 // emmit a QCloseEvent
cancelPressed()126 void manageWizard::cancelPressed()
127 {
128 close(); //emmit a QcloseEvent
129 }
130
131 // QCloseEvent emitted =====================================================================================================
132 //close the wizard (if a process is not running)
closeEvent(QCloseEvent * event)133 void manageWizard::closeEvent(QCloseEvent *event)
134 {
135 if (procRunning)
136 event->ignore();
137 else
138 event->accept();
139 }
140
141 // deleteAfterChanged()=====================================================================================================
142 // actions when checkbox "delete source data" state changed
deleteAfterChanged()143 void manageWizard::deleteAfterChanged()
144 {
145 DeleteAfter = uiW.checkBox_DeleteAfter -> isChecked(); //set bool DeleteAfter according to checkbox (only applies at RESTORE)
146 QString labelText = "<b><font color=red>" + tr("WARNING") + "</font></b>: ";
147 if (DeleteAfter)
148 labelText.append(tr("The restore directory will become identical to the backup snapshot"));
149 else
150 labelText.append(tr("Existing backup files will replace the corresponding files at the restore directory",
151 "information message - line1."));
152
153 labelText.append(".<br>" + tr("If the information above is correct, click <b>start</b> to begin",
154 "information message - line2.\nPlease leave tags <b></b> intact and surrounding 'start translated'") + ".");
155 uiW.label_message -> setText(labelText);
156
157
158
159
160
161 // ********************* FOR TESTING PURPOSES **************************************************************************
162 /*calcCommandArgs();
163 QString ArgsString = "";
164 count=0;
165 while (count < commandArguments.size())
166 {
167 if (commandArguments[count].contains("-e ssh "))
168 {
169 ArgsString.append("-e \"" + commandArguments[count].remove(0,3) + "\" ");
170 }
171 else
172 ArgsString.append(commandArguments[count]+" ");
173 count++;
174 }
175 labelText.append("<br><br><font color=red>" + ArgsString + "</font>");
176 uiW.label_message -> setText(labelText);
177 */
178 // ********************* END TESTING PURPOSES **************************************************************************
179 }
180
181 // start button pressed=====================================================================================================
182 // start the action
startAction()183 void manageWizard::startAction()
184 {
185 DeleteAfter = uiW.checkBox_DeleteAfter -> isChecked(); //set bool DeleteAfter according to checkbox (only applies at RESTORE)
186 DryRun = uiW.checkBox_DryRun -> isChecked(); //set bool DeleteAfter according to checkbox (only applies at RESTORE)
187
188 //change the gui
189 uiW.button_next -> setVisible (true);
190 uiW.button_next -> setEnabled (false);
191 uiW.button_previous -> setVisible (true);
192 uiW.button_previous -> setEnabled (false);
193 uiW.actionView -> setVisible (true);
194 uiW.button_cancel -> setVisible (false);
195 uiW.groupBox_title -> setVisible (false);
196 uiW.button_abort -> setVisible (true);
197 uiW.checkBox_DryRun -> setVisible (false);
198 uiW.button_start -> setVisible (false);
199
200 // logfile actions if real run is performed - is always true for DELETE actions
201 //This is the RESTORE/DELETE logfile
202 if (!DryRun)
203 {
204 if (wizard_type == "restoreBackup")
205 logfilename = logDir + profileName + "-" + Operation[currentOperation] -> GetName() + "-" + time + "-RESTORE" + ".log";
206 else
207 logfilename = logDir + profileName + "-" + Operation[currentOperation] -> GetName() + "-" + time + "-DELETE" + ".log";
208
209 logfile.setFileName(logfilename); // this is the logfile
210
211 if (logfile.open(QIODevice::WriteOnly | QIODevice::Text)) //create a new log file
212 writeToLog = true; //& if it's ok set this to true
213 else
214 writeToLog = false;
215 }
216
217 runProcess();
218 }
219
220 // procFinished =====================================================================================================
221 // actions when the process is finished
procFinished()222 void manageWizard::procFinished()
223 {
224 procRunning = false;
225 if ( (wizard_type == "restoreBackup") && (snapToRestore >= currentSnap ) )
226 {
227 MainRun = false;
228
229 time = Operation[currentOperation] -> GetSnapshotsListItem(snapToRestore);
230 QString tslash;
231 if (WINrunning && Operation[currentOperation] -> GetRemote())
232 tslash=XnixSLASH;
233 else
234 tslash=SLASH;
235 QDir snapSpecificDir(dest + snapDefaultDir + time + tslash);
236
237 if (snapSpecificDir.exists())
238 snapSpecificDirExists = true;
239 else
240 snapSpecificDirExists = false;
241
242 snapToRestore--;
243
244 runProcess();
245 return;
246 }
247
248 logfile.close(); // close the RESTORE/DELETE logfile
249
250 errorsFound = errorCount;
251 errorCount = 0; // reset the error count
252 uiW.button_abort -> setVisible (false);
253 uiW.button_cancel -> setVisible (true);
254 uiW.button_cancel -> setText(tr("close"));
255 if (errorsFound > 0)
256 {
257 firstScroll=true;
258 uiW.button_next -> setEnabled (true);
259 }
260
261 // if delete backup just finished normally (not ABORTED) with no errors
262 if ( (wizard_type == "deleteBackup") && (errorsFound == 0) && (!procKilled) )
263 {
264 //delete the backup logfile
265 uiW.actionView->append("<br><font color=blue><b>..." + tr("Deleting snapshot logfile",
266 "info message displayed during ...logfile deletion") +"</b></font><br>");
267 QString logNameToDelete = logDir + profileName + "-" + Operation[currentOperation] -> GetName() + "-" +
268 time + ".log";
269 QFile logToDelete;
270 logToDelete.setFileName(logNameToDelete); // this is the logfile of the snapshot to delete
271 if (logToDelete.remove())
272 uiW.actionView->append(" <font color=green>" + tr("success",
273 "info message displayed after ...logfile deletion") +"</font><br><br>");
274 else
275 uiW.actionView->append(" <font color=red>" + tr("failed",
276 "info message displayed after ...logfile deletion") +"</font><br><br>");
277
278 //delete the .changes file
279 uiW.actionView->append("<font color=blue><b>..." + tr("Deleting file that lists snapshot changes",
280 "info message displayed during ...file deletion") +"</b></font><br>");
281 QString changesNameToDelete = snapChangesDir + profileName + "-" + Operation[currentOperation] -> GetName() + "-" +
282 time + ".changes.log";
283 QFile changesFileToDelete;
284 changesFileToDelete.setFileName(changesNameToDelete); // this is the .changes file of the snapshot to delete
285 if (changesFileToDelete.remove())
286 uiW.actionView->append(" <font color=green>" + tr("success",
287 "info message displayed after ...file deletion") +"</font><br><br>");
288 else
289 uiW.actionView->append(" <font color=red>" + tr("failed",
290 "info message displayed after ...file deletion") +"</font><br><br>");
291
292 //remove the specific snapshot from the Task
293 uiW.actionView->append("<font color=blue><b>..." + tr("Updating list of snapshots"
294 ,"info message displayed during ...snaps list update") +"</b></font><br>");
295 Operation[currentOperation] -> RemoveSnapshotsListItem (currentSnap);
296 uiW.actionView->append(" <font color=green>" + tr("success",
297 "info message displayed after ...snapshots list update") +"</font><br><br>");
298
299 int currentSnaps = Operation[currentOperation] -> GetSnapshotsListSize(); // this is the current number of snapshots
300 bool emptySnapList = Operation[currentOperation] -> SnapshotsListIsEmpty(); // Is the list empty ??
301
302 //update the last execution time to the last snapshot or to nothing if no snapshots exist
303 QDateTime newTime; // empty time variable
304 if (!emptySnapList)
305 newTime = QDateTime::fromString(Operation[currentOperation]-> GetSnapshotsListItem(currentSnaps-1),"yyyyMMddhhmmss");
306 Operation[currentOperation] -> SetLastExecutionTime(newTime);
307
308 //update the last execution errors to "-1" (not available)
309 Operation[currentOperation] -> SetLastExecutionErrors(-1);
310 }
311
312
313 QString InfoMessage = "<br><font color=magenta>=====================================<br><b>... ";
314 if (procKilled)
315 InfoMessage.append(tr("Aborted") );
316 else
317 InfoMessage.append(tr("Finished") );
318
319 if (DryRun)
320 InfoMessage.append(" (" + tr("simulation") + ")");
321
322 InfoMessage.append("</b></font><br>");
323
324 if (errorsFound ==0 )
325 InfoMessage.append("<font color=green><b>" + tr("no errors") + "</b></font>");
326 else
327 InfoMessage.append("<font color=red><b>" + tr("errors found") + "</b></font>");
328 uiW.actionView->append(InfoMessage);
329 }
330
331 // abort button pressed=====================================================================================================
332 // abort the action
abortAction()333 void manageWizard::abortAction()
334 {
335 if (commandProcess->state() == QProcess::Running)
336 {
337 procKilled = true;
338 MainRun = false;
339 commandProcess -> kill(); //kill commandProcess
340 commandProcess -> waitForFinished();
341 }
342 else
343 procFinished(); // the process might not be running for some weird reason, so call procFinished !!
344 }
345 // appendCommandOutput =====================================================================================================
346 //update dialog with new data (text - progressbar) - also update logfile
appendCommandOutput()347 void manageWizard::appendCommandOutput()
348 {
349 //update textBrowser ------------------------------------------------------------------------------------------------------
350 QTextCodec *codec = QTextCodec::codecForName("UTF-8");
351 outputString = codec->toUnicode(commandProcess -> readAllStandardOutput());
352 outputError = codec->toUnicode(commandProcess -> readAllStandardError());
353
354 uiW.actionView->append(outputString);
355 logFileUpdate("rsync-standard", outputString, 0);
356
357 if (outputError !="")
358 {
359 errorCount++;
360 errorsFound++;
361 uiW.actionView->append(logFileUpdate("rsync-error", outputError, 0));
362 }
363 }
364
365 // change button pressed=====================================================================================================
366 // Change the restore directory
changeRestorePath()367 void manageWizard::changeRestorePath()
368 {
369 QString newRestore = QFileDialog::getExistingDirectory (this, tr("Select new restore directory", "directory selection dialog title"),
370 source, QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
371
372 //'if something is selected indeed
373 if (newRestore != "")
374 {
375 //if the new restore dir does not end with /
376 if (!newRestore.endsWith(SLASH))
377 newRestore.append(SLASH);
378 source = newRestore;
379 }
380 guiInitialize();
381 }
382
383 // previous error button pressed=====================================================================================================
384 // jumb to previous error
prevError()385 void manageWizard::prevError()
386 {
387 errorCount--; //decrease the current error by one
388
389 if (errorCount == 0 ) // if the current error is the first disable the previous button
390 uiW.button_previous -> setEnabled(false);
391
392 if (errorCount < errorsFound-1) //if the current error is less than the last one, enable the next button
393 uiW.button_next -> setEnabled(true);
394
395 uiW.actionView -> scrollToAnchor("error" + countStr.setNum(errorCount+1));
396 }
397
398 // next error button pressed=====================================================================================================
399 // jumb to next error
nextError()400 void manageWizard::nextError()
401 {
402 if (!firstScroll)
403 errorCount++; // increase the current error by one
404 firstScroll = false;
405
406 if (errorCount == errorsFound-1) // If the current error is the last, disable the next button
407 uiW.button_next -> setEnabled(false);
408
409 if (errorCount > 0) // if the current error is greater than the first one enable the previous button
410 uiW.button_previous -> setEnabled(true);
411
412 uiW.actionView -> scrollToAnchor("error" + countStr.setNum(errorCount+1));
413 }
414
415 // FUNCTIONS-------------------------------------------------------------------------------------
416
417 // guiInitialize()=====================================================================================================
418 // Initialize the gui
guiInitialize()419 void manageWizard::guiInitialize()
420 {
421 uiW.button_next -> setVisible (false);
422 uiW.button_previous -> setVisible (false);
423 uiW.button_abort -> setVisible (false);
424 uiW.actionView -> setVisible (false);
425
426 if (wizard_type == "deleteBackup")
427 {
428 uiW.pushButton_logo -> setIcon (QIcon(":/luckyPrefix/remove.png"));
429 uiW.groupBox_title -> setTitle(tr("Delete backup","this is the title of a wizard"));
430 uiW.label_time -> setText(tr("Date & time","simple label of the wizard gui") + ": <font color=magenta><b>" + timeReadable + "</b></font>");
431 uiW.label_paths -> setText(tr("Path","...is a directory path") + ": <font color=blue><b>" + dest + "</b></font>");
432 uiW.label_message -> setText("<b><font color=red>" + tr("WARNING") + "</font></b>: " +
433 tr("You are about to delete backup data","information message - line1") + ".<br>" +
434 tr("If the information above is correct, click <b>start</b> to begin",
435 "information message - line2.\nPlease leave tags <b></b> intact and surrounding 'start' translated") +
436 ".");
437 uiW.checkBox_DryRun -> setVisible (false);
438 uiW.checkBox_DeleteAfter -> setVisible (false);
439 uiW.pushButton_changeRestore -> setVisible (false);
440 }
441 if (wizard_type == "restoreBackup")
442 {
443 uiW.pushButton_logo -> setIcon (QIcon(":/luckyPrefix/manage.png"));
444 //uiW.pushButton_logo -> setText (tr("change"));
445 uiW.groupBox_title -> setTitle(tr("Restore backup","this is the title of a wizard"));
446 uiW.label_time -> setText(tr("Date & time","simple label of the wizard gui") +
447 ": <font color=magenta><b>" + timeReadable + "</b></font><br>" +
448 tr("Backup path","...is the backup directory path") + ": <font color=blue><b>" + dest + "</b></font>");
449
450 uiW.label_paths -> setText(tr("Restore path","...is the restore directory path") + ": <font color=blue><b>" + source +"</b></font>");
451 deleteAfterChanged();
452 }
453 }
454
455 // calcCommandArgs()=====================================================================================================
456 // function to calculate command arguments for RESTORE
calcCommandArgs()457 void manageWizard::calcCommandArgs()
458 {
459 commandArguments.clear();
460 commandArguments = AppendArguments(Operation[currentOperation]);
461 commandArguments.removeLast(); // remove the last 2 arguments (hopefuly source & destination)
462 commandArguments.removeLast();
463
464 // Scan the list and remove all arguments that are not needed
465 // keep all other arguments intact
466 count =0;
467
468 while (count < commandArguments.size())
469 {
470 if ( (commandArguments[count].contains("--exclude")) ||
471 (commandArguments[count].contains("--include")) ||
472 (commandArguments[count] == "--prune-empty-dirs") ||
473 (commandArguments[count] == "-m") ||
474 (commandArguments[count] == "--update") ||
475 (commandArguments[count] == "-u") ||
476 (commandArguments[count] == "--delay-updates") ||
477 (commandArguments[count].contains("--delete")) ||
478 (commandArguments[count].contains("--backup")) ||
479 (commandArguments[count].contains("--filter")) ||
480 (commandArguments[count].contains("--log")) ||
481 (commandArguments[count] == "--del")
482 )
483 commandArguments.removeAt(count); //remove the argument if one of the above is true
484 else
485 count++;
486 }
487
488 if (DryRun)
489 commandArguments.append("--dry-run"); // add --dry-run if checkbox checked
490
491 //exclude stuff that have been backed-up, after the snapshot was made
492 //this means read the changes.logs files from every snapshot after the current one
493 count = currentSnap+1;
494 while (count < snapshotsNo)
495 {
496 commandArguments.append("--exclude-from=" + snapChangesDir + profileName + "-" + Operation[currentOperation] -> GetName() + "-" +
497 (Operation[currentOperation] -> GetSnapshotsListItem(count)) + ".changes.log");
498 count++;
499 }
500
501 if (MainRun)
502 {
503 //exclude the luckybackup snapshots directory cause it is inside the destination
504 commandArguments.append("--exclude=" + snapDefaultDir);
505
506 // add --delete options if checkbox checked
507 if (DeleteAfter)
508 {
509 commandArguments.append("--delete-after");
510 commandArguments.append("--delete-excluded");
511 }
512
513 commandArguments.append(dest); // set new dest as source Argument - as it was calculated at manageWizard
514 }
515 else
516 {
517 QString tslash;
518 if (WINrunning && Operation[currentOperation] -> GetRemote())
519 tslash=XnixSLASH;
520 else
521 tslash=SLASH;
522
523 commandArguments.append(dest + snapDefaultDir + time + tslash + sourceLast);
524 }
525
526 commandArguments.append(source); // set new source as destination ...as it was calculated at manageWizard or changed by changeRestorePath
527 }
528
529
530
531 // runProcess()()=====================================================================================================
532 // Run the desired process
runProcess()533 void manageWizard::runProcess()
534 {
535 bool skipTHIS = false;
536
537 // specify command & arguments & append information message
538 QString commandLocal =""; commandArguments.clear(); // the local command is used here !!
539 QString InfoMessage = "<br><font color=magenta><b>";
540 if (wizard_type == "restoreBackup")
541 {
542 commandLocal = rsyncCommandPath;
543
544 calcCommandArgs(); //calculate command arguments and set commandArguments
545
546 if (MainRun)
547 InfoMessage.append(tr("Restoring data: main trunk","info message displayed during ...data restoration"));
548 else
549 {
550 InfoMessage.append(tr("Restoring data: snapshot files","info message displayed during ...data restoration") + " - " + time);
551 if (!snapSpecificDirExists)
552 skipTHIS = true;
553 }
554 if (DryRun)
555 InfoMessage.append(" (" + tr("simulation") + ")");
556 }
557 else
558 {
559 InfoMessage.append(tr("Deleting data","info message displayed during ...data deletion"));
560 QDir destFolder(dest);
561 if (destFolder.exists())
562 {
563 commandLocal = "rm";
564 commandArguments << "-rvf" << dest;
565 }
566 else
567 skipTHIS = true;
568
569 }
570
571 InfoMessage.append(" ...</b><br>=====================================</font><br>");
572
573 if (skipTHIS)
574 InfoMessage.append("<br><font color=blue>" + tr("No snapshot specific data exist. Skipping...","info message displayed during ...data restoration/deletion") +"</font><br>");
575
576 uiW.actionView->append(InfoMessage);
577
578 // These are for testing purposes~~~~~~~~~~~~~~~~~~~~
579 //commandLocal = "sleep";
580 //commandArguments.clear();commandArguments << "4";
581 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
582
583 // Do not start specific snapshot data restoration if the relevant snapshots dir does not exist
584 // or do not delete the snapshots folder if it does not exist
585 if (skipTHIS)
586 procFinished();
587 else
588 {
589 commandProcess -> start (commandLocal,commandArguments);
590 commandProcess -> waitForStarted();
591 procRunning = true;
592 }
593 }
594 // end of managewizard.cpp ---------------------------------------------------------------------------
595
596