QFileSystemModel tidak memperbarui saat file berubah

Saya mengalami masalah dengan QFileSystemModel yang tidak menampilkan perubahan pada file. Saat file pertama kali dibuat, file tersebut langsung muncul. Namun ketika file itu sendiri berubah, ukuran dan stempel waktu tidak diperbarui. Saya telah melakukan beberapa upaya untuk memaksa model diperbarui namun tidak berhasil. Hal terbaik yang pernah saya capai adalah mengganti model sepenuhnya. Meskipun itu menghasilkan kesalahan ini:

QSortFilterProxyModel: index from wrong model passed to mapToSource

Kode pengujian di bawah ini membuat tampilan tabel dari direktori kosong. Tombol kiri membuat file (foo.txt) ketika diklik. Klik berturut-turut menambahkan data ke file. Menurut pemahaman saya, QFileSystemModel tidak memerlukan penyegaran, tetapi tombol kedua adalah upaya saya untuk melakukannya.

Bantuan apa pun mengenai kesalahan yang saya lakukan akan sangat dihargai!

# 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_())

person shao.lo    schedule 17.12.2017    source sumber
comment
Anda dapat mencoba menggunakan QFileSystemWatcher langsung pada file Anda. Namun ini mungkin memperlambat kinerja jika Anda menonton banyak file.   -  person Dmitry Sazonov    schedule 26.12.2017


Jawaban (2)


PEMBARUAN:

Mulai Qt-5.9.4, variabel lingkungan QT_FILESYSTEMMODEL_WATCH_FILES dapat digunakan untuk mengaktifkan pengamatan per file (lihat QTBUG-46684). Ini perlu disetel sekali ke nilai yang tidak kosong sebelum model mulai menyimpan informasi tentang file dalam cache. Namun perhatikan bahwa ini akan menambahkan pengamat file ke setiap file yang ditemukan, jadi ini mungkin menjadikannya solusi mahal pada beberapa sistem.

Jawaban aslinya ada di bawah sebagai penjelasan masalahnya.


Masalah ini disebabkan oleh bug Qt yang sudah lama ada: QTBUG-2276. Sayangnya, saat ini sepertinya hal tersebut tidak akan diperbaiki dalam waktu dekat. Seperti yang ditunjukkan dalam komentar laporan bug, inti masalahnya adalah sebagai berikut:

Ini adalah batasan OS. Perubahan pada file tidak berarti direktori diubah.

Satu-satunya solusi nyata untuk hal ini adalah dengan melampirkan QFileSystemWatcher ke setiap file, yang jelas bisa berupa sangat mahal (pada beberapa platform).

Selain masalah ini, kelas QFileSystemModel saat ini tidak menyediakan API untuk memaksa penyegaran, dan, seperti yang Anda ketahui, sepertinya tidak ada solusi yang dapat diandalkan untuk itu. Kebanyakan "solusi" yang ditawarkan pada SO dan di tempat lain, menyarankan beberapa varian dari ini:

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

Namun seperti yang Anda ketahui, ini tampaknya hanya berfungsi sekali - mungkin karena beberapa kekhasan dalam cara penerapan cache info file saat ini.

Saat ini tampaknya satu-satunya cara untuk memaksa pembaruan adalah dengan mengganti keseluruhan model. Pesan kesalahan yang dihasilkan oleh penerapan Anda saat ini dapat dicegah dengan memfaktorkan ulang metode init_model Anda seperti ini:

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)

Ini adalah situasi yang sangat tidak memuaskan, namun sepertinya tidak ada jalan keluar yang jelas saat ini.

person ekhumoro    schedule 23.12.2017
comment
Terima kasih banyak atas penjelasannya, tautan ke masalah tersebut, dan perbaikan peringatan yang saya terima dalam upaya menginisialisasi ulang model! - person shao.lo; 26.12.2017

Karena Qt v5.9.4 Anda dapat mengatur variabel lingkungan QT_FILESYSTEMMODEL_WATCH_FILES, Anda dapat membaca lebih lanjut tentangnya di log perubahan:

[QTBUG-46684] Sekarang dimungkinkan untuk mengaktifkan pengamatan per file dengan mengatur variabel lingkungan QT_FILESYSTEMMODEL_WATCH_FILES, yang memungkinkan untuk melacak misalnya perubahan ukuran file.

Beberapa barang:

  • Untuk saat ini Anda perlu mengaturnya sebelum menginisialisasi model, setelah itu Anda dapat mengaturnya ke folder lain tanpa masalah.
  • Namun, ketahuilah bahwa fitur ini menimbulkan potensi beban yang berat.
person BPL    schedule 10.04.2018