mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-01-24 21:46:54 -05:00
443b9e4d37
* Fixes #5295 * Introduce a callback approach to AsyncTask instead of entering multiple QEventLoop in the stack. This has additional benefits including not returning to the original stack location and potentially operating on deleted objects (use after free).
149 lines
4.3 KiB
C++
149 lines
4.3 KiB
C++
/*
|
|
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 2 or (at your option)
|
|
* version 3 of the License.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "FileWatcher.h"
|
|
|
|
#include "core/AsyncTask.h"
|
|
|
|
#include <QCryptographicHash>
|
|
|
|
#ifdef Q_OS_LINUX
|
|
#include <sys/vfs.h>
|
|
#endif
|
|
|
|
FileWatcher::FileWatcher(QObject* parent)
|
|
: QObject(parent)
|
|
{
|
|
connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), SLOT(checkFileChanged()));
|
|
connect(&m_fileChecksumTimer, SIGNAL(timeout()), SLOT(checkFileChanged()));
|
|
connect(&m_fileChangeDelayTimer, &QTimer::timeout, this, [this] { emit fileChanged(m_filePath); });
|
|
m_fileChangeDelayTimer.setSingleShot(true);
|
|
m_fileIgnoreDelayTimer.setSingleShot(true);
|
|
}
|
|
|
|
FileWatcher::~FileWatcher()
|
|
{
|
|
stop();
|
|
}
|
|
|
|
void FileWatcher::start(const QString& filePath, int checksumIntervalSeconds, int checksumSizeKibibytes)
|
|
{
|
|
stop();
|
|
|
|
#if defined(Q_OS_LINUX)
|
|
struct statfs statfsBuf;
|
|
bool forcePolling = false;
|
|
const auto NFS_SUPER_MAGIC = 0x6969;
|
|
|
|
if (!statfs(filePath.toLocal8Bit().constData(), &statfsBuf)) {
|
|
forcePolling = (statfsBuf.f_type == NFS_SUPER_MAGIC);
|
|
} else {
|
|
// if we can't get the fs type let's fall back to polling
|
|
forcePolling = true;
|
|
}
|
|
auto objectName = forcePolling ? QLatin1String("_qt_autotest_force_engine_poller") : QLatin1String("");
|
|
m_fileWatcher.setObjectName(objectName);
|
|
#endif
|
|
|
|
m_fileWatcher.addPath(filePath);
|
|
m_filePath = filePath;
|
|
|
|
// Handle file checksum
|
|
m_fileChecksumSizeBytes = checksumSizeKibibytes * 1024;
|
|
m_fileChecksum = calculateChecksum();
|
|
if (checksumIntervalSeconds > 0) {
|
|
m_fileChecksumTimer.start(checksumIntervalSeconds * 1000);
|
|
}
|
|
|
|
m_ignoreFileChange = false;
|
|
}
|
|
|
|
void FileWatcher::stop()
|
|
{
|
|
if (!m_filePath.isEmpty()) {
|
|
m_fileWatcher.removePath(m_filePath);
|
|
}
|
|
m_filePath.clear();
|
|
m_fileChecksum.clear();
|
|
m_fileChecksumTimer.stop();
|
|
m_fileChangeDelayTimer.stop();
|
|
}
|
|
|
|
void FileWatcher::pause()
|
|
{
|
|
m_ignoreFileChange = true;
|
|
m_fileChangeDelayTimer.stop();
|
|
}
|
|
|
|
void FileWatcher::resume()
|
|
{
|
|
m_ignoreFileChange = false;
|
|
// Add a short delay to start in the next event loop
|
|
if (!m_fileIgnoreDelayTimer.isActive()) {
|
|
m_fileIgnoreDelayTimer.start(0);
|
|
}
|
|
}
|
|
|
|
bool FileWatcher::shouldIgnoreChanges()
|
|
{
|
|
return m_filePath.isEmpty() || m_ignoreFileChange || m_fileIgnoreDelayTimer.isActive()
|
|
|| m_fileChangeDelayTimer.isActive();
|
|
}
|
|
|
|
bool FileWatcher::hasSameFileChecksum()
|
|
{
|
|
return calculateChecksum() == m_fileChecksum;
|
|
}
|
|
|
|
void FileWatcher::checkFileChanged()
|
|
{
|
|
if (shouldIgnoreChanges()) {
|
|
return;
|
|
}
|
|
|
|
// Prevent reentrance
|
|
m_ignoreFileChange = true;
|
|
|
|
AsyncTask::runThenCallback([=] { return calculateChecksum(); },
|
|
this,
|
|
[=](QByteArray checksum) {
|
|
if (checksum != m_fileChecksum) {
|
|
m_fileChecksum = checksum;
|
|
m_fileChangeDelayTimer.start(0);
|
|
}
|
|
|
|
m_ignoreFileChange = false;
|
|
});
|
|
}
|
|
|
|
QByteArray FileWatcher::calculateChecksum()
|
|
{
|
|
QFile file(m_filePath);
|
|
if (file.open(QFile::ReadOnly)) {
|
|
QCryptographicHash hash(QCryptographicHash::Sha256);
|
|
if (m_fileChecksumSizeBytes > 0) {
|
|
hash.addData(file.read(m_fileChecksumSizeBytes));
|
|
} else {
|
|
hash.addData(&file);
|
|
}
|
|
return hash.result();
|
|
}
|
|
// If we fail to open the file return the last known checksum, this
|
|
// prevents unnecessary merge requests on intermittent network shares
|
|
return m_fileChecksum;
|
|
}
|