mirror of
https://github.com/naruxde/revpicommander.git
synced 2025-11-08 16:43:53 +01:00
Add upload progress display in developer, bugfix on file download in developer
This commit is contained in:
50
include/ui/backgroundworker_ui.py
Normal file
50
include/ui/backgroundworker_ui.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Form implementation generated from reading ui file 'backgroundworker.ui'
|
||||||
|
#
|
||||||
|
# Created by: PyQt5 UI code generator 5.14.1
|
||||||
|
#
|
||||||
|
# WARNING! All changes made in this file will be lost!
|
||||||
|
|
||||||
|
|
||||||
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
|
|
||||||
|
class Ui_diag_backgroundworker(object):
|
||||||
|
def setupUi(self, diag_backgroundworker):
|
||||||
|
diag_backgroundworker.setObjectName("diag_backgroundworker")
|
||||||
|
diag_backgroundworker.resize(418, 97)
|
||||||
|
diag_backgroundworker.setModal(True)
|
||||||
|
self.verticalLayout = QtWidgets.QVBoxLayout(diag_backgroundworker)
|
||||||
|
self.verticalLayout.setObjectName("verticalLayout")
|
||||||
|
self.lbl_status = QtWidgets.QLabel(diag_backgroundworker)
|
||||||
|
self.lbl_status.setText("Status message...")
|
||||||
|
self.lbl_status.setObjectName("lbl_status")
|
||||||
|
self.verticalLayout.addWidget(self.lbl_status)
|
||||||
|
self.pgb_status = QtWidgets.QProgressBar(diag_backgroundworker)
|
||||||
|
self.pgb_status.setMinimumSize(QtCore.QSize(400, 0))
|
||||||
|
self.pgb_status.setObjectName("pgb_status")
|
||||||
|
self.verticalLayout.addWidget(self.pgb_status)
|
||||||
|
self.btn_box = QtWidgets.QDialogButtonBox(diag_backgroundworker)
|
||||||
|
self.btn_box.setOrientation(QtCore.Qt.Horizontal)
|
||||||
|
self.btn_box.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel)
|
||||||
|
self.btn_box.setCenterButtons(True)
|
||||||
|
self.btn_box.setObjectName("btn_box")
|
||||||
|
self.verticalLayout.addWidget(self.btn_box)
|
||||||
|
|
||||||
|
self.retranslateUi(diag_backgroundworker)
|
||||||
|
QtCore.QMetaObject.connectSlotsByName(diag_backgroundworker)
|
||||||
|
|
||||||
|
def retranslateUi(self, diag_backgroundworker):
|
||||||
|
_translate = QtCore.QCoreApplication.translate
|
||||||
|
diag_backgroundworker.setWindowTitle(_translate("diag_backgroundworker", "File transfer..."))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
|
app = QtWidgets.QApplication(sys.argv)
|
||||||
|
diag_backgroundworker = QtWidgets.QDialog()
|
||||||
|
ui = Ui_diag_backgroundworker()
|
||||||
|
ui.setupUi(diag_backgroundworker)
|
||||||
|
diag_backgroundworker.show()
|
||||||
|
sys.exit(app.exec_())
|
||||||
54
include/ui_dev/backgroundworker.ui
Normal file
54
include/ui_dev/backgroundworker.ui
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>diag_backgroundworker</class>
|
||||||
|
<widget class="QDialog" name="diag_backgroundworker">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>418</width>
|
||||||
|
<height>97</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>File transfer...</string>
|
||||||
|
</property>
|
||||||
|
<property name="modal">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="lbl_status">
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true">Status message...</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QProgressBar" name="pgb_status">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>400</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="btn_box">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel</set>
|
||||||
|
</property>
|
||||||
|
<property name="centerButtons">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
103
revpicommander/backgroundworker.py
Normal file
103
revpicommander/backgroundworker.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""File transfer system to handle QThreads."""
|
||||||
|
__author__ = "Sven Sager"
|
||||||
|
__copyright__ = "Copyright (C) 2021 Sven Sager"
|
||||||
|
__license__ = "GPLv3"
|
||||||
|
|
||||||
|
from logging import getLogger
|
||||||
|
|
||||||
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
|
from ui.backgroundworker_ui import Ui_diag_backgroundworker
|
||||||
|
|
||||||
|
log = getLogger()
|
||||||
|
|
||||||
|
|
||||||
|
class BackgroundWorker(QtCore.QThread):
|
||||||
|
steps_todo = QtCore.pyqtSignal(int)
|
||||||
|
steps_done = QtCore.pyqtSignal(int)
|
||||||
|
status_message = QtCore.pyqtSignal(str)
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super(BackgroundWorker, self).__init__(parent)
|
||||||
|
|
||||||
|
def check_cancel(self) -> bool:
|
||||||
|
"""
|
||||||
|
Check for interruption of thread and show message
|
||||||
|
|
||||||
|
:return: True, if interruption was requested
|
||||||
|
"""
|
||||||
|
if self.isInterruptionRequested():
|
||||||
|
self.status_message.emit(self.tr("User requested cancellation..."))
|
||||||
|
self.msleep(750)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def exec_dialog(self) -> int:
|
||||||
|
diag = WorkerDialog(self, self.parent())
|
||||||
|
rc = diag.exec()
|
||||||
|
diag.deleteLater()
|
||||||
|
return rc
|
||||||
|
|
||||||
|
def wait_interruptable(self, seconds=-1) -> None:
|
||||||
|
"""Save function to wait and get the cancel buttons."""
|
||||||
|
counter = seconds * 4
|
||||||
|
while counter != 0:
|
||||||
|
counter -= 1
|
||||||
|
self.msleep(250)
|
||||||
|
if self._check_cancel():
|
||||||
|
break
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
"""Worker thread to import pictures from camera."""
|
||||||
|
log.debug("BackgroundWorker.run")
|
||||||
|
self.status_message.emit("Started dummy thread...")
|
||||||
|
self.wait_interruptable(5)
|
||||||
|
self.status_message.emit("Completed dummy thread.")
|
||||||
|
self._save_wait(3)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkerDialog(QtWidgets.QDialog, Ui_diag_backgroundworker):
|
||||||
|
|
||||||
|
def __init__(self, worker_thread: BackgroundWorker, parent=None):
|
||||||
|
"""
|
||||||
|
Base of dialog to show progress from a background thread.
|
||||||
|
|
||||||
|
:param worker_thread: Thread with the logic work to do
|
||||||
|
:param parent: QtWidget
|
||||||
|
"""
|
||||||
|
super(WorkerDialog, self).__init__(parent)
|
||||||
|
self.setupUi(self)
|
||||||
|
|
||||||
|
self._canceled = False
|
||||||
|
|
||||||
|
self._th = worker_thread
|
||||||
|
self._th.finished.connect(self.on_th_finished)
|
||||||
|
self._th.steps_todo.connect(self.pgb_status.setMaximum)
|
||||||
|
self._th.steps_done.connect(self.pgb_status.setValue)
|
||||||
|
self._th.status_message.connect(self.lbl_status.setText)
|
||||||
|
|
||||||
|
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
|
||||||
|
a0.ignore()
|
||||||
|
|
||||||
|
def exec(self) -> int:
|
||||||
|
self._th.start()
|
||||||
|
return super(WorkerDialog, self).exec()
|
||||||
|
|
||||||
|
@QtCore.pyqtSlot()
|
||||||
|
def on_th_finished(self) -> None:
|
||||||
|
"""Check the result of import thread."""
|
||||||
|
if self._canceled:
|
||||||
|
self.reject()
|
||||||
|
else:
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
@QtCore.pyqtSlot(QtWidgets.QAbstractButton)
|
||||||
|
def on_btn_box_clicked(self, button: QtWidgets.QAbstractButton) -> None:
|
||||||
|
"""Control buttons for dialog."""
|
||||||
|
role = self.btn_box.buttonRole(button)
|
||||||
|
log.debug("WorkerDialog.on_btn_box_clicked({0})".format(role))
|
||||||
|
|
||||||
|
if role == QtWidgets.QDialogButtonBox.RejectRole:
|
||||||
|
self._th.requestInterruption()
|
||||||
|
self._canceled = True
|
||||||
@@ -13,6 +13,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
|||||||
|
|
||||||
import helper
|
import helper
|
||||||
import proginit as pi
|
import proginit as pi
|
||||||
|
from backgroundworker import BackgroundWorker
|
||||||
from helper import WidgetData
|
from helper import WidgetData
|
||||||
from ui.files_ui import Ui_win_files
|
from ui.files_ui import Ui_win_files
|
||||||
|
|
||||||
@@ -22,6 +23,57 @@ class NodeType(IntEnum):
|
|||||||
DIR = 1001
|
DIR = 1001
|
||||||
|
|
||||||
|
|
||||||
|
class UploadFiles(BackgroundWorker):
|
||||||
|
|
||||||
|
def __init__(self, file_list: list, parent):
|
||||||
|
super(UploadFiles, self).__init__(parent)
|
||||||
|
self.ec = 1
|
||||||
|
self.file_list = file_list
|
||||||
|
self.plc_program_included = False # Will be True, when opt_program was found in files
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
self.steps_todo.emit(len(self.file_list))
|
||||||
|
|
||||||
|
# Get config to find actual auto start program for warnings
|
||||||
|
opt_program = helper.cm.call_remote_function("get_config", default_value={})
|
||||||
|
opt_program = opt_program.get("plcprogram", "none.py")
|
||||||
|
|
||||||
|
progress_counter = 0
|
||||||
|
for file_name in self.file_list:
|
||||||
|
progress_counter += 1
|
||||||
|
|
||||||
|
# Remove base dir of file to set relative for PyLoad
|
||||||
|
send_name = file_name.replace(helper.cm.develop_watch_path, "")[1:]
|
||||||
|
self.status_message.emit(send_name)
|
||||||
|
|
||||||
|
# Check whether this is the auto start program
|
||||||
|
if send_name == opt_program:
|
||||||
|
self.plc_program_included = True
|
||||||
|
|
||||||
|
|
||||||
|
# Transfer file
|
||||||
|
try:
|
||||||
|
with open(file_name, "rb") as fh:
|
||||||
|
upload_status = helper.cm.call_remote_function(
|
||||||
|
"plcupload", Binary(gzip.compress(fh.read())), send_name,
|
||||||
|
default_value=False
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
pi.logger.error(e)
|
||||||
|
self.ec = -2
|
||||||
|
return
|
||||||
|
|
||||||
|
if not upload_status:
|
||||||
|
self.ec = -1
|
||||||
|
return
|
||||||
|
|
||||||
|
self.steps_done.emit(progress_counter)
|
||||||
|
if self.check_cancel():
|
||||||
|
return
|
||||||
|
|
||||||
|
self.ec = 0
|
||||||
|
|
||||||
|
|
||||||
class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files):
|
class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files):
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
@@ -72,41 +124,13 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get config to find actual auto start program for warnings
|
uploader = UploadFiles(self.file_list_local(), self)
|
||||||
opt_program = helper.cm.call_remote_function("get_config", default_value={})
|
if uploader.exec_dialog() == QtWidgets.QDialog.Rejected:
|
||||||
opt_program = opt_program.get("plcprogram", "none.py")
|
return
|
||||||
uploaded = True # Will be False, when opt_program was found in files
|
|
||||||
ec = 0
|
|
||||||
|
|
||||||
# todo: Do this in a thread with status bar to prevent freezing program on long upload times
|
if uploader.ec == 0:
|
||||||
for file_name in self.file_list_local():
|
|
||||||
# todo: Check exception of local file
|
|
||||||
with open(file_name, "rb") as fh:
|
|
||||||
# Remove base dir of file to set relative for PyLoad
|
|
||||||
send_name = file_name.replace(helper.cm.develop_watch_path, "")[1:]
|
|
||||||
|
|
||||||
# Check whether this is the auto start program
|
|
||||||
if send_name == opt_program:
|
|
||||||
uploaded = False
|
|
||||||
|
|
||||||
# Transfer file
|
|
||||||
try:
|
|
||||||
upload_status = helper.cm.call_remote_function(
|
|
||||||
"plcupload", Binary(gzip.compress(fh.read())), send_name,
|
|
||||||
default_value=False
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
pi.logger.error(e)
|
|
||||||
ec = -2
|
|
||||||
break
|
|
||||||
|
|
||||||
if not upload_status:
|
|
||||||
ec = -1
|
|
||||||
break
|
|
||||||
|
|
||||||
if ec == 0:
|
|
||||||
# Tell user, we did not find the auto start program in files
|
# Tell user, we did not find the auto start program in files
|
||||||
if uploaded:
|
if not uploader.plc_program_included:
|
||||||
QtWidgets.QMessageBox.information(
|
QtWidgets.QMessageBox.information(
|
||||||
self, self.tr("Information"), self.tr(
|
self, self.tr("Information"), self.tr(
|
||||||
"A PLC program has been uploaded. Please check the "
|
"A PLC program has been uploaded. Please check the "
|
||||||
@@ -115,7 +139,7 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
elif ec == -1:
|
elif uploader.ec == -1:
|
||||||
QtWidgets.QMessageBox.critical(
|
QtWidgets.QMessageBox.critical(
|
||||||
self, self.tr("Error"), self.tr(
|
self, self.tr("Error"), self.tr(
|
||||||
"The Revolution Pi could not process some parts of the "
|
"The Revolution Pi could not process some parts of the "
|
||||||
@@ -123,7 +147,7 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
elif ec == -2:
|
elif uploader.ec == -2:
|
||||||
QtWidgets.QMessageBox.critical(
|
QtWidgets.QMessageBox.critical(
|
||||||
self, self.tr("Error"),
|
self, self.tr("Error"),
|
||||||
self.tr("Errors occurred during transmission")
|
self.tr("Errors occurred during transmission")
|
||||||
@@ -489,16 +513,16 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files):
|
|||||||
else:
|
else:
|
||||||
file_name = os.path.join(helper.cm.develop_watch_path, file_name)
|
file_name = os.path.join(helper.cm.develop_watch_path, file_name)
|
||||||
if override is None and os.path.exists(file_name):
|
if override is None and os.path.exists(file_name):
|
||||||
rc = QtWidgets.QMessageBox.question(
|
rc_diag = QtWidgets.QMessageBox.question(
|
||||||
self, self.tr("Override files..."), self.tr(
|
self, self.tr("Override files..."), self.tr(
|
||||||
"One or more files does exist on your computer! Do you want to override the existing"
|
"One or more files does exist on your computer! Do you want to override the existing"
|
||||||
"files?\n\nSelect 'Yes' to override, 'No' to download only missing files."
|
"files?\n\nSelect 'Yes' to override, 'No' to download only missing files."
|
||||||
),
|
),
|
||||||
buttons=QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel
|
buttons=QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel
|
||||||
)
|
)
|
||||||
if rc == QtWidgets.QMessageBox.Cancel:
|
if rc_diag == QtWidgets.QMessageBox.Cancel:
|
||||||
return
|
return
|
||||||
override = rc == QtWidgets.QMessageBox.Yes
|
override = rc_diag == QtWidgets.QMessageBox.Yes
|
||||||
|
|
||||||
if os.path.exists(file_name) and not override:
|
if os.path.exists(file_name) and not override:
|
||||||
pi.logger.debug("Skip existing file '{0}'".format(file_name))
|
pi.logger.debug("Skip existing file '{0}'".format(file_name))
|
||||||
|
|||||||
Reference in New Issue
Block a user