io/fileview: add support for watching changes

This commit is contained in:
outfoxxed 2024-12-06 02:32:19 -08:00
parent ccf885081c
commit 69d13967c9
Signed by: outfoxxed
GPG key ID: 4C88A185FB89301E
2 changed files with 71 additions and 3 deletions

View file

@ -6,6 +6,7 @@
#include <qdir.h>
#include <qfiledevice.h>
#include <qfileinfo.h>
#include <qfilesystemwatcher.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qmutex.h>
@ -280,7 +281,6 @@ void FileViewWriter::write(
if (shouldCancel.loadAcquire()) return;
if (doAtomicWrite) {
qDebug() << "Atomic commit";
if (!reinterpret_cast<QSaveFile*>(file.get())->commit()) { // NOLINT
qmlWarning(view) << "Write of " << state.path << " failed: Atomic commit failed.";
}
@ -477,6 +477,49 @@ void FileView::updatePath() {
} else {
this->emitDataChanged();
}
this->updateWatchedFiles();
}
void FileView::updateWatchedFiles() {
delete this->watcher;
if (!this->targetPath.isEmpty() && this->bWatchChanges) {
qCDebug(logFileView) << "Creating watcher for" << this << "at" << this->targetPath;
this->watcher = new QFileSystemWatcher(this);
this->watcher->addPath(this->targetPath);
this->watcher->addPath(QDir(this->targetPath).dirName());
QObject::connect(
this->watcher,
&QFileSystemWatcher::fileChanged,
this,
&FileView::onWatchedFileChanged
);
QObject::connect(
this->watcher,
&QFileSystemWatcher::directoryChanged,
this,
&FileView::onWatchedDirectoryChanged
);
}
}
void FileView::onWatchedFileChanged() {
if (!this->watcher->files().contains(this->targetPath)) {
this->watcher->addPath(this->targetPath);
}
emit this->fileChanged();
}
void FileView::onWatchedDirectoryChanged() {
if (!this->watcher->files().contains(this->targetPath) && QFileInfo(this->targetPath).exists()) {
// the file was just created
this->watcher->addPath(this->targetPath);
emit this->fileChanged();
}
}
bool FileView::shouldBlockRead() const {

View file

@ -4,6 +4,7 @@
#include <qatomic.h>
#include <qdebug.h>
#include <qfilesystemwatcher.h>
#include <qlogging.h>
#include <qmutex.h>
#include <qobject.h>
@ -187,13 +188,13 @@ class FileView: public QObject {
/// > [!WARNING] We cannot think of a valid use case for this.
/// > You almost definitely want @@blockLoading.
QSDOC_PROPERTY_OVERRIDE(bool blockAllReads READ blockAllReads WRITE setBlockAllReads NOTIFY blockAllReadsChanged);
/// If true (default false), all calls to @@setText or @@setData will block the
/// If true (default false), all calls to @@setText() or @@setData() will block the
/// UI thread until the write succeeds or fails.
///
/// > [!WARNING] Blocking operations should be used carefully to avoid stutters and other performance
/// > degradations. Blocking means that your interface **WILL NOT FUNCTION** during the call.
Q_PROPERTY(bool blockWrites READ default WRITE default NOTIFY blockWritesChanged BINDABLE bindableBlockWrites);
/// If true (default), all calls to @@setText or @@setData will be performed atomically,
/// If true (default), all calls to @@setText() or @@setData() will be performed atomically,
/// meaning if the write fails for any reason, the file will not be modified.
///
/// > [!NOTE] This works by creating another file with the desired content, and renaming
@ -202,6 +203,18 @@ class FileView: public QObject {
/// If true (default), read or write errors will be printed to the quickshell logs.
/// If false, all known errors will not be printed.
QSDOC_PROPERTY_OVERRIDE(bool printErrors READ default WRITE default NOTIFY printErrorsChanged);
/// If true (defaule false), @@fileChanged() will be called whenever the content of the file
/// changes on disk, including when @@setText() or @@setData() are used.
///
/// > [!NOTE] You can reload the file's content whenever it changes on disk like so:
/// > ```qml
/// > FileView {
/// > // ...
/// > watchChanges: true
/// > onFileChanged: this.reload()
/// > }
/// > ```
Q_PROPERTY(bool watchChanges READ default WRITE default NOTIFY watchChangesChanged BINDABLE bindableWatchChanges);
QSDOC_HIDE Q_PROPERTY(QString __path READ path WRITE setPath NOTIFY pathChanged);
QSDOC_HIDE Q_PROPERTY(QString __text READ text NOTIFY internalTextChanged);
@ -297,6 +310,7 @@ public:
[[nodiscard]] QBindable<bool> bindableAtomicWrites() { return &this->bAtomicWrites; }
[[nodiscard]] QBindable<bool> bindablePrintErrors() { return &this->bPrintErrors; }
[[nodiscard]] QBindable<bool> bindableWatchChanges() { return &this->bWatchChanges; }
signals:
/// Emitted if the file was loaded successfully.
@ -307,6 +321,8 @@ signals:
void saved();
/// Emitted if the file failed to save.
void saveFailed(qs::io::FileViewError::Enum error);
/// Emitted if the file changes on disk and @@watchChanges is true.
void fileChanged();
void pathChanged();
QSDOC_HIDE void internalTextChanged();
@ -320,6 +336,7 @@ signals:
void blockWritesChanged();
void atomicWritesChanged();
void printErrorsChanged();
void watchChangesChanged();
private slots:
void operationFinished();
@ -332,6 +349,9 @@ private:
void saveSync();
void updateState(FileViewState& newState);
void updatePath();
void updateWatchedFiles();
void onWatchedFileChanged();
void onWatchedDirectoryChanged();
[[nodiscard]] bool shouldBlockRead() const;
[[nodiscard]] FileViewReader* liveReader() const;
@ -353,6 +373,8 @@ private:
bool mBlockLoading = false;
bool mBlockAllReads = false;
QFileSystemWatcher* watcher = nullptr;
GuardedEmitter<&FileView::internalTextChanged> textChangedEmitter;
GuardedEmitter<&FileView::internalDataChanged> dataChangedEmitter;
void emitDataChanged();
@ -374,8 +396,11 @@ public:
Q_OBJECT_BINDABLE_PROPERTY(FileView, bool, bBlockWrites, &FileView::blockWritesChanged);
Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(FileView, bool, bAtomicWrites, true, &FileView::atomicWritesChanged);
Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(FileView, bool, bPrintErrors, true, &FileView::printErrorsChanged);
Q_OBJECT_BINDABLE_PROPERTY(FileView, bool, bWatchChanges, &FileView::watchChangesChanged);
// clang-format on
QS_BINDING_SUBSCRIBE_METHOD(FileView, bWatchChanges, updateWatchedFiles, onValueChanged);
void setPreload(bool preload);
void setBlockLoading(bool blockLoading);
void setBlockAllReads(bool blockAllReads);