def package_import(self, pack: str): """ Import opsi package file into development folder and open it, if successful :param pack: package file path """ self.logger.debug("Importing package") if self._active_project: if not self.project_close(): return self.reset_state() # extract product id from package path file = Helper.get_file_from_path(pack) product = file[:file.rfind("_")] # remove everything behind product name (version, file extension, etc.) project = Helper.concat_path_native(ConfigHandler.cfg.dev_dir, product) # import try: self.startup.hide_me() self.do_import(pack) except: self.logger.error("Package import unsuccessful!") else: # open try: self.project_load(project) except: self.logger.err("Imported package could not be opened!")
def check_online_status(self): # check if server is generally available # use SSH port for connection test ret = Helper.test_port(confighandler.ConfigHandler.cfg.opsi_server, confighandler.ConfigHandler.cfg.sshport, 0.5) if type(ret) != tuple: # check network access and mount network drive if on linux if sys.platform == 'win32': self.logger.info("System platform: "+ sys.platform) if confighandler.ConfigHandler.cfg.usenetdrive == "False": drives = Helper.get_available_drive_letters() if not drives: self.logger.error("No free drive letter found") else: self.logger.info("Free drive letter found: " + repr(drives)) self.logger.info("Using drive letter: " + drives[::-1][0]) path = "\\\\" + confighandler.ConfigHandler.cfg.opsi_server + "\\" + "opsi_workbench" self.logger.info("Trying to mount path: " + path) ret = MapDrive.mapDrive(drives[::-1][0] + ":", path, confighandler.ConfigHandler.cfg.opsi_user, confighandler.ConfigHandler.cfg.opsi_pass) if ret[0] != 0: self.logger.error("Error mounting path: " + str(ret)) else: self.logger.info("Network drive successfully mounted") oPB.NETDRV = drives[::-1][0] + ":" else: self.logger.info("System platform: "+ sys.platform) self.logger.warning("This is not a windows based system. No network drive will be associated") self.logger.warning("Please take care, if the specified development base path is correct.") else: self.logger.warning("opsi server not available. Offline mode activated.") self.logger.warning("Return value from connection test: " + str(ret)) oPB.NETMODE = "offline"
def package_import(self, pack: str): """ Import opsi package file into development folder and open it, if successful :param pack: package file path """ self.logger.debug("Importing package") if self._active_project: if not self.project_close(): return self.reset_state() # extract product id from package path file = Helper.get_file_from_path(pack) product = file[:file.rfind( "_" )] # remove everything behind product name (version, file extension, etc.) project = Helper.concat_path_native(ConfigHandler.cfg.dev_dir, product) # import try: self.startup.hide_me() self.do_import(pack) except: self.logger.error("Package import unsuccessful!") else: # open try: self.project_load(project) except: self.logger.err("Imported package could not be opened!")
def update_backend_data(self): """Write data from model into backend""" self.logger.debug("Updateing backend data from model") self.controlData.id = self.model_fields.item(0, 1).text() self.controlData.name = self.model_fields.item(0, 2).text() self.controlData.description = self.model_fields.item(0, 3).text() self.controlData.advice = self.model_fields.item(0, 4).text() self.controlData.type = self.model_fields.item(0, 5).text() self.controlData.productversion = self.model_fields.item(0, 6).text() self.controlData.packageversion = self.model_fields.item(0, 7).text() self.controlData.priority = int(self.model_fields.item(0, 8).text()) self.controlData.licenseRequired = self.model_fields.item(0, 9).text() self.controlData.setupScript = self.model_fields.item(0, 10).text() self.controlData.uninstallScript = self.model_fields.item(0, 11).text() self.controlData.updateScript = self.model_fields.item(0, 12).text() self.controlData.alwaysScript = self.model_fields.item(0, 13).text() self.controlData.onceScript = self.model_fields.item(0, 14).text() self.controlData.customScript = self.model_fields.item(0, 15).text() self.controlData.userLoginScript = self.model_fields.item(0, 16).text() rows = self.model_dependencies.rowCount() self.controlData.dependencies = [] if self.model_dependencies.item( 0, 0) is not None: # empty dependency list for i in range(0, rows, 1): #self.logger.debug("Reading dependency: " + str(i)) dep = ProductDependency() dep.dependencyForAction = self.model_dependencies.item( i, 0).text() dep.requiredProductId = self.model_dependencies.item(i, 1).text() dep.requiredAction = self.model_dependencies.item(i, 2).text() dep.requiredInstallationStatus = self.model_dependencies.item( i, 3).text() dep.requirementType = self.model_dependencies.item(i, 4).text() self.controlData.dependencies_append(dep) rows = self.model_properties.rowCount() self.controlData.properties = [] if self.model_properties.item(0, 0) is not None: # empty property list for i in range(0, rows, 1): #self.logger.debug("Reading property: " + str(i)) prop = ProductProperty() prop.name = self.model_properties.item(i, 0).text() prop.type = self.model_properties.item(i, 1).text() prop.multivalue = self.model_properties.item(i, 2).text() prop.editable = self.model_properties.item(i, 3).text() prop.description = self.model_properties.item(i, 4).text() if prop.type == "bool": prop.values = "" prop.default = self.model_properties.item(i, 6).text() else: prop.values = Helper.paramlist2list( self.model_properties.item(i, 5).text()) prop.default = Helper.paramlist2list( self.model_properties.item(i, 6).text()) self.controlData.properties_append(prop)
def update_backend_data(self): """ Write data from model into backend :return: """ self.controlData.id = self.model_fields.item(0, 1).text() self.controlData.name = self.model_fields.item(0, 2).text() self.controlData.description = self.model_fields.item(0, 3).text() self.controlData.advice = self.model_fields.item(0, 4).text() self.controlData.type = self.model_fields.item(0, 5).text() self.controlData.productversion = self.model_fields.item(0, 6).text() self.controlData.packageversion = self.model_fields.item(0, 7).text() self.controlData.priority = int(self.model_fields.item(0, 8).text()) self.controlData.licenseRequired = self.model_fields.item(0, 9).text() self.controlData.setupScript = self.model_fields.item(0, 10).text() self.controlData.uninstallScript = self.model_fields.item(0, 11).text() self.controlData.updateScript = self.model_fields.item(0, 12).text() self.controlData.alwaysScript = self.model_fields.item(0, 13).text() self.controlData.onceScript = self.model_fields.item(0, 14).text() self.controlData.customScript = self.model_fields.item(0, 15).text() self.controlData.userLoginScript = self.model_fields.item(0, 16).text() rows = self.model_dependencies.rowCount() self.controlData.dependencies = [] if type(self.model_dependencies.item(0, 0)) is not None: # empty dependency list for i in range(0, rows, 1): dep = ProductDependency() dep.dependencyForAction = self.model_dependencies.item(i, 0).text() dep.requiredProductId = self.model_dependencies.item(i, 1).text() dep.requiredAction = self.model_dependencies.item(i, 2).text() dep.requiredInstallationStatus = self.model_dependencies.item(i, 3).text() dep.requirementType = self.model_dependencies.item(i, 4).text() self.controlData.dependencies_append(dep) rows = self.model_properties.rowCount() self.controlData.properties = [] if type(self.model_properties.item(0, 0)) is not None: # empty property list for i in range(0, rows, 1): prop = ProductProperty() prop.name = self.model_properties.item(i, 0).text() prop.type = self.model_properties.item(i, 1).text() prop.multivalue = self.model_properties.item(i, 2).text() prop.editable = self.model_properties.item(i, 3).text() prop.description = self.model_properties.item(i, 4).text() if prop.type == "bool": prop.values = "" prop.default = self.model_properties.item(i, 6).text() else: prop.values = Helper.paramlist2list(self.model_properties.item(i, 5).text()) prop.default = Helper.paramlist2list(self.model_properties.item(i, 6).text()) self.controlData.properties_append(prop)
def validate(self, p_str, p_int): if p_str == "": return ScriptFileValidator.Intermediate, p_str, p_int if os.path.exists(Helper.concat_path_and_file(self._parent.lblPacketFolder.text().replace('\\','/') + "/CLIENT_DATA/", p_str)): return ScriptFileValidator.Acceptable, p_str, p_int else: return ScriptFileValidator.Invalid, p_str, p_int
def msgbox(self, msgtext = "", typ = oPB.MsgEnum.MS_STAT, parent = None): """ Message box function (virtual) **HAS TO BE RE-IMPLEMENTED** Valid values for ``typ``: * oPB.MsgEnum.MS_ERR -> Error message (status bar/ popup) * oPB.MsgEnum.MS_WARN -> Warning (status bar/ popup) * oPB.MsgEnum.MS_INFO -> Information (status bar/ popup) * oPB.MsgEnum.MS_STAT -> Information (only status bar) * oPB.MsgEnum.MS_ALWAYS -> Display this message ALWAYS, regardless of which message ``typ`` is deactivated via settings * oPB.MsgEnum.MS_PARSE -> just parse message text and return it * oPB.MsgEnum.MS_QUEST_YESNO -> return True / False * oPB.MsgEnum.MS_QUEST_CTC -> build question: cancel (return 0)/ rebuild return(1)/ add return (2) * oPB.MsgEnum.MS_QUEST_OKCANCEL -> return True / False * oPB.MsgEnum.MS_QUEST_PHRASE -> return text from input box :param msgtext: Message text :param typ: type of message window, see oPB.core enums :return variant: see descriptions for ``typ`` """ if parent is None: parent = self.ui # first parse text msgtext = Helper.parse_text(msgtext) pass
def onlinecheck(self): self.logger.info("Online check") if self._active_side == "left": depot = self._ui_box_left.currentText().split()[0] else: depot = self._ui_box_right.currentText().split()[0] msg = "\n\n" + translate("depotmanagerController", "Selected depot:") + "\n" + depot self.logger.debug("Selected depot: " + depot) ret = Helper.test_port(depot, ConfigHandler.cfg.sshport, 0.5) print(ret) if ret is True: self._parent.msgbox(translate("depotmanagerController", "The selected depot is ONLINE.") + msg, oPB.MsgEnum.MS_ALWAYS, parent=self.ui) else: msg += "\n\n" + str(ret) self._parent.msgbox(translate("depotmanagerController", "The selected depot is OFFLINE.") + msg, oPB.MsgEnum.MS_ALWAYS, parent=self.ui)
def create_project_paths(self, base): for elem in oPB.BASE_FOLDERS: try: path = Path(Helper.concat_path_native(base, elem)) if not path.exists(): path.mkdir(parents=True) except OSError: raise
def set_selected_script(self, script, script_type): """ Receives select signal from window button, writes filename to model and emits itemChanged signal :param script: full pathname to script :param script_type: type of script """ self.logger.debug("Set selected script: (" + script + ") (" + script_type + ")") if script_type == "setup": self.model_fields.item(0, 10).setText( Helper.get_file_from_path(script)) if script_type == "uninstall": self.model_fields.item(0, 11).setText( Helper.get_file_from_path(script)) if script_type == "update": self.model_fields.item(0, 12).setText( Helper.get_file_from_path(script)) if script_type == "always": self.model_fields.item(0, 13).setText( Helper.get_file_from_path(script)) if script_type == "once": self.model_fields.item(0, 14).setText( Helper.get_file_from_path(script)) if script_type == "custom": self.model_fields.item(0, 15).setText( Helper.get_file_from_path(script)) if script_type == "userlogin": self.model_fields.item(0, 16).setText( Helper.get_file_from_path(script))
def get_msiproductcode(self): """Show MSI product code of individual MSI file""" self.logger.debug("Show MSI product code " + platform.system()) if platform.system() in ["Windows"]: ext = "MSI Package (*.msi)" msi = QFileDialog.getOpenFileName(self, translate("MainWindow", "Choose package file"), "", ext) if not msi == ("", ""): self.logger.debug("Selected package: " + msi[0]) prodcode = Helper.get_msi_property(msi[0]) self._parent.msgbox(translate("MainWindow", "Selected MSI: " + Helper.get_file_from_path(msi[0]) + "\n\n" + "Product Code: " + " " + prodcode), oPB.MsgEnum.MS_ALWAYS, self) else: self.logger.debug("Dialog aborted.") else: self._parent.msgbox(translate("MainWindow", "Function not available at the moment for system:" + " " + platform.system()), oPB.MsgEnum.MS_ALWAYS, self)
def select_dev_dir(self): self.logger.debug("Select development directory") directory = QFileDialog.getExistingDirectory(self, translate("SettingsDialog", "Select development folder"), ConfigHandler.cfg.dev_dir, QFileDialog.ShowDirsOnly) if not directory == "": self.logger.info("Chosen directory: " + directory) self.inpDevFolder.setText(Helper.concat_path_and_file(directory, "")) self.dataChanged.emit() else: self.logger.debug("Dialog aborted.")
def __init__(self, parent=None, applangprefix=""): """ Constructor of Translator class :param parent: parent, mostly qApp :param applangprefix: application language file prefix """ if Translator.cfg is None: Translator.cfg = self Translator.cfg._parent = parent super().__init__(Translator.cfg._parent) Translator.cfg._qt_local_path = None Translator.cfg._app_locale_path = None Translator.cfg._syslocale = None Translator.cfg._current_lang = "en" Translator.cfg.applangprefix = applangprefix Translator.cfg._combobox = None Translator.cfg._dialog = None # different translators Translator.cfg.translator_qt = None Translator.cfg.translator_qtbase = None Translator.cfg.translator_qthelp = None Translator.cfg.translator_qtmm = None Translator.cfg.translator_qtwebengine = None Translator.cfg.translator_app = None pyqt_path = os.path.dirname(PyQt5.__file__) if platform.system() == "Windows": Translator.cfg._qt_locale_path = Helper.concat_path_native( pyqt_path, "Qt\\translations") else: if os.path.isdir("/usr/share/qt5/translations/"): Translator.cfg._qt_locale_path = "/usr/share/qt5/translations/" else: # we assume, that library plugin path is sibling to translations path # so let's find the plugins and construct translations path lib_dirs = qApp.libraryPaths() for d in lib_dirs: l = os.path.join(d, "../translations") if os.path.isdir(l): Translator.cfg._qt_locale_path = l break Translator.cfg._app_locale_path = ":locale/" Translator.cfg._syslocale = QtCore.QLocale().system().name()[:2]
def __init__(self, parent = None, applangprefix = ""): """ Constructor of Translator class :param parent: parent, mostly qApp :param applangprefix: application language file prefix """ if Translator.cfg is None: Translator.cfg = self Translator.cfg._parent = parent super().__init__(Translator.cfg._parent) Translator.cfg._qt_local_path = None Translator.cfg._app_locale_path = None Translator.cfg._syslocale = None Translator.cfg._current_lang = "en" Translator.cfg.applangprefix = applangprefix Translator.cfg._combobox = None Translator.cfg._dialog = None # different translators Translator.cfg.translator_qt = None Translator.cfg.translator_qtbase = None Translator.cfg.translator_qthelp = None Translator.cfg.translator_qtmm = None Translator.cfg.translator_qtwebengine = None Translator.cfg.translator_app = None pyqt_path = os.path.dirname(PyQt5.__file__) if platform.system() == "Windows": Translator.cfg._qt_locale_path = Helper.concat_path_native(pyqt_path, "Qt\\translations") else: if os.path.isdir("/usr/share/qt5/translations/"): Translator.cfg._qt_locale_path = "/usr/share/qt5/translations/" else: # we assume, that library plugin path is sibling to translations path # so let's find the plugins and construct translations path lib_dirs = qApp.libraryPaths() for d in lib_dirs: l = os.path.join(d, "../translations") if os.path.isdir(l): Translator.cfg._qt_locale_path = l break Translator.cfg._app_locale_path = ":locale/" Translator.cfg._syslocale = QtCore.QLocale().system().name()[:2]
def check_online_status(self): self.logger.debug("Check online status") # check if server is generally available # use SSH port for connection test ret = Helper.test_port(confighandler.ConfigHandler.cfg.opsi_server, confighandler.ConfigHandler.cfg.sshport, 0.5) if ret is True: # check network access and mount network drive if on linux if sys.platform == 'win32': self.logger.info("System platform: "+ sys.platform) if self.args.nonetdrive is False: if confighandler.ConfigHandler.cfg.usenetdrive == "False": drives = Helper.get_available_drive_letters() if not drives: self.logger.error("No free drive letter found") else: self.logger.info("Free drive letter found: " + repr(drives)) self.logger.info("Using drive letter: " + drives[::-1][0]) path = "\\\\" + confighandler.ConfigHandler.cfg.opsi_server + "\\" + "opsi_workbench" self.logger.info("Trying to mount path: " + path) ret = MapDrive.mapDrive(drives[::-1][0] + ":", path, confighandler.ConfigHandler.cfg.opsi_user, confighandler.ConfigHandler.cfg.opsi_pass) if ret[0] != 0: self.logger.error("Error mounting path: " + str(ret)) else: self.logger.info("Network drive successfully mounted") oPB.NETDRV = drives[::-1][0] + ":" else: self.logger.info("Using existing network drive") else: self.logger.info("Mounting of network drive via command line disabled") else: self.logger.info("System platform: "+ sys.platform) self.logger.warning("This is not a windows based system. No network drive will be associated") self.logger.warning("Please take care, that the specified development base path is correct.") else: self.logger.warning("opsi server not available. Offline mode activated.") self.logger.warning("Return value from connection test: " + str(ret)) oPB.NETMODE = "offline"
def select_keyfile(self): self.logger.debug("Select SSH keyfile dialog") ext = "Private key file (" + ("; ").join(["*." + x for x in oPB.KEYFILE_EXT]) + ")" # generate file extension selection string for dialog script = QFileDialog.getOpenFileName(self, translate("SettingsDialog", "Choose keyfile"), ConfigHandler.cfg.dev_dir, ext) if not script == ("", ""): self.logger.debug("Selected SSH keyfile: " + script[0]) self.inpKeyFile.setText(Helper.concat_path_and_file(script[0], "")) self.dataChanged.emit() else: self.logger.debug("Dialog aborted.")
def select_externaleditor(self): self.logger.debug("Select scripteditor dialog") ext = "Program (" + ("; ").join(["*." + x for x in oPB.PRG_EXT]) + ")" # generate file extension selection string for dialog script = QFileDialog.getOpenFileName(self, translate("SettingsDialog", "Choose Scripteditor"), ConfigHandler.cfg.dev_dir, ext) if not script == ("", ""): self.logger.debug("Selected Scripeditor: " + script[0]) self.inpExternalEditor.setText(Helper.concat_path_and_file(script[0], "")) self.dataChanged.emit() else: self.logger.debug("Dialog aborted.")
def validate(self, p_str, p_int): """ Validator method :param p_str: script full pathname to validate :param p_int: (not used) :return: tuple(QValidator.state, p_str, p_int) """ if p_str == "": return ScriptFileValidator.Intermediate, p_str, p_int if os.path.exists(Helper.concat_path_native(self._parent.lblPacketFolder.text().replace('\\','/') + "/CLIENT_DATA/", p_str)): return ScriptFileValidator.Acceptable, p_str, p_int else: return ScriptFileValidator.Invalid, p_str, p_int
def fill_cmbDepProdID(self): """Fill combobox with values from opsi_depot share""" self.cmbDepProdID.clear() if oPB.NETMODE != "offline": try: self.logger.debug("Retrieve active package list from depot") subpath = "\\\\" + ConfigHandler.cfg.opsi_server + "\\" + oPB.DEPOTSHARE_BASE subdirs = Helper.get_subdirlist(subpath) subdirs.sort() for elem in subdirs: self.cmbDepProdID.addItem(elem) except: pass
def select_dev_dir(self): """Development directory selector dialog""" self.logger.debug("Select development directory") directory = QFileDialog.getExistingDirectory(self, translate("SettingsDialog", "Select development folder"), ConfigHandler.cfg.dev_dir, QFileDialog.ShowDirsOnly) if not directory == "": self.logger.info("Chosen directory: " + directory) value = Helper.concat_path_native(directory, "") if value[-1:] == "/" or value[-1:] == '\\': value = value[:-1] self.inpDevFolder.setText(value) self.inpLocalShareBase.setText(value) self.dataChanged.emit() else: self.logger.debug("Dialog aborted.")
def run_command_line(self): """Process project action via command line""" self.logger.info("Project given via command line: " + self.args.path) if os.path.isabs(self.args.path): project = self.args.path else: project = Helper.concat_path_native(ConfigHandler.cfg.dev_dir, self.args.path) # load project self.load_backend(project) # stop if something happened if oPB.EXITCODE != oPB.RET_OK: return # first, set rights if self.args.setrights is True: self.do_setrights() # wait a short moment for disk flushing sleep(0.5) # stop if something happened if oPB.EXITCODE != oPB.RET_OK: return # build project if self.args.build_mode is not None: self.logger.info("Command line: build") self.do_build() # wait a short moment for disk flushing sleep(0.5) # stop if something happened if oPB.EXITCODE != oPB.RET_OK: return # install / install+setup / uninstall try: self.logger.info("Command line: " + self.args.packetaction[0]) if self.args.packetaction[0] == "install": self.do_install(depot = ConfigHandler.cfg.opsi_server) if self.args.packetaction[0] == "instsetup": self.do_installsetup(depot = ConfigHandler.cfg.opsi_server) if self.args.packetaction[0] == "uninstall": self.do_uninstall(depot = ConfigHandler.cfg.opsi_server) except: pass
def select_dev_dir(self): """Development directory selector dialog""" self.logger.debug("Select development directory") directory = QFileDialog.getExistingDirectory( self, translate("SettingsDialog", "Select development folder"), ConfigHandler.cfg.dev_dir, QFileDialog.ShowDirsOnly) if not directory == "": self.logger.info("Chosen directory: " + directory) value = Helper.concat_path_native(directory, "") if value[-1:] == "/" or value[-1:] == '\\': value = value[:-1] self.inpDevFolder.setText(value) self.inpLocalShareBase.setText(value) self.dataChanged.emit() else: self.logger.debug("Dialog aborted.")
def select_keyfile(self): """SSH keyfile selector dialog""" self.logger.debug("Select SSH keyfile dialog") ext = "Private key file (" + (" ").join([ "*." + x for x in oPB.KEYFILE_EXT ]) + ")" # generate file extension selection string for dialog script = QFileDialog.getOpenFileName( self, translate("SettingsDialog", "Choose keyfile"), ConfigHandler.cfg.dev_dir, ext) if not script == ("", ""): self.logger.debug("Selected SSH keyfile: " + script[0]) self.inpKeyFile.setText(Helper.concat_path_native(script[0], "")) self.dataChanged.emit() else: self.logger.debug("Dialog aborted.")
def validate(self, p_str, p_int): """ Validator method :param p_str: script full pathname to validate :param p_int: (not used) :return: tuple(QValidator.state, p_str, p_int) """ if p_str == "": return ScriptFileValidator.Intermediate, p_str, p_int if os.path.exists( Helper.concat_path_native( self._parent.lblPacketFolder.text().replace('\\', '/') + "/CLIENT_DATA/", p_str)): return ScriptFileValidator.Acceptable, p_str, p_int else: return ScriptFileValidator.Invalid, p_str, p_int
def onlinecheck(self): self.logger.info("Online check") if self._active_side == "left": depot = self._ui_box_left.currentText().split()[0] else: depot = self._ui_box_right.currentText().split()[0] msg = "\n\n" + translate("depotmanagerController", "Selected depot:") + "\n" + depot self.logger.debug("Selected depot: " + depot) ret = Helper.test_port(depot, ConfigHandler.cfg.sshport, 0.5) print(ret) if ret is True: self._parent.msgbox(translate("depotmanagerController", "The selected depot is ONLINE.") + msg, oPB.MsgEnum.MS_ALWAYS, parent = self.ui) else: msg += "\n\n" + str(ret) self._parent.msgbox(translate("depotmanagerController", "The selected depot is OFFLINE.") + msg, oPB.MsgEnum.MS_ALWAYS, parent = self.ui)
def load_backend(self, project_name): """Load project data.""" # itemChanged signal has to be disconnected temporarily, because # if not, dataChanged would be set after loading self.logger.info("Load project: " + project_name) self._dataLoaded = None self.controlData.load_data(Helper.concat_path_native(ConfigHandler.cfg.dev_dir, project_name)) while self._dataLoaded is None: # _dataLoaded has to be True or False pass if not self._dataLoaded: self.logger.error("Backend data could not be loaded.") self.logger.debug("Set exitcode RET_EOPEN") oPB.EXITCODE = oPB.RET_EOPEN self.msgbox(translate("baseController", "Project could not be loaded!"), oPB.MsgEnum.MS_ERR) else: self.logger.info("Backend data loaded") self.msgbox(translate("mainController", "Project loaded successfully!"), oPB.MsgEnum.MS_STAT)
def load_backend(self, project_name): """Load project data.""" # itemChanged signal has to be disconnected temporarily, because # if not, dataChanged would be set after loading self.logger.info("Load project: " + project_name) self._dataLoaded = None self.controlData.load_data(Helper.concat_path_native(ConfigHandler.cfg.dev_dir, project_name)) while self._dataLoaded is None: # _dataLoaded has to be True or False pass if not self._dataLoaded: self.logger.error("Backend data could not be loaded.") self.logger.debug("Set exitcode RET_EOPEN") oPB.EXITCODE = oPB.RET_EOPEN self.msgbox(translate("baseController", "Project could not be loaded!"), oPB.MsgEnum.MS_ERR) else: self.logger.info("Backend data loaded") self.msgbox(translate("baseController", "Project loaded successfully!"), oPB.MsgEnum.MS_STAT)
def select_externaleditor(self): """External editor selector dialog""" self.logger.debug("Select scripteditor dialog") if platform.system() != "Windows": ext = "Program (" + (" ").join([ "*." + x for x in oPB.PRG_EXT ]) + ")" # generate file extension selection string for dialog else: ext = "Any (*)" script = QFileDialog.getOpenFileName( self, translate("SettingsDialog", "Choose Scripteditor"), ConfigHandler.cfg.dev_dir, ext) if not script == ("", ""): self.logger.debug("Selected Scripeditor: " + script[0]) self.inpExternalEditor.setText( Helper.concat_path_native(script[0], "")) self.dataChanged.emit() else: self.logger.debug("Dialog aborted.")
def select_logfile(self): """Logfile selector dialog""" self.logger.debug("Select log file dialog") """ ext = "Log (*.log)" # generate file extension selection string for dialog script = QFileDialog.getOpenFileName(self, translate("SettingsDialog", "Choose folder for logfile"), ConfigHandler.cfg.log_file, ext) if not script == ("", ""): self.logger.debug("Selected Logile: " + script[0]) """ directory = QFileDialog.getExistingDirectory(self, translate("SettingsDialog", "Select logfile folder"), ConfigHandler.cfg.dev_dir, QFileDialog.ShowDirsOnly) if not directory == "": self.logger.info("Chosen directory: " + directory) self.inpLogFile.setText(Helper.concat_path_native(directory, "opb-session.log")) self.dataChanged.emit() else: self.logger.debug("Dialog aborted.")
def select_script_dialog(self, script_type, setvalue = True): """ Opens a dialog to select a script file / Clear field content :param script_type: field type identifier (setup, uninstall, update, always, once, custom, userlogin) :param setvalue: set new value = True, empty field only = False """ self.logger.debug("Select script dialog") ext = "Scripts (" + "; ".join(["*." + x for x in oPB.SCRIPT_EXT]) + ")" # generate file extension selection string for dialog if setvalue: if self.lblPacketFolder.text() == "": script = QFileDialog.getOpenFileName(self, translate("MainWindow", "Choose script"), ConfigHandler.cfg.dev_dir, ext) else: script = QFileDialog.getOpenFileName(self, translate("MainWindow", "Choose script"), Helper.concat_path_and_file(self.lblPacketFolder.text(), "CLIENT_DATA"), ext) if not script == ("", ""): self._parent.set_selected_script(script[0], script_type) else: self._parent.set_selected_script("", script_type)
def select_logfile(self): """Logfile selector dialog""" self.logger.debug("Select log file dialog") """ ext = "Log (*.log)" # generate file extension selection string for dialog script = QFileDialog.getOpenFileName(self, translate("SettingsDialog", "Choose folder for logfile"), ConfigHandler.cfg.log_file, ext) if not script == ("", ""): self.logger.debug("Selected Logile: " + script[0]) """ directory = QFileDialog.getExistingDirectory( self, translate("SettingsDialog", "Select logfile folder"), ConfigHandler.cfg.dev_dir, QFileDialog.ShowDirsOnly) if not directory == "": self.logger.info("Chosen directory: " + directory) self.inpLogFile.setText( Helper.concat_path_native(directory, "opb-session.log")) self.dataChanged.emit() else: self.logger.debug("Dialog aborted.")
def set_selected_script(self, script, script_type): """ Receives select signal from window button, writes filename to model and emits itemChanged signal :param script: full pathname to script :param script_type: type of script """ self.logger.debug("Set selected script: (" + script + ") (" + script_type + ")") if script_type == "setup": self.model_fields.item(0, 10).setText(Helper.get_file_from_path(script)) if script_type == "uninstall": self.model_fields.item(0, 11).setText(Helper.get_file_from_path(script)) if script_type == "update": self.model_fields.item(0, 12).setText(Helper.get_file_from_path(script)) if script_type == "always": self.model_fields.item(0, 13).setText(Helper.get_file_from_path(script)) if script_type == "once": self.model_fields.item(0, 14).setText(Helper.get_file_from_path(script)) if script_type == "custom": self.model_fields.item(0, 15).setText(Helper.get_file_from_path(script)) if script_type == "userlogin": self.model_fields.item(0, 16).setText(Helper.get_file_from_path(script))
def __init__(self, parent = None): """Create a wizard or the mainwindow""" super().__init__(parent) self.logger = None self.args = self.get_args() # redirect system exception hook sys.excepthook = self.excepthook # create new application self.app = QApplication(sys.argv) # Application name self.app.setOrganizationName("opsi Package Builder") self.app.setApplicationName("opsi Package Builder " + oPB.PROGRAM_VERSION) # save ourselves in main instance property to be easily accessd via qApp # i.e. # from PyQt5.QtWidgets import qApp # main = qApp.property("main") self.app.setProperty("main", self) # Initialize the logger self.logWindow = oPB.gui.logging.LogDialog(None, self, self.args.log_level.upper()) self.instantiate_logger(False) #self.instantiate_logger_old() # instantiate configuration class confighandler.ConfigHandler(oPB.CONFIG_INI) # log program version and user self.logger.info("opsi PackageBuilder (MIT licensed) " + oPB.PROGRAM_VERSION) self.logger.info("Current user: "******"Command line arguments given:") for key, val in vars(self.args).items(): self.logger.info(" " + key + ": " + str(val)) if self.args.log_level.upper() not in ["DEBUG", "INFO", "SSHINFO", "WARNING", "ERROR", "CRITICAL", "SSH"]: self.logger.error(" Undefined log level: " + self.args.log_file.upper()) self.logger.error(" Log level has been set to ERROR") for elem in self.app.libraryPaths(): self.logger.debug("QT5 library path: " + elem) self.install_translations() self.check_online_status() # ----------------------------------------------------------------------------------------- # main ui dispatching # startup gui variant if not self.args.nogui: # startup program window self.mainWindow = main.MainWindowController(self.args) self.mainWindow.ui.showLogRequested.connect(self.logWindow.show) self.mainWindow.closeAppRequested.connect(self.logWindow.close) # run main app loop self.app.exec_() # only process commandline else: self.logger.info("No-GUI parameter set") # startup program window self.console = console.ConsoleController(self.args) # ----------------------------------------------------------------------------------------- # unmount drive after end of program if oPB.NETDRV is not None: ret = MapDrive.unMapDrive(oPB.NETDRV) if ret[0] != 0: self.logger.error("Error unmounting path: " + str(ret)) else: self.logger.info("Network drive successfully unmounted") # exit and set return code self.logger.debug("Exit code: " + str(oPB.EXITCODE)) sys.exit(oPB.EXITCODE)
def msgbox(self, msgtext = "", typ = oPB.MsgEnum.MS_STAT, parent = None, preload = ""): """ Messagebox function Valid values for typ: * oPB.MsgEnum.MS_ERR -> Error message (status bar/ popup) * oPB.MsgEnum.MS_WARN -> Warning (status bar/ popup) * oPB.MsgEnum.MS_INFO -> Information (status bar/ popup) * oPB.MsgEnum.MS_STAT -> Information (only status bar) * oPB.MsgEnum.MS_ALWAYS -> Display this message ALWAYS, regardless of which message **typ** is deactivated via settings * oPB.MsgEnum.MS_PARSE -> just parse message text and return it * oPB.MsgEnum.MS_QUEST_YESNO * oPB.MsgEnum.MS_QUEST_CTC * oPB.MsgEnum.MS_QUEST_OKCANCEL * oPB.MsgEnum.MS_QUEST_PHRASE * oPB.MsgEnum.MS_QUEST_PASS * oPB.MsgEnum.MS_QUEST_DEPOT :param msgtext: Message text :param typ: type of message window, see oPB.core enums :param parent: parent ui of message box :param preload: pre-fill input boxes with this text """ if parent is None: parent = self.ui # first parse text, is argument is str if type(msgtext) is str: msgtext = Helper.parse_text(msgtext) if typ == oPB.MsgEnum.MS_ERR: self.msgSend.emit(msgtext) if ConfigHandler.cfg.no_error_msg == "False": QMessageBox.critical(parent, translate("mainController", "Error"), msgtext, QMessageBox.Ok) elif typ == oPB.MsgEnum.MS_WARN: self.msgSend.emit(msgtext) if ConfigHandler.cfg.no_warning_msg == "False": QMessageBox.warning(parent, translate("mainController", "Warning"), msgtext, QMessageBox.Ok) elif typ == oPB.MsgEnum.MS_INFO: self.msgSend.emit(msgtext) if ConfigHandler.cfg.no_info_msg == "False": QMessageBox.information(parent, translate("mainController", "Message"), msgtext, QMessageBox.Ok) elif typ == oPB.MsgEnum.MS_STAT: self.msgSend.emit(msgtext) elif typ == oPB.MsgEnum.MS_ALWAYS: QMessageBox.information(parent, translate("mainController", "Message"), msgtext, QMessageBox.Ok) elif typ == oPB.MsgEnum.MS_PARSE: return msgtext elif typ == oPB.MsgEnum.MS_QUEST_YESNO: retval = QMessageBox.question(parent, translate("mainController", "Question"), msgtext, QMessageBox.Yes, QMessageBox.No) if retval == QMessageBox.No: return False else: return True elif typ == oPB.MsgEnum.MS_QUEST_CTC: msgBox = QMessageBox(parent) msgBox.setWindowTitle(translate("mainController", "Question")) msgBox.setText(msgtext) msgBox.setIcon(QMessageBox.Question) cancelBtn = QPushButton(translate("mainController", "Cancel")) rebuildBtn = QPushButton(translate("mainController", "Rebuild")) addBtn = QPushButton(translate("mainController", "Add version")) msgBox.addButton(cancelBtn, QMessageBox.RejectRole) msgBox.addButton(rebuildBtn, QMessageBox.AcceptRole) msgBox.addButton(addBtn, QMessageBox.AcceptRole) msgBox.exec_() if msgBox.clickedButton() == cancelBtn: return 0 elif msgBox.clickedButton() == rebuildBtn: return 1 else: return 2 elif typ == oPB.MsgEnum.MS_QUEST_OKCANCEL: retval = QMessageBox.question(parent, translate("mainController", "Question"), msgtext, QMessageBox.Ok, QMessageBox.Cancel) if retval == QMessageBox.Cancel: return False else: return True elif typ == oPB.MsgEnum.MS_QUEST_PHRASE: text = QInputDialog.getText(parent, translate("mainController", "Additional information"), msgtext, QLineEdit.Normal, preload) return text elif typ == oPB.MsgEnum.MS_QUEST_PASS: text = QInputDialog.getText(parent, translate("mainController", "Additional information"), msgtext, QLineEdit.Password, preload) return text elif typ == oPB.MsgEnum.MS_QUEST_DEPOT: preselectlist = [i for i, j in enumerate(msgtext) if ConfigHandler.cfg.opsi_server in j] if preselectlist: preselect = preselectlist[0] else: preselect = -1 item = QInputDialog.getItem(parent, translate("mainController", "Question"), translate("mainController", "Select which depot to use:"), msgtext, preselect, False) return item elif typ == oPB.MsgEnum.MS_ABOUTQT: QMessageBox.aboutQt(parent, translate("mainController", "About Qt"))
def create_bundle(self, prods=[]): self.logger.debug("Create bundle from selection") if prods: msg = "\n\n" + translate( "bundleController", "Chosen products:") + "\n\n" + ("\n").join( [p for p in prods]) reply = self._parent.msgbox( translate("bundleController", "Create product bundle now?") + msg, oPB.MsgEnum.MS_QUEST_YESNO) if reply is True: self.logger.debug("Selected product(s): " + str(prods)) ok = False while not ok: comment = "meta-" accept = False while comment == "" or accept == False: ( comment, accept ) = self._parent.msgbox(translate( "bundleController", "Please enter package name (allowed characters: a-z, A-Z, 0-9, ._-):" ), oPB.MsgEnum.MS_QUEST_PHRASE, parent=self.ui, preload=comment) # first test if cancel button was pressed if not accept: self._parent.msgbox(translate( "bundleController", "Package creation canceled!"), oPB.MsgEnum.MS_ALWAYS, parent=self.ui) self.logger.debug( "Package bundle creation canceled by the user." ) self._parent.startup.show_() return None if ConfigHandler.cfg.age == "True": test = re.match(oPB.OPB_PRODUCT_ID_REGEX_NEW, comment) else: test = re.match(oPB.OPB_PRODUCT_ID_REGEX_OLD, comment) if not test: self._parent.msgbox(translate( "bundleController", "Package name is no valid product id!"), oPB.MsgEnum.MS_ALWAYS, parent=self.ui) accept = False directory = Helper.concat_path_native( ConfigHandler.cfg.dev_dir, comment) self.logger.info("Chosen directory for new project: " + directory) if os.path.exists(directory): self._parent.msgbox(translate( "bundleController", "Package name already exists. Please choose another package name!" ), oPB.MsgEnum.MS_ALWAYS, parent=self.ui) else: self._parent.project_create(directory) for p in prods: self._parent.add_setup_before_dependency(p) self._parent.controlData.priority = -100 self._parent.controlData.setupScript = "dummy.opsiscript" self._parent.controlData.create_script_stub( self._parent.controlData.setupScript) self._parent.save_backend() ok = True self.logger.debug("Package bundle creation finished.") else: self.logger.debug("Nothing selected.")
def project_copy(self, project_folder): """ Copies current project data to new project destination :param destination: destination for copied project """ dest_data = "" val = 0 def sizeof_fmt(num, suffix='B'): for unit in ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']: if abs(num) < 1024.0: return "%3.1f%s%s" % (num, unit, suffix) num /= 1024.0 return "%.1f%s%s" % (num, 'Yi', suffix) def copy2_(src, dst): self.logger.debug("Copying to destination file: " + str(dst)) self.msgbox("CLIENT_DATA: " + str(dst).replace(dest_data, ""), oPB.MsgEnum.MS_STAT) self.progressChanged.emit(val) shutil.copy2(src,dst) def countFiles(directory): files = [] total_size = 0 if os.path.isdir(directory): for path, dirs, filenames in os.walk(directory): files.extend(filenames) for f in filenames: fp = os.path.join(path, f) total_size += os.path.getsize(fp) return len(files), total_size def copyDirectory(src, dest): self.logger.info("Copying files") self.logger.debug("Source: " + src) self.logger.debug("Destination:" + dest) if os.path.exists(dest): shutil.rmtree(dest) try: shutil.copytree(src, dest, copy_function=copy2_) # Directories are the same except shutil.Error as e: self.logger.error('Directory not copied. Error: %s' % e) # Any error saying that the directory doesn't exist except OSError as e: self.logger.error('Directory not copied. Error: %s' % e) self.logger.info("Create new project from current: " + project_folder) # create new control object as copy if os.path.exists(Helper.concat_path_native(project_folder, "CLIENT_DATA")): self.logger.error("CLIENT_DATA subdirectory in destination folder detected. This is not allowed for security reason!") self.msgbox(translate("mainController", "CLIENT_DATA subdirectory in destination folder detected. This is not allowed for security reason!"), oPB.MsgEnum.MS_ERR) return newControl = deepcopy(self.controlData) self.create_project_paths(project_folder) project_name = PurePath(project_folder).name.replace(" ","_") newControl.id = project_name newControl.projectfolder = project_folder # add changelog entry for this copy process text = "Project copied from: " + self.controlData.packagename newentry = ChangelogEntry(newControl.id) newentry.version = "(" + newControl.productversion + "-" + newControl.packageversion + ")" newentry.status = oPB.CHLOG_STATI[0] newentry.urgency = oPB.CHLOG_BLOCKMARKER + oPB.CHLOG_URGENCIES[0] newentry.text = "\n" + text + changelog_footer() newControl.changelog_append(newentry) try: self.processingStarted.emit() newControl.save_data() self.logger.debug("New control data saved.") dest_data = Helper.concat_path_native(newControl.projectfolder, "CLIENT_DATA") total, total_size = countFiles(Helper.concat_path_native(self.controlData.projectfolder, "CLIENT_DATA")) val = 100 / total reply = self.msgbox(translate("mainController", "Copy files now? This can't be canceled.") + "\n\nTotal: " + str(total) + "\n" + sizeof_fmt(total_size), oPB.MsgEnum.MS_QUEST_YESNO) if reply is True: copyDirectory(Helper.concat_path_native(self.controlData.projectfolder, "CLIENT_DATA"), Helper.concat_path_native(newControl.projectfolder, "CLIENT_DATA") ) self.logger.debug("Files copied.") self.processingEnded.emit(True) # close current project an re-open clone if not self.project_close(): return self.project_load(project_name) except: self.logger.error("Project could not be copied and re-opened.")
def open_scripteditor(self): """Open script editor after double click on tree view item""" self.logger.debug("Start scripteditor") idx = self.treeView.currentIndex() if idx.isValid(): sibling = idx.sibling(idx.row(), idx.column() + 1) item = self.model.itemFromIndex(sibling) script = item.text() if "(External)" in script: self._parent.msgbox( translate( "MainWindow", "Sorry! You cannot edit a script outside the project folder!" ), oPB.MsgEnum.MS_ERR, self) return if ConfigHandler.cfg.editor_intern == "True": self._parent.msgbox( translate( "MainWindow", "Internal editor not available at the moment. Use external editor instead!" ), oPB.MsgEnum.MS_ALWAYS, self) self._parentUi.actionSettings.trigger() return if os.path.exists(ConfigHandler.cfg.scripteditor): path = Helper.concat_path_native( self._parentUi.lblPacketFolder.text(), "CLIENT_DATA") path = Helper.concat_path_native(path, script) self.logger.debug("Opening script: " + path) cmd = [ConfigHandler.cfg.scripteditor] if (ConfigHandler.cfg.editor_options).strip() != "": for part in (ConfigHandler.cfg.editor_options).split(): cmd.append(part) if ConfigHandler.cfg.editor_attachdirect == "True": cmd[-1] = cmd[-1] + path else: cmd.append(path) else: cmd.append(path) self.logger.debug("Executing subprocess: " + str(cmd)) proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) outs, errs = proc.communicate() self.logger.info(outs) self.logger.error(errs) if proc.returncode != 0: self._parent.msgbox( translate( "MainWindow", "Editor startup did not cleanup correctly.\n\nThe following message(s) returned:" ) + "\n\nStandard Out:\n" + outs + "\n\nStandard Err:\n" + errs, oPB.MsgEnum.MS_WARN, self) else: self._parent.msgbox( translate( "MainWindow", "Editor not found:" + " " + ConfigHandler.cfg.scripteditor), oPB.MsgEnum.MS_ERR, self)
def __init__(self, parent = None): """Create a wizard or the mainwindow""" self._parent = parent super().__init__(self._parent) print("runner/Main parent: ", self._parent, " -> self: ", self) if oPB.PRINTHIER else None self.logger = None self.args = self.get_args() self._log_level = None self._log_file = None self.translator = None # make it really quiet, part 1 if self.args.quiet: self.args.nogui = True # instantiate configuration class confighandler.ConfigHandler(oPB.CONFIG_INI) # redirect system exception hook if not self.args.noexcepthook: sys.excepthook = self.excepthook # pre-instantiating the application, avoid some nasty OpenGL messages QApplication.setAttribute(QtCore.Qt.AA_UseOpenGLES) # create new application and install stylesheet self.app = QApplication(sys.argv) self.install_stylesheet() # Create and display the splash screen, if in ui mode if not self.args.nogui: splash_pix = QPixmap(':/images/splash.png') self.splash = QSplashScreen(splash_pix, QtCore.Qt.WindowStaysOnTopHint) self.splash.setMask(splash_pix.mask()) # splash.showMessage("opsi Package Builder " + oPB.PROGRAM_VERSION + " " + translate("Main", "is loading..."), QtCore.Qt.AlignCenter, QtCore.Qt.white) self.splash.show() self.app.processEvents() # Application name self.app.setOrganizationName("opsi Package Builder") self.app.setApplicationName("opsi Package Builder " + oPB.PROGRAM_VERSION) # save ourselves in main instance property to be easily accessd via qApp # i.e. # from PyQt5.QtWidgets import qApp # main = qApp.property("main") self.app.setProperty("main", self) if confighandler.ConfigHandler.cfg.log_always == "True": if self.args.log_file is not None: self._log_file = self.args.log_file else: self._log_file = confighandler.ConfigHandler.cfg.log_file if self.args.log_level.upper() != "NOTSET": self._log_level = self.args.log_level.upper() else: self._log_level = confighandler.ConfigHandler.cfg.log_level else: self._log_file = self.args.log_file if self.args.log_level.upper() == "NOTSET": self._log_level = "CRITICAL" else: self._log_level = self.args.log_level.upper() if self._log_file is not None: if not pathlib.Path(self._log_file).is_absolute(): if platform.system() == "Windows": self._log_file = str(pathlib.PurePath(oPB.WIN_TMP_PATH, self._log_file)) if platform.system() in ["Darwin", "Linux"]: self._log_file = str(pathlib.PurePath(oPB.UNIX_TMP_PATH, self._log_file)) # overwrite development directory from config with command line arg if self.args.dev_dir is not None: confighandler.ConfigHandler.cfg.dev_dir = self.args.dev_dir # Initialize the logger and reroute QtCore messages to it self.logWindow = oPB.gui.logging.LogDialog(None, self, self._log_level) self.instantiate_logger(False) QtCore.qInstallMessageHandler(self.qt_message_handler) # log program version and user self.logger.info(80 * "-") self.logger.info("opsi PackageBuilder (MIT licensed) " + oPB.PROGRAM_VERSION) self.logger.info("Current user: "******"Command line arguments given:") for key, val in vars(self.args).items(): self.logger.info(" " + key + ": " + str(val)) if self._log_level not in ["DEBUG", "INFO", "SSHINFO", "WARNING", "ERROR", "CRITICAL", "SSH"]: self.logger.error(" Undefined log level: " + self._log_level) self.logger.error(" Log level has been set to ERROR") for elem in self.app.libraryPaths(): self.logger.debug("QT5 library path: " + elem) # write config to log, if necessary confighandler.ConfigHandler.cfg.log_config() self.check_online_status() # ----------------------------------------------------------------------------------------- # main ui dispatching # startup gui variant if not self.args.nogui: # hide console window, but only under Windows and only if app is frozen if sys.platform.lower().startswith('win'): if getattr(sys, 'frozen', False): hideConsole() # installing translators self.translator = Translator(self.app, "opsipackagebuilder") self.translator.install_translations(confighandler.ConfigHandler.cfg.language) # retranslate logWindow, as it is loaded before the translations self.logWindow.retranslateUi(self.logWindow) # create app icon app_icon = QtGui.QIcon() app_icon.addFile(':images/prog_icons/opb/package_16x16.png', QtCore.QSize(16, 16)) app_icon.addFile(':images/prog_icons/opb/package_24x24.png', QtCore.QSize(24, 24)) app_icon.addFile(':images/prog_icons/opb/package_32x32.png', QtCore.QSize(32, 32)) app_icon.addFile(':images/prog_icons/opb/package_48x48.png', QtCore.QSize(48, 48)) app_icon.addFile(':images/prog_icons/opb/package_64x64.png', QtCore.QSize(64, 64)) app_icon.addFile(':images/prog_icons/opb/package_92x92.png', QtCore.QSize(92, 92)) app_icon.addFile(':images/prog_icons/opb/package_128x128.png', QtCore.QSize(128, 128)) app_icon.addFile(':images/prog_icons/opb/package_256x256.png', QtCore.QSize(256, 256)) self.app.setProperty("prog_icon",app_icon) # startup program window self.mainWindow = main.MainWindowController(self.args) self.mainWindow.ui.showLogRequested.connect(self.logWindow.show) self.mainWindow.closeAppRequested.connect(self.logWindow.close) self.splash.finish(self.mainWindow.ui) # check for updates if configured if confighandler.ConfigHandler.cfg.updatecheck == "True": self.mainWindow.update_check() # run main app loop self.app.exec_() # only process commandline else: self.logger.info("No-GUI parameter set") # startup program window self.console = console.ConsoleController(self.args) # ----------------------------------------------------------------------------------------- # unmount drive (if exist) after end of program if (oPB.NETDRV is not None) and oPB.NETDRV != "offline": ret = MapDrive.unMapDrive(oPB.NETDRV) if ret[0] != 0: self.logger.error("Error unmounting path: " + str(ret)) else: self.logger.info("Network drive successfully unmounted") # exit and set return code self.logger.info("Exit code: " + str(oPB.EXITCODE)) # show console window if not self.args.nogui: if sys.platform.lower().startswith('win'): if getattr(sys, 'frozen', False): showConsole() sys.exit(oPB.EXITCODE)
def userLoginScript(self, value): if not Helper.extCheck(value): raise ValueError(translate("ControlFileData", "userLogin script has invalid file extension")) self._userLoginScript = value
def customScript(self, value): if not Helper.extCheck(value): raise ValueError(translate("ControlFileData", "custom script has invalid file extension")) self._customScript = value
def open_scripteditor(self): """ Open configured script editor. Method reaction depends on calling widget (self.sender()) """ self.logger.debug("Start scripteditor") if ConfigHandler.cfg.editor_intern == "True": self._parent.msgbox(translate("MainWindow", "Internal editor not available at the moment. Use external editor instead!"), oPB.MsgEnum.MS_ALWAYS, self) self.actionSettings.trigger() return if os.path.exists(ConfigHandler.cfg.scripteditor): path = Helper.concat_path_native(self.lblPacketFolder.text(), "CLIENT_DATA") if self.sender() == self.btnScrSetupEdit: if self.inpScrSetup.text().strip() == "": script = "setup.opsiscript" else: script = self.inpScrSetup.text() elif self.sender() == self.btnScrUninstallEdit: if self.inpScrUninstall.text().strip() == "": script = "uninstall.opsiscript" else: script = self.inpScrUninstall.text() elif self.sender() == self.btnScrUpdateEdit: if self.inpScrUpdate.text().strip() == "": script = "update.opsiscript" else: script = self.inpScrUpdate.text() elif self.sender() == self.btnScrAlwaysEdit: if self.inpScrAlways.text().strip() == "": script = "always.opsiscript" else: script = self.inpScrAlways.text() elif self.sender() == self.btnScrOnceEdit: if self.inpScrOnce.text().strip() == "": script = "once.opsiscript" else: script = self.inpScrOnce.text() elif self.sender() == self.btnScrCustomEdit: if self.inpScrCustom.text().strip() == "": script = "custom.opsiscript" else: script = self.inpScrCustom.text() elif self.sender() == self.btnScrUserLoginEdit: if self.inpScrUserLogin.text().strip() == "": script = "userlogin.opsiscript" else: script = self.inpScrUserLogin.text() elif self.sender() == self.actionScriptEditor: script = "new.opsiscript" # script editor from menu if path != "" and script != "": path = Helper.concat_path_native(path, script) self.logger.debug("Opening script: " + path) # construct calling array # first add basic scripteditor executable cmd = [ConfigHandler.cfg.scripteditor] # if there are options, split and append them if (ConfigHandler.cfg.editor_options).strip() != "": for part in (ConfigHandler.cfg.editor_options).split(): cmd.append(part) # if attach direct is true, combine last option with script file path if ConfigHandler.cfg.editor_attachdirect == "True": cmd[-1] = cmd[-1] + path # or else, append as separate value to list else: cmd.append(path) else: cmd.append(path) self.logger.debug("Executing subprocess: " + str(cmd)) proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) outs, errs = proc.communicate() self.logger.info(outs) self.logger.error(errs) if proc.returncode != 0 and proc.returncode != 555: self._parent.msgbox(translate("MainWindow", "Editor did not exit as expected.\n\nThe following message(s) returned:") + "\n\nStandard Out:\n" + outs + "\nStandard Err:\n" + errs + "\n\nReturn code: " + str(proc.returncode), oPB.MsgEnum.MS_WARN, self) else: self._parent.msgbox(translate("MainWindow", "Editor not found:" + " " + ConfigHandler.cfg.scripteditor), oPB.MsgEnum.MS_ERR, self)
def msgbox(self, msgtext = "", typ = oPB.MsgEnum.MS_STAT, parent = None): """ Messagebox function Valid values for typ: * oPB.MsgEnum.MS_ERR -> Error message (status bar/ popup) * oPB.MsgEnum.MS_WARN -> Warning (status bar/ popup) * oPB.MsgEnum.MS_INFO -> Information (status bar/ popup) * oPB.MsgEnum.MS_STAT -> Information (only status bar) * oPB.MsgEnum.MS_ALWAYS -> Display this message ALWAYS, regardless of which message **typ** is deactivated via settings * oPB.MsgEnum.MS_PARSE -> just parse message text and return it * oPB.MsgEnum.MS_QUEST_YESNO * oPB.MsgEnum.MS_QUEST_CTC * oPB.MsgEnum.MS_QUEST_OKCANCEL :param msgtext: Message text :param typ: type of message window, see oPB.core enums """ if parent is None: parent = self.ui # first parse text msgtext = Helper.parse_text(msgtext) if typ == oPB.MsgEnum.MS_ERR: self.msgSend.emit(msgtext) if ConfigHandler.cfg.no_error_msg == "False": QMessageBox.critical(parent, translate("mainController", "Error"), msgtext, QMessageBox.Ok) elif typ == oPB.MsgEnum.MS_WARN: self.msgSend.emit(msgtext) if ConfigHandler.cfg.no_warning_msg == "False": QMessageBox.warning(parent, translate("mainController", "Warning"), msgtext, QMessageBox.Ok) elif typ == oPB.MsgEnum.MS_INFO: self.msgSend.emit(msgtext) if ConfigHandler.cfg.no_info_msg == "False": QMessageBox.information(parent, translate("mainController", "Message"), msgtext, QMessageBox.Ok) elif typ == oPB.MsgEnum.MS_STAT: self.msgSend.emit(msgtext) elif typ == oPB.MsgEnum.MS_ALWAYS: QMessageBox.information(parent, translate("mainController", "Message"), msgtext, QMessageBox.Ok) elif typ == oPB.MsgEnum.MS_PARSE: return msgtext elif typ == oPB.MsgEnum.MS_QUEST_YESNO: retval = QMessageBox.question(parent, translate("mainController", "Question"), msgtext, QMessageBox.Yes, QMessageBox.No) if retval == QMessageBox.No: return False else: return True elif typ == oPB.MsgEnum.MS_QUEST_CTC: msgBox = QMessageBox(parent) msgBox.setWindowTitle(translate("mainController", "Question")) msgBox.setText(msgtext) msgBox.setIcon(QMessageBox.Question) cancelBtn = QPushButton(translate("mainController", "Cancel")) rebuildBtn = QPushButton(translate("mainController", "Rebuild")) addBtn = QPushButton(translate("mainController", "Add version")) msgBox.addButton(cancelBtn, QMessageBox.RejectRole) msgBox.addButton(rebuildBtn, QMessageBox.AcceptRole) msgBox.addButton(addBtn, QMessageBox.AcceptRole) msgBox.exec_() if msgBox.clickedButton() == cancelBtn: return 0 elif msgBox.clickedButton() == rebuildBtn: return 1 else: return 2 elif typ == oPB.MsgEnum.MS_QUEST_OKCANCEL: retval = QMessageBox.question(parent, translate("mainController", "Question"), msgtext, QMessageBox.Ok, QMessageBox.Cancel) if retval == QMessageBox.Cancel: return False else: return True elif typ == oPB.MsgEnum.MS_QUEST_PHRASE: text = QInputDialog.getText(parent, translate("mainController", "Additional information"), msgtext, QLineEdit.Normal,"") return text
def changelog_footer() ->str: """ Generate footer for standard changelog entries. :return: footer sring """ return "\n\n" + " -- " + ConfigHandler.cfg.packagemaintainer + " <" + ConfigHandler.cfg.mailaddress + "> " + Helper.timestamp_changelog() + "\n"
def save_data(self): """Save control file data of current project""" controlfile = self._projectfolder + "/OPSI/control" if Path(controlfile).exists(): try: shutil.move(controlfile, controlfile + "-" + Helper.timestamp() + ".bak") except IOError: self.logger.error("Existing control file could not be renamed") self.logger.debug("Emit dataLoaded(False)") self.dataSaved.emit(False) return try: with open(self._projectfolder + "/OPSI/control", "x", encoding="utf-8", newline="\n") as file: self.logger.debug("Control file opened: " + self._projectfolder + "/OPSI/control") file.write("[Package]\n") file.write("version: " + self.packageversion + "\n") file.write("depends: " + self.depends + "\n") file.write("incremental: " + self.incremental + "\n\n") file.write("[Product]\n") file.write("type: " + self.type + "\n") file.write("id: " + self.id + "\n") file.write("name: " + self.name + "\n") file.write("description: " + self.description.strip() + "\n") file.write("advice: " + self.advice + "\n") file.write("version: " + self.productversion + "\n") file.write("priority: " + str(self.priority) + "\n") file.write("licenseRequired: " + self.licenseRequired + "\n") file.write("productClasses: " + self.productClasses + "\n") file.write("setupScript: " + self.setupScript + "\n") file.write("uninstallScript: " + self.uninstallScript + "\n") file.write("updateScript: " + self.updateScript + "\n") file.write("alwaysScript: " + self.alwaysScript + "\n") file.write("onceScript: " + self.onceScript + "\n") file.write("customScript: " + self.customScript + "\n") file.write("userLoginScript: " + self.userLoginScript + "\n") if self.dependencies: for elem in self.dependencies: file.write("\n") file.write("[ProductDependency]\n") file.write("action: " + elem[0] + "\n") file.write("requiredProduct: " + elem[1] + "\n") if elem[2] not in ["", "none"]: file.write("requiredAction: " + elem[2] + "\n") else: file.write("requiredStatus: " + elem[3] + "\n") file.write("requirementType: " + elem[4] + "\n") if self.properties: for elem in self.properties: file.write("\n") file.write("[ProductProperty]\n") file.write("type: " + elem[1] + "\n") file.write("name: " + elem[0] + "\n") if elem[1] == "unicode": file.write("multivalue: " + elem[2] + "\n") file.write("editable: " + elem[3] + "\n") file.write("description: " + elem[4] + "\n") if elem[1] == "unicode": v = json.dumps(elem[5], ensure_ascii=False) d = json.dumps(elem[6], ensure_ascii=False) if v == '""': # empty string returned by json.dumps file.write("values: \n") else: file.write("values: " + v + "\n") if d == '""': # empty string returned by json.dumps file.write("default: \n") else: file.write("default: " + d + "\n") else: file.write("default: " + elem[6] + "\n") file.write("\n") file.write("[Changelog]\n") file.write(self.changelog) file.close() self.logger.debug("Emit dataSaved(True)") self.dataSaved.emit(True) # on success except: self.logger.error("Error writing control file") self.logger.exception("Error writing control file") self.logger.debug("Emit dataSaved(False)") self.dataSaved.emit(False)
def project_copy(self, project_folder): """ Copies current project data to new project destination :param destination: destination for copied project """ dest_data = "" val = 0 def sizeof_fmt(num, suffix='B'): for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']: if abs(num) < 1024.0: return "%3.1f%s%s" % (num, unit, suffix) num /= 1024.0 return "%.1f%s%s" % (num, 'Yi', suffix) def copy2_(src, dst): self.logger.debug("Copying to destination file: " + str(dst)) self.msgbox("CLIENT_DATA: " + str(dst).replace(dest_data, ""), oPB.MsgEnum.MS_STAT) self.progressChanged.emit(val) shutil.copy2(src, dst) def countFiles(directory): files = [] total_size = 0 if os.path.isdir(directory): for path, dirs, filenames in os.walk(directory): files.extend(filenames) for f in filenames: fp = os.path.join(path, f) total_size += os.path.getsize(fp) return len(files), total_size def copyDirectory(src, dest): self.logger.info("Copying files") self.logger.debug("Source: " + src) self.logger.debug("Destination:" + dest) if os.path.exists(dest): shutil.rmtree(dest) try: shutil.copytree(src, dest, copy_function=copy2_) # Directories are the same except shutil.Error as e: self.logger.error('Directory not copied. Error: %s' % e) # Any error saying that the directory doesn't exist except OSError as e: self.logger.error('Directory not copied. Error: %s' % e) self.logger.info("Create new project from current: " + project_folder) # create new control object as copy if os.path.exists( Helper.concat_path_native(project_folder, "CLIENT_DATA")): self.logger.error( "CLIENT_DATA subdirectory in destination folder detected. This is not allowed for security reason!" ) self.msgbox( translate( "mainController", "CLIENT_DATA subdirectory in destination folder detected. This is not allowed for security reason!" ), oPB.MsgEnum.MS_ERR) return newControl = deepcopy(self.controlData) self.create_project_paths(project_folder) project_name = PurePath(project_folder).name.replace(" ", "_") newControl.id = project_name newControl.projectfolder = project_folder # add changelog entry for this copy process text = "Project copied from: " + self.controlData.packagename newentry = ChangelogEntry(newControl.id) newentry.version = "(" + newControl.productversion + "-" + newControl.packageversion + ")" newentry.status = oPB.CHLOG_STATI[0] newentry.urgency = oPB.CHLOG_BLOCKMARKER + oPB.CHLOG_URGENCIES[0] newentry.text = "\n" + text + changelog_footer() newControl.changelog_append(newentry) try: self.processingStarted.emit() newControl.save_data() self.logger.debug("New control data saved.") dest_data = Helper.concat_path_native(newControl.projectfolder, "CLIENT_DATA") total, total_size = countFiles( Helper.concat_path_native(self.controlData.projectfolder, "CLIENT_DATA")) val = 100 / total reply = self.msgbox( translate("mainController", "Copy files now? This can't be canceled.") + "\n\nTotal: " + str(total) + "\n" + sizeof_fmt(total_size), oPB.MsgEnum.MS_QUEST_YESNO) if reply is True: copyDirectory( Helper.concat_path_native(self.controlData.projectfolder, "CLIENT_DATA"), Helper.concat_path_native(newControl.projectfolder, "CLIENT_DATA")) self.logger.debug("Files copied.") self.processingEnded.emit(True) # close current project an re-open clone if not self.project_close(): return self.project_load(project_name) except: self.logger.error("Project could not be copied and re-opened.")
def msgbox(self, msgtext="", typ=oPB.MsgEnum.MS_STAT, parent=None, preload=""): """ Messagebox function Valid values for typ: * oPB.MsgEnum.MS_ERR -> Error message (status bar/ popup) * oPB.MsgEnum.MS_WARN -> Warning (status bar/ popup) * oPB.MsgEnum.MS_INFO -> Information (status bar/ popup) * oPB.MsgEnum.MS_STAT -> Information (only status bar) * oPB.MsgEnum.MS_ALWAYS -> Display this message ALWAYS, regardless of which message **typ** is deactivated via settings * oPB.MsgEnum.MS_PARSE -> just parse message text and return it * oPB.MsgEnum.MS_QUEST_YESNO * oPB.MsgEnum.MS_QUEST_CTC * oPB.MsgEnum.MS_QUEST_OKCANCEL * oPB.MsgEnum.MS_QUEST_PHRASE * oPB.MsgEnum.MS_QUEST_PASS * oPB.MsgEnum.MS_QUEST_DEPOT :param msgtext: Message text :param typ: type of message window, see oPB.core enums :param parent: parent ui of message box :param preload: pre-fill input boxes with this text """ if parent is None: parent = self.ui # first parse text, is argument is str if type(msgtext) is str: msgtext = Helper.parse_text(msgtext) if typ == oPB.MsgEnum.MS_ERR: self.msgSend.emit(msgtext) if ConfigHandler.cfg.no_error_msg == "False": QMessageBox.critical(parent, translate("mainController", "Error"), msgtext, QMessageBox.Ok) elif typ == oPB.MsgEnum.MS_WARN: self.msgSend.emit(msgtext) if ConfigHandler.cfg.no_warning_msg == "False": QMessageBox.warning(parent, translate("mainController", "Warning"), msgtext, QMessageBox.Ok) elif typ == oPB.MsgEnum.MS_INFO: self.msgSend.emit(msgtext) if ConfigHandler.cfg.no_info_msg == "False": QMessageBox.information(parent, translate("mainController", "Message"), msgtext, QMessageBox.Ok) elif typ == oPB.MsgEnum.MS_STAT: self.msgSend.emit(msgtext) elif typ == oPB.MsgEnum.MS_ALWAYS: QMessageBox.information(parent, translate("mainController", "Message"), msgtext, QMessageBox.Ok) elif typ == oPB.MsgEnum.MS_PARSE: return msgtext elif typ == oPB.MsgEnum.MS_QUEST_YESNO: retval = QMessageBox.question( parent, translate("mainController", "Question"), msgtext, QMessageBox.Yes, QMessageBox.No) if retval == QMessageBox.No: return False else: return True elif typ == oPB.MsgEnum.MS_QUEST_CTC: msgBox = QMessageBox(parent) msgBox.setWindowTitle(translate("mainController", "Question")) msgBox.setText(msgtext) msgBox.setIcon(QMessageBox.Question) cancelBtn = QPushButton(translate("mainController", "Cancel")) rebuildBtn = QPushButton(translate("mainController", "Rebuild")) addBtn = QPushButton(translate("mainController", "Add version")) msgBox.addButton(cancelBtn, QMessageBox.RejectRole) msgBox.addButton(rebuildBtn, QMessageBox.AcceptRole) msgBox.addButton(addBtn, QMessageBox.AcceptRole) msgBox.exec_() if msgBox.clickedButton() == cancelBtn: return 0 elif msgBox.clickedButton() == rebuildBtn: return 1 else: return 2 elif typ == oPB.MsgEnum.MS_QUEST_OKCANCEL: retval = QMessageBox.question( parent, translate("mainController", "Question"), msgtext, QMessageBox.Ok, QMessageBox.Cancel) if retval == QMessageBox.Cancel: return False else: return True elif typ == oPB.MsgEnum.MS_QUEST_PHRASE: text = QInputDialog.getText( parent, translate("mainController", "Additional information"), msgtext, QLineEdit.Normal, preload) return text elif typ == oPB.MsgEnum.MS_QUEST_PASS: text = QInputDialog.getText( parent, translate("mainController", "Additional information"), msgtext, QLineEdit.Password, preload) return text elif typ == oPB.MsgEnum.MS_QUEST_DEPOT: preselectlist = [ i for i, j in enumerate(msgtext) if ConfigHandler.cfg.opsi_server in j ] if preselectlist: preselect = preselectlist[0] else: preselect = -1 item = QInputDialog.getItem( parent, translate("mainController", "Question"), translate("mainController", "Select which depot to use:"), msgtext, preselect, False) return item elif typ == oPB.MsgEnum.MS_ABOUTQT: QMessageBox.aboutQt(parent, translate("mainController", "About Qt"))
def _processAction(self, cmd, action, retval): self.logger.sshinfo("Processing action...") # ------------------------------------------------------------------------------------------------------------------------ if self.ret == oPB.RET_OK: # hook into stderr for progress analysing old_stderr = sys.stderr s = AnalyseProgressHook(self, old_stderr) # sys.stdout = s try: with self.shell: try: self.logger.sshinfo("Trying to execute command: " + self._obscurepass(cmd)) if action in [oPB.OpEnum.DO_INSTALL, oPB.OpEnum.DO_QUICKINST, oPB.OpEnum.DO_INSTSETUP, oPB.OpEnum.DO_UPLOAD, oPB.OpEnum.DO_UNINSTALL, oPB.OpEnum.DO_QUICKUNINST]: result = self.shell.run(cmd.split(), cwd = self.control.path_on_server, update_env = self._env, allow_error = True, stderr = s, use_pty = True ) else: result = self.shell.run(cmd.split(), cwd = self.control.path_on_server, update_env = self._env, allow_error = True, stderr = s ) # Log standard out out = Helper.strip_ansi_codes(result.output.decode(encoding='UTF-8')).splitlines() for line in out: line = self._obscurepass(line) self.logger.ssh(line) # Log standard error out = Helper.strip_ansi_codes(result.stderr_output.decode(encoding='UTF-8')).splitlines() for line in out: line = self._obscurepass(line) self.logger.sshinfo(line) isErr = self.hasErrors(line) if isErr[0]: self.ret = retval self.rettype = oPB.MsgEnum.MS_ERR self.retmsg = isErr[1] except spur.NoSuchCommandError: self.logger.error("Set return code to RET_SSHCMDERR") self.ret = oPB.RET_SSHCMDERR self.rettype = oPB.MsgEnum.MS_ERR self.retmsg = translate("OpsiProcessing", "Command not found. See Log for details.") except ConnectionError as error: self.logger.error(repr(error).replace("\\n"," --> ")) self.logger.error("Set return code to RET_SSHCONNERR") self.ret = oPB.RET_SSHCONNERR self.rettype = oPB.MsgEnum.MS_ERR self.retmsg = translate("OpsiProcessing", "Error establishing SSH connection. See Log for details.") # reset hook state #sys.stdout = old_stderr return result.output.decode(encoding='UTF-8') else: return {}