diff --git a/changelog/next.md b/changelog/next.md index 4f550e8..4883c93 100644 --- a/changelog/next.md +++ b/changelog/next.md @@ -33,6 +33,7 @@ set shell id. - IPC operations filter available instances to the current display connection by default. - PwNodeLinkTracker ignores sound level monitoring programs. - Replaced breakpad with cpptrace. +- Reloads are prevented if no file content has changed. ## Bug Fixes diff --git a/src/core/generation.cpp b/src/core/generation.cpp index c68af71..21febc3 100644 --- a/src/core/generation.cpp +++ b/src/core/generation.cpp @@ -209,6 +209,8 @@ bool EngineGeneration::setExtraWatchedFiles(const QVector& files) { for (const auto& file: files) { if (!this->scanner.scannedFiles.contains(file)) { this->extraWatchedFiles.append(file); + QByteArray data; + this->scanner.readAndHashFile(file, data); } } @@ -229,6 +231,11 @@ void EngineGeneration::onFileChanged(const QString& name) { auto fileInfo = QFileInfo(name); if (fileInfo.isFile() && fileInfo.size() == 0) return; + if (!this->scanner.hasFileContentChanged(name)) { + qCDebug(logQmlScanner) << "Ignoring file change with unchanged content:" << name; + return; + } + emit this->filesChanged(); } } @@ -237,6 +244,11 @@ void EngineGeneration::onDirectoryChanged() { // try to find any files that were just deleted from a replace operation for (auto& file: this->deletedWatchedFiles) { if (QFileInfo(file).exists()) { + if (!this->scanner.hasFileContentChanged(file)) { + qCDebug(logQmlScanner) << "Ignoring restored file with unchanged content:" << file; + continue; + } + emit this->filesChanged(); break; } diff --git a/src/core/scan.cpp b/src/core/scan.cpp index 37b0fac..58da38c 100644 --- a/src/core/scan.cpp +++ b/src/core/scan.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -21,6 +22,25 @@ QS_LOGGING_CATEGORY(logQmlScanner, "quickshell.qmlscanner", QtWarningMsg); +bool QmlScanner::readAndHashFile(const QString& path, QByteArray& data) { + auto file = QFile(path); + if (!file.open(QFile::ReadOnly)) return false; + data = file.readAll(); + this->fileHashes.insert(path, QCryptographicHash::hash(data, QCryptographicHash::Md5)); + return true; +} + +bool QmlScanner::hasFileContentChanged(const QString& path) const { + auto it = this->fileHashes.constFind(path); + if (it == this->fileHashes.constEnd()) return true; + + auto file = QFile(path); + if (!file.open(QFile::ReadOnly)) return true; + + auto newHash = QCryptographicHash::hash(file.readAll(), QCryptographicHash::Md5); + return newHash != it.value(); +} + void QmlScanner::scanDir(const QDir& dir) { if (this->scannedDirs.contains(dir)) return; this->scannedDirs.push_back(dir); @@ -109,13 +129,13 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna qCDebug(logQmlScanner) << "Scanning qml file" << path; - auto file = QFile(path); - if (!file.open(QFile::ReadOnly | QFile::Text)) { + QByteArray fileData; + if (!this->readAndHashFile(path, fileData)) { qCWarning(logQmlScanner) << "Failed to open file" << path; return false; } - auto stream = QTextStream(&file); + auto stream = QTextStream(&fileData); auto imports = QVector(); bool inHeader = true; @@ -219,8 +239,6 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna postError("unclosed preprocessor if block"); } - file.close(); - if (isOverridden) { this->fileIntercepts.insert(path, overrideText); } @@ -257,8 +275,11 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna continue; } - if (import.endsWith(".js")) this->scannedFiles.push_back(cpath); - else this->scanDir(cpath); + if (import.endsWith(".js")) { + this->scannedFiles.push_back(cpath); + QByteArray jsData; + this->readAndHashFile(cpath, jsData); + } else this->scanDir(cpath); } return true; @@ -273,14 +294,12 @@ void QmlScanner::scanQmlRoot(const QString& path) { bool QmlScanner::scanQmlJson(const QString& path) { qCDebug(logQmlScanner) << "Scanning qml.json file" << path; - auto file = QFile(path); - if (!file.open(QFile::ReadOnly | QFile::Text)) { + QByteArray data; + if (!this->readAndHashFile(path, data)) { qCWarning(logQmlScanner) << "Failed to open file" << path; return false; } - auto data = file.readAll(); - // Importing this makes CI builds fail for some reason. QJsonParseError error; // NOLINT (misc-include-cleaner) auto json = QJsonDocument::fromJson(data, &error); diff --git a/src/core/scan.hpp b/src/core/scan.hpp index 29f8f6a..26034e1 100644 --- a/src/core/scan.hpp +++ b/src/core/scan.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -21,6 +22,7 @@ public: QVector scannedDirs; QVector scannedFiles; + QHash fileHashes; QHash fileIntercepts; struct ScanError { @@ -31,6 +33,9 @@ public: QVector scanErrors; + bool readAndHashFile(const QString& path, QByteArray& data); + [[nodiscard]] bool hasFileContentChanged(const QString& path) const; + private: QDir rootPath;