Skip to content

Commit 69d1396

Browse files
committed
io/fileview: add support for watching changes
1 parent ccf8850 commit 69d1396

File tree

2 files changed

+71
-3
lines changed

2 files changed

+71
-3
lines changed

src/io/fileview.cpp

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <qdir.h>
77
#include <qfiledevice.h>
88
#include <qfileinfo.h>
9+
#include <qfilesystemwatcher.h>
910
#include <qlogging.h>
1011
#include <qloggingcategory.h>
1112
#include <qmutex.h>
@@ -280,7 +281,6 @@ void FileViewWriter::write(
280281
if (shouldCancel.loadAcquire()) return;
281282

282283
if (doAtomicWrite) {
283-
qDebug() << "Atomic commit";
284284
if (!reinterpret_cast<QSaveFile*>(file.get())->commit()) { // NOLINT
285285
qmlWarning(view) << "Write of " << state.path << " failed: Atomic commit failed.";
286286
}
@@ -477,6 +477,49 @@ void FileView::updatePath() {
477477
} else {
478478
this->emitDataChanged();
479479
}
480+
481+
this->updateWatchedFiles();
482+
}
483+
484+
void FileView::updateWatchedFiles() {
485+
delete this->watcher;
486+
487+
if (!this->targetPath.isEmpty() && this->bWatchChanges) {
488+
qCDebug(logFileView) << "Creating watcher for" << this << "at" << this->targetPath;
489+
this->watcher = new QFileSystemWatcher(this);
490+
this->watcher->addPath(this->targetPath);
491+
this->watcher->addPath(QDir(this->targetPath).dirName());
492+
493+
QObject::connect(
494+
this->watcher,
495+
&QFileSystemWatcher::fileChanged,
496+
this,
497+
&FileView::onWatchedFileChanged
498+
);
499+
500+
QObject::connect(
501+
this->watcher,
502+
&QFileSystemWatcher::directoryChanged,
503+
this,
504+
&FileView::onWatchedDirectoryChanged
505+
);
506+
}
507+
}
508+
509+
void FileView::onWatchedFileChanged() {
510+
if (!this->watcher->files().contains(this->targetPath)) {
511+
this->watcher->addPath(this->targetPath);
512+
}
513+
514+
emit this->fileChanged();
515+
}
516+
517+
void FileView::onWatchedDirectoryChanged() {
518+
if (!this->watcher->files().contains(this->targetPath) && QFileInfo(this->targetPath).exists()) {
519+
// the file was just created
520+
this->watcher->addPath(this->targetPath);
521+
emit this->fileChanged();
522+
}
480523
}
481524

482525
bool FileView::shouldBlockRead() const {

src/io/fileview.hpp

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include <qatomic.h>
66
#include <qdebug.h>
7+
#include <qfilesystemwatcher.h>
78
#include <qlogging.h>
89
#include <qmutex.h>
910
#include <qobject.h>
@@ -187,13 +188,13 @@ class FileView: public QObject {
187188
/// > [!WARNING] We cannot think of a valid use case for this.
188189
/// > You almost definitely want @@blockLoading.
189190
QSDOC_PROPERTY_OVERRIDE(bool blockAllReads READ blockAllReads WRITE setBlockAllReads NOTIFY blockAllReadsChanged);
190-
/// If true (default false), all calls to @@setText or @@setData will block the
191+
/// If true (default false), all calls to @@setText() or @@setData() will block the
191192
/// UI thread until the write succeeds or fails.
192193
///
193194
/// > [!WARNING] Blocking operations should be used carefully to avoid stutters and other performance
194195
/// > degradations. Blocking means that your interface **WILL NOT FUNCTION** during the call.
195196
Q_PROPERTY(bool blockWrites READ default WRITE default NOTIFY blockWritesChanged BINDABLE bindableBlockWrites);
196-
/// If true (default), all calls to @@setText or @@setData will be performed atomically,
197+
/// If true (default), all calls to @@setText() or @@setData() will be performed atomically,
197198
/// meaning if the write fails for any reason, the file will not be modified.
198199
///
199200
/// > [!NOTE] This works by creating another file with the desired content, and renaming
@@ -202,6 +203,18 @@ class FileView: public QObject {
202203
/// If true (default), read or write errors will be printed to the quickshell logs.
203204
/// If false, all known errors will not be printed.
204205
QSDOC_PROPERTY_OVERRIDE(bool printErrors READ default WRITE default NOTIFY printErrorsChanged);
206+
/// If true (defaule false), @@fileChanged() will be called whenever the content of the file
207+
/// changes on disk, including when @@setText() or @@setData() are used.
208+
///
209+
/// > [!NOTE] You can reload the file's content whenever it changes on disk like so:
210+
/// > ```qml
211+
/// > FileView {
212+
/// > // ...
213+
/// > watchChanges: true
214+
/// > onFileChanged: this.reload()
215+
/// > }
216+
/// > ```
217+
Q_PROPERTY(bool watchChanges READ default WRITE default NOTIFY watchChangesChanged BINDABLE bindableWatchChanges);
205218

206219
QSDOC_HIDE Q_PROPERTY(QString __path READ path WRITE setPath NOTIFY pathChanged);
207220
QSDOC_HIDE Q_PROPERTY(QString __text READ text NOTIFY internalTextChanged);
@@ -297,6 +310,7 @@ class FileView: public QObject {
297310
[[nodiscard]] QBindable<bool> bindableAtomicWrites() { return &this->bAtomicWrites; }
298311

299312
[[nodiscard]] QBindable<bool> bindablePrintErrors() { return &this->bPrintErrors; }
313+
[[nodiscard]] QBindable<bool> bindableWatchChanges() { return &this->bWatchChanges; }
300314

301315
signals:
302316
/// Emitted if the file was loaded successfully.
@@ -307,6 +321,8 @@ class FileView: public QObject {
307321
void saved();
308322
/// Emitted if the file failed to save.
309323
void saveFailed(qs::io::FileViewError::Enum error);
324+
/// Emitted if the file changes on disk and @@watchChanges is true.
325+
void fileChanged();
310326

311327
void pathChanged();
312328
QSDOC_HIDE void internalTextChanged();
@@ -320,6 +336,7 @@ class FileView: public QObject {
320336
void blockWritesChanged();
321337
void atomicWritesChanged();
322338
void printErrorsChanged();
339+
void watchChangesChanged();
323340

324341
private slots:
325342
void operationFinished();
@@ -332,6 +349,9 @@ private slots:
332349
void saveSync();
333350
void updateState(FileViewState& newState);
334351
void updatePath();
352+
void updateWatchedFiles();
353+
void onWatchedFileChanged();
354+
void onWatchedDirectoryChanged();
335355

336356
[[nodiscard]] bool shouldBlockRead() const;
337357
[[nodiscard]] FileViewReader* liveReader() const;
@@ -353,6 +373,8 @@ private slots:
353373
bool mBlockLoading = false;
354374
bool mBlockAllReads = false;
355375

376+
QFileSystemWatcher* watcher = nullptr;
377+
356378
GuardedEmitter<&FileView::internalTextChanged> textChangedEmitter;
357379
GuardedEmitter<&FileView::internalDataChanged> dataChangedEmitter;
358380
void emitDataChanged();
@@ -374,8 +396,11 @@ private slots:
374396
Q_OBJECT_BINDABLE_PROPERTY(FileView, bool, bBlockWrites, &FileView::blockWritesChanged);
375397
Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(FileView, bool, bAtomicWrites, true, &FileView::atomicWritesChanged);
376398
Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(FileView, bool, bPrintErrors, true, &FileView::printErrorsChanged);
399+
Q_OBJECT_BINDABLE_PROPERTY(FileView, bool, bWatchChanges, &FileView::watchChangesChanged);
377400
// clang-format on
378401

402+
QS_BINDING_SUBSCRIBE_METHOD(FileView, bWatchChanges, updateWatchedFiles, onValueChanged);
403+
379404
void setPreload(bool preload);
380405
void setBlockLoading(bool blockLoading);
381406
void setBlockAllReads(bool blockAllReads);

0 commit comments

Comments
 (0)