From 98b4879b7f50539c83a621b23253ddbaf4f9f5f1 Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Mon, 9 Jan 2023 17:22:47 +0100 Subject: [PATCH] Manage revolution pi saved settings in an own class Start a complete new setting file with right naming. Import all old saved connections from old settings file. Revolution Pi saved settings are now in an own class with all properties and default values. All modules are using this new setting class, which support the save function. --- setup.iss | 2 +- setup.py | 2 +- src/revpicommander/avahisearch.py | 350 ++++++++---------- src/revpicommander/debugcontrol.py | 10 +- src/revpicommander/debugios.py | 4 +- src/revpicommander/helper.py | 332 ++++++++++------- .../locale/revpicommander_de.qm | Bin 50715 -> 50964 bytes .../locale/revpicommander_de.ts | 215 ++++++----- src/revpicommander/revpicommander.py | 53 ++- src/revpicommander/revpifiles.py | 29 +- src/revpicommander/revpiplclist.py | 175 ++++----- src/revpicommander/revpiprogram.py | 43 +-- src/revpicommander/sshauth.py | 1 + src/revpicommander/ui/avahisearch_ui.py | 4 + ui_dev/avahisearch.ui | 8 + 15 files changed, 632 insertions(+), 596 deletions(-) diff --git a/setup.iss b/setup.iss index 62987bb..2f6ddd4 100755 --- a/setup.iss +++ b/setup.iss @@ -2,7 +2,7 @@ ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! #define MyAppName "RevPi Commander" -#define MyAppVersion "0.9.10rc1" +#define MyAppVersion "0.9.10rc2" #define MyAppPublisher "Sven Sager" #define MyAppURL "https://revpimodio.org/" #define MyAppICO "data\revpicommander.ico" diff --git a/setup.py b/setup.py index dedf910..f1b202b 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ from setuptools import find_namespace_packages, setup setup( name="revpicommander", - version="0.9.10rc1", + version="0.9.10rc2", packages=find_namespace_packages("src"), package_dir={'': 'src'}, diff --git a/src/revpicommander/avahisearch.py b/src/revpicommander/avahisearch.py index 72f39c1..f69d04b 100644 --- a/src/revpicommander/avahisearch.py +++ b/src/revpicommander/avahisearch.py @@ -5,40 +5,18 @@ __copyright__ = "Copyright (C) 2023 Sven Sager" __license__ = "GPLv3" import webbrowser -from os import name as osname from re import compile from sys import platform from PyQt5 import QtCore, QtGui, QtWidgets from zeroconf import IPVersion, ServiceBrowser, Zeroconf +from . import helper from . import proginit as pi -from .helper import WidgetData, settings +from .helper import RevPiSettings, WidgetData, all_revpi_settings from .ui.avahisearch_ui import Ui_diag_search -def find_settings_index(address: str, port: int) -> int: - """ - Search index of saved settings. - - :param address: Host or IP address of Revolution Pi - :param port: Port to connect - :return: Index of settings array or -1, if not found - """ - settings_index = -1 - for i in range(settings.beginReadArray("connections")): - settings.setArrayIndex(i) - - _address = settings.value("address", type=str) - _port = settings.value("port", type=int) - if address.lower() == _address.lower() and port == _port: - settings_index = i - break - - settings.endArray() - return settings_index - - class AvahiSearchThread(QtCore.QThread): """Search thread for Revolution Pi with installed RevPiPyLoad.""" added = QtCore.pyqtSignal(str, str, int, str, str) @@ -49,30 +27,11 @@ class AvahiSearchThread(QtCore.QThread): super(AvahiSearchThread, self).__init__(parent) self._cycle_wait_ms = 1000 - self.__dict_arp = {} self.re_posix = compile( r"(?P(\d{1,3}\.){3}\d{1,3}).*" r"(?P([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2})" ) - def _update_arp(self) -> None: - """Find mac address in arp table.""" - if osname == "posix": - with open("/proc/net/arp") as fh: - for line in fh.readlines(): - ip_mac = self.re_posix.search(line) - if ip_mac: - self.__dict_arp[ip_mac.group("ip")] = ip_mac.group("mac") - - def get_mac(self, ip: str) -> dict: - """ - Get mac address of ip, if known. - - :param ip: IP address to find mac address - :return: MAC address as string or empty string, if unknown - """ - return self.__dict_arp.get(ip, "") - def remove_service(self, zeroconf: Zeroconf, conf_type: str, name: str) -> None: """Revolution Pi disappeared.""" pi.logger.debug("AvahiSearchThread.remove_service") @@ -115,17 +74,20 @@ class AvahiSearch(QtWidgets.QDialog, Ui_diag_search): super(AvahiSearch, self).__init__(parent) self.setupUi(self) + # Global variables to let parent decide other actions + self.connect_settings = None + self.just_save = False + + # Local variables self.clipboard = QtGui.QGuiApplication.clipboard() - self.connect_index = -1 - self.known_hosts = {} - self.th_zero_conf = AvahiSearchThread(self) + self._th_zero_conf = AvahiSearchThread(self) self.tb_revpi.setColumnWidth(0, 250) self.btn_connect.setEnabled(False) self.btn_save.setEnabled(False) - self.restoreGeometry(settings.value("avahisearch/geo", b'')) - column_sizes = settings.value("avahisearch/column_sizes", [], type=list) + self.restoreGeometry(helper.settings.value("avahisearch/geo", b'')) + column_sizes = helper.settings.value("avahisearch/column_sizes", [], type=list) if len(column_sizes) == self.tb_revpi.columnCount(): for i in range(self.tb_revpi.columnCount()): self.tb_revpi.setColumnWidth(i, int(column_sizes[i])) @@ -136,160 +98,133 @@ class AvahiSearch(QtWidgets.QDialog, Ui_diag_search): self.cm_connect_actions.addAction(self.act_connect_xmlrpc) self.cm_quick_actions = QtWidgets.QMenu(self) - self.cm_quick_actions.addAction(self.act_open_pictory) - self.cm_quick_actions.addSeparator() - self.cm_quick_actions.addAction(self.act_copy_ip) - self.cm_quick_actions.addAction(self.act_copy_host) - self.cm_quick_actions.addSeparator() + self.cm_quick_actions.addAction(self.act_connect) self.cm_quick_actions.addAction(self.act_connect_ssh) self.cm_quick_actions.addAction(self.act_connect_xmlrpc) + self.cm_quick_actions.addSeparator() + self.cm_quick_actions.addAction(self.act_open_pictory) + self.cm_quick_actions.addSeparator() + self.cm_quick_actions.addAction(self.act_copy_host) + self.cm_quick_actions.addAction(self.act_copy_ip) self.tb_revpi.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.tb_revpi.customContextMenuRequested.connect(self._cm_quick_exec) - @QtCore.pyqtSlot(QtCore.QPoint) - def _cm_connect_exec(self, position: QtCore.QPoint) -> None: - row = self.tb_revpi.currentRow() - if row == -1: - return - - item = self.tb_revpi.item(row, 0) - settings_index = find_settings_index(item.data(WidgetData.address), item.data(WidgetData.port)) - if settings_index >= 0: - self.connect_index = settings_index - self.accept() - return - - action = self.cm_connect_actions.exec(position) - if action: - action.trigger() - @QtCore.pyqtSlot(QtCore.QPoint) def _cm_quick_exec(self, position: QtCore.QPoint) -> None: - if self.tb_revpi.currentItem() is None: + selected_items = self.tb_revpi.selectedItems() + if not selected_items: return + item = selected_items[0] + + revpi_settings = bool(item.data(WidgetData.revpi_settings)) + self.act_connect.setVisible(revpi_settings) + self.act_connect_ssh.setVisible(not revpi_settings) + self.act_connect_xmlrpc.setVisible(not revpi_settings) sender = self.sender() - action = self.cm_quick_actions.exec(sender.mapToGlobal(position)) - if action: - action.trigger() + self.cm_quick_actions.exec(sender.mapToGlobal(position)) - def _load_known_hosts(self) -> None: - """Load existing connections to show hostname of existing ip addresses""" - self.known_hosts.clear() + self.act_connect.setVisible(True) + self.act_connect_ssh.setVisible(True) + self.act_connect_xmlrpc.setVisible(True) - for i in range(settings.beginReadArray("connections")): - settings.setArrayIndex(i) - - name = settings.value("name", type=str) - folder = settings.value("folder", type=str) - address = settings.value("address", type=str) - self.known_hosts[address] = "{0}/{1}".format(folder, name) if folder else name - - settings.endArray() + @staticmethod + def _find_settings(address: str) -> list[RevPiSettings]: + """Find all settings with known avahi_id.""" + return [ + revpi_setting + for revpi_setting in all_revpi_settings() + if revpi_setting.address.lower() == address.lower() + ] def _restart_search(self) -> None: """Clean up and restart search thread.""" while self.tb_revpi.rowCount() > 0: + # Remove each row, a .clean would destroy the columns self.tb_revpi.removeRow(0) - self.th_zero_conf.requestInterruption() - self.th_zero_conf = AvahiSearchThread(self) - self.th_zero_conf.added.connect(self.on_avahi_added) - self.th_zero_conf.updated.connect(self.on_avahi_added) - self.th_zero_conf.removed.connect(self.on_avahi_removed) - self.th_zero_conf.start() + self._th_zero_conf.requestInterruption() - def _save_connection(self, row: int, ssh_tunnel: bool, no_warn=False) -> int: + self._th_zero_conf = AvahiSearchThread(self) + self._th_zero_conf.added.connect(self.on_avahi_added) + self._th_zero_conf.updated.connect(self.on_avahi_added) + self._th_zero_conf.removed.connect(self.on_avahi_removed) + self._th_zero_conf.start() + + def _create_settings_object(self, row: int, ssh_tunnel: bool) -> RevPiSettings or None: """ - Save the connection from given row to settings. + Create settings object from given row to settings. :param row: Row with connection data :param ssh_tunnel: Save as SSH tunnel connection - :param no_warn: If True, no message boxes will appear - :return: Array index of connection (found or saved) or -1 + :return: RevPi settings with data from avahi search and default values """ item = self.tb_revpi.item(row, 0) if not item: - return -1 + return None - folder_name = self.tr("Auto discovered") - selected_name = item.text() - selected_address = item.data(WidgetData.address) - selected_port = item.data(WidgetData.port) - i = 0 - for i in range(settings.beginReadArray("connections")): - settings.setArrayIndex(i) + settings = RevPiSettings() + settings.folder = self.tr("Auto discovered") + settings.name = item.data(WidgetData.host_name) + settings.address = item.data(WidgetData.address) + settings.port = item.data(WidgetData.port) + settings.ssh_use_tunnel = ssh_tunnel - name = settings.value("name", type=str) - address = settings.value("address", type=str) - port = settings.value("port", type=int) - if address.lower() == selected_address.lower() and port == selected_port: - if not no_warn: - QtWidgets.QMessageBox.information( - self, self.tr("Already in list..."), self.tr( - "The selected Revolution Pi is already saved in your " - "connection list as '{0}'." - ).format(name) - ) - settings.endArray() - return i - - settings.endArray() - settings.beginWriteArray("connections") - - settings.setArrayIndex(i + 1) - settings.setValue("address", selected_address) - settings.setValue("folder", folder_name) - settings.setValue("name", selected_name) - settings.setValue("port", selected_port) - - settings.setValue("ssh_use_tunnel", ssh_tunnel) - settings.setValue("ssh_port", 22) - settings.setValue("ssh_user", "pi") - - settings.endArray() - - if not no_warn: - QtWidgets.QMessageBox.information( - self, self.tr("Success"), self.tr( - "The connection with the name '{0}' was successfully saved " - "to folder '{1}' in your connections." - ).format(selected_name, folder_name) - ) - - return i + 1 + return settings def closeEvent(self, a0: QtGui.QCloseEvent) -> None: - settings.setValue("avahisearch/geo", self.saveGeometry()) - settings.setValue("avahisearch/column_sizes", [ + helper.settings.setValue("avahisearch/geo", self.saveGeometry()) + helper.settings.setValue("avahisearch/column_sizes", [ self.tb_revpi.columnWidth(i) for i in range(self.tb_revpi.columnCount()) ]) def exec(self) -> int: - self._load_known_hosts() + self.connect_settings = None + self.just_save = False self._restart_search() rc = super(AvahiSearch, self).exec() - self.th_zero_conf.requestInterruption() + self._th_zero_conf.requestInterruption() return rc + @QtCore.pyqtSlot() + def on_act_connect_triggered(self) -> None: + """Connect via existing settings or ask for type.""" + pi.logger.debug("AvahiSearch.on_act_connect_triggered") + selected_items = self.tb_revpi.selectedItems() + if not selected_items: + return + item = selected_items[0] + + revpi_settings = item.data(WidgetData.revpi_settings) # type: RevPiSettings + if not revpi_settings: + return + self.connect_settings = revpi_settings + self.accept() + @QtCore.pyqtSlot() def on_act_connect_ssh_triggered(self) -> None: - """Copy ip address of selected item to clipboard.""" + """Create new revpi settings with ssh, save and connect.""" pi.logger.debug("AvahiSearch.on_act_connect_ssh_triggered") if self.tb_revpi.currentRow() == -1: return - self.connect_index = self._save_connection(self.tb_revpi.currentRow(), True, no_warn=True) + + revpi_settings = self._create_settings_object(self.tb_revpi.currentRow(), True) + revpi_settings.save_settings() + self.connect_settings = revpi_settings self.accept() @QtCore.pyqtSlot() def on_act_connect_xmlrpc_triggered(self) -> None: - """Copy ip address of selected item to clipboard.""" + """Create new revpi settings with XML-RPC, save and connect.""" pi.logger.debug("AvahiSearch.on_act_connect_xmlrpc_triggered") if self.tb_revpi.currentRow() == -1: return - self.connect_index = self._save_connection(self.tb_revpi.currentRow(), False, no_warn=True) + + revpi_settings = self._create_settings_object(self.tb_revpi.currentRow(), False) + revpi_settings.save_settings() + self.connect_settings = revpi_settings self.accept() @QtCore.pyqtSlot() @@ -327,45 +262,64 @@ class AvahiSearch(QtWidgets.QDialog, Ui_diag_search): webbrowser.open("http://{0}/".format(item.data(WidgetData.host_name))) @QtCore.pyqtSlot(str, str, int, str, str) - def on_avahi_added(self, name: str, server: str, port: int, conf_type: str, ip: str) -> None: + def on_avahi_added(self, avahi_id: str, server: str, port: int, conf_type: str, ip: str) -> None: """New Revolution Pi found.""" - index = -1 + + def update_tb_revpi_row(row_index: int): + host_name = server[:-1] + + item_name = self.tb_revpi.item(row_index, 0) + item_name.setData(WidgetData.address, ip) + item_name.setData(WidgetData.port, port) + item_name.setData(WidgetData.host_name, host_name) + + revpi_settings = item_name.data(WidgetData.revpi_settings) # type: RevPiSettings + if revpi_settings: + # Generate the name of saved revpi and show the avahi-name in brackets + settings_text = "{0}/{1}".format(revpi_settings.folder, revpi_settings.name) \ + if revpi_settings.folder \ + else revpi_settings.name + if revpi_settings.ssh_use_tunnel: + settings_text += self.tr(" over SSH") + item_name.setText("{0} ({1})".format(settings_text, host_name)) + else: + item_name.setText(host_name) + item_name.setToolTip(item_name.text()) + + item_ip = self.tb_revpi.item(row_index, 1) + item_ip.setText(ip) + item_ip.setToolTip(item_name.text()) + + lst_existing = self._find_settings(ip) + + exists = False for i in range(self.tb_revpi.rowCount()): - if self.tb_revpi.item(i, 0).data(WidgetData.object_name) == name: - index = i - break + item_tb_revpi = self.tb_revpi.item(i, 0) + if item_tb_revpi.data(WidgetData.object_name) == avahi_id: + # Object already discovered + update_tb_revpi_row(i) + exists = True - if index == -1: - # New Row - item_name = QtWidgets.QTableWidgetItem() - item_ip = QtWidgets.QTableWidgetItem() + if not exists: + for known_settings in lst_existing or [None]: + item_name = QtWidgets.QTableWidgetItem() - index = self.tb_revpi.rowCount() - self.tb_revpi.insertRow(index) - self.tb_revpi.setItem(index, 0, item_name) - self.tb_revpi.setItem(index, 1, item_ip) - else: - # Update row - item_name = self.tb_revpi.item(index, 0) - item_ip = self.tb_revpi.item(index, 1) + item_name.setIcon(QtGui.QIcon(":/main/ico/cpu.ico")) + item_name.setData(WidgetData.object_name, avahi_id) + item_name.setData(WidgetData.revpi_settings, known_settings) - host_name = server[:-1] - item_name.setIcon(QtGui.QIcon(":/main/ico/cpu.ico")) - if ip in self.known_hosts: - item_name.setText("{0} ({1})".format(host_name, self.known_hosts[ip])) - else: - item_name.setText(host_name) - item_name.setData(WidgetData.object_name, name) - item_name.setData(WidgetData.address, ip) - item_name.setData(WidgetData.port, port) - item_name.setData(WidgetData.host_name, host_name) - item_ip.setText(ip) + index = self.tb_revpi.rowCount() + self.tb_revpi.insertRow(index) + self.tb_revpi.setItem(index, 0, item_name) + self.tb_revpi.setItem(index, 1, QtWidgets.QTableWidgetItem()) + + update_tb_revpi_row(index) @QtCore.pyqtSlot(str, str) - def on_avahi_removed(self, name: str, conf_type: str) -> None: + def on_avahi_removed(self, avahi_id: str, conf_type: str) -> None: """Revolution Pi disappeared.""" for i in range(self.tb_revpi.rowCount()): - if self.tb_revpi.item(i, 0).data(WidgetData.object_name) == name: + if self.tb_revpi.item(i, 0).data(WidgetData.object_name) == avahi_id: self.tb_revpi.removeRow(i) break @@ -373,8 +327,17 @@ class AvahiSearch(QtWidgets.QDialog, Ui_diag_search): def on_tb_revpi_cellDoubleClicked(self, row: int, column: int) -> None: """Connect to double-clicked Revolution Pi.""" pi.logger.debug("AvahiSearch.on_tb_revpi_cellDoubleClicked") - cur = QtGui.QCursor() - self._cm_connect_exec(cur.pos()) + selected_items = self.tb_revpi.selectedItems() + if not selected_items: + return + item = selected_items[0] + + revpi_settings = bool(item.data(WidgetData.revpi_settings)) + if revpi_settings: + self.act_connect.trigger() + else: + cur = QtGui.QCursor() + self.cm_connect_actions.exec(cur.pos()) @QtCore.pyqtSlot(int, int, int, int) def on_tb_revpi_currentCellChanged(self, row: int, column: int, last_row: int, last_column: int) -> None: @@ -386,18 +349,29 @@ class AvahiSearch(QtWidgets.QDialog, Ui_diag_search): def on_btn_connect_clicked(self) -> None: """Connect to selected Revolution Pi.""" pi.logger.debug("AvahiSearch.on_btn_connect_clicked") - # Open context menu under the button - pos = self.btn_connect.pos() - pos.setY(pos.y() + self.btn_connect.height()) - self._cm_connect_exec(self.mapToGlobal(pos)) + selected_items = self.tb_revpi.selectedItems() + if not selected_items: + return + item = selected_items[0] + + revpi_settings = bool(item.data(WidgetData.revpi_settings)) + if revpi_settings: + self.act_connect.trigger() + else: + pos_context_menu = self.btn_connect.pos() + pos_context_menu.setY(pos_context_menu.y() + self.btn_connect.height()) + self.cm_connect_actions.exec(self.mapToGlobal(pos_context_menu)) @QtCore.pyqtSlot() def on_btn_save_clicked(self) -> None: """Save selected Revolution Pi.""" pi.logger.debug("AvahiSearch.on_btn_save_clicked") - if self.tb_revpi.currentRow() == -1: + row_index = self.tb_revpi.currentRow() + if row_index == -1: return - self.connect_index = self._save_connection(self.tb_revpi.currentRow(), ssh_tunnel=True) + self.connect_settings = self._create_settings_object(row_index, True) + self.just_save = True + self.accept() @QtCore.pyqtSlot() def on_btn_restart_clicked(self) -> None: diff --git a/src/revpicommander/debugcontrol.py b/src/revpicommander/debugcontrol.py index 0bdafe7..1af0d17 100644 --- a/src/revpicommander/debugcontrol.py +++ b/src/revpicommander/debugcontrol.py @@ -9,7 +9,7 @@ from xmlrpc.client import Binary, Fault, MultiCall, MultiCallIterator from PyQt5 import QtCore, QtWidgets -from .import helper +from . import helper from . import proginit as pi from .debugios import DebugIos from .ui.debugcontrol_ui import Ui_wid_debugcontrol @@ -74,7 +74,7 @@ class DebugControl(QtWidgets.QWidget, Ui_wid_debugcontrol): QtWidgets.QSpacerItem(20, 1, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) ) self.cbx_write.setEnabled(False) - self.cbx_stay_on_top.setChecked(helper.settings.value("stay_on_top", False, bool)) + self.cbx_stay_on_top.setChecked(helper.settings.value("debugcontrol/stay_on_top", False, bool)) def __del__(self): pi.logger.debug("DebugControl.__del__") @@ -342,7 +342,7 @@ class DebugControl(QtWidgets.QWidget, Ui_wid_debugcontrol): self._work_values(refresh=True) self._set_gui_control_states() - self.cbx_refresh.setChecked(helper.settings.value("auto_refresh", False, bool)) + self.cbx_refresh.setChecked(helper.settings.value("debugcontrol/auto_refresh", False, bool)) return True @@ -415,12 +415,12 @@ class DebugControl(QtWidgets.QWidget, Ui_wid_debugcontrol): @QtCore.pyqtSlot(bool) def on_cbx_refresh_clicked(self, state: bool): """Save the state on user action.""" - helper.settings.setValue("auto_refresh", state) + helper.settings.setValue("debugcontrol/auto_refresh", state) @QtCore.pyqtSlot(bool) def on_cbx_stay_on_top_clicked(self, state: bool): """Save the state on user action.""" - helper.settings.setValue("stay_on_top", state) + helper.settings.setValue("debugcontrol/stay_on_top", state) @QtCore.pyqtSlot(int) def on_cbx_write_stateChanged(self, state: int): diff --git a/src/revpicommander/debugios.py b/src/revpicommander/debugios.py index 990564b..40ef2e0 100644 --- a/src/revpicommander/debugios.py +++ b/src/revpicommander/debugios.py @@ -26,7 +26,7 @@ class DebugIos(QtWidgets.QMainWindow, Ui_win_debugios): super(DebugIos, self).__init__(parent) self.setupUi(self) - self.restoreGeometry(helper.cm.debug_geos.get(position, b'')) + self.restoreGeometry(helper.cm.settings.debug_geos.get(position, b'')) self.setWindowTitle("{0} - {1}".format(position, name)) self.gb_io.setTitle(self.gb_io.title().format(name)) @@ -58,7 +58,7 @@ class DebugIos(QtWidgets.QMainWindow, Ui_win_debugios): def closeEvent(self, a0: QtGui.QCloseEvent): pi.logger.debug("DebugIos.closeEvent") - helper.cm.debug_geos[self.position] = self.saveGeometry() + helper.cm.settings.debug_geos[self.position] = self.saveGeometry() self.device_closed.emit(self.position) @staticmethod diff --git a/src/revpicommander/helper.py b/src/revpicommander/helper.py index d2f83c4..eb7519b 100644 --- a/src/revpicommander/helper.py +++ b/src/revpicommander/helper.py @@ -12,6 +12,7 @@ from os import environ, remove from os.path import exists from queue import Queue from threading import Lock +from uuid import uuid4 from xmlrpc.client import Binary, ServerProxy from PyQt5 import QtCore, QtWidgets @@ -21,31 +22,166 @@ from . import proginit as pi from .ssh_tunneling.server import SSHLocalTunnel from .sshauth import SSHAuth, SSHAuthType +settings = QtCore.QSettings("revpimodio.org", "RevPiCommander") +"""Global application settings.""" + +homedir = environ.get("HOME", "") or environ.get("APPDATA", "") +"""Home dir of user.""" + class WidgetData(IntEnum): address = 260 - replace_ios_config = 261 acl_level = 262 has_error = 263 port = 264 object_name = 265 - timeout = 266 host_name = 267 - last_dir_upload = 301 - last_file_upload = 302 - last_dir_pictory = 303 - last_dir_picontrol = 304 - last_dir_selected = 305 - last_pictory_file = 306 - last_tar_file = 307 - last_zip_file = 308 file_name = 309 - watch_files = 310 - watch_path = 311 - debug_geos = 312 - ssh_use_tunnel = 313 - ssh_port = 315 - ssh_user = 316 + revpi_settings = 320 + + +class RevPiSettings: + + def __init__(self, load_index: int = None, settings_storage: QtCore.QSettings = None): + """ + Revolution Pi saved settings. + + :param load_index: Load settings from index, same as .load_from_index + :param settings_storage: Change QSettings object to work on from default to this one + """ + self._settings = settings_storage or settings + self.internal_id = "" + + self.name = "New connection" + self.folder = "" + self.address = "127.0.0.1" + self.port = 55123 + self.timeout = 5 + + self.ssh_use_tunnel = False + self.ssh_port = 22 + self.ssh_user = "pi" + + self.last_dir_upload = "." + self.last_file_upload = "." + self.last_dir_pictory = "." + self.last_dir_picontrol = "." + self.last_dir_selected = "." + self.last_pictory_file = "" + self.last_tar_file = "" + self.last_zip_file = "" + self.watch_files = [] + self.watch_path = "" + + self.debug_geos = {} + + if load_index is not None: + self.load_from_index(load_index) + + def load_from_index(self, settings_index: int) -> None: + """Load settings from 'connections' index.""" + self._settings.beginReadArray("connections") + self._settings.setArrayIndex(settings_index) + + # Flag as "legacy" connection to generate missing internal_id on save_settings() + self.internal_id = self._settings.value("internal_id", "legacy", type=str) + + self.name = self._settings.value("name", type=str) + self.folder = self._settings.value("folder", "", type=str) + self.address = self._settings.value("address", type=str) + self.port = self._settings.value("port", 55123, type=int) + self.timeout = self._settings.value("timeout", 5, type=int) + + self.ssh_use_tunnel = self._settings.value("ssh_use_tunnel", False, type=bool) + self.ssh_port = self._settings.value("ssh_port", 22, type=int) + self.ssh_user = self._settings.value("ssh_user", "pi", type=str) + + self.last_dir_upload = self._settings.value("last_dir_upload", ".", type=str) + self.last_file_upload = self._settings.value("last_file_upload", ".", type=str) + self.last_dir_pictory = self._settings.value("last_dir_pictory", ".", type=str) + self.last_dir_picontrol = self._settings.value("last_dir_picontrol", ".", type=str) + self.last_dir_selected = self._settings.value("last_dir_selected", ".", type=str) + self.last_pictory_file = self._settings.value("last_pictory_file", "", type=str) + self.last_tar_file = self._settings.value("last_tar_file", "", type=str) + self.last_zip_file = self._settings.value("last_zip_file", "", type=str) + self.watch_files = self._settings.value("watch_files", [], type=list) + self.watch_path = self._settings.value("watch_path", "", type=str) + + try: + # Bytes with QSettings are a little difficult sometimes + self.debug_geos = self._settings.value("debug_geos", {}, type=dict) + except Exception: + # Just drop the geos of IO windows + pass + + # These values must exists + if not (self.name and self.address and self.port): + raise ValueError("Could not geht all required values from saved settings") + + self._settings.endArray() + + def save_settings(self): + """Save all settings.""" + + count_settings = self._settings.beginReadArray("connections") + + def create_new_array_member(): + """Insert a new setting at the end of the array.""" + + # Close the active array action to reopen a write action to expand the array + self._settings.endArray() + self._settings.beginWriteArray("connections") + self._settings.setArrayIndex(count_settings) + + self.internal_id = uuid4().hex + + if not self.internal_id: + create_new_array_member() + + else: + # Always search setting in array, because connection manager could reorganize array indexes + index = -1 + for index in range(count_settings): + self._settings.setArrayIndex(index) + + if self.internal_id == "legacy": + # Legacy connection without internal_id + if self._settings.value("address") == self.address: + # Set missing internal_id + self.internal_id = uuid4().hex + break + else: + if self._settings.value("internal_id") == self.internal_id: + break + + if index == count_settings - 1: + # On this point, we iterate all settings and found none, so create new one + create_new_array_member() + + self._settings.setValue("internal_id", self.internal_id) + self._settings.setValue("name", self.name) + self._settings.setValue("folder", self.folder) + self._settings.setValue("address", self.address) + self._settings.setValue("port", self.port) + self._settings.setValue("timeout", self.timeout) + + self._settings.setValue("ssh_use_tunnel", self.ssh_use_tunnel) + self._settings.setValue("ssh_port", self.ssh_port) + self._settings.setValue("ssh_user", self.ssh_user) + + self._settings.setValue("last_dir_upload", self.last_dir_upload) + self._settings.setValue("last_file_upload", self.last_file_upload) + self._settings.setValue("last_dir_pictory", self.last_dir_pictory) + self._settings.setValue("last_dir_picontrol", self.last_dir_picontrol) + self._settings.setValue("last_dir_selected", self.last_dir_selected) + self._settings.setValue("last_pictory_file", self.last_pictory_file) + self._settings.setValue("last_tar_file", self.last_tar_file) + self._settings.setValue("last_zip_file", self.last_zip_file) + self._settings.setValue("watch_files", self.watch_files) + self._settings.setValue("watch_path", self.watch_path) + self._settings.setValue("debug_geos", self.debug_geos) + + self._settings.endArray() class ConnectionManager(QtCore.QThread): @@ -75,29 +211,11 @@ class ConnectionManager(QtCore.QThread): self._revpi = None self._revpi_output = None - self.address = "" - self.name = "" - self.port = 55123 + self.settings = RevPiSettings() self.ssh_tunnel_server = None # type: SSHLocalTunnel - self.ssh_use_tunnel = False - self.ssh_port = 22 - self.ssh_user = "pi" self.ssh_pass = "" - # Sync this with revpiplclist to preserve settings - self.program_last_dir_upload = "" - self.program_last_file_upload = "" - self.program_last_dir_pictory = "" - self.program_last_dir_picontrol = "" - self.program_last_dir_selected = "" - self.program_last_pictory_file = "" - self.program_last_tar_file = "" - self.program_last_zip_file = "" - self.develop_watch_files = [] - self.develop_watch_path = "" - self.debug_geos = {} - self.pyload_version = (0, 0, 0) """Version number of RevPiPyLoad 0.0.0 with values.""" self.xml_funcs = [] @@ -178,60 +296,19 @@ class ConnectionManager(QtCore.QThread): def _clear_settings(self): """Clear connection settings.""" - self.address = "" - self.name = "" - self.port = 55123 + self.settings = RevPiSettings() - self.ssh_use_tunnel = False - self.ssh_port = 22 - self.ssh_user = "pi" self.ssh_pass = "" self.pyload_version = (0, 0, 0) self.xml_funcs.clear() self.xml_mode = -1 - self.program_last_dir_upload = "" - self.program_last_file_upload = "" - self.program_last_dir_pictory = "" - self.program_last_dir_picontrol = "" - self.program_last_dir_selected = "" - self.program_last_pictory_file = "" - self.program_last_tar_file = "" - self.program_last_zip_file = "" - self.develop_watch_files = [] - self.develop_watch_path = "" - self.debug_geos = {} - - def _save_settings(self): - """Save settings to named Revolution Pi.""" - for i in range(settings.beginReadArray("connections")): - settings.setArrayIndex(i) - if settings.value("address") != self.address: - # Search used connection, because connection manager could reorganize array - continue - - settings.setValue("last_dir_upload", self.program_last_dir_upload) - settings.setValue("last_file_upload", self.program_last_file_upload) - settings.setValue("last_dir_pictory", self.program_last_dir_pictory) - settings.setValue("last_dir_picontrol", self.program_last_dir_picontrol) - settings.setValue("last_dir_selected", self.program_last_dir_selected) - settings.setValue("last_pictory_file", self.program_last_pictory_file) - settings.setValue("last_tar_file", self.program_last_tar_file) - settings.setValue("last_zip_file", self.program_last_zip_file) - settings.setValue("watch_files", self.develop_watch_files) - settings.setValue("watch_path", self.develop_watch_path) - settings.setValue("debug_geos", self.debug_geos) - - break - - settings.endArray() - - def pyload_connect(self, settings_index: int, parent=None) -> bool: + def pyload_connect(self, settings: RevPiSettings, parent=None) -> bool: """ Create a new connection from settings object. - :param settings_index: Index of settings array 'connections' + :param settings: Revolution Pi saved connection settings :param parent: Qt parent window for dialog positioning :return: True, if the connection was successfully established """ @@ -239,48 +316,27 @@ class ConnectionManager(QtCore.QThread): # First disconnect to send signal and clean up values self.pyload_disconnect() - settings.beginReadArray("connections") - settings.setArrayIndex(settings_index) - - address = settings.value("address", str) - name = settings.value("name", str) - port = settings.value("port", 55123, int) - timeout = settings.value("timeout", 5, int) - ssh_tunnel_server = None - ssh_use_tunnel = settings.value("ssh_use_tunnel", False, bool) - ssh_port = settings.value("ssh_port", 22, int) - ssh_user = settings.value("ssh_user", "pi", str) ssh_tunnel_port = 0 ssh_pass = "" - self.program_last_dir_upload = settings.value("last_dir_upload", ".", str) - self.program_last_file_upload = settings.value("last_file_upload", ".", str) - self.program_last_dir_pictory = settings.value("last_dir_pictory", ".", str) - self.program_last_dir_picontrol = settings.value("last_dir_picontrol", ".", str) - self.program_last_dir_selected = settings.value("last_dir_selected", ".", str) - self.program_last_pictory_file = settings.value("last_pictory_file", "{0}.rsc".format(name), str) - self.program_last_tar_file = settings.value("last_tar_file", "{0}.tgz".format(name), str) - self.program_last_zip_file = settings.value("last_zip_file", "{0}.zip".format(name), str) - self.develop_watch_files = settings.value("watch_files", [], list) - self.develop_watch_path = settings.value("watch_path", "", str) - self.debug_geos = settings.value("debug_geos", {}, dict) - - settings.endArray() - socket.setdefaulttimeout(2) - if ssh_use_tunnel: + if settings.ssh_use_tunnel: while True: diag_ssh_auth = SSHAuth(SSHAuthType.PASS, parent) - diag_ssh_auth.username = ssh_user + diag_ssh_auth.username = settings.ssh_user if not diag_ssh_auth.exec() == QtWidgets.QDialog.Accepted: self._clear_settings() return False ssh_user = diag_ssh_auth.username ssh_pass = diag_ssh_auth.password - ssh_tunnel_server = SSHLocalTunnel(port, address, ssh_port) + ssh_tunnel_server = SSHLocalTunnel( + settings.port, + settings.address, + settings.ssh_port + ) try: ssh_tunnel_port = ssh_tunnel_server.connect_by_credentials(ssh_user, ssh_pass) break @@ -304,7 +360,7 @@ class ConnectionManager(QtCore.QThread): sp = ServerProxy("http://127.0.0.1:{0}".format(ssh_tunnel_port)) else: - sp = ServerProxy("http://{0}:{1}".format(address, port)) + sp = ServerProxy("http://{0}:{1}".format(settings.address, settings.port)) # Load values and test connection to Revolution Pi try: @@ -315,7 +371,7 @@ class ConnectionManager(QtCore.QThread): pi.logger.exception(e) self.connection_error_observed.emit(str(e)) - if not self.ssh_use_tunnel: + if not settings.ssh_use_tunnel: # todo: Change message, that user can use ssh QtWidgets.QMessageBox.critical( parent, self.tr("Error"), self.tr( @@ -330,24 +386,19 @@ class ConnectionManager(QtCore.QThread): return False - self.address = address - self.name = name - self.port = port - self.ssh_use_tunnel = ssh_use_tunnel - self.ssh_port = ssh_port - self.ssh_user = ssh_user + self.settings = settings self.ssh_pass = ssh_pass self.pyload_version = pyload_version self.xml_funcs = xml_funcs self.xml_mode = xml_mode with self._lck_cli: - socket.setdefaulttimeout(timeout) + socket.setdefaulttimeout(settings.timeout) self.ssh_tunnel_server = ssh_tunnel_server self._cli = sp self._cli_connect.put_nowait(( - "127.0.0.1" if ssh_use_tunnel else address, - ssh_tunnel_port if ssh_use_tunnel else port + "127.0.0.1" if settings.ssh_use_tunnel else settings.address, + ssh_tunnel_port if settings.ssh_use_tunnel else settings.port )) self.connection_established.emit() @@ -371,9 +422,9 @@ class ConnectionManager(QtCore.QThread): elif self._cli is not None: - # Tell all widget, that we want to disconnect, to save the settings + # Tell all widget, that we want to disconnect self.connection_disconnecting.emit() - self._save_settings() + self.settings.save_settings() with self._lck_cli: if self._ps_started: @@ -475,10 +526,14 @@ class ConnectionManager(QtCore.QThread): if self.ssh_tunnel_server and not self.ssh_tunnel_server.connected: self.ssh_tunnel_server.disconnect() - ssh_tunnel_server = SSHLocalTunnel(self.port, self.address, self.ssh_port) + ssh_tunnel_server = SSHLocalTunnel( + self.settings.port, + self.settings.address, + self.settings.ssh_port + ) try: ssh_tunnel_port = self.ssh_tunnel_server.connect_by_credentials( - self.ssh_user, + self.settings.ssh_user, self.ssh_pass ) sp = ServerProxy("http://127.0.0.1:{0}".format(ssh_tunnel_port)) @@ -563,9 +618,9 @@ class ConnectionManager(QtCore.QThread): Use connection_recovered signal to figure out new parameters. """ - if not self.ssh_use_tunnel and self.address and self.port: - return ServerProxy("http://{0}:{1}".format(self.address, self.port)) - if self.ssh_use_tunnel and self.ssh_tunnel_server and self.ssh_tunnel_server.connected: + if not self.settings.ssh_use_tunnel and self.settings.address and self.settings.port: + return ServerProxy("http://{0}:{1}".format(self.settings.address, self.settings.port)) + if self.settings.ssh_use_tunnel and self.ssh_tunnel_server and self.ssh_tunnel_server.connected: return ServerProxy("http://127.0.0.1:{0}".format(self.ssh_tunnel_server.local_tunnel_port)) return None @@ -592,8 +647,27 @@ class ConnectionManager(QtCore.QThread): cm = ConnectionManager() """Clobal connection manager instance.""" -settings = QtCore.QSettings("revpipyplc", "revpipyload") -"""Global application settings.""" -homedir = environ.get("HOME", "") or environ.get("APPDATA", "") -"""Home dir of user.""" +def all_revpi_settings() -> [RevPiSettings]: + """Get all revpi settings objects.""" + # Get length of array and close it, the RevPiSettings-class need it + count_settings = settings.beginReadArray("connections") + settings.endArray() + return [RevPiSettings(i) for i in range(count_settings)] + + +def import_old_settings(): + if not settings.value("revpicommander/imported_settings", False, type=bool): + settings.setValue("revpicommander/imported_settings", True) + + old_settings = QtCore.QSettings("revpipyplc", "revpipyload") + count_settings = old_settings.beginReadArray("connections") + old_settings.endArray() + + lst_revpi_settings = [RevPiSettings(i, settings_storage=old_settings) for i in range(count_settings)] + for revpi_setting in lst_revpi_settings: + revpi_setting._settings = settings + revpi_setting.save_settings() + + +import_old_settings() diff --git a/src/revpicommander/locale/revpicommander_de.qm b/src/revpicommander/locale/revpicommander_de.qm index d9ed5327a3eb8c39e0b1b4b397cec77c9638a567..82d94a4ba322691fcf965c4d9a2075297cc966ce 100644 GIT binary patch delta 2896 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 diff --git a/src/revpicommander/locale/revpicommander_de.ts b/src/revpicommander/locale/revpicommander_de.ts index 2cce3e7..caae61b 100644 --- a/src/revpicommander/locale/revpicommander_de.ts +++ b/src/revpicommander/locale/revpicommander_de.ts @@ -68,90 +68,95 @@ Nicht gespeicherte Änderunen gehen verloren AvahiSearch - + Auto discovered Automatisch erkannt - + Already in list... Bereits in Liste... - + Success Erfolgreich - + The connection with the name '{0}' was successfully saved to folder '{1}' in your connections. Die Verbindung mit dem Namen '{0}' wurde erfolgreich im Ordner '{1}' gespeichert. - + The selected Revolution Pi is already saved in your connection list as '{0}'. Der ausgewählte RevPi ist schon in der Verbindungsliste als '{0}'. + + + over SSH + über SSH + ConnectionManager - + SIMULATING SIMULATION - + NOT CONNECTED NICHT VERBUNDEN - + SERVER ERROR SERVER FEHLER - + RUNNING LÄUFT - + PLC FILE NOT FOUND SPS PROGRAMM NICHT GEFUNDEN - + NOT RUNNING (NO STATUS) LÄUFT NICHT (KEIN STATUS) - + PROGRAM KILLED PROGRAMM GETÖTET - + PROGRAM TERMED PROGRAMM BEENDET - + NOT RUNNING LÄUFT NICHT - + FINISHED WITH CODE {0} BEENDET MIT CODE {0} - + Error Fehler - + The combination of username and password was rejected from the SSH server. Try again. @@ -160,7 +165,7 @@ Try again. Bitte erneut versuchen. - + Could not establish a SSH connection to server: {0} @@ -169,7 +174,7 @@ Bitte erneut versuchen. {0} - + Can not connect to RevPi XML-RPC Service! This could have the following reasons: The RevPi is not online, the XML-RPC service is not running / bind to localhost or the ACL permission is not set for your IP!!! @@ -278,86 +283,86 @@ Ungesicherte Änderungen gehen verloren. RevPiCommander - + Simulator started... Simulator gestartet... - + Can not start... Kann nicht gestartet werden... - + Warning Warnung - + This version of Logviewer ist not supported in version {0} of RevPiPyLoad on your RevPi! You need at least version 0.4.1. Diese Version vom Logbetrachter wird in RevPiPyLoad Version {0} nicht unterstützt! Es wird mindestens Version 0.4.1 benötigt. - + XML-RPC access mode in the RevPiPyLoad configuration is too small to access this dialog! XML-RPC Zugriffsberechtigung in der RevPiPyLoad Konfiguraiton ist zu klein für diese Einstellungen! - + Error Fehler - + The Version of RevPiPyLoad on your Revolution Pi ({0}) is to old. This Version of RevPiCommander require at least version 0.6.0 of RevPiPyLoad. Please update your Revolution Pi! Die Version von RevPiPyLoad ({0}) auf dem Revolution Pi ist zu alt. Diese Version vom RevPiCommander braucht mindestens Version 0.6.0. Bitte aktualisiere deinen Revolution Pi! - + Question Frage - + Are you sure to reset piControl? The pictory configuration will be reloaded. During that time the process image will be interrupted and could rise errors on running control programs! Soll piControl wirklich zurückgesetzt werden? Die piCtory Konfiguration wird neu geladen. Das Prozessabbild wird in dieser Zeit nicht verfügbar sein und es könnten Fehler in Steuerungsprogrammen ausgelöst werden! - + Success Erfolgreich - + piControl reset executed successfully piControl wurde erfolgreich zurückgesetzt - + piControl reset could not be executed successfully piControl konnte nicht zurückgesetzt werden - + Reset to piCtory defaults... Standardwerte von piCtory laden... - + The watch mode ist not supported in version {0} of RevPiPyLoad on your RevPi! You need at least version 0.5.3! Maybe the python3-revpimodio2 module is not installed on your RevPi at least version 2.0.0. Der SPS Betrachter ist in Version {0} von RevPiPyLoad auf dem Rev Pi nicht unterstützt! Es muss mindestens Version 0.5.3 installiert sein! Vielleicht fehlt auch das python3-revpimodio2 Modul, welches mindestens Version 2.0.0 haben muss. - + Can not load this function, because your ACL level is to low! You need at least level 1 to read or level 3 to write. Für diese Funktion ist das Berechtigungslevel zu gering! Es muss mindestens Level 1 zum Lesen oder Level 3 zu Schreiben sein. - + Can not load piCtory configuration. Did you create a hardware configuration? Please check this in piCtory! Kann piCtory Konfiguration nicht laden. @@ -377,7 +382,7 @@ Das kann eine der folgenden Ursachen haben: Der Rev Pi ist nicht online, der XML Führe 'sudo revpipyload_secure_installation' auf dem Revolution Pi aus um diese Funktion zu konfigurieren! - + The simulator is running! You can work with this simulator if your call RevPiModIO with this additional parameters: @@ -394,24 +399,24 @@ configrsc={1} Dies kann aus der Textbox oben kopiert werden. - + Can not start the simulator! Maybe the piCtory file is corrupt or you have no write permissions for '{0}'. Kann Simulator nicht starten! Vielleicht ist die piCtory Datei defekt oder es gibt keine Schreibberechtigung für '{0}`. - + Do you want to reset your process image to {0} values? You have to stop other RevPiModIO programs before doing that, because they could reset the outputs. Soll das virtuelle Prozessabbild auf {0} zurückgesetzt werden? Es sollten alle RevPiModIO Programme vorher beendet werden, da diese ihre IO Werte sofort wieder schreiben würden. - + zero null - + piCtory default piCtory Standardwerte @@ -419,92 +424,92 @@ Es sollten alle RevPiModIO Programme vorher beendet werden, da diese ihre IO Wer RevPiFiles - + Please select... Bitte auswählen... - + Error Fehler - + Can not stop plc program on Revolution Pi. Kann SPS Programm auf Rev Pi nicht stoppen. - + The Revolution Pi could not process some parts of the transmission. Der Revolution Pi hat Teile der Übertragung nicht durchgeführt. - + Errors occurred during transmission Fehler bei Übertragung aufgetreten - + Warning Warnung - + Could not start the plc program on Revolution Pi. Kann das SPS Programm auf dem Revolution Pi nicht starten. - + The RevPiPyLoad version on the Revolution Pi is to old. Die RevPiPyLoad Version auf dem Revolution Pi ist zu alt. - + Can not open last directory '{0}'. Kann letztes Verzeichnis '{0}' nicht öffnen. - + Stop scanning for files, because we found more than {0} files. Dateisuche wurde angehalten, da mehr als {0} Dateien gefunden wurden. - + Could not load path of working dir Kann Arbeitsverzeichnis nicht laden - + Can not load file list from Revolution Pi. Kann Dateiliste vom Revolution Pi nicht laden. - + Select folder... Ordner auswählen... - + Can not access the folder '{0}' to read files. Keine Berechtigung für Zugriff auf Ordner '{0}'. - + Error... Fehler... - + Error while download file '{0}'. Fehler beim Herunterladen der Datei '{0}'. - + Override files... Dateien überschreiben... - + One or more files does exist on your computer! Do you want to override the existingfiles? Select 'Yes' to override, 'No' to download only missing files. @@ -513,32 +518,32 @@ Select 'Yes' to override, 'No' to download only missing file Wählen Sie 'Ja' zum Überschreiben, 'Nein' um nur fehlende Dateien zu laden. - + Delete files from Revolution Pi... Dateien auf Rev Pi löschen... - + Do you want to delete {0} files from revolution pi? Sollen {0} Dateien vom Revolution Pi gelöscht werden? - + Error while delete file '{0}'. Fehler beim Löschen der Datei '{0}'. - + Information Information - + A PLC program has been uploaded. Please check the PLC program settings to see if the correct program is specified as the start program. Ein SPS Programm wurde hochgeladen. Bitte prüfe die SPS Programmeinstellungen ob das richtige Startprogramm gewählt ist. - + Choose a local directory first. Lokales Verzeichnis wählen. @@ -658,17 +663,17 @@ Ungesicherte Änderungen gehen verloren. RevPiPlcList - + New connection Neue Verbindung - + Question Frage - + Do you really want to quit? Unsaved changes will be lost. Soll das Fenster wirklich geschlossen werden? @@ -678,7 +683,7 @@ Ungesicherte Änderungen gehen verloren. RevPiProgram - + Error Fehler @@ -735,7 +740,7 @@ Please try again. Versuche es erneut. - + Success Erfolgreich @@ -787,160 +792,160 @@ An error occurred on piControl reset! Es trat jedoch ein Fehler beim Zurücksetzen von piControl auf! - + Save ZIP archive... ZIP Archiv speichern... - + ZIP archive (*.zip);;All files (*.*) - ZIP Archive (*.tgz);;Alle Dateien (*.*) + ZIP Archive (*.zip);;Alle Dateien (*.*) - + Save TGZ archive... TGZ Archiv speichern... - + TGZ archive (*.tgz);;All files (*.*) TAR Archive (*.tgz);;Alle Dateien (*.*) - + Could not load PLC program from Revolution Pi. Kann SPS Programm nicht vom Revolution Pi laden. - + Coud not save the archive or extract the files! Please retry. Konnte das Archiv nicht speichern oder extrahieren! Versuche es erneut. - + Transfer successfully completed. Übertragung erfolgreich abgeschlossen. - + Upload content of ZIP archive... ZIP Archiv hochladen... - + The selected file ist not a ZIP archive. Die ausgewählte Datei ist kein ZIP Archiv. - + Upload content of TAR archive... TAR Archiv hochladen... - + TAR archive (*.tgz);;All files (*.*) TAR Archive (*.tgz);;Alle Dateien (*.*) - + The selected file ist not a TAR archive. Die ausgewählte Datei ist kein TAR Archiv. - + No files to upload... Keine Dateien zum Hochladen... - + Found no files to upload in given location or archive. Konnte keine Dateien in der Quelle zum Hochladen finden. - + There was an error deleting the files on the Revolution Pi. Upload aborted! Please try again. Beim Löschen der Dateien auf dem Revolution Pi ist ein Fehler aufgetreten. Hochladen abgebrochen! Versuche es erneut. - + The PLC program was transferred successfully. Das SPS Programm wurde erfolgreich übertragen. - + Information Information - + Could not find the selected PLC start program in uploaded files. This is not an error, if the file was already on the Revolution Pi. Check PLC start program field Konnte eingestelltes SPS Starprogramm in hochgeladenen Dateien nicht finden. Dies ist kein Fehler, wenn das SPS Startprogramm bereits auf dem Rev Pi ist. Prüfe SPS Programm Einstellungen - + There is no piCtory configuration in this archive. Kann keine piCtory Konfiguration im Archiv finden. - + The Revolution Pi could not process some parts of the transmission. Der Revolution Pi konnte Teile der Übertragung nicht verarbeiten. - + Errors occurred during transmission. Fehler bei Übertragung aufgetreten. - + Save piCtory file... piCtory Datei speichern... - + piCtory file (*.rsc);;All files (*.*) piCtory Datei (*.rsc);;Alle Dateien (*.*) - + Could not load piCtory file from Revolution Pi. Kann piCtory Konfiguration nicht vom Revolution Pi laden. - + piCtory configuration successfully loaded and saved to: {0}. piCtory Konfiguration erfolgreich geladen und gespeichert als: {0}. - + Upload piCtory file... piCtory datei hochladen... - + Save piControl file... piCtory Datei speichern... - + Process image file (*.img);;All files (*.*) Processabbild (*.img);;Alle Dateien (*.*) - + Could not load process image from Revolution Pi. Kann Prozessabbild von Revolution Pi nicht laden. - + Process image successfully loaded and saved to: {0}. Prozessabbild erfolgreich geladen und gespeichert als: @@ -1556,6 +1561,16 @@ applicable law. You have to configure your Revolution Pi to accept this connections Sie müssen den Revolution Pi für diese Art der Verbindung konfigurieren + + + Connect + Verbinden + + + + Connect to Revoluton Pi + Mit Revolution Pi verbinden + diag_simulator diff --git a/src/revpicommander/revpicommander.py b/src/revpicommander/revpicommander.py index 77a28b5..5fbeed5 100644 --- a/src/revpicommander/revpicommander.py +++ b/src/revpicommander/revpicommander.py @@ -5,10 +5,10 @@ __author__ = "Sven Sager" __copyright__ = "Copyright (C) 2018 Sven Sager" __license__ = "GPLv3" -__version__ = "0.9.10rc1" +__version__ = "0.9.10rc2" import webbrowser -from os.path import basename, dirname, join +from os.path import dirname, join from PyQt5 import QtCore, QtGui, QtWidgets @@ -17,6 +17,7 @@ from . import proginit as pi from . import revpilogfile from .avahisearch import AvahiSearch from .debugcontrol import DebugControl +from .helper import RevPiSettings from .revpifiles import RevPiFiles from .revpiinfo import RevPiInfo from .revpioption import RevPiOption @@ -61,14 +62,14 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): helper.cm.connection_error_observed.connect(self.on_cm_connection_error_observed) helper.cm.status_changed.connect(self.on_cm_status_changed) - self.restoreGeometry(helper.settings.value("geo", b'')) + self.restoreGeometry(helper.settings.value("revpicommander/geo", b'')) self.setWindowFlag(QtCore.Qt.WindowMaximizeButtonHint, False) def closeEvent(self, a0: QtGui.QCloseEvent) -> None: pi.logger.debug("RevPiCommander.closeEvent") helper.cm.pyload_disconnect() - helper.settings.setValue("geo", self.saveGeometry()) + helper.settings.setValue("revpicommander/geo", self.saveGeometry()) def _set_gui_control_states(self): """Setup states of actions and buttons.""" @@ -96,9 +97,6 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # region # REGION: Connection management - def _pyload_connect(self, settings_index: int) -> None: - helper.cm.pyload_connect(settings_index, self) - @QtCore.pyqtSlot(str) def on_cm_connection_error_observed(self, message: str): """ @@ -148,8 +146,8 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): helper.cm.simulating_procimg, )) else: - self.txt_host.setText(helper.cm.name) - self.txt_connection.setText(helper.cm.address) + self.txt_host.setText(helper.cm.settings.name) + self.txt_connection.setText(helper.cm.settings.address) self.win_files = RevPiFiles(self) @QtCore.pyqtSlot(str, str) @@ -169,34 +167,27 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): self.men_connections.clear() self.dict_men_connections_subfolder.clear() - for i in range(helper.settings.beginReadArray("connections")): - helper.settings.setArrayIndex(i) - - if helper.settings.value("folder"): - if helper.settings.value("folder") not in self.dict_men_connections_subfolder: + for settings in helper.all_revpi_settings(): # type: RevPiSettings + if settings.folder: + if settings.folder not in self.dict_men_connections_subfolder: men_sub = QtWidgets.QMenu(self.men_connections) - men_sub.setTitle(helper.settings.value("folder")) - self.dict_men_connections_subfolder[helper.settings.value("folder")] = men_sub + men_sub.setTitle(settings.folder) + self.dict_men_connections_subfolder[settings.folder] = men_sub self.men_connections.addMenu(men_sub) - parent_menu = self.dict_men_connections_subfolder[helper.settings.value("folder")] + parent_menu = self.dict_men_connections_subfolder[settings.folder] else: parent_menu = self.men_connections - display_name = helper.settings.value("name") - if helper.settings.value("ssh_use_tunnel", False, bool): + display_name = settings.name + if settings.ssh_use_tunnel: display_name += " (SSH)" act = QtWidgets.QAction(parent_menu) act.setText(display_name) - act.setData(i) - act.setToolTip("{0}:{1}".format( - helper.settings.value("address"), - helper.settings.value("port"), - )) + act.setData(settings) + act.setToolTip("{0}:{1}".format(settings.address, settings.port)) parent_menu.addAction(act) - helper.settings.endArray() - @QtCore.pyqtSlot() def on_act_connections_triggered(self): """Edit saved connections to Revolution Pi devices.""" @@ -207,8 +198,11 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): def on_act_search_triggered(self): """Search for Revolution Pi with zero conf.""" if self.diag_search.exec() == QtWidgets.QDialog.Accepted: - if self.diag_search.connect_index >= 0: - self._pyload_connect(self.diag_search.connect_index) + if self.diag_search.connect_settings: + if self.diag_search.just_save: + self.diag_connections.exec_with_presets(self.diag_search.connect_settings) + else: + helper.cm.pyload_connect(self.diag_search.connect_settings, self) self._load_men_connections() @@ -367,7 +361,7 @@ class RevPiCommander(QtWidgets.QMainWindow, Ui_win_revpicommander): @QtCore.pyqtSlot(QtWidgets.QAction) def on_men_connections_triggered(self, action: QtWidgets.QAction): """A connection is selected in the men_connections menu.""" - self._pyload_connect(action.data()) + helper.cm.pyload_connect(action.data(), self) @QtCore.pyqtSlot() def on_act_webpage_triggered(self): @@ -506,4 +500,5 @@ def main() -> int: if __name__ == "__main__": import sys + sys.exit(main()) diff --git a/src/revpicommander/revpifiles.py b/src/revpicommander/revpifiles.py index 7fa0ce2..d6a6f8e 100644 --- a/src/revpicommander/revpifiles.py +++ b/src/revpicommander/revpifiles.py @@ -43,14 +43,13 @@ class UploadFiles(BackgroundWorker): progress_counter += 1 # Remove base dir of file to set relative for PyLoad - send_name = file_name.replace(helper.cm.develop_watch_path, "")[1:] + send_name = file_name.replace(helper.cm.settings.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: @@ -83,7 +82,7 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): self.dc_settings = {} self.tree_files_counter = 0 self.tree_files_counter_max = 10000 - self.lbl_path_local.setText(helper.cm.develop_watch_path or self.tr("Please select...")) + self.lbl_path_local.setText(helper.cm.settings.watch_path or self.tr("Please select...")) self.lbl_path_local.setToolTip(self.lbl_path_local.text()) self.btn_all.setEnabled(False) @@ -91,7 +90,7 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): self.btn_to_right.setEnabled(False) self.btn_delete_revpi.setEnabled(False) - if helper.cm.develop_watch_path: + if helper.cm.settings.watch_path: self._load_files_local(True) if helper.cm.connected: self._load_files_revpi(True) @@ -176,7 +175,7 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): if "plcdownload_file" not in helper.cm.xml_funcs: self.btn_to_left.setEnabled(False) self.btn_to_left.setToolTip(self.tr("The RevPiPyLoad version on the Revolution Pi is to old.")) - elif not helper.cm.develop_watch_path: + elif not helper.cm.settings.watch_path: self.btn_to_left.setEnabled(False) self.btn_to_left.setToolTip(self.tr("Choose a local directory first.")) else: @@ -231,7 +230,7 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): @QtCore.pyqtSlot() def on_tree_files_local_itemSelectionChanged(self): self.__item_selection_changed(self.tree_files_local) - helper.cm.develop_watch_files = self.file_list_local() + helper.cm.settings.watch_files = self.file_list_local() @QtCore.pyqtSlot() def on_tree_files_revpi_itemSelectionChanged(self): @@ -288,7 +287,7 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): else: self.tree_files_local.addTopLevelItem(item) - item.setSelected(de.path in helper.cm.develop_watch_files) + item.setSelected(de.path in helper.cm.settings.watch_files) self._parent_selection_state(item) def _load_files_local(self, silent=False): @@ -302,7 +301,7 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): self.tree_files_counter = 0 self.tree_files_local.blockSignals(True) self.tree_files_local.clear() - self.__insert_files_local(helper.cm.develop_watch_path) + self.__insert_files_local(helper.cm.settings.watch_path) self.tree_files_local.sortItems(0, QtCore.Qt.AscendingOrder) self.tree_files_local.blockSignals(False) @@ -446,7 +445,7 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): diag_folder = QtWidgets.QFileDialog( self, self.tr("Select folder..."), - helper.cm.develop_watch_path, + helper.cm.settings.watch_path, ) diag_folder.setFileMode(QtWidgets.QFileDialog.DirectoryOnly) if diag_folder.exec() != QtWidgets.QFileDialog.Accepted: @@ -460,14 +459,14 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): "Can not access the folder '{0}' to read files." ) ) - helper.cm.develop_watch_files = [] - helper.cm.develop_watch_path = "" + helper.cm.settings.watch_files = [] + helper.cm.settings.watch_path = "" return self.lbl_path_local.setText(selected_dir) self.lbl_path_local.setToolTip(self.lbl_path_local.text()) - helper.cm.develop_watch_path = selected_dir - helper.cm.develop_watch_files = [] + helper.cm.settings.watch_path = selected_dir + helper.cm.settings.watch_files = [] self._load_files_local(False) @@ -511,7 +510,7 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): ).format(file_name) ) else: - file_name = os.path.join(helper.cm.develop_watch_path, file_name) + file_name = os.path.join(helper.cm.settings.watch_path, file_name) if override is None and os.path.exists(file_name): rc_diag = QtWidgets.QMessageBox.question( self, self.tr("Override files..."), self.tr( @@ -530,7 +529,7 @@ class RevPiFiles(QtWidgets.QMainWindow, Ui_win_files): os.makedirs(os.path.dirname(file_name), exist_ok=True) file_data = gzip.decompress(rc) - with open(os.path.join(helper.cm.develop_watch_path, file_name), "wb") as fh: + with open(os.path.join(helper.cm.settings.watch_path, file_name), "wb") as fh: fh.write(file_data) self._load_files_local() diff --git a/src/revpicommander/revpiplclist.py b/src/revpicommander/revpiplclist.py index 2df72bc..49210ff 100644 --- a/src/revpicommander/revpiplclist.py +++ b/src/revpicommander/revpiplclist.py @@ -8,8 +8,9 @@ from enum import IntEnum from PyQt5 import QtCore, QtGui, QtWidgets +from . import helper from . import proginit as pi -from .helper import WidgetData, settings +from .helper import RevPiSettings, WidgetData from .ui.revpiplclist_ui import Ui_diag_connections @@ -41,38 +42,22 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): self.tre_connections.clear() self.cbb_folder.clear() self.cbb_folder.addItem("") - for i in range(settings.beginReadArray("connections")): - settings.setArrayIndex(i) + + # Get length of array and close it, the RevPiSettings-class need it + count_settings = helper.settings.beginReadArray("connections") + helper.settings.endArray() + + for i in range(count_settings): + settings = RevPiSettings(i) con_item = QtWidgets.QTreeWidgetItem(NodeType.CON) con_item.setIcon(0, QtGui.QIcon(":/main/ico/cpu.ico")) - con_item.setText(0, settings.value("name", "Revolution Pi", str)) - con_item.setText(1, settings.value("address", "127.0.0.1", str)) - con_item.setData(0, WidgetData.port, settings.value("port", self.__default_port, int)) - con_item.setData(0, WidgetData.timeout, settings.value("timeout", 5, int)) + con_item.setText(0, settings.name) + con_item.setText(1, settings.address) - con_item.setData(0, WidgetData.ssh_use_tunnel, settings.value("ssh_use_tunnel", False, bool)) - con_item.setData(0, WidgetData.ssh_port, settings.value("ssh_port", 22, int)) - con_item.setData(0, WidgetData.ssh_user, settings.value("ssh_user", "pi", str)) + con_item.setData(0, WidgetData.revpi_settings, settings) - con_item.setData(0, WidgetData.last_dir_upload, settings.value("last_dir_upload")) - con_item.setData(0, WidgetData.last_file_upload, settings.value("last_file_upload")) - con_item.setData(0, WidgetData.last_dir_pictory, settings.value("last_dir_pictory")) - con_item.setData(0, WidgetData.last_dir_picontrol, settings.value("last_dir_picontrol")) - con_item.setData(0, WidgetData.last_dir_selected, settings.value("last_dir_selected")) - con_item.setData(0, WidgetData.last_pictory_file, settings.value("last_pictory_file")) - con_item.setData(0, WidgetData.last_tar_file, settings.value("last_tar_file")) - con_item.setData(0, WidgetData.last_zip_file, settings.value("last_zip_file")) - con_item.setData(0, WidgetData.watch_files, settings.value("watch_files")) - con_item.setData(0, WidgetData.watch_path, settings.value("watch_path")) - try: - # Bytes with QSettings are a little difficult sometimes - con_item.setData(0, WidgetData.debug_geos, settings.value("debug_geos")) - except Exception: - # Just drop the geos of IO windows - pass - - folder = settings.value("folder", "", str) + folder = settings.folder if folder: sub_folder = self._get_folder_item(folder) if sub_folder is None: @@ -86,10 +71,8 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): else: self.tre_connections.addTopLevelItem(con_item) - settings.endArray() - self.tre_connections.expandAll() - self.changes = True + self.changes = False if self.tre_connections.topLevelItemCount() == 0: self._edit_state() @@ -97,57 +80,19 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): def accept(self) -> None: pi.logger.debug("RevPiPlcList.accept") - def set_settings(node: QtWidgets.QTreeWidgetItem): - parent = node.parent() - settings.setValue("address", node.text(1)) - settings.setValue("folder", parent.text(0) if parent else "") - settings.setValue("name", node.text(0)) - settings.setValue("port", node.data(0, WidgetData.port)) - settings.setValue("timeout", node.data(0, WidgetData.timeout)) - settings.setValue("ssh_use_tunnel", node.data(0, WidgetData.ssh_use_tunnel)) - settings.setValue("ssh_port", node.data(0, WidgetData.ssh_port)) - settings.setValue("ssh_user", node.data(0, WidgetData.ssh_user)) + helper.settings.remove("connections") - if node.data(0, WidgetData.last_dir_upload): - settings.setValue("last_dir_upload", node.data(0, WidgetData.last_dir_upload)) - if node.data(0, WidgetData.last_file_upload): - settings.setValue("last_file_upload", node.data(0, WidgetData.last_file_upload)) - if node.data(0, WidgetData.last_dir_pictory): - settings.setValue("last_dir_pictory", node.data(0, WidgetData.last_dir_pictory)) - if node.data(0, WidgetData.last_dir_picontrol): - settings.setValue("last_dir_picontrol", node.data(0, WidgetData.last_dir_picontrol)) - if node.data(0, WidgetData.last_dir_selected): - settings.setValue("last_dir_selected", node.data(0, WidgetData.last_dir_selected)) - if node.data(0, WidgetData.last_pictory_file): - settings.setValue("last_pictory_file", node.data(0, WidgetData.last_pictory_file)) - if node.data(0, WidgetData.last_tar_file): - settings.setValue("last_tar_file", node.data(0, WidgetData.last_tar_file)) - if node.data(0, WidgetData.last_zip_file): - settings.setValue("last_zip_file", node.data(0, WidgetData.last_zip_file)) - if node.data(0, WidgetData.watch_files): - settings.setValue("watch_files", node.data(0, WidgetData.watch_files)) - if node.data(0, WidgetData.watch_path): - settings.setValue("watch_path", node.data(0, WidgetData.watch_path)) - if node.data(0, WidgetData.debug_geos): - settings.setValue("debug_geos", node.data(0, WidgetData.debug_geos)) - - settings.remove("connections") - settings.beginWriteArray("connections") - - counter_index = 0 for i in range(self.tre_connections.topLevelItemCount()): root_item = self.tre_connections.topLevelItem(i) if root_item.type() == NodeType.DIR: for k in range(root_item.childCount()): - settings.setArrayIndex(counter_index) - set_settings(root_item.child(k)) - counter_index += 1 + revpi_settings = root_item.child(k).data(0, WidgetData.revpi_settings) # type: RevPiSettings + revpi_settings.folder = root_item.text(0) + revpi_settings.save_settings() elif root_item.type() == NodeType.CON: - settings.setArrayIndex(counter_index) - set_settings(root_item) - counter_index += 1 - - settings.endArray() + revpi_settings = root_item.data(0, WidgetData.revpi_settings) # type: RevPiSettings + revpi_settings.folder = "" + revpi_settings.save_settings() self.changes = False super(RevPiPlcList, self).accept() @@ -170,6 +115,17 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): self._load_settings() return super(RevPiPlcList, self).exec() + def exec_with_presets(self, presets: RevPiSettings) -> int: + """ + Start dialog with new created settings object and presets. + + :param presets: Use these settings as preset + :return: Dialog status + """ + self._load_settings() + self.on_btn_add_pressed(presets) + return super(RevPiPlcList, self).exec() + @QtCore.pyqtSlot(QtWidgets.QAbstractButton) def on_btn_box_clicked(self, button: QtWidgets.QAbstractButton): if self.btn_box.buttonRole(button) == QtWidgets.QDialogButtonBox.DestructiveRole: @@ -179,6 +135,7 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): # region # REGION: Connection management def _edit_state(self): + """Set enabled status of all controls, depending on selected item.""" item = self.tre_connections.currentItem() if item is None: up_ok = False @@ -232,6 +189,7 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): if 0 <= new_index < dir_item.childCount(): item = dir_item.takeChild(index) dir_item.insertChild(new_index, item) + self.tre_connections.expandItem(dir_item) else: index = self.tre_connections.indexOfTopLevelItem(item) new_index = index + count @@ -249,22 +207,25 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): self._edit_state() if current and current.type() == NodeType.CON: self.__current_item = current - self.txt_name.setText(current.text(0)) - self.txt_address.setText(current.text(1)) - self.sbx_port.setValue(current.data(0, WidgetData.port)) - self.sbx_timeout.setValue(current.data(0, WidgetData.timeout)) + + settings = current.data(0, WidgetData.revpi_settings) # type: RevPiSettings + self.txt_name.setText(settings.name) + self.txt_address.setText(settings.address) + self.sbx_port.setValue(settings.port) + self.sbx_timeout.setValue(settings.timeout) if current.parent() is None: self.cbb_folder.setCurrentIndex(0) else: self.cbb_folder.setCurrentText(current.parent().text(0)) - self.cbx_ssh_use_tunnel.setChecked(current.data(0, WidgetData.ssh_use_tunnel)) - self.sbx_ssh_port.setValue(current.data(0, WidgetData.ssh_port)) - self.txt_ssh_user.setText(current.data(0, WidgetData.ssh_user)) + self.cbx_ssh_use_tunnel.setChecked(settings.ssh_use_tunnel) + self.sbx_ssh_port.setValue(settings.ssh_port) + self.txt_ssh_user.setText(settings.ssh_user) elif current and current.type() == NodeType.DIR: self.__current_item = current self.cbb_folder.setCurrentText(current.text(0)) + else: self.__current_item = QtWidgets.QTreeWidgetItem() self.cbb_folder.setCurrentText(current.text(0) if current else "") @@ -292,23 +253,21 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): self._edit_state() @QtCore.pyqtSlot() - def on_btn_add_pressed(self): + def on_btn_add_pressed(self, settings_preset: RevPiSettings = None): """Create new element.""" - self.__current_item = QtWidgets.QTreeWidgetItem(NodeType.CON) - self.__current_item.setIcon(0, QtGui.QIcon(":/main/ico/cpu.ico")) - self.__current_item.setText(0, self.__default_name) - self.__current_item.setData(0, WidgetData.port, self.__default_port) - self.__current_item.setData(0, WidgetData.timeout, 5) - self.__current_item.setData(0, WidgetData.ssh_use_tunnel, False) - self.__current_item.setData(0, WidgetData.ssh_port, 22) - self.__current_item.setData(0, WidgetData.ssh_user, "pi") + settings = settings_preset or RevPiSettings() + new_item = QtWidgets.QTreeWidgetItem(NodeType.CON) + new_item.setIcon(0, QtGui.QIcon(":/main/ico/cpu.ico")) + new_item.setText(0, settings.name) + new_item.setData(0, WidgetData.revpi_settings, settings) sub_folder = self._get_folder_item(self.cbb_folder.currentText()) if sub_folder: - sub_folder.addChild(self.__current_item) + sub_folder.addChild(new_item) else: - self.tre_connections.addTopLevelItem(self.__current_item) + self.tre_connections.addTopLevelItem(new_item) - self.tre_connections.setCurrentItem(self.__current_item) + # This will load all settings and prepare widgets + self.tre_connections.setCurrentItem(new_item) self.txt_name.setFocus() self.txt_name.selectAll() @@ -317,42 +276,58 @@ class RevPiPlcList(QtWidgets.QDialog, Ui_diag_connections): if self.__current_item.type() != NodeType.CON: return self.__current_item.setText(0, text) + settings = self.__current_item.data(0, WidgetData.revpi_settings) # type: RevPiSettings + settings.name = text + self.changes = True @QtCore.pyqtSlot(str) def on_txt_address_textEdited(self, text): if self.__current_item.type() != NodeType.CON: return self.__current_item.setText(1, text) + settings = self.__current_item.data(0, WidgetData.revpi_settings) # type: RevPiSettings + settings.address = text + self.changes = True @QtCore.pyqtSlot(int) def on_sbx_port_valueChanged(self, value: int): if self.__current_item.type() != NodeType.CON: return - self.__current_item.setData(0, WidgetData.port, value) + settings = self.__current_item.data(0, WidgetData.revpi_settings) # type: RevPiSettings + settings.port = value + self.changes = True @QtCore.pyqtSlot(int) def on_sbx_timeout_valueChanged(self, value: int): if self.__current_item.type() != NodeType.CON: return - self.__current_item.setData(0, WidgetData.timeout, value) + settings = self.__current_item.data(0, WidgetData.revpi_settings) # type: RevPiSettings + settings.timeout = value + self.changes = True @QtCore.pyqtSlot(int) def on_cbx_ssh_use_tunnel_stateChanged(self, check_state: int): if self.__current_item.type() != NodeType.CON: return - self.__current_item.setData(0, WidgetData.ssh_use_tunnel, check_state == QtCore.Qt.CheckState.Checked) + settings = self.__current_item.data(0, WidgetData.revpi_settings) # type: RevPiSettings + settings.ssh_use_tunnel = check_state == QtCore.Qt.CheckState.Checked + self.changes = True @QtCore.pyqtSlot(int) def on_sbx_ssh_port_valueChanged(self, value: int): if self.__current_item.type() != NodeType.CON: return - self.__current_item.setData(0, WidgetData.ssh_port, value) + settings = self.__current_item.data(0, WidgetData.revpi_settings) # type: RevPiSettings + settings.ssh_port = value + self.changes = True @QtCore.pyqtSlot(str) def on_txt_ssh_user_textEdited(self, text): if self.__current_item.type() != NodeType.CON: return - self.__current_item.setData(0, WidgetData.ssh_user, text) + settings = self.__current_item.data(0, WidgetData.revpi_settings) # type: RevPiSettings + settings.ssh_user = text + self.changes = True @QtCore.pyqtSlot(str) def on_cbb_folder_editTextChanged(self, text: str): diff --git a/src/revpicommander/revpiprogram.py b/src/revpicommander/revpiprogram.py index 364642e..e9ee879 100644 --- a/src/revpicommander/revpiprogram.py +++ b/src/revpicommander/revpiprogram.py @@ -58,8 +58,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): :return: True, if unsaved changes was found """ - return \ - self.cbb_plcprogram.currentText() != self.dc.get("plcprogram", "") or \ + return self.cbb_plcprogram.currentText() != self.dc.get("plcprogram", "") or \ self.txt_plcarguments.text() != self.dc.get("plcarguments", "") or \ self.rbn_pythonversion_2.isChecked() != (self.dc.get("pythonversion", 3) == 2) or \ self.rbn_pythonversion_3.isChecked() != (self.dc.get("pythonversion", 3) == 3) or \ @@ -213,7 +212,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): ) ) elif ec == 0: - helper.cm.program_last_pictory_file = filename + helper.cm.settings.last_pictory_file = filename if ask == QtWidgets.QMessageBox.Yes: QtWidgets.QMessageBox.information( self, self.tr("Success"), self.tr( @@ -324,16 +323,11 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): if not helper.cm.connected: return - selected_dir = "" - if self.cbb_format.currentIndex() == 0: # Save files as zip archive diag_save = QtWidgets.QFileDialog( self, self.tr("Save ZIP archive..."), - os.path.join( - helper.cm.program_last_zip_file, - "{0}.zip".format(helper.cm.name) - ), + helper.cm.settings.last_zip_file or "{0}.zip".format(helper.cm.settings.name), self.tr("ZIP archive (*.zip);;All files (*.*)") ) diag_save.setAcceptMode(QtWidgets.QFileDialog.AcceptSave) @@ -345,16 +339,13 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): filename = diag_save.selectedFiles()[0] fh = open(filename, "wb") - helper.cm.program_last_zip_file = filename + helper.cm.settings.last_zip_file = filename elif self.cbb_format.currentIndex() == 1: # Save files as TarGz archive diag_save = QtWidgets.QFileDialog( self, self.tr("Save TGZ archive..."), - os.path.join( - helper.cm.program_last_tar_file, - "{0}.tgz".format(helper.cm.name) - ), + helper.cm.settings.last_tar_file or "{0}.tgz".format(helper.cm.settings.name), self.tr("TGZ archive (*.tgz);;All files (*.*)") ) diag_save.setAcceptMode(QtWidgets.QFileDialog.AcceptSave) @@ -366,7 +357,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): filename = diag_save.selectedFiles()[0] fh = open(filename, "wb") - helper.cm.program_last_tar_file = filename + helper.cm.settings.last_tar_file = filename else: # Other indexes are not allowed for download @@ -426,7 +417,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): # Upload zip archive content diag_open = QtWidgets.QFileDialog( self, self.tr("Upload content of ZIP archive..."), - helper.cm.program_last_file_upload, + helper.cm.settings.last_file_upload, self.tr("ZIP archive (*.zip);;All files (*.*)") ) diag_open.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen) @@ -438,7 +429,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): return filename = diag_open.selectedFiles()[0] - helper.cm.program_last_file_upload = filename + helper.cm.settings.last_file_upload = filename if zipfile.is_zipfile(filename): dirtmp = mkdtemp() fhz = zipfile.ZipFile(filename) @@ -460,7 +451,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): # Upload TarGz content diag_open = QtWidgets.QFileDialog( self, self.tr("Upload content of TAR archive..."), - helper.cm.program_last_file_upload, + helper.cm.settings.last_file_upload, self.tr("TAR archive (*.tgz);;All files (*.*)") ) diag_open.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen) @@ -472,7 +463,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): return filename = diag_open.selectedFiles()[0] - helper.cm.program_last_file_upload = filename + helper.cm.settings.last_file_upload = filename if tarfile.is_tarfile(filename): dirtmp = mkdtemp() fht = tarfile.open(filename) @@ -606,8 +597,8 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): diag_save = QtWidgets.QFileDialog( self, self.tr("Save piCtory file..."), os.path.join( - helper.cm.program_last_dir_pictory, - "{0}.rsc".format(helper.cm.name) + helper.cm.settings.last_dir_pictory, + "{0}.rsc".format(helper.cm.settings.name) ), self.tr("piCtory file (*.rsc);;All files (*.*)") ) @@ -619,7 +610,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): return filename = diag_save.selectedFiles()[0] - helper.cm.program_last_dir_pictory = os.path.dirname(filename) + helper.cm.settings.last_dir_pictory = os.path.dirname(filename) bin_buffer = helper.cm.call_remote_function("get_pictoryrsc") # type: Binary if bin_buffer is None: QtWidgets.QMessageBox.critical( @@ -645,7 +636,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): diag_open = QtWidgets.QFileDialog( self, self.tr("Upload piCtory file..."), - helper.cm.program_last_pictory_file, + helper.cm.settings.last_pictory_file or "{0}.rsc".format(helper.cm.settings.name), self.tr("piCtory file (*.rsc);;All files (*.*)") ) diag_open.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen) @@ -668,8 +659,8 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): self, self.tr("Save piControl file..."), os.path.join( - helper.cm.program_last_dir_picontrol, - "{0}.img".format(helper.cm.name) + helper.cm.settings.last_dir_picontrol, + "{0}.img".format(helper.cm.settings.name) ), self.tr("Process image file (*.img);;All files (*.*)") ) @@ -681,7 +672,7 @@ class RevPiProgram(QtWidgets.QDialog, Ui_diag_program): return filename = diag_save.selectedFiles()[0] - helper.cm.program_last_dir_picontrol = os.path.dirname(filename) + helper.cm.settings.last_dir_picontrol = os.path.dirname(filename) bin_buffer = helper.cm.call_remote_function("get_procimg") # type: Binary if bin_buffer is None: diff --git a/src/revpicommander/sshauth.py b/src/revpicommander/sshauth.py index c1b3b1c..fd0f531 100644 --- a/src/revpicommander/sshauth.py +++ b/src/revpicommander/sshauth.py @@ -15,6 +15,7 @@ class SSHAuthType(Enum): PASS = "pass" KEYS = "keys" + class SSHAuth(QtWidgets.QDialog, Ui_diag_sshauth): """Version information window.""" diff --git a/src/revpicommander/ui/avahisearch_ui.py b/src/revpicommander/ui/avahisearch_ui.py index 9ebf312..9588c4a 100644 --- a/src/revpicommander/ui/avahisearch_ui.py +++ b/src/revpicommander/ui/avahisearch_ui.py @@ -75,6 +75,8 @@ class Ui_diag_search(object): self.act_connect_ssh.setObjectName("act_connect_ssh") self.act_connect_xmlrpc = QtWidgets.QAction(diag_search) self.act_connect_xmlrpc.setObjectName("act_connect_xmlrpc") + self.act_connect = QtWidgets.QAction(diag_search) + self.act_connect.setObjectName("act_connect") self.retranslateUi(diag_search) self.btn_box.rejected.connect(diag_search.reject) # type: ignore @@ -99,6 +101,8 @@ class Ui_diag_search(object): self.act_connect_ssh.setToolTip(_translate("diag_search", "Establish a connection via encrypted SSH tunnel")) self.act_connect_xmlrpc.setText(_translate("diag_search", "Connect via XML-RPC")) self.act_connect_xmlrpc.setToolTip(_translate("diag_search", "You have to configure your Revolution Pi to accept this connections")) + self.act_connect.setText(_translate("diag_search", "Connect")) + self.act_connect.setToolTip(_translate("diag_search", "Connect to Revoluton Pi")) from . import ressources_rc diff --git a/ui_dev/avahisearch.ui b/ui_dev/avahisearch.ui index ba8dbb8..35ce2e9 100644 --- a/ui_dev/avahisearch.ui +++ b/ui_dev/avahisearch.ui @@ -151,6 +151,14 @@ You have to configure your Revolution Pi to accept this connections + + + Connect + + + Connect to Revoluton Pi + +