Skip to content
This repository was archived by the owner on Sep 27, 2024. It is now read-only.

Commit 75b2f2f

Browse files
authored
Merge pull request #177 from cipherboy/file-handling
Better UX around opened content
2 parents 874d54d + adc23e0 commit 75b2f2f

File tree

6 files changed

+292
-14
lines changed

6 files changed

+292
-14
lines changed

include/MainWindow.h

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include <QThread>
3030
#include <QMenu>
3131
#include <QMessageBox>
32+
#include <QFileSystemWatcher>
3233

3334
extern "C"
3435
{
@@ -65,7 +66,7 @@ class MainWindow : public QMainWindow
6566
/**
6667
* @brief Opens a specific file
6768
*/
68-
void openFile(const QString& path);
69+
void openFile(const QString& path, bool reload = false);
6970

7071
void openSSGDialog(const QString& customDismissLabel = "");
7172

@@ -95,6 +96,15 @@ class MainWindow : public QMainWindow
9596
*/
9697
void closeMainWindowAsync();
9798

99+
/**
100+
* @bried Reloads the opened content
101+
*
102+
*
103+
* If a content file is opened, it might have changed on disk. This
104+
* allows the user to update reload it in case it has changed.
105+
*/
106+
void reloadContent();
107+
98108
/**
99109
* @brief Checks whether a file is currently opened
100110
*/
@@ -236,6 +246,10 @@ class MainWindow : public QMainWindow
236246
/// If true, the profile combobox change signal is ignored, this avoids unnecessary profile refreshes
237247
bool mIgnoreProfileComboBox;
238248

249+
/// Implement watching of original XCCDF/DS
250+
QFileSystemWatcher* mFSWatch;
251+
QString mFSLastSeen;
252+
239253
signals:
240254
/**
241255
* @brief We signal this to show the dialog
@@ -382,6 +396,11 @@ class MainWindow : public QMainWindow
382396
void markLoadedTailoringFile(const QString& filePath);
383397
bool unsavedTailoringChanges() const;
384398

399+
/**
400+
* @brief Slot handler for notifying user that opened files changed
401+
*/
402+
void fileChanged(const QString& path);
403+
385404
public:
386405
QString getDefaultSaveDirectory();
387406
void notifySaveActionConfirmed(const QString& path, bool isDir);

include/ScanningSession.h

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
#include "ForwardDecls.h"
2626

27+
#include <QTemporaryDir>
2728
#include <QTemporaryFile>
2829
#include <QSet>
2930
#include <QDir>
@@ -61,7 +62,7 @@ class ScanningSession
6162
* Passed file may be an XCCDF file (any openscap supported version)
6263
* or source datastream (SDS) file (any openscap supported version)
6364
*/
64-
void openFile(const QString& path);
65+
void openFile(const QString& path, bool reload = false);
6566

6667
/**
6768
* @brief Closes currently opened file (if any)
@@ -75,6 +76,16 @@ class ScanningSession
7576
*/
7677
QString getOpenedFilePath() const;
7778

79+
/**
80+
* @brief Retrieves full absolute path of the opened file
81+
*/
82+
QString getOriginalFilePath() const;
83+
84+
/**
85+
* @brief Retrives the closure set of the original file
86+
*/
87+
QSet<QString> getOriginalClosure() const;
88+
7889
/**
7990
* @brief A helper method that gets the longest common ancestor dir from a set of paths
8091
*/
@@ -280,6 +291,15 @@ class ScanningSession
280291
/// Our own tailoring that may or may not initially be loaded from a file
281292
mutable struct xccdf_tailoring* mTailoring;
282293

294+
/// Temporary copy of opened DS or XCCDF file
295+
QTemporaryDir* mTempOpenDir;
296+
/// Path to temporary DS or XCCDF file
297+
QString mTempOpenPath;
298+
/// Path to original DS or XCCDF file
299+
QString mOriginalOpenPath;
300+
/// Closure of original DS or XCCDF file
301+
QSet<QString> mClosureOfOriginalFile;
302+
283303
/// Temporary file provides auto deletion and a valid temp file path
284304
QTemporaryFile mTailoringFile;
285305
/// Temporary file provides auto deletion and a valid temp file path
@@ -297,6 +317,16 @@ class ScanningSession
297317

298318
QString mUserTailoringFile;
299319
QString mUserTailoringCID;
320+
321+
/// Gets the dependency closure of the specified file.
322+
void updateDependencyClosureOfFile(const QString& filePath, QSet<QString>& targetSet) const;
323+
324+
/// Clones openFile(path) to a Temporary File
325+
void cloneToTemporaryFile(const QString& path);
326+
/// Closes mOpenFile if it is open
327+
void cleanTmpDir();
328+
/// Copies all files from the path into the temporary location
329+
void copyTempFiles(QString path, QString baseDirectory, const QFileInfo pathInfo);
300330
};
301331

