Как обновить информацию о файле в QFileSystemModel?

Прежде всего, этот вопрос похож на этот другой QFileSystemModel не обновляется при изменении файлов с основным отличием, чем в этом случае, я не хочу заменять всю модель каждый раз, когда обновляется один из моих файлов.

В реальном примере мое приложение открыло несколько файлов, поэтому я в основном просто пытаюсь понять, как обновить информацию (размер, дату изменения) одного конкретного элемента QFileSystemModel, ниже у вас есть немного mcve для игры, как вы можете видеть в этом коде, который я безуспешно пытался использовать установить данные:

import sys
import os

from PyQt5.Qt import *  # noqa


class DirectoryTreeWidget(QTreeView):

    def __init__(self, path=QDir.currentPath(), *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.init_model(path)
        self.expandsOnDoubleClick = False
        self.header().setSectionResizeMode(0, QHeaderView.ResizeToContents)
        self.setAutoScroll(True)

    def init_model(self, path):
        self.extensions = ["*.*"]
        self.path = path
        model = QFileSystemModel(self)
        model.setRootPath(QDir.rootPath())
        model.setReadOnly(False)
        model.setFilter(QDir.AllDirs | QDir.NoDot | QDir.AllEntries)
        self.setModel(model)
        self.set_path(path)

    def set_path(self, path):
        self.path = path
        model = self.model()
        index = model.index(str(self.path))

        if os.path.isfile(path):
            self.setRootIndex(model.index(
                os.path.dirname(str(self.path))))
            self.scrollTo(index)
            self.setCurrentIndex(index)
        else:
            self.setRootIndex(index)


class Foo(QWidget):

    def __init__(self, path):
        super().__init__()

        self.path = path

        self.tree_view = DirectoryTreeWidget(path=".")
        self.tree_view.show()
        bt = QPushButton(f"Update {path}")
        bt.pressed.connect(self.update_file)

        layout = QVBoxLayout()
        layout.addWidget(self.tree_view)
        layout.addWidget(bt)

        self.setLayout(layout)

        # New file will automatically refresh QFileSystemModel
        self.create_file()

    def create_file(self):
        with open(self.path, "w") as f:
            data = "This new file contains xx bytes"
            f.write(data.replace("xx", str(len(data))))

    def update_file(self):
        model = self.tree_view.model()

        # Updating a file won't refresh QFileSystemModel, the question is,
        # how can we update that particular item to be refreshed?
        data = "The file updated is much larger, it contains xx bytes"
        with open(self.path, "w") as f:
            f.write(data.replace("xx", str(len(data))))

        # file_info = self.obj.model.fileInfo(index)
        # file_info.refresh()
        index = model.index(self.path)
        model.setData(index, model.data(index))
        QMessageBox.about(None, "Info", f"{self.path} updated, new size is {len(data)}")


def main():
    app = QApplication(sys.argv)
    foo = Foo("foo.txt")
    foo.setMinimumSize(640, 480)
    foo.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

Итак, вопрос будет заключаться в том, как я могу добиться того, чтобы update_file обновлял информацию об этом конкретном файле foo.txt?

Целью будет обновление только этого файла без замены всей модели, как показано здесь, а также после обновления этого элемента модели. d быть хорошим, элемент вообще не сортируется/не перемещается в представлении.


person BPL    schedule 08.04.2018    source источник
comment
Это действительно ничем не отличается от связанного вопроса. Если бы существовал способ принудительно обновить один файл, то то же самое можно было бы сделать и для всех файлов. Очень грубым и глючным хаком было бы двойное переименование файла для принудительного обновления. Но нет никакого способа избежать условий гонки при этом, поэтому я не буду рекомендовать это. (PS: вызов setData пытается переименовать файл. Но если имя не изменилось, это не повлияет).   -  person ekhumoro    schedule 08.04.2018


Ответы (1)


Qt v5.9.4 представила переменную среды QT_FILESYSTEMMODEL_WATCH_FILES для обращения к QTBUG-46684, вы можете прочитать больше об этом в журнал изменений:

QTBUG-46684 Теперь можно включить наблюдение за каждым файлом, задав переменную среды QT_FILESYSTEMMODEL_WATCH_FILES, что позволяет отслеживать, например, изменения размера файла.

Итак, чтобы пример работал, вы можете просто установить envar один раз в непустое значение, то есть:

import sys
import os

from PyQt5.Qt import *  # noqa


class DirectoryTreeWidget(QTreeView):

    def __init__(self, path=QDir.currentPath(), *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.init_model(path)
        self.expandsOnDoubleClick = False
        self.header().setSectionResizeMode(0, QHeaderView.ResizeToContents)
        self.setAutoScroll(True)

    def init_model(self, path):
        os.environ["QT_FILESYSTEMMODEL_WATCH_FILES"] = '1'

        self.extensions = ["*.*"]
        self.path = path
        model = QFileSystemModel(self)
        model.setRootPath(QDir.rootPath())
        model.setReadOnly(False)
        model.setFilter(QDir.AllDirs | QDir.NoDot | QDir.AllEntries)
        self.setModel(model)
        self.set_path(path)

    def set_path(self, path):
        self.path = path
        model = self.model()
        index = model.index(str(self.path))

        if os.path.isfile(path):
            self.setRootIndex(model.index(
                os.path.dirname(str(self.path))))
            self.scrollTo(index)
            self.setCurrentIndex(index)
        else:
            self.setRootIndex(index)


class Foo(QWidget):

    def __init__(self, path):
        super().__init__()

        self.path = path

        self.tree_view = DirectoryTreeWidget(path=".")
        self.tree_view.show()
        bt = QPushButton(f"Update {path}")
        bt.pressed.connect(self.update_file)

        layout = QVBoxLayout()
        layout.addWidget(self.tree_view)
        layout.addWidget(bt)

        self.setLayout(layout)
        self.create_file()

    def create_file(self):
        with open(self.path, "w") as f:
            data = "This new file contains xx bytes"
            f.write(data.replace("xx", str(len(data))))

    def update_file(self):
        model = self.tree_view.model()

        data = "The file updated is much larger, it contains xx bytes"
        with open(self.path, "w") as f:
            f.write(data.replace("xx", str(len(data))))

        index = model.index(self.path)
        model.setData(index, model.data(index))
        QMessageBox.about(None, "Info", f"{self.path} updated, new size is {len(data)}")


def main():
    app = QApplication(sys.argv)
    foo = Foo("foo.txt")
    foo.setMinimumSize(640, 480)
    foo.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

Пара комментариев:

  • Вам нужно установить этот env.var перед инициализацией модели.
  • Однако за эту функцию приходится платить потенциально большой нагрузкой. Кэшированные файлы будут отслеживаться, если этот env.var включен.
person BPL    schedule 10.04.2018
comment
Это хорошая находка, но жаль, что она не задокументирована должным образом. Это также кажется довольно тяжелым решением. Что действительно необходимо, так это слот/метод, который может явно обновлять кеш для отдельных файлов и/или каталогов. - person ekhumoro; 10.04.2018
comment
PS: ваш ответ неверен, если предположить, что это можно использовать для указания определенного каталога, в котором будут просматриваться файлы. Envar — это просто переключатель, который включает наблюдение за каждым файлом для любых файлов, которые кэшируются, независимо от того, в каком каталоге они находятся. Так что это всегда имеет смысл чтобы установить для envar once непустое значение, например os.environ['QT_FILESYSTEMMODEL_WATCH_FILES'] = '1'. - person ekhumoro; 10.04.2018
comment
@ekhumoro Я полностью с вами согласен, в моем реальном случае это работает +/- хорошо. то есть: у меня есть виджет, состоящий из 2 виджетов + 2 модели, один виджет показывает папки, а другой извлекает файлы из выбранного каталога. Каждый раз, когда я меняю текущий каталог, я обновляю этот env.var, чтобы он отслеживал этот конкретный каталог... пока он работает довольно хорошо, но это также правда, я только что протестировал каталоги с несколькими сотнями файлов. Я имею в виду, что это не идеально, но кодирование моей собственной QFileSystemModel оказалось довольно трудоемким. Что мне не очень нравится, так это тот факт, что QFileSystemModel предоставляет очень минимальный общедоступный интерфейс :( - person BPL; 10.04.2018
comment
@ekhumoro Мой предыдущий комментарий связан с вашим первым комментарием. Что касается вашего второго комментария, вы уверены в этом? Спасибо, что указали на это, я проверю это позже на своей машине и обновлю ответ, если это так. - person BPL; 10.04.2018
comment
Да, я уверен на 99,9%. Установка значения '1' в вашем скрипте не влияет на поведение. Исходный код qt в настоящее время использует этот envar только в одном месте, и он не использует это значение как что-либо кроме простого переключателя (т.е. он не интерпретирует его как путь к файлу). - person ekhumoro; 10.04.2018
comment
@ekhumoro Спасибо, вы были совершенно правы, я неправильно понял поведение этой функции, моя ошибка, я снова проверил, и она работает в соответствии с тем, что вы объяснили в своем предыдущем комментарии. Знаете ли вы, как QFileSystemModel управляет кешем? то есть: LRU или аналогичный? Спрашиваю вас, потому что, если кеш растет без ограничений, приложение в конечном итоге станет очень медленным ... и я думаю, вы не можете очистить кеш, не так ли? :/ - person BPL; 11.04.2018
comment
Кэш основан на QFileInfo, но вам придется прочитать исходный код, чтобы точно увидеть, как это делается. Совершенно непонятно, как и когда записи в кеше становятся недействительными (но я не стал долго на это смотреть). Нет общедоступных API, которые можно использовать для управления кешем — даже косвенно, AFAICS. - person ekhumoro; 11.04.2018