forked from quickshell/quickshell
		
	io/fileview: add support for watching changes
This commit is contained in:
		
							parent
							
								
									ccf885081c
								
							
						
					
					
						commit
						69d13967c9
					
				
					 2 changed files with 71 additions and 3 deletions
				
			
		| 
						 | 
					@ -6,6 +6,7 @@
 | 
				
			||||||
#include <qdir.h>
 | 
					#include <qdir.h>
 | 
				
			||||||
#include <qfiledevice.h>
 | 
					#include <qfiledevice.h>
 | 
				
			||||||
#include <qfileinfo.h>
 | 
					#include <qfileinfo.h>
 | 
				
			||||||
 | 
					#include <qfilesystemwatcher.h>
 | 
				
			||||||
#include <qlogging.h>
 | 
					#include <qlogging.h>
 | 
				
			||||||
#include <qloggingcategory.h>
 | 
					#include <qloggingcategory.h>
 | 
				
			||||||
#include <qmutex.h>
 | 
					#include <qmutex.h>
 | 
				
			||||||
| 
						 | 
					@ -280,7 +281,6 @@ void FileViewWriter::write(
 | 
				
			||||||
	if (shouldCancel.loadAcquire()) return;
 | 
						if (shouldCancel.loadAcquire()) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (doAtomicWrite) {
 | 
						if (doAtomicWrite) {
 | 
				
			||||||
		qDebug() << "Atomic commit";
 | 
					 | 
				
			||||||
		if (!reinterpret_cast<QSaveFile*>(file.get())->commit()) { // NOLINT
 | 
							if (!reinterpret_cast<QSaveFile*>(file.get())->commit()) { // NOLINT
 | 
				
			||||||
			qmlWarning(view) << "Write of " << state.path << " failed: Atomic commit failed.";
 | 
								qmlWarning(view) << "Write of " << state.path << " failed: Atomic commit failed.";
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -477,6 +477,49 @@ void FileView::updatePath() {
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		this->emitDataChanged();
 | 
							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 {
 | 
					bool FileView::shouldBlockRead() const {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <qatomic.h>
 | 
					#include <qatomic.h>
 | 
				
			||||||
#include <qdebug.h>
 | 
					#include <qdebug.h>
 | 
				
			||||||
 | 
					#include <qfilesystemwatcher.h>
 | 
				
			||||||
#include <qlogging.h>
 | 
					#include <qlogging.h>
 | 
				
			||||||
#include <qmutex.h>
 | 
					#include <qmutex.h>
 | 
				
			||||||
#include <qobject.h>
 | 
					#include <qobject.h>
 | 
				
			||||||
| 
						 | 
					@ -187,13 +188,13 @@ class FileView: public QObject {
 | 
				
			||||||
	/// > [!WARNING] We cannot think of a valid use case for this.
 | 
						/// > [!WARNING] We cannot think of a valid use case for this.
 | 
				
			||||||
	/// > You almost definitely want @@blockLoading.
 | 
						/// > You almost definitely want @@blockLoading.
 | 
				
			||||||
	QSDOC_PROPERTY_OVERRIDE(bool blockAllReads READ blockAllReads WRITE setBlockAllReads NOTIFY blockAllReadsChanged);
 | 
						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.
 | 
						/// UI thread until the write succeeds or fails.
 | 
				
			||||||
	///
 | 
						///
 | 
				
			||||||
	/// > [!WARNING] Blocking operations should be used carefully to avoid stutters and other performance
 | 
						/// > [!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.
 | 
						/// > degradations. Blocking means that your interface **WILL NOT FUNCTION** during the call.
 | 
				
			||||||
	Q_PROPERTY(bool blockWrites READ default WRITE default NOTIFY blockWritesChanged BINDABLE bindableBlockWrites);
 | 
						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.
 | 
						/// 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
 | 
						/// > [!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 true (default), read or write errors will be printed to the quickshell logs.
 | 
				
			||||||
	/// If false, all known errors will not be printed.
 | 
						/// If false, all known errors will not be printed.
 | 
				
			||||||
	QSDOC_PROPERTY_OVERRIDE(bool printErrors READ default WRITE default NOTIFY printErrorsChanged);
 | 
						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 __path READ path WRITE setPath NOTIFY pathChanged);
 | 
				
			||||||
	QSDOC_HIDE Q_PROPERTY(QString __text READ text NOTIFY internalTextChanged);
 | 
						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> bindableAtomicWrites() { return &this->bAtomicWrites; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	[[nodiscard]] QBindable<bool> bindablePrintErrors() { return &this->bPrintErrors; }
 | 
						[[nodiscard]] QBindable<bool> bindablePrintErrors() { return &this->bPrintErrors; }
 | 
				
			||||||
 | 
						[[nodiscard]] QBindable<bool> bindableWatchChanges() { return &this->bWatchChanges; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
signals:
 | 
					signals:
 | 
				
			||||||
	/// Emitted if the file was loaded successfully.
 | 
						/// Emitted if the file was loaded successfully.
 | 
				
			||||||
| 
						 | 
					@ -307,6 +321,8 @@ signals:
 | 
				
			||||||
	void saved();
 | 
						void saved();
 | 
				
			||||||
	/// Emitted if the file failed to save.
 | 
						/// Emitted if the file failed to save.
 | 
				
			||||||
	void saveFailed(qs::io::FileViewError::Enum error);
 | 
						void saveFailed(qs::io::FileViewError::Enum error);
 | 
				
			||||||
 | 
						/// Emitted if the file changes on disk and @@watchChanges is true.
 | 
				
			||||||
 | 
						void fileChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void pathChanged();
 | 
						void pathChanged();
 | 
				
			||||||
	QSDOC_HIDE void internalTextChanged();
 | 
						QSDOC_HIDE void internalTextChanged();
 | 
				
			||||||
| 
						 | 
					@ -320,6 +336,7 @@ signals:
 | 
				
			||||||
	void blockWritesChanged();
 | 
						void blockWritesChanged();
 | 
				
			||||||
	void atomicWritesChanged();
 | 
						void atomicWritesChanged();
 | 
				
			||||||
	void printErrorsChanged();
 | 
						void printErrorsChanged();
 | 
				
			||||||
 | 
						void watchChangesChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private slots:
 | 
					private slots:
 | 
				
			||||||
	void operationFinished();
 | 
						void operationFinished();
 | 
				
			||||||
| 
						 | 
					@ -332,6 +349,9 @@ private:
 | 
				
			||||||
	void saveSync();
 | 
						void saveSync();
 | 
				
			||||||
	void updateState(FileViewState& newState);
 | 
						void updateState(FileViewState& newState);
 | 
				
			||||||
	void updatePath();
 | 
						void updatePath();
 | 
				
			||||||
 | 
						void updateWatchedFiles();
 | 
				
			||||||
 | 
						void onWatchedFileChanged();
 | 
				
			||||||
 | 
						void onWatchedDirectoryChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	[[nodiscard]] bool shouldBlockRead() const;
 | 
						[[nodiscard]] bool shouldBlockRead() const;
 | 
				
			||||||
	[[nodiscard]] FileViewReader* liveReader() const;
 | 
						[[nodiscard]] FileViewReader* liveReader() const;
 | 
				
			||||||
| 
						 | 
					@ -353,6 +373,8 @@ private:
 | 
				
			||||||
	bool mBlockLoading = false;
 | 
						bool mBlockLoading = false;
 | 
				
			||||||
	bool mBlockAllReads = false;
 | 
						bool mBlockAllReads = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QFileSystemWatcher* watcher = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	GuardedEmitter<&FileView::internalTextChanged> textChangedEmitter;
 | 
						GuardedEmitter<&FileView::internalTextChanged> textChangedEmitter;
 | 
				
			||||||
	GuardedEmitter<&FileView::internalDataChanged> dataChangedEmitter;
 | 
						GuardedEmitter<&FileView::internalDataChanged> dataChangedEmitter;
 | 
				
			||||||
	void emitDataChanged();
 | 
						void emitDataChanged();
 | 
				
			||||||
| 
						 | 
					@ -374,8 +396,11 @@ public:
 | 
				
			||||||
	Q_OBJECT_BINDABLE_PROPERTY(FileView, bool, bBlockWrites, &FileView::blockWritesChanged);
 | 
						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, bAtomicWrites, true, &FileView::atomicWritesChanged);
 | 
				
			||||||
	Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(FileView, bool, bPrintErrors, true, &FileView::printErrorsChanged);
 | 
						Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(FileView, bool, bPrintErrors, true, &FileView::printErrorsChanged);
 | 
				
			||||||
 | 
						Q_OBJECT_BINDABLE_PROPERTY(FileView, bool, bWatchChanges, &FileView::watchChangesChanged);
 | 
				
			||||||
	// clang-format on
 | 
						// clang-format on
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QS_BINDING_SUBSCRIBE_METHOD(FileView, bWatchChanges, updateWatchedFiles, onValueChanged);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void setPreload(bool preload);
 | 
						void setPreload(bool preload);
 | 
				
			||||||
	void setBlockLoading(bool blockLoading);
 | 
						void setBlockLoading(bool blockLoading);
 | 
				
			||||||
	void setBlockAllReads(bool blockAllReads);
 | 
						void setBlockAllReads(bool blockAllReads);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue