Search code examples
pythonftppyqtprogress-barftplib

Update PyQt progress from another thread running FTP download


I want to access progress bar's (which is in the Ui_MainWindow() class) setMaximum() from another class/thread (DownloadThread() class).

I tried making DownloadThread() class inherit from Ui_MainWindow: DownloadThread(Ui_MainWindow). But when I try to set the maximum progress bar value:

Ui_MainWindow.progressBar.setMaximum(100)

I get this error:

AttributeError: type object 'Ui_MainWindow' has no attribute 'progressBar'

My code:

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        # ...
        self.updateButton = QtGui.QPushButton(self.centralwidget)
        self.progressBar = QtGui.QProgressBar(self.centralwidget)
        self.updateStatusText = QtGui.QLabel(self.centralwidget)
        # ...
        self.updateButton.clicked.connect(self.download_file)
        # ...

    def download_file(self):
        self.thread = DownloadThread()
        self.thread.data_downloaded.connect(self.on_data_ready)
        self.thread.start()

    def on_data_ready(self, data):
        self.updateStatusText.setText(str(data))


class DownloadThread(QtCore.QThread, Ui_MainWindow):

    data_downloaded = QtCore.pyqtSignal(object)

    def run(self):
        self.data_downloaded.emit('Status: Connecting...')

        ftp = FTP('example.com')
        ftp.login(user='user', passwd='pass')

        ftp.cwd('/some_directory/')

        filename = '100MB.bin'
        totalsize = ftp.size(filename)
        print(totalsize)

        # SET THE MAXIMUM VALUE OF THE PROGRESS BAR
        Ui_MainWindow.progressBar.setMaximum(totalsize)          

        self.data_downloaded.emit('Status: Downloading...')

        global localfile
        with open(filename, 'wb') as localfile:
            ftp.retrbinary('RETR ' + filename, self.file_write)

        ftp.quit()
        localfile.close()

        self.data_downloaded.emit('Status: Updated!')

    def file_write(self, data):
        global localfile
        localfile.write(data)
        print(len(data))

Solution

  • The immediate problem is that Ui_MainWindow is a class, not an instance of the class. You would have to pass your "window" self to the DownloadThread. But that's not the right solution anyway. You cannot access PyQt widgets from another thread. Instead, use the same technique as you already do, to update the status text (FTP download with text label showing the current status of the download).

    class Ui_MainWindow(object):
        def download_file(self):
            self.thread = DownloadThread()
            self.thread.data_downloaded.connect(self.on_data_ready)
            self.thread.data_progress.connect(self.on_progress_ready)
            self.progress_initialized = False
            self.thread.start()
    
        def on_progress_ready(self, data):
            # The first signal sets the maximum, the other signals increase a progress
            if self.progress_initialized:
                self.progressBar.setValue(self.progressBar.value() + int(data))
            else:
                self.progressBar.setMaximum(int(data))
                self.progress_initialized = True
    
    class DownloadThread(QtCore.QThread):
    
        data_downloaded = QtCore.pyqtSignal(object)
        data_progress = QtCore.pyqtSignal(object)
    
        def run(self):
            self.data_downloaded.emit('Status: Connecting...')
    
            with FTP('example.com') as ftp:
                ftp.login(user='user', passwd='pass')
    
                ftp.cwd('/some_directory/')
    
                filename = '100MB.bin'
                totalsize = ftp.size(filename)
                print(totalsize)
    
                # The first signal sets the maximum
                self.data_progress.emit(str(totalsize))
    
                self.data_downloaded.emit('Status: Downloading...')
    
                with open(filename, 'wb') as self.localfile:
                    ftp.retrbinary('RETR ' + filename, self.file_write)
    
            self.data_downloaded.emit('Status: Updated!')
    
        def file_write(self, data):
            self.localfile.write(data)
            # The other signals increase a progress
            self.data_progress.emit(str(len(data)))
    

    Other changes to your code:

    • global localfile is a bad practice. Use self.localfile instead.
    • There's no need for localfile.close(), with takes care of that.
    • Similarly ftp.quit() should be replaced with with.
    • There's no need for DownloadThread to inherit from Ui_MainWindow.