302332
#endif

src/MainWindow.cpp

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@
3737
#include "RemediationRoleSaver.h"
3838

3939
#include <QFileDialog>
40+
#include <QPushButton>
4041
#include <QAbstractEventDispatcher>
4142
#include <QCloseEvent>
43+
#include <QFileSystemWatcher>
4244
#include <QDesktopWidget>
4345
#include <QMenu>
4446

@@ -85,6 +87,9 @@ MainWindow::MainWindow(QWidget* parent):
8587

8688
mIgnoreProfileComboBox(false),
8789

90+
mFSWatch(new QFileSystemWatcher()),
91+
mFSLastSeen(""),
92+
8893
mRuleResultsExpanded(false)
8994
{
9095
mUI.setupUi(this);
@@ -125,6 +130,10 @@ MainWindow::MainWindow(QWidget* parent):
125130
mUI.actionOpenCustomizationFile, SIGNAL(triggered()),
126131
this, SLOT(openCustomizationFile())
127132
);
133+
QObject::connect(
134+
mUI.actionReloadContent, SIGNAL(triggered()),
135+
this, SLOT(reloadContent())
136+
);
128137
QObject::connect(
129138
mUI.checklistComboBox, SIGNAL(currentIndexChanged(int)),
130139
this, SLOT(checklistComboboxChanged(int))
@@ -272,6 +281,11 @@ MainWindow::MainWindow(QWidget* parent):
272281
remediationButtonMenu->addAction(genAnsibleRemediation);
273282
remediationButtonMenu->addAction(genPuppetRemediation);
274283
mUI.genRemediationButton->setMenu(remediationButtonMenu);
284+
285+
QObject::connect(
286+
mFSWatch, SIGNAL(fileChanged(const QString&)),
287+
this, SLOT(fileChanged(const QString&))
288+
);
275289
}
276290

277291
MainWindow::~MainWindow()
@@ -288,6 +302,9 @@ MainWindow::~MainWindow()
288302

289303
delete mQSettings;
290304
mQSettings = 0;
305+
306+
delete mFSWatch;
307+
mFSWatch = NULL;
291308
}
292309

293310
void MainWindow::setSkipValid(bool skipValid)
@@ -333,7 +350,7 @@ void MainWindow::clearResults()
333350
mUI.actionOpen->setEnabled(true);
334351
}
335352

336-
void MainWindow::openFile(const QString& path)
353+
void MainWindow::openFile(const QString& path, bool reload)
337354
{
338355
try
339356
{
@@ -348,7 +365,7 @@ void MainWindow::openFile(const QString& path)
348365
}
349366

350367
mScanningSession->setSkipValid(mSkipValid);
351-
mScanningSession->openFile(inputPath);
368+
mScanningSession->openFile(inputPath, reload);
352369

353370
// In case openscap autonegotiated opening a tailoring file directly
354371
if (tailoringPath.isEmpty() && mScanningSession->hasTailoring())
@@ -375,6 +392,14 @@ void MainWindow::openFile(const QString& path)
375392

376393
centralWidget()->setEnabled(true);
377394

395+
// Refill mFSWatch after opening file
396+
mFSWatch->removePaths(mFSWatch->files());
397+
for (const QString path : mScanningSession->getOriginalClosure())
398+
{
399+
mFSWatch->addPath(path);
400+
}
401+
mFSLastSeen = "";
402+
378403
mDiagnosticsDialog->infoMessage(QObject::tr("Opened file '%1'.").arg(path));
379404
}
380405
catch (const std::exception& e)
@@ -423,7 +448,7 @@ void MainWindow::openFileDialog()
423448

