class NexEventPoller(QRunnable): def __init__(self, device: NexDevice, poll_interval_ms: int): super().__init__() self._device = device self._logger = logging.getLogger("pynextion.NexEventPoller") self._run_poll_loop = True self._run_poll_loop_lock = QReadWriteLock() self._thread_local = threading.local() self._poll_interval_ms = poll_interval_ms def stop(self): self._logger.info("Stopping Nextion event poller loop") try: self._run_poll_loop_lock.lockForWrite() self._run_poll_loop = False finally: self._run_poll_loop_lock.unlock() def run(self): self._logger.info( "Starting Nextion event poller with a %d [ms] interval", self._poll_interval_ms) while True: try: self._run_poll_loop_lock.lockForRead() self._thread_local.run = self._run_poll_loop finally: self._run_poll_loop_lock.unlock() if not self._thread_local.run: self._logger.info("Exiting Nextion event poller loop") break self._device.poll() QThread.msleep(self._poll_interval_ms)
class CSerWind(QMainWindow): """ 主窗口 """ def __init__(self, inp_host, inp_port): super().__init__() self.lock = QReadWriteLock() self.recordSignal = RecordSignal() self.recordSignal.sendSignal.connect(self.my_record) self.tcpServer = TcpServer(self.recordSignal, self.lock) if not self.tcpServer.listen(QHostAddress(inp_host), inp_port): QMessageBox.critical( self, '交易服务器', '服务器启动失败:{0}'.format(self.tcpServer.errorString())) rec_text = my_cur_time() + ' 服务器启动失败!' try: self.lock.lockForWrite() self.recordSignal.sendSignal.emit(rec_text) finally: self.lock.unlock() self.close() return else: self._initUI() rec_text = my_cur_time() + ' 开启交易服务器!' try: self.lock.lockForWrite() self.recordSignal.sendSignal.emit(rec_text) finally: self.lock.unlock() def _initUI(self): self.setFixedSize(600, 400) self.move(0, 60) self.setWindowTitle('服务器:交易跟单——复制信号') self.setWindowIcon(QIcon('myIcon.ico')) # TableView设置MT4账号及Files文件夹路径 self.model = QStandardItemModel(2, 2) self.model.setHorizontalHeaderLabels(['MT4账号', 'Files文件夹路径']) row = 0 for key, value in account_dir.items(): self.model.setItem(row, 0, QStandardItem(key)) self.model.setItem(row, 1, QStandardItem(value)) row += 1 self.table = QTableView(self) self.table.setModel(self.model) self.table.resize(500, 180) self.table.move(10, 15) self.table.horizontalHeader().setStretchLastSection(True) tbn_append = QPushButton('添 加', self) tbn_append.resize(70, 25) tbn_append.move(520, 30) tbn_append.clicked.connect(self.my_btn_append_clicked) btn_delete = QPushButton('删 除', self) btn_delete.resize(70, 25) btn_delete.move(520, 90) btn_delete.clicked.connect(self.my_btn_delete_clicked) btn_save = QPushButton('保 存', self) btn_save.resize(70, 25) btn_save.move(520, 150) btn_save.clicked.connect(self.my_btn_save_clicked) # 逐条显示交易服务器操作的每个记录 self.text_browser = QTextBrowser(self) self.text_browser.resize(580, 180) self.text_browser.move(10, 210) self.show() def closeEvent(self, event): reply = QMessageBox.question(self, '操作提示!', '您确定要关闭“交易服务器”?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: rec_text = my_cur_time() + ' 关闭交易服务器!' try: self.lock.lockForWrite() self.recordSignal.sendSignal.emit(rec_text) finally: self.lock.unlock() event.accept() else: event.ignore() def keyPressEvent(self, e): if e.key() == Qt.Key_Escape: self.showMinimized() def my_btn_append_clicked(self): """ 增加一个MT4账户 """ self.model.appendRow([QStandardItem(''), QStandardItem('')]) def my_btn_delete_clicked(self): """ 删除当前行的MT4账号信息 """ reply = QMessageBox.question(self, '操作提示!', '确定要删除这条数据?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: index = self.table.currentIndex() self.model.removeRow(index.row()) def my_btn_save_clicked(self): """ 保存MT4账户信息到account_dir.txt文件中 """ reply = QMessageBox.question(self, '操作提示!', '确定要覆盖原有数据?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: account_dir_new = {} rows = self.model.rowCount() for row in range(rows): key = self.model.item(row, 0).text() # 对MT4账号进行正则表达式匹配检查 if not re.match(r'^[1-9]\d+$', key): QMessageBox.critical( self, '错误提示!', self.tr('第 %s 行MT4账号格式不对!' % str(row + 1))) return value = self.model.item(row, 1).text() if not os.path.exists(value) or value.find('\MQL4\Files') < 0: QMessageBox.critical( self, '错误提示!', self.tr('第 %s 行Files文件路径不对!' % str(row + 1))) return account_dir_new[key] = value # 保存文件 try: account_dir = account_dir_new with open('account_dir.txt', 'w') as file_object: file_object.write(str(account_dir)) except Exception as e: QMessageBox.critical( self, '错误提示!', self.tr('保存MT4账户信息出现错误!{0}'.format(str(Exception)))) # 记录这一操作 rec_text = my_cur_time() + ' 已经保存了当前列表中MT4账户信息!' try: self.lock.lockForWrite() self.recordSignal.sendSignal.emit(rec_text) finally: self.lock.unlock() def my_record(self, inp_text): """ 记录服务器操作日志保存到日志文件,并在主窗口TextBrowser组件中显示出来 """ now = time.localtime() file_name = 'ser_' + time.strftime('%Y-%m-%d', now) + '.txt' rec_text = inp_text + '\n' with open(file_name, 'a+', encoding='UTF-8') as file_object: file_object.write(rec_text) # text_browser只显示当天的操作记录 pre_text = self.text_browser.toPlainText() if len(pre_text) > 0 and pre_text[0:10] != inp_text[0:10]: self.text_browser.setText('') self.text_browser.append(inp_text)
class __Config(object): def __init__(self, debug=False): self.debug = debug self._dict = dict() self._prepare_dir() self._remove_tmps() self._lock = QReadWriteLock() def __getitem__(self, key): self._lock.lockForRead() r = self._dict[key] self._lock.unlock() return r def __setitem__(self, key, value): self._lock.lockForWrite() self._dict[key] = value self._lock.unlock() def get(self, key, default=None): self._lock.lockForRead() r = self._dict.get(key, default) self._lock.unlock() return r def pop(self, key, default=None): self._lock.lockForWrite() r = self._dict.pop(key, default) self._lock.unlock() return r def __contains__(self, key): self._lock.lockForRead() r = self._dict.__contains__(key) self._lock.unlock() return r def __str__(self): self._lock.lockForRead() s = "".join( [ "Config(", ", ".join("{0}: {1}".format(k, v) for (k, v) in self._dict), ")", ] ) self._lock.unlock() return s @property def _config_dir(self): # Windows if sys.platform.startswith("win"): if "LOCALAPPDATA" in os.environ: return os.path.join(os.environ["LOCALAPPDATA"], "LDOCE5Viewer") else: return os.path.join(os.environ["APPDATA"], "LDOCE5Viewer") # Mac OS X elif sys.platform.startswith("darwin"): return os.path.expanduser("~/Library/Application Support/LDOCE5Viewer") # Linux else: base = os.path.join(os.path.expanduser("~"), ".config") # XDG try: import xdg.BaseDirectory base = xdg.BaseDirectory.xdg_config_home except ImportError: if "XDG_CONFIG_HOME" in os.environ: base = os.environ["XDG_CONFIG_HOME"] return os.path.join(base, "ldoce5viewer") @property def _data_dir(self): # Windows if sys.platform.startswith("win"): return self._config_dir # Mac OS X elif sys.platform.startswith("darwin"): return self._config_dir # Linux else: base = os.path.join(os.path.expanduser("~"), ".local/share/") # XDG try: import xdg.BaseDirectory base = xdg.BaseDirectory.xdg_data_home except ImportError: if "XDG_DATA_HOME" in os.environ: base = os.environ["XDG_DATA_HOME"] return os.path.join(base, "ldoce5viewer") @property def app_name(self): return "LDOCE5 Viewer" @property def _config_path(self): return os.path.join(self._config_dir, "config.pickle") @property def filemap_path(self): return os.path.join(self._data_dir, "filemap.cdb") @property def variations_path(self): return os.path.join(self._data_dir, "variations.cdb") @property def incremental_path(self): return os.path.join(self._data_dir, "incremental.db") @property def fulltext_hwdphr_path(self): return os.path.join(self._data_dir, "fulltext_hp") @property def fulltext_defexa_path(self): return os.path.join(self._data_dir, "fulltext_de") @property def scan_tmp_path(self): return os.path.join(self._data_dir, "scan" + self.tmp_suffix) @property def tmp_suffix(self): return ".tmp" def _remove_tmps(self): for name in os.listdir(self._config_dir) + os.listdir(self._data_dir): if name.endswith(self.tmp_suffix): path = os.path.join(self._config_dir, name) try: if os.path.isfile(path): os.remove(path) elif os.path.isdir(path): shutil.rmtree(path) except OSError: pass def _prepare_dir(self): if not os.path.exists(self._config_dir): os.makedirs(self._config_dir) if not os.path.exists(self._data_dir): os.makedirs(self._data_dir) def load(self): self._lock.lockForWrite() try: with open(self._config_path, "rb") as f: self._dict.clear() try: data = pickle.load(f) except: pass else: self._dict.update(data) except IOError: self._dict.clear() self._lock.unlock() def save(self): self._lock.lockForRead() if sys.platform == "win32": with open(self._config_path, "wb") as f: pickle.dump(self._dict, f) else: f = tempfile.NamedTemporaryFile( dir=self._config_dir, delete=False, suffix=self.tmp_suffix ) pickle.dump(self._dict, f, protocol=0) f.close() os.rename(f.name, self._config_path) self._lock.unlock()
class __Config(object): def __init__(self, debug=False): self.debug = debug self._dict = dict() self._prepare_dir() self._remove_tmps() self._lock = QReadWriteLock() def __getitem__(self, key): self._lock.lockForRead() r = self._dict[key] self._lock.unlock() return r def __setitem__(self, key, value): self._lock.lockForWrite() self._dict[key] = value self._lock.unlock() def get(self, key, default=None): self._lock.lockForRead() r = self._dict.get(key, default) self._lock.unlock() return r def pop(self, key, default=None): self._lock.lockForWrite() r = self._dict.pop(key, default) self._lock.unlock() return r def __contains__(self, key): self._lock.lockForRead() r = self._dict.__contains__(key) self._lock.unlock() return r def __str__(self): self._lock.lockForRead() s = ''.join([ 'Config(', ', '.join("{0}: {1}".format(k, v) for (k, v) in self._dict), ')' ]) self._lock.unlock() return s @property def _config_dir(self): # Windows if sys.platform.startswith('win'): if 'LOCALAPPDATA' in os.environ: return os.path.join(os.environ['LOCALAPPDATA'], 'LDOCE5Viewer') else: return os.path.join(os.environ['APPDATA'], 'LDOCE5Viewer') # Mac OS X elif sys.platform.startswith('darwin'): return os.path.expanduser( '~/Library/Application Support/LDOCE5Viewer') # Linux else: base = os.path.join(os.path.expanduser('~'), '.config') # XDG try: import xdg.BaseDirectory base = xdg.BaseDirectory.xdg_config_home except ImportError: if 'XDG_CONFIG_HOME' in os.environ: base = os.environ['XDG_CONFIG_HOME'] return os.path.join(base, 'ldoce5viewer') @property def _data_dir(self): # Windows if sys.platform.startswith('win'): return self._config_dir # Mac OS X elif sys.platform.startswith('darwin'): return self._config_dir # Linux else: base = os.path.join(os.path.expanduser('~'), '.local/share/') # XDG try: import xdg.BaseDirectory base = xdg.BaseDirectory.xdg_data_home except ImportError: if 'XDG_DATA_HOME' in os.environ: base = os.environ['XDG_DATA_HOME'] return os.path.join(base, 'ldoce5viewer') @property def app_name(self): return 'LDOCE5 Viewer' @property def _config_path(self): return os.path.join(self._config_dir, 'config.pickle') @property def filemap_path(self): return os.path.join(self._data_dir, 'filemap.cdb') @property def variations_path(self): return os.path.join(self._data_dir, 'variations.cdb') @property def incremental_path(self): return os.path.join(self._data_dir, 'incremental.db') @property def fulltext_hwdphr_path(self): return os.path.join(self._data_dir, 'fulltext_hp') @property def fulltext_defexa_path(self): return os.path.join(self._data_dir, 'fulltext_de') @property def scan_tmp_path(self): return os.path.join(self._data_dir, 'scan' + self.tmp_suffix) @property def tmp_suffix(self): return '.tmp' def _remove_tmps(self): for name in os.listdir(self._config_dir) + os.listdir(self._data_dir): if name.endswith(self.tmp_suffix): path = os.path.join(self._config_dir, name) try: if os.path.isfile(path): os.remove(path) elif os.path.isdir(path): shutil.rmtree(path) except OSError: pass def _prepare_dir(self): if not os.path.exists(self._config_dir): os.makedirs(self._config_dir) if not os.path.exists(self._data_dir): os.makedirs(self._data_dir) def load(self): self._lock.lockForWrite() try: with open(self._config_path, 'rb') as f: self._dict.clear() try: data = pickle.load(f) except: pass else: self._dict.update(data) except IOError: self._dict.clear() self._lock.unlock() def save(self): self._lock.lockForRead() if sys.platform == 'win32': with open(self._config_path, 'wb') as f: pickle.dump(self._dict, f) else: f = tempfile.NamedTemporaryFile(dir=self._config_dir, delete=False, suffix=self.tmp_suffix) pickle.dump(self._dict, f, protocol=0) f.close() os.rename(f.name, self._config_path) self._lock.unlock()