Skip to content

Commit 9a9c605

Browse files
committed
core: hash scanned files and don't trigger a reload if matching
Nix builds often trip QFileSystemWatcher, causing random reloads.
1 parent bd62179 commit 9a9c605

4 files changed

Lines changed: 48 additions & 11 deletions

File tree

changelog/next.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ set shell id.
3333
- IPC operations filter available instances to the current display connection by default.
3434
- PwNodeLinkTracker ignores sound level monitoring programs.
3535
- Replaced breakpad with cpptrace.
36+
- Reloads are prevented if no file content has changed.
3637

3738
## Bug Fixes
3839

src/core/generation.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ bool EngineGeneration::setExtraWatchedFiles(const QVector<QString>& files) {
209209
for (const auto& file: files) {
210210
if (!this->scanner.scannedFiles.contains(file)) {
211211
this->extraWatchedFiles.append(file);
212+
QByteArray data;
213+
this->scanner.readAndHashFile(file, data);
212214
}
213215
}
214216

@@ -229,6 +231,11 @@ void EngineGeneration::onFileChanged(const QString& name) {
229231
auto fileInfo = QFileInfo(name);
230232
if (fileInfo.isFile() && fileInfo.size() == 0) return;
231233

234+
if (!this->scanner.hasFileContentChanged(name)) {
235+
qCDebug(logQmlScanner) << "Ignoring file change with unchanged content:" << name;
236+
return;
237+
}
238+
232239
emit this->filesChanged();
233240
}
234241
}
@@ -237,6 +244,11 @@ void EngineGeneration::onDirectoryChanged() {
237244
// try to find any files that were just deleted from a replace operation
238245
for (auto& file: this->deletedWatchedFiles) {
239246
if (QFileInfo(file).exists()) {
247+
if (!this->scanner.hasFileContentChanged(file)) {
248+
qCDebug(logQmlScanner) << "Ignoring restored file with unchanged content:" << file;
249+
continue;
250+
}
251+
240252
emit this->filesChanged();
241253
break;
242254
}

src/core/scan.cpp

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <utility>
44

55
#include <qcontainerfwd.h>
6+
#include <qcryptographichash.h>
67
#include <qdir.h>
78
#include <qfileinfo.h>
89
#include <qjsengine.h>
@@ -21,6 +22,25 @@
2122

2223
QS_LOGGING_CATEGORY(logQmlScanner, "quickshell.qmlscanner", QtWarningMsg);
2324

25+
bool QmlScanner::readAndHashFile(const QString& path, QByteArray& data) {
26+
auto file = QFile(path);
27+
if (!file.open(QFile::ReadOnly)) return false;
28+
data = file.readAll();
29+
this->fileHashes.insert(path, QCryptographicHash::hash(data, QCryptographicHash::Md5));
30+
return true;
31+
}
32+
33+
bool QmlScanner::hasFileContentChanged(const QString& path) const {
34+
auto it = this->fileHashes.constFind(path);
35+
if (it == this->fileHashes.constEnd()) return true;
36+
37+
auto file = QFile(path);
38+
if (!file.open(QFile::ReadOnly)) return true;
39+
40+
auto newHash = QCryptographicHash::hash(file.readAll(), QCryptographicHash::Md5);
41+
return newHash != it.value();
42+
}
43+
2444
void QmlScanner::scanDir(const QDir& dir) {
2545
if (this->scannedDirs.contains(dir)) return;
2646
this->scannedDirs.push_back(dir);
@@ -109,13 +129,13 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
109129

110130
qCDebug(logQmlScanner) << "Scanning qml file" << path;
111131

112-
auto file = QFile(path);
113-
if (!file.open(QFile::ReadOnly | QFile::Text)) {
132+
QByteArray fileData;
133+
if (!this->readAndHashFile(path, fileData)) {
114134
qCWarning(logQmlScanner) << "Failed to open file" << path;
115135
return false;
116136
}
117137

118-
auto stream = QTextStream(&file);
138+
auto stream = QTextStream(&fileData);
119139
auto imports = QVector<QString>();
120140

121141
bool inHeader = true;
@@ -219,8 +239,6 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
219239
postError("unclosed preprocessor if block");
220240
}
221241

222-
file.close();
223-
224242
if (isOverridden) {
225243
this->fileIntercepts.insert(path, overrideText);
226244
}
@@ -257,8 +275,11 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
257275
continue;
258276
}
259277

260-
if (import.endsWith(".js")) this->scannedFiles.push_back(cpath);
261-
else this->scanDir(cpath);
278+
if (import.endsWith(".js")) {
279+
this->scannedFiles.push_back(cpath);
280+
QByteArray jsData;
281+
this->readAndHashFile(cpath, jsData);
282+
} else this->scanDir(cpath);
262283
}
263284

264285
return true;
@@ -273,14 +294,12 @@ void QmlScanner::scanQmlRoot(const QString& path) {
273294
bool QmlScanner::scanQmlJson(const QString& path) {
274295
qCDebug(logQmlScanner) << "Scanning qml.json file" << path;
275296

276-
auto file = QFile(path);
277-
if (!file.open(QFile::ReadOnly | QFile::Text)) {
297+
QByteArray data;
298+
if (!this->readAndHashFile(path, data)) {
278299
qCWarning(logQmlScanner) << "Failed to open file" << path;
279300
return false;
280301
}
281302

282-
auto data = file.readAll();
283-
284303
// Importing this makes CI builds fail for some reason.
285304
QJsonParseError error; // NOLINT (misc-include-cleaner)
286305
auto json = QJsonDocument::fromJson(data, &error);

src/core/scan.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <qbytearray.h>
34
#include <qcontainerfwd.h>
45
#include <qdir.h>
56
#include <qhash.h>
@@ -21,6 +22,7 @@ class QmlScanner {
2122

2223
QVector<QDir> scannedDirs;
2324
QVector<QString> scannedFiles;
25+
QHash<QString, QByteArray> fileHashes;
2426
QHash<QString, QString> fileIntercepts;
2527

2628
struct ScanError {
@@ -31,6 +33,9 @@ class QmlScanner {
3133

3234
QVector<ScanError> scanErrors;
3335

36+
bool readAndHashFile(const QString& path, QByteArray& data);
37+
[[nodiscard]] bool hasFileContentChanged(const QString& path) const;
38+
3439
private:
3540
QDir rootPath;
3641

0 commit comments

Comments
 (0)