def start_threads(self): self.log.append('starting {} threads'.format(self.NUM_THREADS)) self.button_start_threads.setDisabled(True) self.button_stop_threads.setEnabled(True) self.__workers_done = 0 self.__threads = [] for idx in range(self.NUM_THREADS): worker = Worker(idx) thread = QThread() thread.setObjectName('thread_' + str(idx)) self.__threads.append((thread, worker)) # need to store worker too otherwise will be gc'd worker.moveToThread(thread) # get progress messages from worker: worker.sig_step.connect(self.on_worker_step) worker.sig_done.connect(self.on_worker_done) worker.sig_msg.connect(self.log.append) # control worker: self.sig_abort_workers.connect(worker.abort) # get read to start worker: # self.sig_start.connect(worker.work) # needed due to PyCharm debugger bug (!); comment out next line thread.started.connect(worker.work) thread.start() # this will emit 'started' and start thread's event loop
def start_threads_and_send_mails(self): mails_to_send = [] for i in range(self.listWidget_emails.count()): item = self.listWidget_emails.item(i) if item.checkState(): item.setSelected(True) xlsx_row = item.xlsx_row # Сохраняем номер строки, чтобы потом легко пометить её зелёным xlsx_row['QListWidgetIndex_WcCRve89'] = i mails_to_send.append(item.xlsx_row) if not mails_to_send: msg = 'Ни одно письмо для отправки не выбрано' QMessageBox.information(self.parent, 'OK', msg) raise Exception(msg) # Создаём по отправляльщику на каждый worker # Равномерно распределяем на них на всех почту self.USE_THREADS = min(self.NUM_THREADS, len(mails_to_send)) envelopes = [self.envelope.copy() for __ in range(self.USE_THREADS)] for i, xlsx_row in enumerate(mails_to_send): envelopes[i % self.USE_THREADS].add_mail_to_queue( recipients=xlsx_row['email'], subject=xlsx_row['subject'], html=self.template.format(**xlsx_row), files=xlsx_row['attach_list'], xls_id=xlsx_row[ORIGINAL_ROW_NUM], qt_id=xlsx_row['QListWidgetIndex_WcCRve89']) # Лочим кнопки self.pushButton_ask_and_send.setDisabled(True) self.pushButton_open_list_and_template.setDisabled(True) self.pushButton_cancel_send.setEnabled(True) # Готовим worker'ов self.__workers_done = 0 self.__threads = [] for idx in range(self.USE_THREADS): worker = Worker(idx, envelopes[idx]) thread = QThread() thread.setObjectName('thread_' + str(idx)) self.__threads.append( (thread, worker)) # need to store worker too otherwise will be gc'd worker.moveToThread(thread) # get progress messages from worker: worker.sig_step.connect(self.on_worker_step) worker.sig_done.connect(self.on_worker_done) worker.sig_mail_sent.connect(self.on_mail_sent) worker.sig_mail_error.connect(self.on_mail_error) # control worker: self.sig_abort_workers.connect(worker.abort) # get read to start worker: thread.started.connect(worker.work) thread.start( ) # this will emit 'started' and start thread's event loop
class ProcessController(QObject): processBeanChanged = Signal() settingsBeanChanged = Signal() closingApplicationSignal = Signal() showDialogSignal = Signal(str, arguments=['message']) # laserFolderWatcherConnectedSignal = Signal(bool, arguments=['isConnected']) def __init__(self, parent=None): super().__init__(parent) self.__processBean: ProcessBean = None self.__settingsBean: SettingsBean = None self.__ftpWatcherThread: QThread = None self.__ftpWatcher: FTPWatcher = None self.__sshWatcher: SSHWatcher = None self.__fsWatcher: FSWatcher = None self.__fsWatcherThread: QThread = None self.__csvRegeneratorThread = CSVRegenerator() self.setupSignalsAndSlots() def getProcessBean(self): return self.__processBean def setProcessBean(self, bean: ProcessBean): self.__processBean = bean self.processBeanChanged.emit() def getSettingsBean(self): return self.__settingsBean def setSettingsBean(self, bean: SettingsBean): self.__settingsBean = bean self.settingsBeanChanged.emit() pProcessBean = Property(ProcessBean, getProcessBean, setProcessBean, notify=processBeanChanged) pSettingsBean = Property(SettingsBean, getSettingsBean, setSettingsBean, notify=settingsBeanChanged) def setupSignalsAndSlots(self): self.closingApplicationSignal.connect(self.freeResources) @Slot() def freeResources(self): if isinstance(self.__ftpWatcherThread, QThread): if self.__processBean.isLaserWatcherRunning(): Logger().debug("Stop laser watcher thread") QMetaObject.invokeMethod(self.__ftpWatcher, "stopProcess") if isinstance(self.__fsWatcherThread, QThread): if self.__processBean.isCameraWatcherRunning(): Logger().debug("Stop camera watcher thread") QMetaObject.invokeMethod(self.__fsWatcher, "stopProcess") @Slot() def initBean(self): QTimer.singleShot(2000, self.startCameraWatcher) QTimer.singleShot(2500, self.startLaserWatcher) @Slot(None) def saveParameters(self): s = Settings() s.saveCurrentParameters() @Slot(str) def setLocalLoadingPath(self, path: str): path = self.handleUrlPath(path) Logger().debug("Impostato loading path: " + path) self.__settingsBean.setLocalLoadingPath(path) @Slot(str) def setLocalDownloadingPath(self, path: str): path = self.handleUrlPath(path) Logger().debug("Impostato downloading path: " + path) self.__settingsBean.setLocalDownloadingPath(path) @Slot(str) def setCameraRemotePath(self, path: str): path = self.handleUrlPath(path) Logger().debug("Impostato camera path: " + path) self.__settingsBean.setCameraRemotePath(path) def handleUrlPath(self, path: str) -> str: url = QUrl(path) pathOk = "" if url.isLocalFile(): pathOk = QDir.toNativeSeparators(url.toLocalFile()) else: pathOk = path return pathOk @Slot(str, result=str) def getUrlFromNativePath(self, path: str): path = QDir.fromNativeSeparators(path) url = QUrl.fromLocalFile(path) res = str(url.toString()) return res @Slot(None) def startLaserWatcher(self): Logger().debug("Start laser watcher thread") self.__ftpWatcherThread = QThread() self.__ftpWatcherThread.setObjectName("CSVWatcherThread") self.__ftpWatcher = FTPWatcher( remotePath=self.__settingsBean.getLaserRemotePath(), ftpAddress=self.__settingsBean.getLaserIp(), intervalMs=self.__settingsBean.getLaserPollingTimeMs(), ftpPort=self.__settingsBean.getLaserPort()) self.__ftpWatcher.moveToThread(self.__ftpWatcherThread) self.__ftpWatcherThread.started.connect(self.__ftpWatcher.startProcess) self.__ftpWatcher.itemsPathUpdatedSignal.connect( lambda items: (self.__processBean.setLaserFolderItems(items), self.analizeFolderItems())) # self.__csvWatcher.isConnectedSignal.connect(self.laserFolderWatcherConnectedSignal) self.__ftpWatcher.isConnectedSignal.connect( lambda isConnected: self.__processBean.setLaserConnectionUp( isConnected)) self.__ftpWatcher.startedSignal.connect( lambda: self.__processBean.setLaserWatcherRunning(True)) self.__ftpWatcher.stoppedSignal.connect( lambda: self.__processBean.setLaserWatcherRunning(False)) self.__ftpWatcher.stoppedSignal.connect(self.__ftpWatcherThread.quit) self.__ftpWatcherThread.finished.connect( self.__ftpWatcherThread.deleteLater) self.__ftpWatcherThread.finished.connect(self.__ftpWatcher.deleteLater) Logger().debug("Laser watcher thread avviato") self.__ftpWatcherThread.start() @Slot(None) def stopLaserWatcher(self): Logger().debug("Stop laser watcher thread") QMetaObject.invokeMethod(self.__ftpWatcher, "stopProcess") @Slot(None) def startCameraWatcher(self): Logger().debug("Start camera watcher thread") self.__fsWatcherThread = QThread() self.__fsWatcherThread.setObjectName("FSWatcherThread") self.__fsWatcher = FSWatcher( path=self.__settingsBean.getCameraRemotePath(), intervalMs=self.__settingsBean.getCameraPollingTimeMs()) self.__fsWatcher.moveToThread(self.__fsWatcherThread) self.__fsWatcherThread.started.connect(self.__fsWatcher.startProcess) self.__fsWatcher.itemsPathUpdatedSignal.connect( lambda items: self.__processBean.setCameraFolderItems(items)) self.__fsWatcher.isConnectedSignal.connect( lambda isConnected: self.__processBean.setCameraConnectionUp( isConnected)) self.__fsWatcher.startedSignal.connect( lambda: self.__processBean.setCameraWatcherRunning(True)) self.__fsWatcher.stoppedSignal.connect( lambda: self.__processBean.setCameraWatcherRunning(False)) self.__fsWatcher.stoppedSignal.connect(self.__fsWatcherThread.quit) self.__fsWatcherThread.finished.connect( self.__fsWatcherThread.deleteLater) self.__fsWatcherThread.finished.connect(self.__fsWatcher.deleteLater) Logger().debug("Camera watcher thread avviato") self.__fsWatcherThread.start() @Slot(None) def stopCameraWatcher(self): Logger().debug("Stop camera watcher thread") QMetaObject.invokeMethod(self.__fsWatcher, "stopProcess") @Slot(None) def sendCsvFileToDevices(self): localloadingPath = self.__settingsBean.getLocalLoadingPath() csvFilename = self.__settingsBean.getLocalCsvFilename() csvLocalPath = localloadingPath + "\\" + csvFilename cameraPath = self.__settingsBean.getCameraRemotePath() csvCameraPath = cameraPath + "\\" + csvFilename if not os.path.exists(csvLocalPath): Logger().error("File " + csvLocalPath + " non trovato") self.showDialogSignal.emit( "Nessun file trovato nella cartella locale") return # invio file csv al laser Logger().info("Invio file " + csvFilename + " al laser") ftpController = FTP() ftpController.host = self.__settingsBean.getLaserIp() ftpController.port = self.__settingsBean.getLaserPort() laserFTPRemotePath = self.__settingsBean.getLaserRemotePath() try: ftpController.connect() ftpController.login() ftpController.cwd(laserFTPRemotePath) cmd = "STOR " + csvFilename Logger().debug("Comando: " + cmd) fp = open(csvLocalPath, "rb") ftpController.storbinary(cmd, fp) except ftplib.all_errors as ftpErr: Logger().error("Error on FTP:" + str(ftpErr)) ftpController.close() self.showDialogSignal.emit("Errore invio file csv al laser") return if self.__settingsBean.getCameraSendCSV(): # invio il file csv alla camera Logger().info("Invio file " + csvFilename + " alla camera") try: shutil.copy(csvLocalPath, csvCameraPath) except: Logger().error("Impossibile copiare il file csv in camera") self.showDialogSignal.emit("Errore invio file csv alla camera") return self.showDialogSignal.emit("Invio OK al laser e camera") else: Logger().info("Invio file " + csvFilename + " alla camera non richiesto") self.showDialogSignal.emit("Invio OK al laser") self.__processBean.setCsvRegThreadCsvNewEmpty(False) @Slot(None) def removeCsvFileFromDevices(self): Logger().info("Rimozione file cartella laser") ftpController = FTP() ftpController.host = self.__settingsBean.getLaserIp() ftpController.port = self.__settingsBean.getLaserPort() laserFTPRemotePath = self.__settingsBean.getLaserRemotePath() cameraPath = self.__settingsBean.getCameraRemotePath() csvFilename = self.__settingsBean.getLocalCsvFilename() csvCameraPath = cameraPath + "\\" + csvFilename try: ftpController.connect() ftpController.login() ftpController.cwd(laserFTPRemotePath) Logger().info("Rimozione file: " + csvFilename) ftpController.delete(csvFilename) except ftplib.all_errors as ftpErr: Logger().error("Errore rimozione file: " + str(ftpErr)) self.showDialogSignal.emit( "Errore cancellazione file csv dal laser - " + str(ftpErr)) finally: ftpController.close() try: if os.path.exists(csvCameraPath): Logger().debug("Rimozione file .csv dalla camera") os.remove(csvCameraPath) except OSError as err: if err != errno.ENOENT: Logger().error("Errore rimozione file dalla camera") self.showDialogSignal.emit( "Errore cancellazione file csv dalla camera") Logger().info("Rimozione file cartella laser OK") @Slot() def removeErrorFileFromLaser(self): Logger().info("Rimozione file error dal laser") ftpController = FTP() ftpController.host = self.__settingsBean.getLaserIp() ftpController.port = self.__settingsBean.getLaserPort() laserFTPRemotePath = self.__settingsBean.getLaserRemotePath() errorFilename = self.__settingsBean.getLocalLaserErrorFilename() try: ftpController.connect() ftpController.login() ftpController.cwd(laserFTPRemotePath) Logger().info("Rimozione file: " + errorFilename) ftpController.delete(errorFilename) except ftplib.all_errors as ftpErr: Logger().error("Errore rimozione file: " + str(ftpErr)) self.showDialogSignal.emit( "Errore cancellazione file csv dal laser - " + str(ftpErr)) finally: ftpController.close() Logger().info("Rimozione file error dal laser") @Slot(bool) def changeStopRequestValue(self, stop: bool): self.__csvRegeneratorThread.setStopRequest(stop) self.__processBean.setStopRegThread(stop) @Slot(bool) def changePauseRequestValue(self, pause: bool): self.__csvRegeneratorThread.setPauseRequest(pause) self.__processBean.setPauseRegThread(pause) def analizeFolderItems(self): # Logger().debug("Analisi lista file laser") items = self.getProcessBean().getLaserFolderItems() isErrorFounded = False isCsvFounded = False for item in items: if item.lower() == self.__settingsBean.getLocalLaserErrorFilename( ).lower(): # Logger().debug("Trovato error file: "+self.__settingsBean.getLocalLaserErrorFilename().lower()) isErrorFounded = True if item.lower() == self.__settingsBean.getLocalCsvFilename().lower( ): # Logger().debug("Trovato csv file: "+self.__settingsBean.getLocalCsvFilename().lower()) isCsvFounded = True if isCsvFounded and isErrorFounded: break self.__processBean.setErrorFileFounded(isErrorFounded) if self.__csvRegeneratorThread.isRunning(): return if self.__processBean.isStopRegThread(): return if isCsvFounded and isErrorFounded: Logger().debug( "Trovato error file: " + self.__settingsBean.getLocalLaserErrorFilename().lower()) Logger().debug("Trovato csv file: " + self.__settingsBean.getLocalCsvFilename().lower()) Logger().debug("Avvio thread per rigenerazione file") self.__csvRegeneratorThread.setLaserFileList(items) self.__csvRegeneratorThread.setLaserConnectionParameters( self.__settingsBean.getLaserIp(), self.__settingsBean.getLaserPort(), self.__settingsBean.getLaserRemotePath()) self.__csvRegeneratorThread.setLocalWaitTimeBeforeStart( self.__settingsBean.getLocalWaitTimeBeforeProcess()) self.__csvRegeneratorThread.setLocalLoadingPath( self.__settingsBean.getLocalLoadingPath()) self.__csvRegeneratorThread.setLocalDownloadingPath( self.__settingsBean.getLocalDownloadingPath()) self.__csvRegeneratorThread.setCameraConnectionParameters( self.__settingsBean.getCameraRemotePath()) self.__csvRegeneratorThread.setCameraSendCSV( self.__settingsBean.getCameraSendCSV()) self.__csvRegeneratorThread.setCsvFilename( self.__settingsBean.getLocalCsvFilename()) self.__csvRegeneratorThread.setErrorFilename( self.__settingsBean.getLocalLaserErrorFilename()) self.__csvRegeneratorThread.setLogFilename( self.__settingsBean.getLocalLaserLogFilename()) self.__csvRegeneratorThread.setRowMargin( self.__settingsBean.getLocalRowMargin()) self.__csvRegeneratorThread.cleanLocalFolderStepSignal.connect( lambda value: self.__processBean. setCsvRegThreadCleanLocalFolderStatus(value)) self.__csvRegeneratorThread.cleanCameraFolderStepSignal.connect( lambda value: self.__processBean. setCsvRegThreadCleanCameraFolderStatus(value)) self.__csvRegeneratorThread.renameLaserItemsStepSignal.connect( lambda value: self.__processBean. setCsvRegThreadRenameLaserItemsStatus(value)) self.__csvRegeneratorThread.downloadItemsFromLaserStepSignal.connect( lambda value: self.__processBean. setCsvRegThreadDownloadItemsStatus(value)) self.__csvRegeneratorThread.cleanLaserFolderStepSignal.connect( lambda value: self.__processBean. setCsvRegThreadCleanLaserFolderStatus(value)) self.__csvRegeneratorThread.csvBuildProcessStepSignal.connect( lambda value: self.__processBean. setCsvRegThreadCsvCreationProcessStatus(value)) self.__csvRegeneratorThread.sendCsvToLaserStepSignal.connect( lambda value: self.__processBean. setCsvRegThreadSendCsvToLaserStatus(value)) self.__csvRegeneratorThread.sendCsvToCameraStepSignal.connect( lambda value: self.__processBean. setCsvRegThreadSendCsvToCameraStatus(value)) self.__csvRegeneratorThread.exitCodeSignal.connect( lambda value: self.__processBean.setCsvRegThreadExitCode(value )) self.__csvRegeneratorThread.started.connect( lambda: self.__processBean.setCsvRegThreadRunning(True)) self.__csvRegeneratorThread.finished.connect( lambda: self.__processBean.setCsvRegThreadRunning(False)) self.__csvRegeneratorThread.threadPausedSignal.connect( lambda value: self.__processBean.setCsvRegThreadPause(value)) self.__csvRegeneratorThread.cvsNewFileEmptySignal.connect( lambda isEmpty: self.__processBean.setCsvRegThreadCsvNewEmpty( isEmpty)) self.__csvRegeneratorThread.start()
class DevicesView(QTreeView): def __init__(self, *args, **kwargs): super().__init__() self.setModel(DevicesModel()) self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.customContextMenuRequested.connect(self.context_menu_handler) self.reload() self.device_monitor = DeviceMonitor() self.thread = QThread(self) self.device_monitor.device_signal.connect( self.device_signal__handler ) # set handler for device_monitor "device_signal" event self.device_monitor.moveToThread(self.thread) self.thread.setObjectName('DeviceMonitorThread') self.thread.started.connect(self.device_monitor.monitor_devices ) # set handler for thread "started" event self.thread.start() @Slot(object) # handler def device_signal__handler(self, device): action = device.action logging.debug(' '.join( ['handling device event:', str(action), str(device)])) device_path = device.sys_path if not device_path.startswith('/sys/devices/'): return elif action == 'add': item = self.model().insert(device.sys_path) self.set_current_item(item) elif action == 'remove': current_device_path = self.get_current_device() parent_item = self.model().remove(device.sys_path) if current_device_path.startswith(device_path): self.set_current_index(parent_item.index()) def reload(self): current_device_path = self.get_current_device() self.model().reload() if current_device_path is not None: self.set_current_device(current_device_path) def currentChanged(self, current, previous): QTreeView.currentChanged(self, current, previous) device_path = self.model().itemFromIndex(current).data() logging.debug('device selected: ' + device_path) device_props_tree_widget.clear() device_props_tree_widget.addTopLevelItems([ item for item in iter_device_props_tree_items( device_path, devices_dict[device_path]) ]) device_props_tree_widget.expandAll() if self.history_push_flag: self.history_push(device_path) self.history_push_flag = True def get_current_device(self): current_index = self.currentIndex() if current_index is not None: current_item = self.model().itemFromIndex(current_index) if current_item is not None: return current_item.data() def set_current_index(self, index): self.setCurrentIndex(index) self.scrollTo(index) # scrollTo() doesn't expand everything (bug?), thus have to expand() every ancestor explicitly def set_current_item(self, item): parent = item.parent() while parent is not None: self.expand(parent.index()) parent = parent.parent() index = item.index() self.set_current_index(index) def set_current_device(self, device_path): item = items_dict[device_path] self.set_current_item(item) def context_menu_handler(self, point): device_path = self.get_current_device() context_menu = QMenu() action_copy_path = context_menu.addAction('Copy path') device_listdir = os.listdir(device_path) if 'firmware_node' in device_listdir: action_to_firmware = context_menu.addAction('Go to firmware node') elif 'physical_node' in device_listdir: action_to_physical = context_menu.addAction('Go to physical node') # plugins which extend device actions menu must implement add_device_actions(device, menu, tuples) method # it should add actions to the menu object # and append (action, handler, handler_args, handler_kwargs) to the tuples list # tuples list is then used to check for chosen action and to call matching handler with args plugins_actions_tuples = [] device = devices_dict[device_path] for plugin in [plugins.attach_to_vm]: if 'add_device_actions' in dir(plugin): plugin.add_device_actions(device, context_menu, plugins_actions_tuples) chosen_action = context_menu.exec_(self.mapToGlobal(point)) if chosen_action == action_copy_path: clipboard.setText(device_path) elif 'firmware_node' in device_listdir and chosen_action is action_to_firmware: target_device_path = os.path.abspath( os.path.join( device_path, os.readlink(os.path.join(device_path, 'firmware_node')))) self.set_current_device(target_device_path) elif 'physical_node' in device_listdir and chosen_action is action_to_physical: target_device_path = os.path.abspath( os.path.join( device_path, os.readlink(os.path.join(device_path, 'physical_node')))) self.set_current_device(target_device_path) else: for action, handler, args, kwargs in plugins_actions_tuples: if chosen_action == action: handler(device, *args, **kwargs) break # QTreeView doesn't seem to have any navigation history logic, thus implement our own # Provides signal for external widgets like toolbar nav buttons to update state when history state changes history_list = [] history_index = 0 history_push_flag = True history_back_flag = False history_forward_flag = False history_signal = Signal(bool, bool) def history_push(self, device_path): self.history_list = self.history_list[self.history_index:self. history_index + 16] self.history_list.insert(0, device_path) self.history_index = 0 self.history_forward_flag = False if len(self.history_list) > 1: self.history_back_flag = True self.history_signal.emit(self.history_back_flag, self.history_forward_flag) def history_back(self): # TODO: device can be already gone self.history_push_flag = False self.history_index += 1 self.set_current_device(self.history_list[self.history_index]) self.history_forward_flag = True if self.history_index == len(self.history_list) - 1: self.history_back_flag = False self.history_signal.emit(self.history_back_flag, self.history_forward_flag) def history_forward(self): self.history_push_flag = False self.history_index -= 1 self.set_current_device(self.history_list[self.history_index]) self.history_back_flag = True if self.history_index == 0: self.history_forward_flag = False self.history_signal.emit(self.history_back_flag, self.history_forward_flag)
@Slot() def update_app_language(): app.installTranslator(translator.translator) engine.retranslate() if __name__ == '__main__': # os.environ['QT_QUICK_CONTROLS_CONF'] = 'resources/qtquickcontrols2.conf' app = QGuiApplication(sys.argv) app.instance().thread().setObjectName('MainThread') worker_manager = WorkerManager() worker_interface_thread = QThread() worker_interface_thread.setObjectName("WorkerInterfaceThread") worker_interface = WorkerInterface(start_signal=worker_manager.start, stop_signal=worker_manager.stop) worker_interface.msg_from_job.connect(worker_manager.receive_msg) worker_interface.moveToThread(worker_interface_thread) worker_interface_thread.start() viewer = Viewer() translator = Translator() translator.updateAppLanguage.connect(update_app_language) qml_file = os.path.abspath( os.path.join(os.path.dirname(__file__), 'main.qml')) engine = QQmlApplicationEngine()