QFileSystemModel not updating when files change

只谈情不闲聊 提交于 2019-12-13 19:30:50

问题


I'm having trouble with QFileSystemModel not showing changes to files. When a file is first created it immediately shows up. But when the file itself changes, the size and timestamp don't update. I've made multiple attempts at trying to force the model to update with no real success. The best I've achieved is to completely replace the model. Although that results in this error:

QSortFilterProxyModel: index from wrong model passed to mapToSource

The test code below creates a table view of an empty directory. The left button creates a file (foo.txt) when clicked. Successive clicks append data to the file. It was my understanding that the QFileSystemModel didn't need a refresh, but the second button is my attempt at that.

Any help as to what I'm doing wrong would be greatly appreciated!

# Testing with python3.6.3 and pip installed pyqt5 5.9.2 in virtualenv on Ubuntu
import os, sys, tempfile
from PyQt5 import QtCore, QtWidgets


class Widget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        QtWidgets.QWidget.__init__(self, parent)

        layout = QtWidgets.QVBoxLayout()
        self.setLayout(layout)
        self._view = QtWidgets.QTableView()
        layout.addWidget(self._view)

        self._modify_button = QtWidgets.QPushButton('Create')
        layout.addWidget(self._modify_button)
        self._refresh_button = QtWidgets.QPushButton('Refresh')
        layout.addWidget(self._refresh_button)

        self._modify_button.clicked.connect(self._modify)
        self._refresh_button.clicked.connect(self._refresh)

        self._model, self._proxy = None, None
        self.temp_dir = tempfile.TemporaryDirectory(dir=os.path.dirname(os.path.abspath(__file__)))
        self.init_model(self.temp_dir.name)

    def init_model(self, path):
        self._model = QtWidgets.QFileSystemModel()
        self._model.setFilter(QtCore.QDir.AllDirs | QtCore.QDir.AllEntries)

        self._proxy = QtCore.QSortFilterProxyModel(self)
        self._proxy.setSourceModel(self._model)
        self._view.setModel(self._proxy)
        # self._view.setModel(self._model)

        self._model.directoryLoaded.connect(self._loaded)
        self._model.setRootPath(path)

    def _loaded(self):
        path = self._model.rootPath()
        source_index = self._model.index(path)
        index = self._proxy.mapFromSource(source_index)
        self._view.setRootIndex(index)
        # self._view.setRootIndex(source_index)

    def _modify(self):
        """Create or modify foo.txt..model should see and update"""
        self._modify_button.setText('Modify')
        file_name = os.path.join(self.temp_dir.name, 'foo.txt')
        with open(file_name, 'a') as txt_file:
            print('foo', file=txt_file)

    # def _refresh(self):
    #     # This only seems to work once..and its a flawed approach since it requires permission to write
    #     temp = tempfile.NamedTemporaryFile(dir=self.temp_dir.name)

    # def _refresh(self):
    #     self._model.beginResetModel()
    #     self._model.endResetModel()

    # def _refresh(self):
    #     self._proxy.setFilterRegExp('foo')
    #     self._proxy.setFilterRegExp(None)
    #     self._proxy.invalidate()
    #     self._proxy.invalidateFilter()
    #     self._proxy.reset()
    #
    #     root_index = self._model.index(self._model.rootPath())
    #     rows = self._model.rowCount(root_index)
    #     proxy_root_index = self._proxy.mapFromSource(root_index)
    #     topLeft = self._proxy.index(0, 0, proxy_root_index)
    #     bottomRight = self._proxy.index(rows - 1, self._model.columnCount(proxy_root_index) - 1, proxy_root_index)
    #     # self._proxy.dataChanged.emit(topLeft, bottomRight)
    #     self._model.dataChanged.emit(topLeft, bottomRight)

    # def _refresh(self):
    #     # This only seems to work once
    #     self._model.setRootPath('')
    #     self._model.setRootPath(self.temp_dir.name)

    def _refresh(self):
        # This seems heavy handed..but seems to work
        # ..though generates "QSortFilterProxyModel: index from wrong model passed to mapToSource" spam in console
        self.init_model(self.temp_dir.name)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    widget = Widget()
    widget.show()
    sys.exit(app.exec_())

回答1:


UPDATE:

As of Qt-5.9.4, the QT_FILESYSTEMMODEL_WATCH_FILES envrionment variable can be used to switch on per-file watching (see QTBUG-46684). This needs to be set once to a non-empty value before the model starts caching information about files. But note that this will add a file-watcher to every file that is encountered, so this may make it an expensive solution on some systems.

The original answer is left below as an explanation of the problem.


This problem is caused by a long-standing Qt bug: QTBUG-2276. Unfortunately, at present, it does not look likely that it will be fixed any time soon. As indicated in the bug report comments, the core of the issue seems to be this:

It's an OS limitation. A change to a file does not mean the directory is modified.

The only real work-around for this would be to attach a QFileSystemWatcher to every single file, which could obviously be prohibitively expensive (on some platforms, anyway).

In addition to this problem, the QFileSystemModel class doesn't currently provide an API for forcing a refresh, and, as you have discovered, there does not seem to be any reliable work-around for that. Most "solutions" offered on SO and elsewhere, suggest some variant of this:

root = fsmodel.rootPath()
fsmodel.setRootPath('')
fsmodel.setRootPath(root)

But as you know, this seems to work only once - probably due to some quirk in the way file-info caching is currently implemented.

At present it appears the only way to force an update is to replace the entire model. The error messages produced by your current implementation of this can be prevented by refactoring your init_model method like this:

def init_model(self, path):
    if self._proxy is None:
        self._proxy = QtCore.QSortFilterProxyModel(self)
    else:
        # remove the current source model
        self._proxy.setSourceModel(None)
    self._model = QtWidgets.QFileSystemModel()
    self._model.setFilter(QtCore.QDir.AllDirs | QtCore.QDir.AllEntries)
    self._proxy.setSourceModel(self._model)
    self._view.setModel(self._proxy)
    self._model.directoryLoaded.connect(self._loaded)
    self._model.setRootPath(path)

This is a very unsatisfactory situation, but there just doesn't seem to be any obvious way around it at the moment.




回答2:


Since Qt v5.9.4 you can set the environment variable QT_FILESYSTEMMODEL_WATCH_FILES, you can read more about it in the changelog:

[QTBUG-46684] It is now possible to enable per-file watching by setting the environment variable QT_FILESYSTEMMODEL_WATCH_FILES, allowing to track for example changes in file size.

Couple of things:

  • For the time being you need to set it before initializing the model, after that you can set it to another folder without any problem.
  • Be aware this feature comes at the cost of potentially heavy load, though.


来源:https://stackoverflow.com/questions/47851152/qfilesystemmodel-not-updating-when-files-change

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!