424449
if (fileOpened())
425450
{
426-
if (openNewFileQuestionDialog(mScanningSession->getOpenedFilePath()) == QMessageBox::Yes)
451+
if (openNewFileQuestionDialog(mScanningSession->getOriginalFilePath()) == QMessageBox::Yes)
427452
closeFile();
428453
else
429454
// user cancelled closing current file, we have to abort
@@ -489,7 +514,7 @@ void MainWindow::openSSGDialog(const QString& customDismissLabel)
489514

490515
if (fileOpened())
491516
{
492-
if (openNewFileQuestionDialog(mScanningSession->getOpenedFilePath()) == QMessageBox::Yes)
517+
if (openNewFileQuestionDialog(mScanningSession->getOriginalFilePath()) == QMessageBox::Yes)
493518
{
494519
closeFile();
495520
}
@@ -524,6 +549,24 @@ void MainWindow::openTailoringFile(const QString& path)
524549
refreshTailoringProfiles();
525550
}
526551

552+
void MainWindow::reloadContent()
553+
{
554+
const QString currentFile = mScanningSession->getOriginalFilePath();
555+
openFile(currentFile, true);
556+
557+
if (!fileOpened())
558+
{
559+
// Error occurred, keep pumping events and don't move on until user
560+
// dismisses diagnostics dialog.
561+
mDiagnosticsDialog->waitUntilHidden();
562+
563+
if (!close())
564+
{
565+
throw MainWindowException("Failed to close main window!");
566+
}
567+
}
568+
}
569+
527570
void MainWindow::closeMainWindowAsync()
528571
{
529572
emit closeMainWindow();
@@ -581,6 +624,7 @@ void MainWindow::scanAsync(ScannerMode scannerMode)
581624

582625
mUI.menuSave->setEnabled(false);
583626
mUI.actionOpen->setEnabled(false);
627+
mUI.actionReloadContent->setEnabled(false);
584628

585629
mUI.ruleResultsTree->prepareForScanning();
586630

@@ -599,6 +643,7 @@ void MainWindow::scanAsync(ScannerMode scannerMode)
599643

600644
mUI.menuSave->setEnabled(true);
601645
mUI.actionOpen->setEnabled(true);
646+
mUI.actionReloadContent->setEnabled(true);
602647

603648
return;
604649
}
@@ -1275,6 +1320,7 @@ void MainWindow::scanEnded(bool canceled)
12751320

12761321
mUI.menuSave->setEnabled(true);
12771322
mUI.actionOpen->setEnabled(true);
1323+
mUI.actionReloadContent->setEnabled(true);
12781324

12791325
cleanupScanThread();
12801326
}
@@ -1507,6 +1553,29 @@ bool MainWindow::unsavedTailoringChanges() const
15071553
return mUI.tailoringFileComboBox->currentIndex() == idx;
15081554
}
15091555

1556+
void MainWindow::fileChanged(const QString& path)
1557+
{
1558+
if (path == mFSLastSeen)
1559+
return;
1560+
mFSLastSeen = path;
1561+
1562+
QMessageBox question;
1563+
question.setText(QObject::tr("The following file was modified after opening: %1").arg(path));
1564+
question.setInformativeText(QObject::tr("Do you wish to reload it, losing any unsaved tailoring changes?"));
1565+
1566+
QPushButton *reload = question.addButton(QObject::tr("Reload"), QMessageBox::AcceptRole);
1567+
QPushButton *ignore = question.addButton(QObject::tr("Ignore"), QMessageBox::RejectRole);
1568+
question.setDefaultButton(ignore);
1569+
question.setIcon(QMessageBox::Question);
1570+
1571+
question.exec();
1572+
1573+
if (question.clickedButton() == reload)
1574+
{
1575+
reloadContent();
1576+
}
1577+
}
1578+
15101579
QString MainWindow::getDefaultSaveDirectory()
15111580
{
15121581
return mQSettings->value("last_save_directory", "").toString();

0 commit comments

Comments
 (0)