Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/FileWatcher.cpp')
-rw-r--r--src/core/FileWatcher.cpp231
1 files changed, 20 insertions, 211 deletions
diff --git a/src/core/FileWatcher.cpp b/src/core/FileWatcher.cpp
index 0bc5e3444..2d37734aa 100644
--- a/src/core/FileWatcher.cpp
+++ b/src/core/FileWatcher.cpp
@@ -1,6 +1,5 @@
/*
- * Copyright (C) 2011 Felix Geyer <debfx@fobos.de>
- * Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+ * 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
@@ -19,30 +18,28 @@
#include "FileWatcher.h"
#include "core/AsyncTask.h"
-#include "core/Clock.h"
#include <QCryptographicHash>
-#include <QFileInfo>
#ifdef Q_OS_LINUX
#include <sys/vfs.h>
#endif
-namespace
-{
- const int FileChangeDelay = 200;
-} // namespace
-
FileWatcher::FileWatcher(QObject* parent)
: QObject(parent)
{
connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), SLOT(checkFileChanged()));
connect(&m_fileChecksumTimer, SIGNAL(timeout()), SLOT(checkFileChanged()));
- connect(&m_fileChangeDelayTimer, SIGNAL(timeout()), SIGNAL(fileChanged()));
+ 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();
@@ -82,6 +79,7 @@ void FileWatcher::stop()
}
m_filePath.clear();
m_fileChecksum.clear();
+ m_fileChecksumTimer.stop();
m_fileChangeDelayTimer.stop();
}
@@ -120,8 +118,7 @@ void FileWatcher::checkFileChanged()
// Prevent reentrance
m_ignoreFileChange = true;
- // Only trigger the change notice if there is a checksum mismatch
- auto checksum = calculateChecksum();
+ auto checksum = AsyncTask::runAndWaitForFuture([this]() -> QByteArray { return calculateChecksum(); });
if (checksum != m_fileChecksum) {
m_fileChecksum = checksum;
m_fileChangeDelayTimer.start(0);
@@ -132,205 +129,17 @@ void FileWatcher::checkFileChanged()
QByteArray FileWatcher::calculateChecksum()
{
- return AsyncTask::runAndWaitForFuture([this]() -> QByteArray {
- 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;
- });
-}
-
-BulkFileWatcher::BulkFileWatcher(QObject* parent)
- : QObject(parent)
-{
- connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), SLOT(handleFileChanged(QString)));
- connect(&m_fileWatcher, SIGNAL(directoryChanged(QString)), SLOT(handleDirectoryChanged(QString)));
- connect(&m_watchedFilesIgnoreTimer, SIGNAL(timeout()), this, SLOT(observeFileChanges()));
- connect(&m_pendingSignalsTimer, SIGNAL(timeout()), this, SLOT(emitSignals()));
- m_watchedFilesIgnoreTimer.setSingleShot(true);
- m_pendingSignalsTimer.setSingleShot(true);
-}
-
-void BulkFileWatcher::clear()
-{
- for (const QString& path : m_fileWatcher.files() + m_fileWatcher.directories()) {
- const QFileInfo info(path);
- m_fileWatcher.removePath(info.absoluteFilePath());
- m_fileWatcher.removePath(info.absolutePath());
- }
- m_watchedPaths.clear();
- m_watchedFilesInDirectory.clear();
- m_watchedFilesIgnored.clear();
-}
-
-void BulkFileWatcher::removePath(const QString& path)
-{
- const QFileInfo info(path);
- const QString filePath = info.absoluteFilePath();
- const QString directoryPath = info.absolutePath();
- m_watchedFilesInDirectory[directoryPath].remove(filePath);
- m_fileWatcher.removePath(filePath);
- m_watchedPaths.remove(filePath);
- if (m_watchedFilesInDirectory[directoryPath].isEmpty()) {
- m_fileWatcher.removePath(directoryPath);
- m_watchedPaths.remove(directoryPath);
- m_watchedFilesInDirectory.remove(directoryPath);
- }
-}
-
-void BulkFileWatcher::addPath(const QString& path)
-{
- const QFileInfo info(path);
- const QString filePath = info.absoluteFilePath();
- const QString directoryPath = info.absolutePath();
- if (!m_watchedPaths.value(filePath)) {
- const bool fileSuccess = m_fileWatcher.addPath(filePath);
- m_watchedPaths[filePath] = fileSuccess;
- }
- if (!m_watchedPaths.value(directoryPath)) {
- const bool directorySuccess = m_fileWatcher.addPath(directoryPath);
- m_watchedPaths[directoryPath] = directorySuccess;
- }
- m_watchedFilesInDirectory[directoryPath][filePath] = info.exists() ? info.lastModified().toMSecsSinceEpoch() : 0;
-}
-
-void BulkFileWatcher::handleFileChanged(const QString& path)
-{
- const QFileInfo info(path);
- const QString filePath = info.absoluteFilePath();
- const QString directoryPath = info.absolutePath();
- const QMap<QString, qint64>& watchedFiles = m_watchedFilesInDirectory[directoryPath];
- const qint64 lastModificationTime = info.lastModified().toMSecsSinceEpoch();
- const bool created = watchedFiles[filePath] == 0 && info.exists();
- const bool deleted = watchedFiles[filePath] != 0 && !info.exists();
- const bool changed = !created && !deleted && lastModificationTime != watchedFiles[filePath];
-
- addPath(path);
-
- if (m_watchedFilesIgnored[info.canonicalFilePath()] > Clock::currentDateTimeUtc()) {
- // changes are blocked
- return;
- }
- if (created) {
- qDebug("File created %s", qPrintable(path));
- scheduleSignal(Created, filePath);
- }
- if (changed) {
- qDebug("File changed %s", qPrintable(path));
- scheduleSignal(Updated, filePath);
- }
- if (deleted) {
- qDebug("File removed %s", qPrintable(path));
- scheduleSignal(Removed, filePath);
- }
-}
-
-void BulkFileWatcher::handleDirectoryChanged(const QString& path)
-{
- qDebug("Directory changed %s", qPrintable(path));
- const QFileInfo directoryInfo(path);
- const QString directoryPath = directoryInfo.absoluteFilePath();
- QMap<QString, qint64>& watchedFiles = m_watchedFilesInDirectory[directoryPath];
- for (const QString& filename : watchedFiles.keys()) {
- const QFileInfo fileInfo(filename);
- const QString filePath = fileInfo.absoluteFilePath();
- const qint64 previousModificationTime = watchedFiles[filePath];
- const qint64 lastModificationTime = fileInfo.lastModified().toMSecsSinceEpoch();
- if (!fileInfo.exists() && previousModificationTime != 0) {
- qDebug("Remove watch file %s", qPrintable(fileInfo.absoluteFilePath()));
- m_fileWatcher.removePath(filePath);
- m_watchedPaths.remove(filePath);
- watchedFiles.remove(filePath);
- scheduleSignal(Removed, filePath);
- }
- if (previousModificationTime == 0 && fileInfo.exists()) {
- qDebug("Add watch file %s", qPrintable(fileInfo.absoluteFilePath()));
- if (!m_watchedPaths.value(filePath)) {
- const bool success = m_fileWatcher.addPath(filePath);
- m_watchedPaths[filePath] = success;
- watchedFiles[filePath] = lastModificationTime;
- }
- scheduleSignal(Created, filePath);
- }
- if (fileInfo.exists() && previousModificationTime != lastModificationTime) {
- // this case is handled using
- qDebug("Refresh watch file %s", qPrintable(fileInfo.absoluteFilePath()));
- m_fileWatcher.removePath(fileInfo.absolutePath());
- m_fileWatcher.addPath(fileInfo.absolutePath());
- scheduleSignal(Updated, filePath);
- }
- m_watchedFilesInDirectory[directoryPath][filePath] = fileInfo.exists() ? lastModificationTime : 0;
- }
-}
-
-void BulkFileWatcher::emitSignals()
-{
- QMap<QString, QList<Signal>> queued;
- m_pendingSignals.swap(queued);
- for (const auto& path : queued.keys()) {
- const auto& signal = queued[path];
- if (signal.last() == Removed) {
- qDebug("Emit %s removed", qPrintable(path));
- emit fileRemoved(path);
- continue;
- }
- if (signal.first() == Created) {
- qDebug("Emit %s created", qPrintable(path));
- emit fileCreated(path);
- continue;
- }
- qDebug("Emit %s changed", qPrintable(path));
- emit fileChanged(path);
- }
-}
-
-void BulkFileWatcher::scheduleSignal(Signal signal, const QString& path)
-{
- // we need to collect signals since the file watcher API may send multiple signals for a "single" change
- // therefore we wait until the event loop finished before starting to import any changes
- const QString filePath = QFileInfo(path).absoluteFilePath();
- m_pendingSignals[filePath] << signal;
-
- if (!m_pendingSignalsTimer.isActive()) {
- m_pendingSignalsTimer.start();
- }
-}
-
-void BulkFileWatcher::ignoreFileChanges(const QString& path)
-{
- const QFileInfo info(path);
- m_watchedFilesIgnored[info.canonicalFilePath()] = Clock::currentDateTimeUtc().addMSecs(FileChangeDelay);
-}
-
-void BulkFileWatcher::observeFileChanges(bool delayed)
-{
- int timeout = 0;
- if (delayed) {
- timeout = FileChangeDelay;
- } else {
- const QDateTime current = Clock::currentDateTimeUtc();
- for (const QString& key : m_watchedFilesIgnored.keys()) {
- if (m_watchedFilesIgnored[key] < current) {
- // We assume that there was no concurrent change of the database
- // during our block - so no need to reimport
- qDebug("Remove block from %s", qPrintable(key));
- m_watchedFilesIgnored.remove(key);
- continue;
- }
- qDebug("Keep block from %s", qPrintable(key));
- timeout = qMin(timeout, static_cast<int>(current.msecsTo(m_watchedFilesIgnored[key])));
+ 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 (timeout > 0 && !m_watchedFilesIgnoreTimer.isActive()) {
- m_watchedFilesIgnoreTimer.start(timeout);
- }
+ // If we fail to open the file return the last known checksum, this
+ // prevents unnecessary merge requests on intermittent network shares
+ return m_fileChecksum;
}