diff options
Diffstat (limited to 'src/core/FileWatcher.cpp')
-rw-r--r-- | src/core/FileWatcher.cpp | 231 |
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; } |