Загрузка по FTP с текстовой меткой, показывающей текущий статус загрузки

Я сделал графический интерфейс, в котором после того, как я нажму кнопку «Загрузить», программа загрузит файлы с FTP-сервера. При этом я хочу, чтобы метка обновлялась, например: «Подключение ...» -> «Загрузка ...» -> «Загружено!» Я пробовал сделать это с помощью модуля потоковой передачи, но, похоже, он не работает:

    def updater(self):
        self.updateStatusText.setText("Status: Connecting...")

        thread = threading.Thread(target=self.download)
        thread.start()

        while thread.isAlive():
            self.updateStatusText.setText("Status: Still Downloading...")


    def download(self):
        ftp = FTP('testdomain.com')
        ftp.login(user='username', passwd='password')

        ftp.cwd('/main_directory/')

        filename = 'testfile.bin'

        with open(filename, 'wb') as localfile:
            ftp.retrbinary('RETR ' + filename, localfile.write, 1024)

        ftp.quit()
        localfile.close()

Он просто загружает файл и вообще не меняет текстовую метку. Должен ли я здесь использовать QThread? Я также пробовал использовать asyncio, но ожидание self.updateStatusText.setText("Connecting..."), похоже, возвращает None, и я получаю TypeError ...


person Qiasm    schedule 22.08.2019    source источник


Ответы (1)


Подойдет следующий код:

class DownloadThread(QtCore.QThread):

    data_downloaded = QtCore.pyqtSignal(object)

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

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

        ftp.cwd('/main_directory/')

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

        filename = 'testfile.bin'
        with open(filename, 'wb') as localfile:
            ftp.retrbinary('RETR ' + filename, localfile.write)

        ftp.quit()

        self.data_downloaded.emit('Done')

class MainWindow(QtGui.QWidget):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.label = QtGui.QLabel
        self.button = QtGui.QPushButton("Start")
        self.button.clicked.connect(self.start_download)
        layout = QtGui.QVBoxLayout()
        layout.addWidget(self.button)
        layout.addWidget(self.label)
        self.setLayout(layout)

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

    def on_data_ready(self, data):
        self.label.setText(unicode(data))

На основе: Обновление элементов графического интерфейса в многопоточном PyQT.

Ваш дополнительный вопрос: Обновите прогресс PyQt из другого потока, выполняющего загрузку по FTP

person Martin Prikryl    schedule 22.08.2019
comment
Показывает ошибку: QThread: Destroyed while thread is still running ... - person Qiasm; 22.08.2019
comment
Я думаю, что это почти работает, но когда я заполняю этот DownloadThread () параметром self: thread = DownloadThread(self), я получаю эту ошибку: TypeError: QThread(parent: QObject = None): argument 1 has unexpected type 'Ui_MainWindow' Для этого требуется объект, такой как переменная, я думаю, но я понятия не имею, что мне делать ... - person Qiasm; 22.08.2019
comment
Поток уничтожается, потому что после того, как start начинает выполнение, он немедленно возвращается, а поскольку объект DownloadThread объявлен только в рамках start_download(), он впоследствии собирается мусором. Установка self в качестве родителя работает, но это не очень хороший способ использования потоков, поскольку вы потеряете любую ссылку на поток после его запуска, как только функция будет возвращена. Вместо этого вы должны создать внутреннюю ссылку на поток (и, возможно, установить систему сигналов / слотов, чтобы избежать повторных вызовов для создания новых потоков, пока загрузка не будет завершена или отменена). - person musicamante; 22.08.2019
comment
Если это сборщик мусора, есть ли способ сказать сборщику мусора не уничтожать его? Как sys.exit(app.exec_()), но для QThread? А что такое внутренняя ссылка? Единственное, что приходит в голову, это глобальная переменная ... - person Qiasm; 22.08.2019
comment
Просто измените thread на членство в виджете. Я уже обновил свой ответ. - person Martin Prikryl; 22.08.2019
comment
@Aspect имейте в виду, что, хотя эта реализация работает нормально, вы можете столкнуться с некоторыми проблемами, если не убедитесь, что одновременных загрузок не более одной (включая попытки сети). Возможно, вам придется создать механизм, который разрешает только одну попытку загрузки за раз, возможно, с некоторым сигналом тайм-аута, испускаемым через некоторое время после того, как поток загрузки был запущен или получил некоторые данные. - person musicamante; 22.08.2019