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