1

Here's the basic premise of my application:

I've established a QFileSystemWatcher to watch a directory.

Path = [r'C:\Users\user\Documents\Images']
DirectoryWatcher = QtCore.QFileSystemWatcher(Path)
DirectoryWatcher.directoryChanged.connect(showImages.UpdateImages)

I've used QFileSystemWatcher in the past, and it's always worked perfectly (for both directory and file changes).

The application will display a slideshow of the images in the \Images folder. When a new image is placed in the \Images folder, the slideshow is reset to include the new image. If an image is removed from the \Images folder, the slideshow is again reset.

The issue I'm having is this: if I drag multiple images into the \Images folder, the directoryChanged signal is fired multiple times. The signal is fired, and the corresponding UpdateImages() routine is run for each image that's been added to the folder, even when they're added at the same time (i.e. select multiple images, drag and drop them into \Images).

This is playing havoc with my routine. Is there any way to fire a directoryChanged signal once for a batch of directory changes? I.e. can I disable the signal until the final image has been added to the directory?

Many thanks!

jars121
  • 1,127
  • 2
  • 20
  • 35
  • 1
    I don't suppose that the images are truly copies simultaneously. Unless you implement some time-lag, this is the expected behaviour. – mdurant Feb 17 '15 at 22:04
  • 1
    It doesn't look like there is a signal that suits your needs, but you could connect `directoryChanged` to a smarter function that sets a flag or has a timer rather than update the images immediately. That way you can filter the events yourself. – 101 Feb 17 '15 at 22:05
  • 1
    You could also research `eventFilter`, which lets you intercept and manipulate events of a certain type. – 101 Feb 17 '15 at 22:08
  • Thanks for the input guys, much appreciated. I haven't played with 'eventFilter' too much, but I can see how that might be useful. Does Windows (7, if that makes a difference) 'announce' how many files are about to be added to a directory? If I know the number of files to be moved, prior to them moving, I could set up a filtering event which is run when the directoryChanged signal is emitted. – jars121 Feb 17 '15 at 22:28

1 Answers1

1

The solution is to simply avoid connecting the directoryChanged signal to the slot that updates the images. Instead, just set a flag whenever any changes occur, and then periodically check the flag to see if any updates are needed (this can be done with a simple timer mechanism).

Here's a basic script that demonstrates the idea:

import sys, os
from PyQt4 import QtCore, QtGui

class Window(QtGui.QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.list = QtGui.QListWidget(self)
        self.button = QtGui.QPushButton('Choose Directory', self)
        self.button.clicked.connect(self.handleSetDirectory)
        layout = QtGui.QVBoxLayout(self)
        layout.addWidget(self.list)
        layout.addWidget(self.button)
        self.watcher = QtCore.QFileSystemWatcher(self)
        self.watcher.directoryChanged.connect(self.handleDirectoryChanged)
        self.timer = QtCore.QTimer(self)
        self.timer.setInterval(500)
        self.timer.timeout.connect(self.handleTimer)
        self.handleSetDirectory(QtCore.QDir.tempPath())

    def handleDirectoryChanged(self):
        self.timer.stop()
        print('Directory Changed')
        self._changed = True
        self.timer.start()

    def handleSetDirectory(self, directory=None):
        if not directory:
            directory = QtGui.QFileDialog.getExistingDirectory(self)
        if directory:
            self.timer.stop()
            self.watcher.removePaths(self.watcher.directories())
            self._changed = False
            self.watcher.addPath(directory)
            self.updateList()
            self.timer.start()

    def handleTimer(self):
        if self._changed:
            self._changed = False
            self.updateList()

    def updateList(self):
        print('Update List')
        self.list.clear()
        for directory in self.watcher.directories():
            self.list.addItems(os.listdir(directory))

if __name__ == '__main__':

    import sys
    app = QtGui.QApplication(sys.argv)
    window = Window()
    window.resize(250, 600)
    window.show()
    sys.exit(app.exec_())
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • Thanks for your comment, it's much appreciated (as always!). I'm going to go through your code line by line, but in the meantime, is polling really the only practical way to go about this? I'm not opposed to polling in this particular circumstance, I had just assumed there would be a greater level of control around the established signal processes. Thanks again! – jars121 Feb 18 '15 at 02:05
  • 1
    @jars121. The issue has got nothing to do with signals, or even Qt in general: it's purely to do with how filesystems work. Qt is entirely dependent on the OS when it comes to filesystem notifications. A few operations are strictly atomic (such as file renaming, under certain circumstances), but most are not. `QFileSystemWatcher` is a fairly basic wrapper class that hides most of the details between the various platforms. This makes it convenient to use for simple tasks, but it obviously lacks much of the fine-grained control that the OS itself might provide. – ekhumoro Feb 18 '15 at 02:52
  • Fair point. I've since done some more reading, and it seems that a single 'simple' event, like copying a file to a directory, actually may involve several low-level signals being emitted by the OS itself. Given that Python is merely reading the emitted OS signals (as you've stated), there's not much that can be done in this instance. I'm currently re-writing the application to involve flags and polling, and it looks like it will be a more than suitable alternative method. Thanks again! – jars121 Feb 18 '15 at 03:20